source: rtems-source-builder/tb/build.py @ bf13d27

4.104.114.95
Last change on this file since bf13d27 was bf13d27, checked in by Chris Johns <chrisj@…>, on Oct 29, 2012 at 11:37:12 PM

Initial import.

  • Property mode set to 100644
File size: 14.8 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2012 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Permission to use, copy, modify, and/or distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#
21# This code builds a package given a config file. It only builds to be
22# installed not to be package unless you run a packager around this.
23#
24
25import getopt
26import os
27import shutil
28import stat
29import sys
30import urllib2
31import urlparse
32
33import config
34import defaults
35import error
36import execute
37import log
38
39#
40# Version of Tools Builder.
41#
42version = '0.1'
43
44def _notice(opts, text):
45    if not opts.quiet() and not log.default.has_stdout():
46        print text
47    log.output(text)
48    log.flush()
49
50class script:
51    """Create and manage a shell script."""
52
53    def __init__(self, quiet = True):
54        self.quiet = quiet
55        self.reset()
56
57    def reset(self):
58        self.body = []
59        self.lc = 0
60
61    def append(self, text):
62        if type(text) is str:
63            text = text.splitlines()
64        if not self.quiet:
65            i = 0
66            for l in text:
67                i += 1
68                log.output('script:%3d: ' % (self.lc + i) + l)
69        self.lc += len(text)
70        self.body.extend(text)
71
72    def write(self, name, check_for_errors = False):
73        s = None
74        try:
75            s = open(name, 'w')
76            s.write('\n'.join(self.body))
77            s.close()
78            os.chmod(name, stat.S_IRWXU | \
79                         stat.S_IRGRP | stat.S_IXGRP | \
80                         stat.S_IROTH | stat.S_IXOTH)
81        except IOError, err:
82            raise error.general('creating script: ' + name)
83        except:
84            if s is not None:
85                s.close()
86            raise
87        if s is not None:
88            s.close()
89
90class build:
91    """Build a package given a config file."""
92
93    def __init__(self, name, _defaults, opts):
94        self.opts = opts
95        _notice(opts, 'building: ' + name)
96        self.config = config.file(name, _defaults = _defaults, opts = opts)
97        self.script = script(quiet = opts.quiet())
98
99    def _output(self, text):
100        if not self.opts.quiet():
101            log.output(text)
102
103    def rmdir(self, path):
104        self._output('removing: ' + path)
105        if not self.opts.dry_run():
106            if os.path.exists(path):
107                try:
108                    shutil.rmtree(path)
109                except IOError, err:
110                    raise error.error('error removing: ' + path)
111
112    def mkdir(self, path):
113        self._output('making dir: ' + path)
114        if not self.opts.dry_run():
115            try:
116                os.makedirs(path)
117            except IOError, err:
118                raise error.general('error creating path: ' + path)
119
120    def get_file(self, url, local):
121        if not os.path.isdir(os.path.dirname(local)):
122            raise error.general('source path not found: ' + os.path.dirname(local))
123        if not os.path.exists(local):
124            #
125            # Not localy found so we need to download it. Check if a URL
126            # has been provided on the command line.
127            #
128            url_bases = self.opts.urls()
129            urls = []
130            if url_bases is not None:
131                for base in url_bases:
132                    if base[-1:] != '/':
133                        base += '/'
134                    url_path = urlparse.urlsplit(url)[2]
135                    slash = url_path.rfind('/')
136                    if slash < 0:
137                        url_file = url_path
138                    else:
139                        url_file = url_path[slash + 1:]
140                    urls.append(urlparse.urljoin(base, url_file))
141            urls.append(url)
142            for url in urls:
143                _notice(self.opts, 'download: ' + url + ' -> ' + local)
144                if not self.opts.dry_run():
145                    failed = False
146                    _in = None
147                    _out = None
148                    try:
149                        _in = urllib2.urlopen(url)
150                        _out = open(local, 'wb')
151                        _out.write(_in.read())
152                    except IOError, err:
153                        _notice(self.opts, 'download: ' + url + ': failed: ' + str(err))
154                        if os.path.exists(local):
155                            os.remove(local)
156                        failed = True
157                    except:
158                        if _out is not None:
159                            _out.close()
160                        raise
161                    if _out is not None:
162                        _out.close()
163                    if _in is not None:
164                        del _in
165                    if not failed:
166                        if not os.path.isfile(local):
167                            raise error.general('source is not a file: ' + local)
168                        return
169            raise error.general('downloading ' + url + ': all paths have failed, giving up')
170
171    def parse_url(self, url):
172        #
173        # Split the source up into the parts we need.
174        #
175        source = {}
176        source['url'] = url
177        source['path'] = os.path.dirname(url)
178        source['file'] = os.path.basename(url)
179        source['name'], source['ext'] = os.path.splitext(source['file'])
180        #
181        # Get the file. Checks the local source directory first.
182        #
183        source['local'] = os.path.join(self.config.abspath('_sourcedir'),
184                                       source['file'])
185        #
186        # Is the file compressed ?
187        #
188        esl = source['ext'].split('.')
189        if esl[-1:][0] == 'gz':
190            source['compressed'] = '%{__gzip} -dc'
191        elif esl[-1:][0] == 'bz2':
192            source['compressed'] = '%{__bzip2} -dc'
193        elif esl[-1:][0] == 'bz2':
194            source['compressed'] = '%{__zip} -u'
195        elif esl[-1:][0] == 'xz':
196            source['compressed'] = '%{__xz} -dc'
197        source['script'] = ''
198        return source
199
200    def source(self, package, source_tag):
201        #
202        # Scan the sources found in the config file for the one we are
203        # after. Infos or tags are lists.
204        #
205        sources = package.sources()
206        url = None
207        for s in sources:
208            tag = s[len('source'):]
209            if tag.isdigit():
210                if int(tag) == source_tag:
211                    url = sources[s][0]
212                    break
213        if url is None:
214            raise error.general('source tag not found: source' + str(source_tag))
215        source = self.parse_url(url)
216        self.get_file(source['url'], source['local'])
217        if 'compressed' in source:
218            source['script'] = source['compressed'] + ' ' + \
219                source['local'] + ' | %{__tar_extract} -'
220        else:
221            source['script'] = '%{__tar_extract} ' + source['local']
222        return source
223
224    def patch(self, package, args):
225        #
226        # Scan the patches found in the config file for the one we are
227        # after. Infos or tags are lists.
228        #
229        patches = package.patches()
230        url = None
231        for p in patches:
232            if args[0][1:].lower() == p:
233                url = patches[p][0]
234                break
235        if url is None:
236            raise error.general('patch tag not found: ' + args[0])
237        patch = self.parse_url(url)
238        self.get_file(patch['url'], patch['local'])
239        if 'compressed' in patch:
240            patch['script'] = patch['compressed'] + ' ' +  patch['local']
241        else:
242            patch['script'] = '%{__cat} ' + patch['local']
243        patch['script'] += ' | %{__patch} ' + ' '.join(args[1:])
244        self.script.append(self.config.expand(patch['script']))
245
246    def setup(self, package, args):
247        self._output('prep: ' + package.name() + ': ' + ' '.join(args))
248        opts, args = getopt.getopt(args[1:], 'qDcTn:b:a:')
249        source_tag = 0
250        quiet = False
251        unpack_default_source = True
252        delete_before_unpack = True
253        create_dir = False
254        name = None
255        unpack_before_chdir = True
256        for o in opts:
257            if o[0] == '-q':
258                quiet = True
259            elif o[0] == '-D':
260                delete_before_unpack = False
261            elif o[0] == '-c':
262                create_dir = True
263            elif o[0] == '-T':
264                unpack_default_source = False
265            elif o[0] == '-n':
266                name = o[1]
267            elif o[0] == '-b':
268                unpack_before_chdir = True
269                if not o[1].isdigit():
270                    raise error.general('setup source tag no a number: ' + o[1])
271                source_tag = int(o[1])
272            elif o[0] == '-a':
273                unpack_before_chdir = False
274                source_tag = int(o[1])
275        source0 = None
276        source = self.source(package, source_tag)
277        if name is None:
278            if source:
279                name = source['name']
280            else:
281                name = source0['name']
282        self.script.append(self.config.expand('cd %{_builddir}'))
283        if delete_before_unpack:
284            self.script.append(self.config.expand('%{__rm} -rf ' + name))
285        if create_dir:
286            self.script.append(self.config.expand('%{__mkdir_p} ' + name))
287        #
288        # If -a? then change directory before unpacking.
289        #
290        if not unpack_before_chdir:
291            self.script.append(self.config.expand('cd ' + name))
292        #
293        # Unpacking the source. Note, treated the same as -a0.
294        #
295        if unpack_default_source and source_tag != 0:
296            source0 = self.source(package, 0)
297            if source0 is None:
298                raise error.general('no setup source0 tag found')
299            self.script.append(self.config.expand(source0['script']))
300        self.script.append(self.config.expand(source['script']))
301        if unpack_before_chdir:
302            self.script.append(self.config.expand('cd ' + name))
303        self.script.append(self.config.expand('%{__setup_post}'))
304        if create_dir:
305            self.script.append(self.config.expand('cd ..'))
306
307    def run(self, command, shell_opts = '', cwd = None):
308        e = execute.capture_execution(log = log.default, dump = self.opts.quiet())
309        cmd = self.config.expand('%{___build_shell} -ex ' + shell_opts + ' ' + command)
310        self._output('run: ' + cmd)
311        exit_code, proc, output = e.shell(cmd, cwd = cwd)
312        if exit_code != 0:
313            raise error.general('shell cmd failed: ' + cmd)
314
315    def builddir(self):
316        builddir = self.config.abspath('_builddir')
317        self.rmdir(builddir)
318        if not self.opts.dry_run():
319            self.mkdir(builddir)
320
321    def prep(self, package):
322        self.script.append('echo "==> %prep:"')
323        _prep = package.prep()
324        for l in _prep:
325            args = l.split()
326            if args[0] == '%setup':
327                self.setup(package, args)
328            elif args[0].startswith('%patch'):
329                self.patch(package, args)
330            else:
331                self.script.append(' '.join(args))
332
333    def build(self, package):
334        self.script.append('echo "==> %build:"')
335        _build = package.build()
336        for l in _build:
337            args = l.split()
338            self.script.append(' '.join(args))
339
340    def install(self, package):
341        self.script.append('echo "==> %install:"')
342        _install = package.install()
343        for l in _install:
344            args = l.split()
345            self.script.append(' '.join(args))
346
347    def files(self, package):
348        self.script.append('echo "==> %files:"')
349        prefixbase = self.opts.prefixbase()
350        if prefixbase is None:
351            prefixbase = ''
352        inpath = os.path.join('%{buildroot}', prefixbase)
353        tardir = os.path.abspath(self.config.expand('%{_tardir}'))
354        self.script.append('mkdir -p %s' % tardir)
355        self.script.append(self.config.expand('cd ' + inpath))
356        tar = os.path.join(tardir, package.long_name() + '.tar.bz2')
357        cmd = self.config.expand('%{__tar} -cf - . ' + '| %{__bzip2} > ' + tar)
358        self.script.append(cmd)
359        self.script.append(self.config.expand('cd %{_builddir}'))
360
361    def clean(self, package):
362        self.script.append('echo "==> %clean:"')
363        _clean = package.clean()
364        if _clean is not None:
365            for l in _clean:
366                args = l.split()
367                self.script.append(' '.join(args))
368
369    def cleanup(self):
370        if not self.opts.no_clean():
371            buildroot = self.config.abspath('buildroot')
372            builddir = self.config.abspath('_builddir')
373            _notice(self.opts, 'cleanup: %s' % (buildroot))
374            self.rmdir(buildroot)
375            _notice(self.opts, 'cleanup: %s' % (builddir))
376            self.rmdir(builddir)
377
378    def make(self):
379        packages = self.config.packages()
380        package = packages['main']
381        name = package.name()
382        _notice(self.opts, 'package: %s' % (name))
383        self.script.reset()
384        self.script.append(self.config.expand('%{___build_template}'))
385        self.script.append('echo "=> ' + name + ':"')
386        self.prep(package)
387        self.build(package)
388        self.install(package)
389        self.files(package)
390        if not self.opts.no_clean():
391            self.clean(package)
392        if not self.opts.dry_run():
393            self.builddir()
394            sn = self.config.expand(os.path.join('%{_builddir}', 'doit'))
395            self._output('write script: ' + sn)
396            self.script.write(sn)
397            _notice(self.opts, 'building: ' + name)
398            self.run(sn)
399
400    def name(self):
401        packages = self.config.packages()
402        package = packages['main']
403        return package.name()
404
405def run(args):
406    try:
407        opts, _defaults = defaults.load(args)
408        log.default = log.log(opts.logfiles())
409        _notice(opts, 'Tools Builder, v%s' % (version))
410        for config_file in opts.config_files():
411            b = build(config_file, _defaults = _defaults, opts = opts)
412            b.make()
413            del b
414    except error.general, gerr:
415        print gerr
416        sys.exit(1)
417    except error.internal, ierr:
418        print ierr
419        sys.exit(1)
420    except error.exit, eerr:
421        pass
422    except KeyboardInterrupt:
423        _notice(opts, 'user terminated')
424        sys.exit(1)
425    sys.exit(0)
426
427if __name__ == "__main__":
428    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.