source: rtems-source-builder/source-builder/sb/build.py @ 5fdd664

4.104.114.95
Last change on this file since 5fdd664 was 5fdd664, checked in by Chris Johns <chrisj@…>, on 10/31/14 at 04:10:00

sb: Convert the '-' to '_' in symlinks for git repos.

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