source: rtems-source-builder/source-builder/sb/build.py @ 7518590

4.11
Last change on this file since 7518590 was 7518590, checked in by Chris Johns <chrisj@…>, on Feb 22, 2016 at 6:37:37 AM

Fix sb-build to reference the opts correctly.

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