source: rtems-source-builder/source-builder/sb/build.py @ 383f7e6

4.104.95
Last change on this file since 383f7e6 was 383f7e6, checked in by Chris Johns <chrisj@…>, on 04/15/16 at 01:44:27

sb: Add --rsb-file options to %source and %patch to set a file name.

Override the automatic file name of a downloaded file and use the file
name provided by the option. This is useful if the URL has no meanful
file that can be automatically extracted from the URL.

  • Property mode set to 100644
File size: 23.0 KB
RevLine 
[bf13d27]1#
2# RTEMS Tools Project (http://www.rtems.org/)
[649a64c]3# Copyright 2010-2013 Chris Johns (chrisj@rtems.org)
[bf13d27]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
[3a972f6]25from __future__ import print_function
26
[6444d58]27import copy
[bf13d27]28import getopt
[015fa1b]29import glob
[bf13d27]30import os
31import shutil
32import stat
33import sys
34
[efb6688]35try:
36    import check
37    import config
[649a64c]38    import download
[efb6688]39    import error
[74da24c]40    import ereport
[efb6688]41    import execute
42    import log
[cb12e48]43    import options
[efb6688]44    import path
[9a15c40]45    import sources
[efb6688]46    import version
47except KeyboardInterrupt:
[3a972f6]48    print('abort: user terminated')
[efb6688]49    sys.exit(1)
50except:
[3a972f6]51    print('error: unknown application load error')
[efb6688]52    sys.exit(1)
[bf13d27]53
54class script:
55    """Create and manage a shell script."""
56
[5142bec]57    def __init__(self):
[bf13d27]58        self.reset()
59
60    def reset(self):
61        self.body = []
62        self.lc = 0
63
64    def append(self, text):
65        if type(text) is str:
66            text = text.splitlines()
[5142bec]67        if not log.quiet:
[bf13d27]68            i = 0
69            for l in text:
70                i += 1
[cafbcc6]71                log.output('script:%3d: %s' % (self.lc + i, l))
[bf13d27]72        self.lc += len(text)
73        self.body.extend(text)
74
75    def write(self, name, check_for_errors = False):
76        s = None
77        try:
[ab8319a]78            s = open(path.host(name), 'w')
[bf13d27]79            s.write('\n'.join(self.body))
80            s.close()
[ab8319a]81            os.chmod(path.host(name), stat.S_IRWXU | \
[bf13d27]82                         stat.S_IRGRP | stat.S_IXGRP | \
83                         stat.S_IROTH | stat.S_IXOTH)
[3a972f6]84        except IOError as err:
[bf13d27]85            raise error.general('creating script: ' + name)
86        except:
87            if s is not None:
88                s.close()
89            raise
90        if s is not None:
91            s.close()
92
93class build:
94    """Build a package given a config file."""
95
[5237f1cc]96    def _name_(self, name):
97        #
98        # If on Windows use shorter names to keep the build paths.
99        #
100        if options.host_windows:
101            buildname = ''
102            add = True
103            for c in name:
104                if c == '-':
105                    add = True
106                elif add:
107                    buildname += c
108                    add = False
109            return buildname
110        else:
111            return name
112
[53290f9]113    def _generate_report_(self, header, footer = None):
[40e4222]114        label, result = self.opts.with_arg('error-report')
[8d0e377]115        if (label.startswith('without_') and result != 'yes') or \
116           (label.startswith('with_') and result != 'no'):
[40e4222]117            ereport.generate('rsb-report-%s.txt' % self.macros['name'],
118                             self.opts, header, footer)
[65d9457]119
[cb12e48]120    def __init__(self, name, create_tar_files, opts, macros = None):
[339f92f]121        try:
122            self.opts = opts
[6444d58]123            self.init_name = name
124            self.init_macros = macros
125            self.config = None
[339f92f]126            self.create_tar_files = create_tar_files
127            log.notice('config: ' + name)
[6444d58]128            self.set_macros(macros)
[339f92f]129            self.config = config.file(name, opts, self.macros)
130            self.script = script()
131            self.macros['buildname'] = self._name_(self.macros['name'])
[3a972f6]132        except error.general as gerr:
[339f92f]133            log.notice(str(gerr))
134            log.stderr('Build FAILED')
135            raise
[3a972f6]136        except error.internal as ierr:
[339f92f]137            log.notice(str(ierr))
138            log.stderr('Internal Build FAILED')
139            raise
140        except:
141            raise
[bf13d27]142
[6444d58]143    def copy_init_macros(self):
144        return copy.copy(self.init_macros)
145
146    def copy_macros(self):
147        return copy.copy(self.macros)
148
149    def set_macros(self, macros):
150        if macros is None:
[610ae57]151            self.macros = copy.copy(self.opts.defaults)
[6444d58]152        else:
153            self.macros = copy.copy(macros)
154        if self.config:
155            self.config.set_macros(self.macros)
156
[ab8319a]157    def rmdir(self, rmpath):
[5142bec]158        log.output('removing: %s' % (path.host(rmpath)))
[bf13d27]159        if not self.opts.dry_run():
[ab8319a]160            if path.exists(rmpath):
[ee47d72]161                path.removeall(rmpath)
[bf13d27]162
[ab8319a]163    def mkdir(self, mkpath):
[5142bec]164        log.output('making dir: %s' % (path.host(mkpath)))
[bf13d27]165        if not self.opts.dry_run():
[ee47d72]166            path.mkdir(mkpath)
[bf13d27]167
[4f26bdb]168    def canadian_cross(self):
169        _host = self.config.expand('%{_host}')
170        _build = self.config.expand('%{_build}')
171        _target = self.config.expand('%{_target}')
[6444d58]172        _allowed = self.config.defined('%{allow_cxc}')
173        if len(_host) and len(_build) and (_target) and \
174           _allowed and _host != _build and _host != _target:
175            return True
176        return False
177
178    def installable(self):
179        _host = self.config.expand('%{_host}')
180        _build = self.config.expand('%{_build}')
181        _canadian_cross = self.canadian_cross()
182        if self.macros.get('_disable_installing') and \
183           self.config.expand('%{_disable_installing}') == 'yes':
184            _disable_installing = True
185        else:
186            _disable_installing = False
187        _no_install = self.opts.no_install()
188        log.trace('_build: installable: host=%s build=%s ' \
189                  'no-install=%r Cxc=%r disable_installing=%r disabled=%r' % \
190                  (_host, _build, _no_install, _canadian_cross, _disable_installing, \
191                   self.disabled()))
192        return len(_host) and len(_build) and \
193            not self.disabled() and \
194            not _disable_installing and \
195            not _canadian_cross
[4f26bdb]196
[9a15c40]197    def source(self, name):
198        #
199        # Return the list of sources. Merge in any macro defined sources as
200        # these may be overridden by user loaded macros.
201        #
202        _map = 'source-%s' % (name)
[d3629a9]203        src_keys = [s for s in self.macros.map_keys(_map) if s != 'setup']
[9a15c40]204        if len(src_keys) == 0:
205            raise error.general('no source set: %s (%s)' % (name, _map))
206        srcs = []
207        for s in src_keys:
208            sm = self.macros.get(s, globals = False, maps = _map)
209            if sm is None:
210                raise error.internal('source macro not found: %s in %s (%s)' % \
211                                         (s, name, _map))
[383f7e6]212            opts = []
213            url = []
214            for sp in sm[2].split():
215                if len(url) == 0 and sp[0] == '-':
216                    opts += [sp]
217                else:
218                    url += [sp]
219            if len(url) == 0:
220                raise error.general('source URL not found: %s' % (' '.join(args)))
221            #
222            # Look for --rsb-file as an option we use as a local file name.
223            # This can be used if a URL has no reasonable file name the
224            # download URL parser can figure out.
225            #
226            file_override = None
227            if len(opts) > 0:
228                for o in opts:
229                    if o.startswith('--rsb-file'):
230                       os_ = o.split('=')
231                       if len(os_) != 2:
232                           raise error.general('invalid --rsb-file option: %s' % (' '.join(args)))
233                       if os_[0] != '--rsb-file':
234                           raise error.general('invalid --rsb-file option: %s' % (' '.join(args)))
235                       file_override = os_[1]
236                opts = [o for o in opts if not o.startswith('--rsb-')]
237            url = self.config.expand(' '.join(url))
238            src = download.parse_url(url, '_sourcedir', self.config, self.opts, file_override)
[9a15c40]239            download.get_file(src['url'], src['local'], self.opts, self.config)
240            if 'symlink' in src:
[5fdd664]241                sname = name.replace('-', '_')
242                src['script'] = '%%{__ln_s} %s ${source_dir_%s}' % (src['symlink'], sname)
[9a15c40]243            elif 'compressed' in src:
244                #
245                # Zip files unpack as well so do not use tar.
246                #
247                src['script'] = '%s %s' % (src['compressed'], src['local'])
248                if src['compressed-type'] != 'zip':
249                    src['script'] += ' | %{__tar_extract} -'
250            else:
[83586f7]251                src['script'] = '%%{__tar_extract} %s' % (src['local'])
[9a15c40]252            srcs += [src]
253        return srcs
254
255    def source_setup(self, package, args):
256        log.output('source setup: %s: %s' % (package.name(), ' '.join(args)))
257        setup_name = args[1]
258        args = args[1:]
259        try:
[4a315d3]260            opts, args = getopt.getopt(args[1:], 'qDcn:ba')
[3a972f6]261        except getopt.GetoptError as ge:
[9a15c40]262            raise error.general('source setup error: %s' % str(ge))
[bf13d27]263        quiet = False
[7618a74c]264        unpack_before_chdir = True
[bf13d27]265        delete_before_unpack = True
266        create_dir = False
[9a15c40]267        deleted_dir = False
268        created_dir = False
269        changed_dir = False
270        opt_name = None
[bf13d27]271        for o in opts:
272            if o[0] == '-q':
273                quiet = True
274            elif o[0] == '-D':
275                delete_before_unpack = False
276            elif o[0] == '-c':
277                create_dir = True
278            elif o[0] == '-n':
[9a15c40]279                opt_name = o[1]
[bf13d27]280            elif o[0] == '-b':
281                unpack_before_chdir = True
282            elif o[0] == '-a':
283                unpack_before_chdir = False
[9a15c40]284        name = None
285        for source in self.source(setup_name):
286            if name is None:
287                if opt_name is None:
288                    if source:
289                        opt_name = source['name']
290                    else:
291                        raise error.general('setup source tag not found: %d' % (source_tag))
292                else:
293                    name = opt_name
294            self.script.append(self.config.expand('cd %{_builddir}'))
295            if not deleted_dir and  delete_before_unpack:
296                self.script.append(self.config.expand('%{__rm} -rf ' + name))
297                deleted_dir = True
298            if not created_dir and create_dir:
299                self.script.append(self.config.expand('%{__mkdir_p} ' + name))
300                created_dir = True
301            if not changed_dir and (not unpack_before_chdir or create_dir):
302                self.script.append(self.config.expand('cd ' + name))
303                changed_dir = True
304            self.script.append(self.config.expand(source['script']))
305        if not changed_dir and (unpack_before_chdir and not create_dir):
[bf13d27]306            self.script.append(self.config.expand('cd ' + name))
[9a15c40]307            changed_dir = True
[bf13d27]308        self.script.append(self.config.expand('%{__setup_post}'))
309
[9a15c40]310    def patch_setup(self, package, args):
311        name = args[1]
312        args = args[2:]
313        _map = 'patch-%s' % (name)
314        default_opts = ' '.join(args)
[d3629a9]315        patch_keys = [p for p in self.macros.map_keys(_map) if p != 'setup']
[9a15c40]316        patches = []
317        for p in patch_keys:
318            pm = self.macros.get(p, globals = False, maps = _map)
319            if pm is None:
320                raise error.internal('patch macro not found: %s in %s (%s)' % \
321                                         (p, name, _map))
322            opts = []
323            url = []
324            for pp in pm[2].split():
325                if len(url) == 0 and pp[0] == '-':
326                    opts += [pp]
327                else:
328                    url += [pp]
329            if len(url) == 0:
330                raise error.general('patch URL not found: %s' % (' '.join(args)))
[383f7e6]331            #
332            # Look for --rsb-file as an option we use as a local file name.
333            # This can be used if a URL has no reasonable file name the
334            # download URL parser can figure out.
335            #
336            file_override = None
337            if len(opts) > 0:
338                for o in opts:
339                    if o.startswith('--rsb-file'):
340                       os_ = o.split('=')
341                       if len(os_) != 2:
342                           raise error.general('invalid --rsb-file option: %s' % (' '.join(args)))
343                       if os_[0] != '--rsb-file':
344                           raise error.general('invalid --rsb-file option: %s' % (' '.join(args)))
345                       file_override = os_[1]
346                opts = [o for o in opts if not o.startswith('--rsb-')]
[9a15c40]347            if len(opts) == 0:
348                opts = default_opts
349            else:
350                opts = ' '.join(opts)
351            opts = self.config.expand(opts)
352            url = self.config.expand(' '.join(url))
353            #
354            # Parse the URL first in the source builder's patch directory.
355            #
[383f7e6]356            patch = download.parse_url(url, '_patchdir', self.config, self.opts, file_override)
[9a15c40]357            #
[383f7e6]358            # Download the patch
[9a15c40]359            #
360            download.get_file(patch['url'], patch['local'], self.opts, self.config)
361            if 'compressed' in patch:
362                patch['script'] = patch['compressed'] + ' ' +  patch['local']
363            else:
364                patch['script'] = '%{__cat} ' + patch['local']
365            patch['script'] += ' | %%{__patch} %s' % (opts)
366            self.script.append(self.config.expand(patch['script']))
367
[bf13d27]368    def run(self, command, shell_opts = '', cwd = None):
369        e = execute.capture_execution(log = log.default, dump = self.opts.quiet())
370        cmd = self.config.expand('%{___build_shell} -ex ' + shell_opts + ' ' + command)
[5142bec]371        log.output('run: ' + cmd)
[ab8319a]372        exit_code, proc, output = e.shell(cmd, cwd = path.host(cwd))
[bf13d27]373        if exit_code != 0:
[74da24c]374            log.output('shell cmd failed: %s' % (cmd))
375            raise error.general('building %s' % (self.macros['buildname']))
[bf13d27]376
377    def builddir(self):
378        builddir = self.config.abspath('_builddir')
379        self.rmdir(builddir)
380        if not self.opts.dry_run():
381            self.mkdir(builddir)
382
383    def prep(self, package):
384        self.script.append('echo "==> %prep:"')
385        _prep = package.prep()
[ebf8a1f]386        if _prep:
387            for l in _prep:
388                args = l.split()
[9a15c40]389                if len(args):
[a083b52]390                    def err(msg):
391                        raise error.general('%s: %s' % (package, msg))
[9a15c40]392                    if args[0] == '%setup':
393                        if len(args) == 1:
[6444d58]394                            raise error.general('invalid %%setup directive: %s' % \
395                                                (' '.join(args)))
[9a15c40]396                        if args[1] == 'source':
397                            self.source_setup(package, args[1:])
398                        elif args[1] == 'patch':
399                            self.patch_setup(package, args[1:])
[a083b52]400                    elif args[0] in ['%patch', '%source']:
401                        sources.process(args[0][1:], args[1:], self.macros, err)
402                    elif args[0] == '%hash':
403                        sources.hash(args[1:], self.macros, err)
404                        self.hash(package, args)
[9a15c40]405                    else:
406                        self.script.append(' '.join(args))
[bf13d27]407
408    def build(self, package):
[4f26bdb]409        self.script.append('echo "==> clean %{buildroot}: ${SB_BUILD_ROOT}"')
410        self.script.append('%s ${SB_BUILD_ROOT}' %
411                           (self.config.expand('%{__rmdir}')))
412        self.script.append('%s ${SB_BUILD_ROOT}' %
413                           (self.config.expand('%{__mkdir_p}')))
[bf13d27]414        self.script.append('echo "==> %build:"')
415        _build = package.build()
[ebf8a1f]416        if _build:
417            for l in _build:
418                self.script.append(l)
[bf13d27]419
420    def install(self, package):
421        self.script.append('echo "==> %install:"')
422        _install = package.install()
[ebf8a1f]423        if _install:
424            for l in _install:
425                args = l.split()
426                self.script.append(' '.join(args))
[bf13d27]427
428    def files(self, package):
[ebf8a1f]429        if self.create_tar_files \
430           and not self.macros.get('%{_disable_packaging'):
[cafbcc6]431            self.script.append('echo "==> %files:"')
[0add2ea]432            inpath = path.abspath(self.config.expand('%{buildroot}'))
[cafbcc6]433            tardir = path.abspath(self.config.expand('%{_tardir}'))
434            self.script.append(self.config.expand('if test -d %s; then' % (inpath)))
[4f26bdb]435            self.script.append(self.config.expand('  %%{__mkdir_p} %s' % tardir))
[cafbcc6]436            self.script.append(self.config.expand('  cd ' + inpath))
437            tar = path.join(tardir, package.long_name() + '.tar.bz2')
438            cmd = self.config.expand('  %{__tar} -cf - . ' + '| %{__bzip2} > ' + tar)
439            self.script.append(cmd)
440            self.script.append(self.config.expand('  cd %{_builddir}'))
441            self.script.append('fi')
[bf13d27]442
443    def clean(self, package):
444        self.script.append('echo "==> %clean:"')
445        _clean = package.clean()
446        if _clean is not None:
447            for l in _clean:
448                args = l.split()
449                self.script.append(' '.join(args))
450
[4f26bdb]451    def build_package(self, package):
452        if self.canadian_cross():
[6444d58]453            if not self.config.defined('%{allow_cxc}'):
454                raise error.general('Canadian Cross is not allowed')
[4f26bdb]455            self.script.append('echo "==> Candian-cross build/target:"')
456            self.script.append('SB_CXC="yes"')
457        else:
458            self.script.append('SB_CXC="no"')
459        self.build(package)
460        self.install(package)
461        self.files(package)
462        if not self.opts.no_clean():
463            self.clean(package)
464
[bf13d27]465    def cleanup(self):
[ebf8a1f]466        package = self.main_package()
467        if not package.disabled() and not self.opts.no_clean():
[bf13d27]468            buildroot = self.config.abspath('buildroot')
469            builddir = self.config.abspath('_builddir')
[4f26bdb]470            buildcxcdir = self.config.abspath('_buildcxcdir')
[8d7624e]471            tmproot = self.config.abspath('_tmproot')
[5142bec]472            log.trace('cleanup: %s' % (buildroot))
[bf13d27]473            self.rmdir(buildroot)
[5142bec]474            log.trace('cleanup: %s' % (builddir))
[bf13d27]475            self.rmdir(builddir)
[4f26bdb]476            if self.canadian_cross():
[5142bec]477                log.trace('cleanup: %s' % (buildcxcdir))
[4f26bdb]478                self.rmdir(buildcxcdir)
[5142bec]479            log.trace('cleanup: %s' % (tmproot))
[8d7624e]480            self.rmdir(tmproot)
[bf13d27]481
[54d76bb]482    def main_package(self):
[bf13d27]483        packages = self.config.packages()
[54d76bb]484        return packages['main']
485
[6444d58]486    def reload(self):
487        self.config.load(self.init_name)
488
[54d76bb]489    def make(self):
490        package = self.main_package()
[ebf8a1f]491        if package.disabled():
492            log.notice('package: nothing to build')
[4f26bdb]493        else:
[65d9457]494            try:
495                name = package.name()
[ebf8a1f]496                if self.canadian_cross():
[6444d58]497                    cxc_label = '(Cxc) '
[ebf8a1f]498                else:
[6444d58]499                    cxc_label = ''
500                log.notice('package: %s%s' % (cxc_label, name))
501                log.trace('---- macro maps %s' % ('-' * 55))
502                log.trace('%s' % (str(self.config.macros)))
503                log.trace('-' * 70)
[65d9457]504                self.script.reset()
505                self.script.append(self.config.expand('%{___build_template}'))
506                self.script.append('echo "=> ' + name + ':"')
507                self.prep(package)
508                self.build_package(package)
509                if not self.opts.dry_run():
510                    self.builddir()
511                    sn = path.join(self.config.expand('%{_builddir}'), 'doit')
512                    log.output('write script: ' + sn)
513                    self.script.write(sn)
[6444d58]514                    log.notice('building: %s%s' % (cxc_label, name))
[65d9457]515                    self.run(sn)
[3a972f6]516            except error.general as gerr:
[65d9457]517                log.notice(str(gerr))
518                log.stderr('Build FAILED')
519                self._generate_report_('Build: %s' % (gerr))
520                raise
[3a972f6]521            except error.internal as ierr:
[65d9457]522                log.notice(str(ierr))
523                log.stderr('Internal Build FAILED')
524                self._generate_report_('Build: %s' % (ierr))
525                raise
526            except:
527                raise
528            if self.opts.dry_run():
[53290f9]529                self._generate_report_('Build: dry run (no actual error)',
530                                       'Build: dry run (no actual error)')
[bf13d27]531
532    def name(self):
533        packages = self.config.packages()
534        package = packages['main']
535        return package.name()
536
[ebf8a1f]537    def disabled(self):
538        packages = self.config.packages()
539        package = packages['main']
540        return package.disabled()
541
[cb12e48]542def get_configs(opts):
[e908afb]543
544    def _scan(_path, ext):
545        configs = []
546        for root, dirs, files in os.walk(_path):
547            prefix = root[len(_path) + 1:]
548            for file in files:
[fba1136]549                for e in ext:
550                    if file.endswith(e):
551                        configs += [path.join(prefix, file)]
[e908afb]552        return configs
553
[fba1136]554    configs = { 'paths': [], 'files': [] }
[cb12e48]555    for cp in opts.defaults.expand('%{_configdir}').split(':'):
[bc71066]556        hcp = path.host(path.abspath(cp))
557        configs['paths'] += [hcp]
558        configs['files'] += _scan(hcp, ['.cfg', '.bset'])
[4900498]559    configs['files'] = sorted(set(configs['files']))
[fba1136]560    return configs
[71b8893]561
[0759d98]562def find_config(config, configs):
563    config_root, config_ext = path.splitext(config)
564    if config_ext not in ['', '.bset', '.cfg']:
565        config_root = config
566        config_ext = ''
567    for c in configs['files']:
568        r, e = path.splitext(c)
569        if config_root == r:
570            if config_ext == '' or config_ext == e:
571                return c
572    return None
573
[bf13d27]574def run(args):
[74da24c]575    ec = 0
[bf13d27]576    try:
[71b8893]577        optargs = { '--list-configs': 'List available configurations' }
[cb12e48]578        opts = options.load(args, optargs)
[47d703fd]579        log.notice('RTEMS Source Builder, Package Builder, %s' % (version.str()))
[610ae57]580        opts.log_info()
[cb12e48]581        if not check.host_setup(opts):
[71b8893]582            if not opts.force():
[0add2ea]583                raise error.general('host build environment is not set up' +
[4f26bdb]584                                    ' correctly (use --force to proceed)')
[5142bec]585            log.notice('warning: forcing build with known host setup problems')
[71b8893]586        if opts.get_arg('--list-configs'):
[cb12e48]587            configs = get_configs(opts)
[cafbcc6]588            for p in configs['paths']:
[3a972f6]589                print('Examining: %s' % (os.path.relpath(p)))
[cafbcc6]590                for c in configs['files']:
591                    if c.endswith('.cfg'):
[3a972f6]592                        print('    %s' % (c))
[71b8893]593        else:
594            for config_file in opts.config_files():
[cb12e48]595                b = build(config_file, True, opts)
[71b8893]596                b.make()
[74da24c]597                b = None
[3a972f6]598    except error.general as gerr:
[74da24c]599        log.stderr('Build FAILED')
600        ec = 1
[3a972f6]601    except error.internal as ierr:
[74da24c]602        log.stderr('Internal Build FAILED')
603        ec = 1
[3a972f6]604    except error.exit as eerr:
[bf13d27]605        pass
606    except KeyboardInterrupt:
[5142bec]607        log.notice('abort: user terminated')
[74da24c]608        ec = 1
609    sys.exit(ec)
[bf13d27]610
611if __name__ == "__main__":
612    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.