Completed
Push — develop ( b745b5...362316 )
by Wu
9s
created

FilePackageMaker   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 121
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
c 1
b 0
f 0
dl 0
loc 121
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A _register_instance() 0 6 2
B __init__() 0 59 5
A _unpack() 0 13 1
B unpack_patch_build() 0 36 4
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