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

4.104.9
Last change on this file since 383f7e6 was 383f7e6, checked in by Chris Johns <chrisj@…>, on Apr 15, 2016 at 1:44:27 AM

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
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
25from __future__ import print_function
26
27import copy
28import getopt
29import glob
30import os
31import shutil
32import stat
33import sys
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 as 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 != 'yes') or \
116           (label.startswith('with_') and result != 'no'):
117            ereport.generate('rsb-report-%s.txt' % self.macros['name'],
118                             self.opts, header, footer)
119
120    def __init__(self, name, create_tar_files, opts, macros = None):
121        try:
122            self.opts = opts
123            self.init_name = name
124            self.init_macros = macros
125            self.config = None
126            self.create_tar_files = create_tar_files
127            log.notice('config: ' + name)
128            self.set_macros(macros)
129            self.config = config.file(name, opts, self.macros)
130            self.script = script()
131            self.macros['buildname'] = self._name_(self.macros['name'])
132        except error.general as gerr:
133            log.notice(str(gerr))
134            log.stderr('Build FAILED')
135            raise
136        except error.internal as ierr:
137            log.notice(str(ierr))
138            log.stderr('Internal Build FAILED')
139            raise
140        except:
141            raise
142
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:
151            self.macros = copy.copy(self.opts.defaults)
152        else:
153            self.macros = copy.copy(macros)
154        if self.config:
155            self.config.set_macros(self.macros)
156
157    def rmdir(self, rmpath):
158        log.output('removing: %s' % (path.host(rmpath)))
159        if not self.opts.dry_run():
160            if path.exists(rmpath):
161                path.removeall(rmpath)
162
163    def mkdir(self, mkpath):
164        log.output('making dir: %s' % (path.host(mkpath)))
165        if not self.opts.dry_run():
166            path.mkdir(mkpath)
167
168    def canadian_cross(self):
169        _host = self.config.expand('%{_host}')
170        _build = self.config.expand('%{_build}')
171        _target = self.config.expand('%{_target}')
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
196
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)
203        src_keys = [s for s in self.macros.map_keys(_map) if s != 'setup']
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))
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)
239            download.get_file(src['url'], src['local'], self.opts, self.config)
240            if 'symlink' in src:
241                sname = name.replace('-', '_')
242                src['script'] = '%%{__ln_s} %s ${source_dir_%s}' % (src['symlink'], sname)
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:
251                src['script'] = '%%{__tar_extract} %s' % (src['local'])
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:
260            opts, args = getopt.getopt(args[1:], 'qDcn:ba')
261        except getopt.GetoptError as ge:
262            raise error.general('source setup error: %s' % str(ge))
263        quiet = False
264        unpack_before_chdir = True
265        delete_before_unpack = True
266        create_dir = False
267        deleted_dir = False
268        created_dir = False
269        changed_dir = False
270        opt_name = None
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':
279                opt_name = o[1]
280            elif o[0] == '-b':
281                unpack_before_chdir = True
282            elif o[0] == '-a':
283                unpack_before_chdir = False
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):
306            self.script.append(self.config.expand('cd ' + name))
307            changed_dir = True
308        self.script.append(self.config.expand('%{__setup_post}'))
309
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)
315        patch_keys = [p for p in self.macros.map_keys(_map) if p != 'setup']
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)))
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-')]
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            #
356            patch = download.parse_url(url, '_patchdir', self.config, self.opts, file_override)
357            #
358            # Download the patch
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
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)
371        log.output('run: ' + cmd)
372        exit_code, proc, output = e.shell(cmd, cwd = path.host(cwd))
373        if exit_code != 0:
374            log.output('shell cmd failed: %s' % (cmd))
375            raise error.general('building %s' % (self.macros['buildname']))
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()
386        if _prep:
387            for l in _prep:
388                args = l.split()
389                if len(args):
390                    def err(msg):
391                        raise error.general('%s: %s' % (package, msg))
392                    if args[0] == '%setup':
393                        if len(args) == 1:
394                            raise error.general('invalid %%setup directive: %s' % \
395                                                (' '.join(args)))
396                        if args[1] == 'source':
397                            self.source_setup(package, args[1:])
398                        elif args[1] == 'patch':
399                            self.patch_setup(package, args[1:])
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)
405                    else:
406                        self.script.append(' '.join(args))
407
408    def build(self, package):
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}')))
414        self.script.append('echo "==> %build:"')
415        _build = package.build()
416        if _build:
417            for l in _build:
418                self.script.append(l)
419
420    def install(self, package):
421        self.script.append('echo "==> %install:"')
422        _install = package.install()
423        if _install:
424            for l in _install:
425                args = l.split()
426                self.script.append(' '.join(args))
427
428    def files(self, package):
429        if self.create_tar_files \
430           and not self.macros.get('%{_disable_packaging'):
431            self.script.append('echo "==> %files:"')
432            inpath = path.abspath(self.config.expand('%{buildroot}'))
433            tardir = path.abspath(self.config.expand('%{_tardir}'))
434            self.script.append(self.config.expand('if test -d %s; then' % (inpath)))
435            self.script.append(self.config.expand(%%{__mkdir_p} %s' % tardir))
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')
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
451    def build_package(self, package):
452        if self.canadian_cross():
453            if not self.config.defined('%{allow_cxc}'):
454                raise error.general('Canadian Cross is not allowed')
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
465    def cleanup(self):
466        package = self.main_package()
467        if not package.disabled() and not self.opts.no_clean():
468            buildroot = self.config.abspath('buildroot')
469            builddir = self.config.abspath('_builddir')
470            buildcxcdir = self.config.abspath('_buildcxcdir')
471            tmproot = self.config.abspath('_tmproot')
472            log.trace('cleanup: %s' % (buildroot))
473            self.rmdir(buildroot)
474            log.trace('cleanup: %s' % (builddir))
475            self.rmdir(builddir)
476            if self.canadian_cross():
477                log.trace('cleanup: %s' % (buildcxcdir))
478                self.rmdir(buildcxcdir)
479            log.trace('cleanup: %s' % (tmproot))
480            self.rmdir(tmproot)
481
482    def main_package(self):
483        packages = self.config.packages()
484        return packages['main']
485
486    def reload(self):
487        self.config.load(self.init_name)
488
489    def make(self):
490        package = self.main_package()
491        if package.disabled():
492            log.notice('package: nothing to build')
493        else:
494            try:
495                name = package.name()
496                if self.canadian_cross():
497                    cxc_label = '(Cxc) '
498                else:
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)
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)
514                    log.notice('building: %s%s' % (cxc_label, name))
515                    self.run(sn)
516            except error.general as gerr:
517                log.notice(str(gerr))
518                log.stderr('Build FAILED')
519                self._generate_report_('Build: %s' % (gerr))
520                raise
521            except error.internal as ierr:
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():
529                self._generate_report_('Build: dry run (no actual error)',
530                                       'Build: dry run (no actual error)')
531
532    def name(self):
533        packages = self.config.packages()
534        package = packages['main']
535        return package.name()
536
537    def disabled(self):
538        packages = self.config.packages()
539        package = packages['main']
540        return package.disabled()
541
542def get_configs(opts):
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:
549                for e in ext:
550                    if file.endswith(e):
551                        configs += [path.join(prefix, file)]
552        return configs
553
554    configs = { 'paths': [], 'files': [] }
555    for cp in opts.defaults.expand('%{_configdir}').split(':'):
556        hcp = path.host(path.abspath(cp))
557        configs['paths'] += [hcp]
558        configs['files'] += _scan(hcp, ['.cfg', '.bset'])
559    configs['files'] = sorted(set(configs['files']))
560    return configs
561
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
574def run(args):
575    ec = 0
576    try:
577        optargs = { '--list-configs': 'List available configurations' }
578        opts = options.load(args, optargs)
579        log.notice('RTEMS Source Builder, Package Builder, %s' % (version.str()))
580        opts.log_info()
581        if not check.host_setup(opts):
582            if not opts.force():
583                raise error.general('host build environment is not set up' +
584                                    ' correctly (use --force to proceed)')
585            log.notice('warning: forcing build with known host setup problems')
586        if opts.get_arg('--list-configs'):
587            configs = get_configs(opts)
588            for p in configs['paths']:
589                print('Examining: %s' % (os.path.relpath(p)))
590                for c in configs['files']:
591                    if c.endswith('.cfg'):
592                        print('    %s' % (c))
593        else:
594            for config_file in opts.config_files():
595                b = build(config_file, True, opts)
596                b.make()
597                b = None
598    except error.general as gerr:
599        log.stderr('Build FAILED')
600        ec = 1
601    except error.internal as ierr:
602        log.stderr('Internal Build FAILED')
603        ec = 1
604    except error.exit as eerr:
605        pass
606    except KeyboardInterrupt:
607        log.notice('abort: user terminated')
608        ec = 1
609    sys.exit(ec)
610
611if __name__ == "__main__":
612    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.