source: rtems-source-builder/source-builder/sb/build.py @ 53290f9

4.104.114.95
Last change on this file since 53290f9 was 53290f9, checked in by Chris Johns <chrisj@…>, on 08/13/14 at 21:10:52

sb: Add a dry run footer to ereports.

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