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

4.104.114.95
Last change on this file since d3629a9 was d3629a9, checked in by Chris Johns <chrisj@…>, on 08/12/14 at 02:59:22

sb: Fix sources related bugs.

Updates the sources module introduced bugs in the build and download
modules. The commit fixes those modules.

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