1
|
|
|
from __future__ import print_function |
2
|
|
|
|
3
|
|
|
from os.path import split as psplit, join as pjoin |
4
|
|
|
from subprocess import Popen, PIPE |
5
|
|
|
|
6
|
|
|
def back_tick(cmd, ret_err=False, as_str=True, shell=False): |
7
|
|
|
""" Run command `cmd`, return stdout, or stdout, stderr if `ret_err` |
8
|
|
|
|
9
|
|
|
Roughly equivalent to ``check_output`` in Python 2.7 |
10
|
|
|
|
11
|
|
|
Parameters |
12
|
|
|
---------- |
13
|
|
|
cmd : str |
14
|
|
|
command to execute |
15
|
|
|
ret_err : bool, optional |
16
|
|
|
If True, return stderr in addition to stdout. If False, just return |
17
|
|
|
stdout |
18
|
|
|
as_str : bool, optional |
19
|
|
|
Whether to decode outputs to unicode string on exit. |
20
|
|
|
|
21
|
|
|
Returns |
22
|
|
|
------- |
23
|
|
|
out : str or tuple |
24
|
|
|
If `ret_err` is False, return stripped string containing stdout from |
25
|
|
|
`cmd`. If `ret_err` is True, return tuple of (stdout, stderr) where |
26
|
|
|
``stdout`` is the stripped stdout, and ``stderr`` is the stripped |
27
|
|
|
stderr. |
28
|
|
|
|
29
|
|
|
Raises |
30
|
|
|
------ |
31
|
|
|
Raises RuntimeError if command returns non-zero exit code |
32
|
|
|
""" |
33
|
|
|
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=shell) |
34
|
|
|
out, err = proc.communicate() |
35
|
|
|
retcode = proc.returncode |
36
|
|
|
if retcode is None: |
37
|
|
|
proc.terminate() |
38
|
|
|
raise RuntimeError(cmd + ' process did not terminate') |
39
|
|
|
if retcode != 0: |
40
|
|
|
raise RuntimeError(cmd + ' process returned code %d' % retcode) |
41
|
|
|
out = out.strip() |
42
|
|
|
if as_str: |
43
|
|
|
out = out.decode('latin-1') |
44
|
|
|
if not ret_err: |
45
|
|
|
return out |
46
|
|
|
err = err.strip() |
47
|
|
|
if as_str: |
48
|
|
|
err = err.decode('latin-1') |
49
|
|
|
return out, err |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
def seq_to_list(seq): |
53
|
|
|
""" Convert non-sequence to 1 element sequence, tuples to lists |
54
|
|
|
""" |
55
|
|
|
if not isinstance(seq, (list, tuple)): |
56
|
|
|
return [seq] |
57
|
|
|
return list(seq) |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
class FilePackageMaker(object): |
61
|
|
|
# all packages |
62
|
|
|
instances = {} |
63
|
|
|
|
64
|
|
|
def __init__(self, name, filename, build_cmd, |
65
|
|
|
depends=(), |
66
|
|
|
after=(), |
67
|
|
|
patcher=None, |
68
|
|
|
unpacked_sdir=None, |
69
|
|
|
build_src_sdir='src', |
70
|
|
|
): |
71
|
|
|
""" Initialize object for creating unpack, patch, build tasks |
72
|
|
|
|
73
|
|
|
Unpacking assumed to have no dependencies. |
74
|
|
|
|
75
|
|
|
Patching assumed to depend only on the unpacking. |
76
|
|
|
|
77
|
|
|
Build depends on packing / patching and on given dependencies |
78
|
|
|
|
79
|
|
|
Parameters |
80
|
|
|
---------- |
81
|
|
|
name : str |
82
|
|
|
package name |
83
|
|
|
filename : str |
84
|
|
|
filename containing source archive to unpack |
85
|
|
|
build_cmd : str or callable |
86
|
|
|
command to build after extracting |
87
|
|
|
depends : str or sequence, optional |
88
|
|
|
depends for build |
89
|
|
|
after : str or sequence, optional |
90
|
|
|
names to set build to follow after (task name depends) |
91
|
|
|
patcher : None or str or callable, optional |
92
|
|
|
If str, a file containing a ``-p1`` patch for the sources. If |
93
|
|
|
callable, then a rule to apply for patching. If None, don't patch |
94
|
|
|
unpacked_sdir : str or None, optional |
95
|
|
|
directory created by unpacking `filename`. If None we guess from |
96
|
|
|
`filename` |
97
|
|
|
build_src_sdir : str, optional |
98
|
|
|
subdirectory in build directory into which to unpack |
99
|
|
|
""" |
100
|
|
|
self.name = name |
101
|
|
|
self.filename = filename |
102
|
|
|
self.build_cmd = build_cmd |
103
|
|
|
_, fname = psplit(filename) |
104
|
|
|
if fname.endswith('.tar.gz'): |
105
|
|
|
self.unpack_cmd = 'tar zxf' |
106
|
|
|
fname = fname[:-7] |
107
|
|
|
elif fname.endswith('.tar.bz2'): |
108
|
|
|
self.unpack_cmd = 'tar jxf' |
109
|
|
|
fname = fname[:-8] |
110
|
|
|
elif fname.endswith('.zip'): |
111
|
|
|
self.unpack_cmd = 'unzip' |
112
|
|
|
fname = fname[:-4] |
113
|
|
|
else: |
114
|
|
|
raise ValueError("Can't work out type of archive " + fname) |
115
|
|
|
self.patcher = patcher |
116
|
|
|
if unpacked_sdir is None: # Guess at output subdirectory |
117
|
|
|
unpacked_sdir = fname |
118
|
|
|
self.unpacked_sdir = unpacked_sdir |
119
|
|
|
self.depends = seq_to_list(depends) |
120
|
|
|
self.after = seq_to_list(after) |
121
|
|
|
self.build_src_sdir = build_src_sdir |
122
|
|
|
self._register_instance() |
123
|
|
|
|
124
|
|
|
def _register_instance(self): |
125
|
|
|
""" Register instance to class dictionary """ |
126
|
|
|
if self.name in self.instances: |
127
|
|
|
raise ValueError('Name "{0}" already in instance ' |
128
|
|
|
'dict'.format(self.name)) |
129
|
|
|
self.instances[self.name] = self |
130
|
|
|
|
131
|
|
|
def _unpack(self, bctx): |
132
|
|
|
task_name = self.name + '.unpack' |
133
|
|
|
dir_relpath = pjoin(self.build_src_sdir, self.unpacked_sdir) |
134
|
|
|
dir_node = bctx.bldnode.make_node(dir_relpath) |
135
|
|
|
archive_path = pjoin(bctx.srcnode.abspath(), self.filename) |
136
|
|
|
rule = 'cd {dir_path} && {unpack_cmd} {archive_path}'.format( |
137
|
|
|
dir_path = pjoin(bctx.bldnode.abspath(), self.build_src_sdir), |
138
|
|
|
unpack_cmd = self.unpack_cmd, |
139
|
|
|
archive_path = archive_path) |
140
|
|
|
bctx(rule = rule, |
141
|
|
|
target = dir_node, |
142
|
|
|
name = task_name) |
143
|
|
|
return task_name, dir_node |
144
|
|
|
|
145
|
|
|
def unpack_patch_build(self, bctx): |
146
|
|
|
""" Make task generators to unpack, patch and build |
147
|
|
|
|
148
|
|
|
Parameters |
149
|
|
|
---------- |
150
|
|
|
bctx : build context |
151
|
|
|
|
152
|
|
|
Returns |
153
|
|
|
------- |
154
|
|
|
build_name : str |
155
|
|
|
name of build task, for other tasks to depend on if necessary |
156
|
|
|
dir_node : :class:`Node` instance |
157
|
|
|
node pointing to directory containing unpacked and built sources |
158
|
|
|
""" |
159
|
|
|
task_name, dir_node = self._unpack(bctx) |
160
|
|
|
if not self.patcher is None: |
161
|
|
|
if hasattr(self.patcher, '__call__'): # patch function |
162
|
|
|
rule = self.patcher |
163
|
|
|
else: # assume filename in source tree |
164
|
|
|
patch_node = bctx.srcnode.find_node(self.patcher) |
165
|
|
|
if patch_node is None: |
166
|
|
|
bctx.fatal('Patch file {0} does not exist'.format( |
167
|
|
|
self.patcher)) |
168
|
|
|
rule = 'cd ${SRC} && patch -p1 < ' + patch_node.abspath() |
169
|
|
|
task_name = self.name + '.patch' |
170
|
|
|
bctx( |
171
|
|
|
rule = rule, |
172
|
|
|
source = dir_node, |
173
|
|
|
name = task_name) |
174
|
|
|
build_name = self.name + '.build' |
175
|
|
|
bctx( |
176
|
|
|
rule = self.build_cmd, |
177
|
|
|
source = [dir_node] + self.depends, |
178
|
|
|
after = [task_name] + self.after, |
179
|
|
|
name = build_name) |
180
|
|
|
return build_name, dir_node |
181
|
|
|
|
182
|
|
|
|
183
|
|
|
class GitPackageMaker(FilePackageMaker): |
184
|
|
|
# all packages |
185
|
|
|
instances = {} |
186
|
|
|
|
187
|
|
|
def __init__(self, name, commit, build_cmd, |
188
|
|
|
depends=(), |
189
|
|
|
after=(), |
190
|
|
|
patcher=None, |
191
|
|
|
out_sdir=None, |
192
|
|
|
git_sdir=None, |
193
|
|
|
build_src_sdir='src', |
194
|
|
|
): |
195
|
|
|
""" Initialize object for creating unpack, patch, build tasks |
196
|
|
|
|
197
|
|
|
* Unpacking assumed to have no dependencies. |
198
|
|
|
* Patching assumed to depend only on the unpacking. |
199
|
|
|
* Build depends on packing / patching and on given dependencies |
200
|
|
|
|
201
|
|
|
Parameters |
202
|
|
|
---------- |
203
|
|
|
name : str |
204
|
|
|
package name |
205
|
|
|
commit : str |
206
|
|
|
identifier for commit to unarchive |
207
|
|
|
build_cmd : str or callable |
208
|
|
|
command to build after extracting |
209
|
|
|
depends : str or sequence, optional |
210
|
|
|
depends for build |
211
|
|
|
after : str or sequence, optional |
212
|
|
|
names to set build to follow after (task name depends) |
213
|
|
|
patcher : None or str or callable, optional |
214
|
|
|
If str, a file containing a ``-p1`` patch for the sources. If |
215
|
|
|
callable, then a rule to apply for patching. If None, don't patch |
216
|
|
|
out_sdir : None or str, optional |
217
|
|
|
subdirectory in `build_src_sdir` in which to unpack. If None, use |
218
|
|
|
`name` |
219
|
|
|
git_sdir : str or None, optional |
220
|
|
|
directory containing git repository. Defaults to `name` |
221
|
|
|
build_src_sdir : str, optional |
222
|
|
|
subdirectory in build directory into which to unpack |
223
|
|
|
""" |
224
|
|
|
self.name = name |
225
|
|
|
self.commit = commit |
226
|
|
|
self.build_cmd = build_cmd |
227
|
|
|
self.patcher = patcher |
228
|
|
|
if git_sdir is None: |
229
|
|
|
git_sdir = name |
230
|
|
|
self.git_sdir = git_sdir |
231
|
|
|
self.depends = seq_to_list(depends) |
232
|
|
|
self.after = seq_to_list(after) |
233
|
|
|
self.build_src_sdir = build_src_sdir |
234
|
|
|
self.out_sdir = name if out_sdir is None else out_sdir |
235
|
|
|
self._register_instance() |
236
|
|
|
|
237
|
|
|
def _unpack(self, bctx): |
238
|
|
|
src_path = bctx.srcnode.abspath() |
239
|
|
|
bld_path = bctx.bldnode.abspath() |
240
|
|
|
task_name = self.name + '.unpack' |
241
|
|
|
dir_relpath = pjoin(self.build_src_sdir, self.out_sdir) |
242
|
|
|
dir_node = bctx.bldnode.make_node(dir_relpath) |
243
|
|
|
git_dir = pjoin(src_path, self.git_sdir) |
244
|
|
|
bctx( |
245
|
|
|
rule = ('cd {git_dir} && ' |
246
|
|
|
'git archive --prefix={dir_relpath}/ {commit} | ' |
247
|
|
|
'( cd {bld_path} && tar x )'.format( |
248
|
|
|
git_dir = git_dir, |
249
|
|
|
dir_relpath = dir_relpath, |
250
|
|
|
commit = self.commit, |
251
|
|
|
bld_path = bld_path)), |
252
|
|
|
target = dir_node, |
253
|
|
|
name = task_name) |
254
|
|
|
return task_name, dir_node |
255
|
|
|
|