source: rtems-source-builder/source-builder/sb/build.py @ 0565e1f

4.104.114.9
Last change on this file since 0565e1f was 0565e1f, checked in by Chris Johns <chrisj@…>, on Apr 13, 2013 at 8:29:30 AM

Add support for snapshot testing.

User macro files passed on the command line allow a user to
override the defaults in configuration files to test new changes
in pending releases.

Fix macros issues with keys with more than one map.

  • Property mode set to 100644
File size: 20.6 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 glob
27import os
28import shutil
29import stat
30import sys
31import urllib2
32import urlparse
33
34try:
35    import check
36    import config
37    import error
38    import execute
39    import log
40    import options
41    import path
42    import version
43except KeyboardInterrupt:
44    print 'abort: user terminated'
45    sys.exit(1)
46except:
47    print 'error: unknown application load error'
48    sys.exit(1)
49
50def _notice(opts, text):
51    if not opts.quiet() and not log.default.has_stdout():
52        print text
53    log.output(text)
54    log.flush()
55
56class script:
57    """Create and manage a shell script."""
58
59    def __init__(self, quiet = True, trace = False):
60        self.quiet = quiet
61        self.trace = trace
62        self.reset()
63
64    def reset(self):
65        self.body = []
66        self.lc = 0
67
68    def append(self, text):
69        if type(text) is str:
70            text = text.splitlines()
71        if not self.quiet:
72            i = 0
73            for l in text:
74                i += 1
75                log.output('script:%3d: %s' % (self.lc + i, l))
76                if self.trace:
77                    print '%3d: S %s' % (self.lc + i,  l)
78        self.lc += len(text)
79        self.body.extend(text)
80
81    def write(self, name, check_for_errors = False):
82        s = None
83        try:
84            s = open(path.host(name), 'w')
85            s.write('\n'.join(self.body))
86            s.close()
87            os.chmod(path.host(name), stat.S_IRWXU | \
88                         stat.S_IRGRP | stat.S_IXGRP | \
89                         stat.S_IROTH | stat.S_IXOTH)
90        except IOError, err:
91            raise error.general('creating script: ' + name)
92        except:
93            if s is not None:
94                s.close()
95            raise
96        if s is not None:
97            s.close()
98
99class build:
100    """Build a package given a config file."""
101
102    def __init__(self, name, create_tar_files, opts, macros = None):
103        self.opts = opts
104        if macros is None:
105            self.macros = opts.defaults
106        else:
107            self.macros = macros
108        self.create_tar_files = create_tar_files
109        _notice(opts, 'config: ' + name)
110        self.config = config.file(name, opts, self.macros)
111        self.script = script(quiet = opts.quiet(), trace = opts.trace())
112
113    def _output(self, text):
114        if not self.opts.quiet():
115            log.output(text)
116
117    def rmdir(self, rmpath):
118        self._output('removing: %s' % (path.host(rmpath)))
119        if not self.opts.dry_run():
120            if path.exists(rmpath):
121                path.removeall(rmpath)
122
123    def mkdir(self, mkpath):
124        self._output('making dir: %s' % (path.host(mkpath)))
125        if not self.opts.dry_run():
126            path.mkdir(mkpath)
127
128    def get_file(self, url, local):
129        if local is None:
130            raise error.general('source/patch path invalid')
131        if not path.isdir(path.dirname(local)):
132            _notice(self.opts,
133                    'Creating source directory: %s' % (os.path.relpath(path.host(path.dirname(local)))))
134            self.mkdir(path.host(path.dirname(local)))
135        if not path.exists(local):
136            #
137            # Not localy found so we need to download it. Check if a URL has
138            # been provided on the command line.
139            #
140            url_bases = self.opts.urls()
141            urls = []
142            if url_bases is not None:
143                for base in url_bases:
144                    if base[-1:] != '/':
145                        base += '/'
146                    url_path = urlparse.urlsplit(url)[2]
147                    slash = url_path.rfind('/')
148                    if slash < 0:
149                        url_file = url_path
150                    else:
151                        url_file = url_path[slash + 1:]
152                    urls.append(urlparse.urljoin(base, url_file))
153            urls.append(url)
154            if self.opts.trace():
155                print '_url:', ','.join(urls), '->', local
156            for url in urls:
157                #
158                # Hack for GitHub.
159                #
160                if url.startswith('https://api.github.com'):
161                    url = urlparse.urljoin(url, self.config.expand('tarball/%{version}'))
162                _notice(self.opts, 'download: %s -> %s' % (url, os.path.relpath(path.host(local))))
163                if not self.opts.dry_run():
164                    failed = False
165                    _in = None
166                    _out = None
167                    try:
168                        _in = urllib2.urlopen(url)
169                        _out = open(path.host(local), 'wb')
170                        _out.write(_in.read())
171                    except IOError, err:
172                        msg = 'download: %s: error: %s' % (url, str(err))
173                        _notice(self.opts, msg)
174                        if path.exists(local):
175                            os.remove(path.host(local))
176                        failed = True
177                    except ValueError, err:
178                        msg = 'download: %s: error: %s' % (url, str(err))
179                        _notice(self.opts, msg)
180                        if path.exists(local):
181                            os.remove(path.host(local))
182                        failed = True
183                    except:
184                        msg = 'download: %s: error' % (url)
185                        print >> sys.stderr, msg
186                        if _out is not None:
187                            _out.close()
188                        raise
189                    if _out is not None:
190                        _out.close()
191                    if _in is not None:
192                        del _in
193                    if not failed:
194                        if not path.isfile(local):
195                            raise error.general('source is not a file: %s' % (path.host(local)))
196                        return
197            if not self.opts.dry_run():
198                raise error.general('downloading %s: all paths have failed, giving up' % (url))
199
200    def parse_url(self, url, pathkey):
201        #
202        # Split the source up into the parts we need.
203        #
204        source = {}
205        source['url'] = url
206        source['path'] = path.dirname(url)
207        source['file'] = path.basename(url)
208        source['name'], source['ext'] = path.splitext(source['file'])
209        #
210        # Get the file. Checks the local source directory first.
211        #
212        source['local'] = None
213        for p in self.config.define(pathkey).split(':'):
214            local = path.join(path.abspath(p), source['file'])
215            if source['local'] is None:
216                source['local'] = local
217            if path.exists(local):
218                source['local'] = local
219                break
220        #
221        # Is the file compressed ?
222        #
223        esl = source['ext'].split('.')
224        if esl[-1:][0] == 'gz':
225            source['compressed'] = '%{__gzip} -dc'
226        elif esl[-1:][0] == 'bz2':
227            source['compressed'] = '%{__bzip2} -dc'
228        elif esl[-1:][0] == 'bz2':
229            source['compressed'] = '%{__zip} -u'
230        elif esl[-1:][0] == 'xz':
231            source['compressed'] = '%{__xz} -dc'
232        source['script'] = ''
233        return source
234
235    def source(self, package, source_tag):
236        #
237        # Scan the sources found in the config file for the one we are
238        # after. Infos or tags are lists. Merge in any macro defined
239        # sources as these may be overridden by user loaded macros.
240        #
241        sources = package.sources()
242        url = None
243        for s in sources:
244            tag = s[len('source'):]
245            if tag.isdigit():
246                if int(tag) == source_tag:
247                    url = sources[s][0]
248                    break
249        if url is None:
250            raise error.general('source tag not found: source%d' % (source_tag))
251        source = self.parse_url(url, '_sourcedir')
252        self.get_file(source['url'], source['local'])
253        if 'compressed' in source:
254            source['script'] = source['compressed'] + ' ' + \
255                source['local'] + ' | %{__tar_extract} -'
256        else:
257            source['script'] = '%{__tar_extract} ' + source['local']
258        return source
259
260    def patch(self, package, args):
261        #
262        # Scan the patches found in the config file for the one we are
263        # after. Infos or tags are lists.
264        #
265        patches = package.patches()
266        url = None
267        for p in patches:
268            if args[0][1:].lower() == p:
269                url = patches[p][0]
270                break
271        if url is None:
272            raise error.general('patch tag not found: %s' % (args[0]))
273        #
274        # Parse the URL first in the source builder's patch directory.
275        #
276        patch = self.parse_url(url, '_patchdir')
277        #
278        # If not in the source builder package check the source directory.
279        #
280        if not path.exists(patch['local']):
281            patch = self.parse_url(url, '_patchdir')
282        self.get_file(patch['url'], patch['local'])
283        if 'compressed' in patch:
284            patch['script'] = patch['compressed'] + ' ' +  patch['local']
285        else:
286            patch['script'] = '%{__cat} ' + patch['local']
287        patch['script'] += ' | %{__patch} ' + ' '.join(args[1:])
288        self.script.append(self.config.expand(patch['script']))
289
290    def canadian_cross(self):
291        _host = self.config.expand('%{_host}')
292        _build = self.config.expand('%{_build}')
293        _target = self.config.expand('%{_target}')
294        return self.config.defined('%{allow_cxc}') and \
295            _host != _build and _host != _target
296
297    def setup(self, package, args):
298        self._output('prep: %s: %s' % (package.name(), ' '.join(args)))
299        opts, args = getopt.getopt(args[1:], 'qDcTn:b:a:')
300        source_tag = 0
301        quiet = False
302        unpack_default_source = True
303        unpack_before_chdir = True
304        delete_before_unpack = True
305        create_dir = False
306        name = None
307        for o in opts:
308            if o[0] == '-q':
309                quiet = True
310            elif o[0] == '-D':
311                delete_before_unpack = False
312            elif o[0] == '-c':
313                create_dir = True
314            elif o[0] == '-T':
315                unpack_default_source = False
316            elif o[0] == '-n':
317                name = o[1]
318            elif o[0] == '-b':
319                unpack_before_chdir = True
320                if not o[1].isdigit():
321                    raise error.general('setup -b source tag is not a number: %s' % (o[1]))
322                source_tag = int(o[1])
323            elif o[0] == '-a':
324                unpack_before_chdir = False
325                if not o[1].isdigit():
326                    raise error.general('setup -a source tag is not a number: %s' % (o[1]))
327                source_tag = int(o[1])
328        source0 = None
329        source = self.source(package, source_tag)
330        if name is None:
331            if source:
332                name = source['name']
333            else:
334                raise error.general('setup source tag not found: %d' % (source_tag))
335        self.script.append(self.config.expand('cd %{_builddir}'))
336        if delete_before_unpack:
337            self.script.append(self.config.expand('%{__rm} -rf ' + name))
338        if create_dir:
339            self.script.append(self.config.expand('%{__mkdir_p} ' + name))
340        #
341        # If -a? then change directory before unpacking.
342        #
343        if not unpack_before_chdir or create_dir:
344            self.script.append(self.config.expand('cd ' + name))
345        #
346        # Unpacking the source. Note, treated the same as -a0.
347        #
348        if unpack_default_source and source_tag != 0:
349            source0 = self.source(package, 0)
350            if source0 is None:
351                raise error.general('no setup source0 tag found')
352            self.script.append(self.config.expand(source0['script']))
353        self.script.append(self.config.expand(source['script']))
354        if unpack_before_chdir and not create_dir:
355            self.script.append(self.config.expand('cd ' + name))
356        self.script.append(self.config.expand('%{__setup_post}'))
357
358    def run(self, command, shell_opts = '', cwd = None):
359        e = execute.capture_execution(log = log.default, dump = self.opts.quiet())
360        cmd = self.config.expand('%{___build_shell} -ex ' + shell_opts + ' ' + command)
361        self._output('run: ' + cmd)
362        exit_code, proc, output = e.shell(cmd, cwd = path.host(cwd))
363        if exit_code != 0:
364            raise error.general('shell cmd failed: %s' % (cmd))
365
366    def builddir(self):
367        builddir = self.config.abspath('_builddir')
368        self.rmdir(builddir)
369        if not self.opts.dry_run():
370            self.mkdir(builddir)
371
372    def prep(self, package):
373        self.script.append('echo "==> %prep:"')
374        _prep = package.prep()
375        for l in _prep:
376            args = l.split()
377            if args[0] == '%setup':
378                self.setup(package, args)
379            elif args[0].startswith('%patch'):
380                self.patch(package, args)
381            else:
382                self.script.append(' '.join(args))
383
384    def build(self, package):
385        self.script.append('echo "==> clean %{buildroot}: ${SB_BUILD_ROOT}"')
386        self.script.append('%s ${SB_BUILD_ROOT}' %
387                           (self.config.expand('%{__rmdir}')))
388        self.script.append('%s ${SB_BUILD_ROOT}' %
389                           (self.config.expand('%{__mkdir_p}')))
390        self.script.append('echo "==> %build:"')
391        _build = package.build()
392        for l in _build:
393            args = l.split()
394            self.script.append(' '.join(args))
395
396    def install(self, package):
397        self.script.append('echo "==> %install:"')
398        _install = package.install()
399        for l in _install:
400            args = l.split()
401            self.script.append(' '.join(args))
402
403    def files(self, package):
404        if self.create_tar_files:
405            self.script.append('echo "==> %files:"')
406            inpath = path.abspath(self.config.expand('%{buildroot}'))
407            tardir = path.abspath(self.config.expand('%{_tardir}'))
408            self.script.append(self.config.expand('if test -d %s; then' % (inpath)))
409            self.script.append(self.config.expand(%%{__mkdir_p} %s' % tardir))
410            self.script.append(self.config.expand('  cd ' + inpath))
411            tar = path.join(tardir, package.long_name() + '.tar.bz2')
412            cmd = self.config.expand('  %{__tar} -cf - . ' + '| %{__bzip2} > ' + tar)
413            self.script.append(cmd)
414            self.script.append(self.config.expand('  cd %{_builddir}'))
415            self.script.append('fi')
416
417    def clean(self, package):
418        self.script.append('echo "==> %clean:"')
419        _clean = package.clean()
420        if _clean is not None:
421            for l in _clean:
422                args = l.split()
423                self.script.append(' '.join(args))
424
425    def build_package(self, package):
426        if self.canadian_cross():
427            self.script.append('echo "==> Candian-cross build/target:"')
428            self.script.append('SB_CXC="yes"')
429        else:
430            self.script.append('SB_CXC="no"')
431        self.build(package)
432        self.install(package)
433        self.files(package)
434        if not self.opts.no_clean():
435            self.clean(package)
436
437    def cleanup(self):
438        if not self.opts.no_clean():
439            buildroot = self.config.abspath('buildroot')
440            builddir = self.config.abspath('_builddir')
441            buildcxcdir = self.config.abspath('_buildcxcdir')
442            tmproot = self.config.abspath('_tmproot')
443            if self.opts.trace():
444                _notice(self.opts, 'cleanup: %s' % (buildroot))
445            self.rmdir(buildroot)
446            if self.opts.trace():
447                _notice(self.opts, 'cleanup: %s' % (builddir))
448            self.rmdir(builddir)
449            if self.canadian_cross():
450                if self.opts.trace():
451                    _notice(self.opts, 'cleanup: %s' % (buildcxcdir))
452                self.rmdir(buildcxcdir)
453            if self.opts.trace():
454                _notice(self.opts, 'cleanup: %s' % (tmproot))
455            self.rmdir(tmproot)
456
457    def main_package(self):
458        packages = self.config.packages()
459        return packages['main']
460
461    def make(self):
462        package = self.main_package()
463        name = package.name()
464        if self.canadian_cross():
465            _notice(self.opts, 'package: (Cxc) %s' % (name))
466        else:
467            _notice(self.opts, 'package: %s' % (name))
468        if self.opts.trace():
469            print '---- macro maps', '-' * 55
470            print self.config.macros
471            print '-' * 70
472        self.script.reset()
473        self.script.append(self.config.expand('%{___build_template}'))
474        self.script.append('echo "=> ' + name + ':"')
475        self.prep(package)
476        self.build_package(package)
477        if not self.opts.dry_run():
478            self.builddir()
479            sn = path.join(self.config.expand('%{_builddir}'), 'doit')
480            self._output('write script: ' + sn)
481            self.script.write(sn)
482            if self.canadian_cross():
483                _notice(self.opts, 'building: (Cxc) %s' % (name))
484            else:
485                _notice(self.opts, 'building: %s' % (name))
486            self.run(sn)
487
488    def name(self):
489        packages = self.config.packages()
490        package = packages['main']
491        return package.name()
492
493def get_configs(opts):
494
495    def _scan(_path, ext):
496        configs = []
497        for root, dirs, files in os.walk(_path):
498            prefix = root[len(_path) + 1:]
499            for file in files:
500                for e in ext:
501                    if file.endswith(e):
502                        configs += [path.join(prefix, file)]
503        return configs
504
505    configs = { 'paths': [], 'files': [] }
506    for cp in opts.defaults.expand('%{_configdir}').split(':'):
507        hcp = path.host(path.abspath(cp))
508        configs['paths'] += [hcp]
509        configs['files'] += _scan(hcp, ['.cfg', '.bset'])
510    configs['files'] = sorted(configs['files'])
511    return configs
512
513def find_config(config, configs):
514    config_root, config_ext = path.splitext(config)
515    if config_ext not in ['', '.bset', '.cfg']:
516        config_root = config
517        config_ext = ''
518    for c in configs['files']:
519        r, e = path.splitext(c)
520        if config_root == r:
521            if config_ext == '' or config_ext == e:
522                return c
523    return None
524
525def run(args):
526    try:
527        optargs = { '--list-configs': 'List available configurations' }
528        opts = options.load(args, optargs)
529        log.default = log.log(opts.logfiles())
530        _notice(opts, 'RTEMS Source Builder, Package Builder v%s' % (version.str()))
531        if not check.host_setup(opts):
532            if not opts.force():
533                raise error.general('host build environment is not set up' +
534                                    ' correctly (use --force to proceed)')
535            _notice(opts, 'warning: forcing build with known host setup problems')
536        if opts.get_arg('--list-configs'):
537            configs = get_configs(opts)
538            for p in configs['paths']:
539                print 'Examining: %s' % (os.path.relpath(p))
540                for c in configs['files']:
541                    if c.endswith('.cfg'):
542                        print '    %s' % (c)
543        else:
544            for config_file in opts.config_files():
545                b = build(config_file, True, opts)
546                b.make()
547                del b
548    except error.general, gerr:
549        print gerr
550        print >> sys.stderr, 'Build FAILED'
551        sys.exit(1)
552    except error.internal, ierr:
553        print ierr
554        print >> sys.stderr, 'Build FAILED'
555        sys.exit(1)
556    except error.exit, eerr:
557        pass
558    except KeyboardInterrupt:
559        _notice(opts, 'abort: user terminated')
560        sys.exit(1)
561    sys.exit(0)
562
563if __name__ == "__main__":
564    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.