source: rtems-source-builder/source-builder/sb/options.py @ f936478

4.104.95
Last change on this file since f936478 was 6444d58, checked in by Chris Johns <chrisj@…>, on 07/20/15 at 03:49:42

Canandian Cross Compiling and RTEMS 3rd party package building Fixes.

The change fixes installing for RTEMS 3rd Party packages where the
RSB considered them Canadian Cross Compiling (Cxc). Fixing the
Cxc issue broke real Cxc builds. The change corrects the issue of
macros being changed in the Cxc and the prep data not being udpated.
The configuration is loaded again after the updated macros. The
macros are also copied and restored to ensure a clean stable base.

The change also introduces --rtems-tools and --rtems-bsp to align
the command line with the waf configure process or RTEMS application.

  • Property mode set to 100644
File size: 24.3 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# Determine the defaults and load the specific file.
22#
23
24import datetime
25import glob
26import pprint
27import re
28import os
29import string
30
31import error
32import execute
33import git
34import log
35import macros
36import path
37import sys
38
39import version
40
41basepath = 'sb'
42
43#
44# Save the host state.
45#
46host_windows = False
47
48class command_line:
49    """Process the command line in a common way for all Tool Builder commands."""
50
51    def __init__(self, argv, optargs, _defaults, command_path):
52        self._long_opts = {
53            # key                 macro                handler            param  defs   init
54            '--prefix'         : ('_prefix',           self._lo_path,     True,  None,  False),
55            '--topdir'         : ('_topdir',           self._lo_path,     True,  None,  False),
56            '--configdir'      : ('_configdir',        self._lo_path,     True,  None,  False),
57            '--builddir'       : ('_builddir',         self._lo_path,     True,  None,  False),
58            '--sourcedir'      : ('_sourcedir',        self._lo_path,     True,  None,  False),
59            '--tmppath'        : ('_tmppath',          self._lo_path,     True,  None,  False),
60            '--jobs'           : ('_jobs',             self._lo_jobs,     True,  'max', True),
61            '--log'            : ('_logfile',          self._lo_string,   True,  None,  False),
62            '--url'            : ('_url_base',         self._lo_string,   True,  None,  False),
63            '--no-download'    : ('_disable_download', self._lo_bool,     False, '0',   True),
64            '--macros'         : ('_macros',           self._lo_string,   True,  None,  False),
65            '--targetcflags'   : ('_targetcflags',     self._lo_string,   True,  None,  False),
66            '--targetcxxflags' : ('_targetcxxflags',   self._lo_string,   True,  None,  False),
67            '--libstdcxxflags' : ('_libstdcxxflags',   self._lo_string,   True,  None,  False),
68            '--force'          : ('_force',            self._lo_bool,     False, '0',   True),
69            '--quiet'          : ('_quiet',            self._lo_bool,     False, '0',   True),
70            '--trace'          : ('_trace',            self._lo_bool,     False, '0',   True),
71            '--dry-run'        : ('_dry_run',          self._lo_bool,     False, '0',   True),
72            '--warn-all'       : ('_warn_all',         self._lo_bool,     False, '0',   True),
73            '--no-clean'       : ('_no_clean',         self._lo_bool,     False, '0',   True),
74            '--keep-going'     : ('_keep_going',       self._lo_bool,     False, '0',   True),
75            '--always-clean'   : ('_always_clean',     self._lo_bool,     False, '0',   True),
76            '--no-install'     : ('_no_install',       self._lo_bool,     False, '0',   True),
77            '--regression'     : ('_regression',       self._lo_bool,     False, '0',   True),
78            '--host'           : ('_host',             self._lo_triplets, True,  None,  False),
79            '--build'          : ('_build',            self._lo_triplets, True,  None,  False),
80            '--target'         : ('_target',           self._lo_triplets, True,  None,  False),
81            '--help'           : (None,                self._lo_help,     False, None,  False)
82            }
83
84        self.command_path = command_path
85        self.command_name = path.basename(argv[0])
86        self.argv = argv
87        self.args = argv[1:]
88        self.optargs = optargs
89        self.defaults = _defaults
90        self.opts = { 'params' : [] }
91        for lo in self._long_opts:
92            self.opts[lo[2:]] = self._long_opts[lo][3]
93            if self._long_opts[lo][4]:
94                self.defaults[self._long_opts[lo][0]] = ('none', 'none', self._long_opts[lo][3])
95
96    def __str__(self):
97        def _dict(dd):
98            s = ''
99            ddl = dd.keys()
100            ddl.sort()
101            for d in ddl:
102                s += '  ' + d + ': ' + str(dd[d]) + '\n'
103            return s
104
105        s = 'command: ' + self.command() + \
106            '\nargs: ' + str(self.args) + \
107            '\nopts:\n' + _dict(self.opts)
108
109        return s
110
111    def _lo_string(self, opt, macro, value):
112        if value is None:
113            raise error.general('option requires a value: %s' % (opt))
114        self.opts[opt[2:]] = value
115        self.defaults[macro] = value
116
117    def _lo_path(self, opt, macro, value):
118        if value is None:
119            raise error.general('option requires a path: %s' % (opt))
120        value = path.abspath(value)
121        self.opts[opt[2:]] = value
122        self.defaults[macro] = value
123
124    def _lo_jobs(self, opt, macro, value):
125        if value is None:
126            raise error.general('option requires a value: %s' % (opt))
127        ok = False
128        if value in ['max', 'none', 'half']:
129            ok = True
130        else:
131            try:
132                i = int(value)
133                ok = True
134            except:
135                pass
136            if not ok:
137                try:
138                    f = float(value)
139                    ok = True
140                except:
141                    pass
142        if not ok:
143            raise error.general('invalid jobs option: %s' % (value))
144        self.defaults[macro] = value
145        self.opts[opt[2:]] = value
146
147    def _lo_bool(self, opt, macro, value):
148        if value is not None:
149            raise error.general('option does not take a value: %s' % (opt))
150        self.opts[opt[2:]] = '1'
151        self.defaults[macro] = '1'
152
153    def _lo_triplets(self, opt, macro, value):
154        #
155        # This is a target triplet. Run it past config.sub to make make sure it
156        # is ok.  The target triplet is 'cpu-vendor-os'.
157        #
158        e = execute.capture_execution()
159        config_sub = path.join(self.command_path,
160                               basepath, 'config.sub')
161        exit_code, proc, output = e.shell(config_sub + ' ' + value)
162        if exit_code == 0:
163            value = output
164        self.defaults[macro] = ('triplet', 'none', value)
165        self.opts[opt[2:]] = value
166        _cpu = macro + '_cpu'
167        _arch = macro + '_arch'
168        _vendor = macro + '_vendor'
169        _os = macro + '_os'
170        _arch_value = ''
171        _vendor_value = ''
172        _os_value = ''
173        dash = value.find('-')
174        if dash >= 0:
175            _arch_value = value[:dash]
176            value = value[dash + 1:]
177        dash = value.find('-')
178        if dash >= 0:
179            _vendor_value = value[:dash]
180            value = value[dash + 1:]
181        if len(value):
182            _os_value = value
183        self.defaults[_cpu]    = _arch_value
184        self.defaults[_arch]   = _arch_value
185        self.defaults[_vendor] = _vendor_value
186        self.defaults[_os]     = _os_value
187
188    def _lo_help(self, opt, macro, value):
189        self.help()
190
191    def help(self):
192        print '%s: [options] [args]' % (self.command_name)
193        print 'RTEMS Source Builder, an RTEMS Tools Project (c) 2012-2015 Chris Johns'
194        print 'Options and arguments:'
195        print '--force                : Force the build to proceed'
196        print '--quiet                : Quiet output (not used)'
197        print '--trace                : Trace the execution'
198        print '--dry-run              : Do everything but actually run the build'
199        print '--warn-all             : Generate warnings'
200        print '--no-clean             : Do not clean up the build tree'
201        print '--always-clean         : Always clean the build tree, even with an error'
202        print '--keep-going           : Do not stop on an error.'
203        print '--regression           : Set --no-install, --keep-going and --always-clean'
204        print '--jobs                 : Run with specified number of jobs, default: num CPUs.'
205        print '--host                 : Set the host triplet'
206        print '--build                : Set the build triplet'
207        print '--target               : Set the target triplet'
208        print '--prefix path          : Tools build prefix, ie where they are installed'
209        print '--topdir path          : Top of the build tree, default is $PWD'
210        print '--configdir path       : Path to the configuration directory, default: ./config'
211        print '--builddir path        : Path to the build directory, default: ./build'
212        print '--sourcedir path       : Path to the source directory, default: ./source'
213        print '--tmppath path         : Path to the temp directory, default: ./tmp'
214        print '--macros file[,[file]  : Macro format files to load after the defaults'
215        print '--log file             : Log file where all build out is written too'
216        print '--url url[,url]        : URL to look for source'
217        print '--no-download          : Disable the source downloader'
218        print '--no-install           : Do not install the packages to the prefix'
219        print '--targetcflags flags   : List of C flags for the target code'
220        print '--targetcxxflags flags : List of C++ flags for the target code'
221        print '--libstdcxxflags flags : List of C++ flags to build the target libstdc++ code'
222        print '--with-<label>         : Add the --with-<label> to the build'
223        print '--without-<label>      : Add the --without-<label> to the build'
224        print '--rtems-tools path     : Path to an install RTEMS tool set'
225        print '--rtems-bsp arc/bsp    : Standard RTEMS architecure and BSP specifier'
226        print '--rtems-version ver    : The RTEMS major/minor version string'
227        if self.optargs:
228            for a in self.optargs:
229                print '%-22s : %s' % (a, self.optargs[a])
230        raise error.exit()
231
232    def process(self):
233        arg = 0
234        while arg < len(self.args):
235            a = self.args[arg]
236            if a == '-?':
237                self.help()
238            elif a.startswith('--'):
239                los = a.split('=')
240                lo = los[0]
241                if lo in self._long_opts:
242                    long_opt = self._long_opts[lo]
243                    if len(los) == 1:
244                        if long_opt[2]:
245                            if arg == len(self.args) - 1:
246                                raise error.general('option requires a parameter: %s' % (lo))
247                            arg += 1
248                            value = self.args[arg]
249                        else:
250                            value = None
251                    else:
252                        value = '='.join(los[1:])
253                    long_opt[1](lo, long_opt[0], value)
254            else:
255                self.opts['params'].append(a)
256            arg += 1
257
258    def post_process(self):
259        # Handle the log first.
260        log.default = log.log(self.logfiles())
261        if self.trace():
262            log.tracing = True
263        if self.quiet():
264            log.quiet = True
265        # Must have a host
266        if self.defaults['_host'] == self.defaults['nil']:
267            raise error.general('--host not set')
268        # Must have a host
269        if self.defaults['_build'] == self.defaults['nil']:
270            raise error.general('--build not set')
271        # Manage the regression option
272        if self.opts['regression'] != '0':
273            self.opts['no-install'] = '1'
274            self.defaults['_no_install'] = '1'
275            self.opts['keep-going'] = '1'
276            self.defaults['_keep_going'] = '1'
277            self.opts['always-clean'] = '1'
278            self.defaults['_always_clean'] = '1'
279        # Handle the jobs for make
280        if '_ncpus' not in self.defaults:
281            raise error.general('host number of CPUs not set')
282        ncpus = self.jobs(self.defaults['_ncpus'])
283        if ncpus > 1:
284            self.defaults['_smp_mflags'] = '-j %d' % (ncpus)
285        else:
286            self.defaults['_smp_mflags'] = self.defaults['nil']
287        # Load user macro files
288        um = self.user_macros()
289        if um:
290            checked = path.exists(um)
291            if False in checked:
292                raise error.general('macro file not found: %s' % (um[checked.index(False)]))
293            for m in um:
294                self.defaults.load(m)
295        # Check if the user has a private set of macros to load
296        if 'RSB_MACROS' in os.environ:
297            if path.exists(os.environ['RSB_MACROS']):
298                self.defaults.load(os.environ['RSB_MACROS'])
299        if 'HOME' in os.environ:
300            rsb_macros = path.join(os.environ['HOME'], '.rsb_macros')
301            if path.exists(rsb_macros):
302                self.defaults.load(rsb_macros)
303
304    def sb_git(self):
305        repo = git.repo(self.defaults.expand('%{_sbdir}'), self)
306        if repo.valid():
307            repo_valid = '1'
308            repo_head = repo.head()
309            repo_clean = not repo.dirty()
310            repo_remotes = '%{nil}'
311            remotes = repo.remotes()
312            if 'origin' in remotes:
313                repo_remotes = '%s/origin' % (remotes['origin']['url'])
314            repo_id = repo_head
315            if not repo_clean:
316                repo_id += '-modified'
317            repo_mail = repo.email()
318        else:
319            repo_valid = '0'
320            repo_head = '%{nil}'
321            repo_clean = '%{nil}'
322            repo_remotes = '%{nil}'
323            repo_id = 'no-repo'
324            repo_mail = None
325        self.defaults['_sbgit_valid'] = repo_valid
326        self.defaults['_sbgit_head']  = repo_head
327        self.defaults['_sbgit_clean'] = str(repo_clean)
328        self.defaults['_sbgit_remotes'] = str(repo_remotes)
329        self.defaults['_sbgit_id']    = repo_id
330        if repo_mail is not None:
331            self.defaults['_sbgit_mail'] = repo_mail
332
333    def command(self):
334        return path.join(self.command_path, self.command_name)
335
336    def force(self):
337        return self.opts['force'] != '0'
338
339    def dry_run(self):
340        return self.opts['dry-run'] != '0'
341
342    def set_dry_run(self):
343        self.opts['dry-run'] = '1'
344
345    def quiet(self):
346        return self.opts['quiet'] != '0'
347
348    def trace(self):
349        return self.opts['trace'] != '0'
350
351    def warn_all(self):
352        return self.opts['warn-all'] != '0'
353
354    def keep_going(self):
355        return self.opts['keep-going'] != '0'
356
357    def no_clean(self):
358        return self.opts['no-clean'] != '0'
359
360    def always_clean(self):
361        return self.opts['always-clean'] != '0'
362
363    def no_install(self):
364        return self.opts['no-install'] != '0'
365
366    def canadian_cross(self):
367        _host = self.defaults.expand('%{_host}')
368        _build = self.defaults.expand('%{_build}')
369        _target = self.defaults.expand('%{_target}')
370        if len(_target):
371            return len(_host) and len(_build) and (_target) and \
372                _host != _build and _host != _target
373        return len(_host) and len(_build) and _host != _build
374
375    def user_macros(self):
376        #
377        # Return something even if it does not exist.
378        #
379        if self.opts['macros'] is None:
380            return None
381        um = []
382        configs = self.defaults.expand('%{_configdir}').split(':')
383        for m in self.opts['macros'].split(','):
384            if path.exists(m):
385                um += [m]
386            else:
387                # Get the expanded config macros then check them.
388                cm = path.expand(m, configs)
389                ccm = path.exists(cm)
390                if True in ccm:
391                    # Pick the first found
392                    um += [cm[ccm.index(True)]]
393                else:
394                    um += [m]
395        return um if len(um) else None
396
397    def jobs(self, cpus):
398        cpus = int(cpus)
399        if self.opts['jobs'] == 'none':
400            cpus = 0
401        elif self.opts['jobs'] == 'max':
402            pass
403        elif self.opts['jobs'] == 'half':
404            cpus = cpus / 2
405        else:
406            ok = False
407            try:
408                i = int(self.opts['jobs'])
409                cpus = i
410                ok = True
411            except:
412                pass
413            if not ok:
414                try:
415                    f = float(self.opts['jobs'])
416                    cpus = f * cpus
417                    ok = True
418                except:
419                    pass
420                if not ok:
421                    raise error.internal('bad jobs option: %s' % (self.opts['jobs']))
422        if cpus <= 0:
423            cpu = 1
424        return cpus
425
426    def params(self):
427        return self.opts['params']
428
429    def parse_args(self, arg, error = True, extra = True):
430        for a in range(0, len(self.args)):
431            if self.args[a].startswith(arg):
432                lhs = None
433                rhs = None
434                if '=' in self.args[a]:
435                    eqs = self.args[a].split('=')
436                    lhs = eqs[0]
437                    if len(eqs) > 2:
438                        rhs = '='.join(eqs[1:])
439                    else:
440                        rhs = eqs[1]
441                elif extra:
442                    lhs = self.args[a]
443                    a += 1
444                    if a < len(self.args):
445                        rhs = self.args[a]
446                return [lhs, rhs]
447            a += 1
448        return None
449
450    def get_arg(self, arg):
451        if self.optargs is None or arg not in self.optargs:
452            return None
453        return self.parse_args(arg)
454
455    def with_arg(self, label):
456        for pre in ['with', 'without']:
457            arg_str = '--%s-%s' % (pre, label)
458            arg_label = '%s_%s' % (pre, label)
459            arg = self.parse_args(arg_str, error = False, extra = False)
460            if arg is not None:
461                if arg[1] is  None:
462                    result = 'yes'
463                else:
464                    result = arg[1]
465                break
466            if pre == 'with':
467                result = 'yes'
468            else:
469                result = 'no'
470        return [arg_label, result]
471
472    def get_config_files(self, config):
473        #
474        # Convert to shell paths and return shell paths.
475        #
476        # @fixme should this use a passed in set of defaults and not
477        #        not the initial set of values ?
478        #
479        config = path.shell(config)
480        if '*' in config or '?' in config:
481            print config
482            configdir = path.dirname(config)
483            configbase = path.basename(config)
484            if len(configbase) == 0:
485                configbase = '*'
486            if not configbase.endswith('.cfg'):
487                configbase = configbase + '.cfg'
488            if len(configdir) == 0:
489                configdir = self.macros.expand(self.defaults['_configdir'])
490            configs = []
491            for cp in configdir.split(':'):
492                hostconfigdir = path.host(cp)
493                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
494                    configs += path.shell(f)
495        else:
496            configs = [config]
497        return configs
498
499    def config_files(self):
500        configs = []
501        for config in self.opts['params']:
502            configs.extend(self.get_config_files(config))
503        return configs
504
505    def logfiles(self):
506        if 'log' in self.opts and self.opts['log'] is not None:
507            return self.opts['log'].split(',')
508        return ['rsb-log-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))]
509
510    def urls(self):
511        if self.opts['url'] is not None:
512            return self.opts['url'].split(',')
513        return None
514
515    def download_disabled(self):
516        return self.opts['no-download'] != '0'
517
518    def disable_install(self):
519        self.opts['no-install'] = '1'
520
521    def info(self):
522        s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep)
523        s += ' Python: %s' % (sys.version.replace('\n', ''))
524        return s
525
526    def log_info(self):
527        log.output(self.info())
528
529    def rtems_options(self):
530        # Check for RTEMS specific helper options.
531        rtems_tools = self.parse_args('--rtems-tools')
532        if rtems_tools is not None:
533            if self.get_arg('--with-tools') is not None:
534                raise error.general('--rtems-tools and --with-tools cannot be used together')
535            self.args.append('--with-tools=%s' % (rtems_tools[1]))
536        rtems_arch_bsp = self.parse_args('--rtems-bsp')
537        if rtems_arch_bsp is not None:
538            if self.get_arg('--target') is not None:
539                raise error.general('--rtems-bsp and --target cannot be used together')
540            ab = rtems_arch_bsp[1].split('/')
541            if len(ab) != 2:
542                raise error.general('invalid --rtems-bsp option')
543            rtems_version = self.parse_args('--rtems-version')
544            if rtems_version is None:
545                rtems_version = '%d.%d' % (version.major, version.minor)
546            else:
547                rtems_version = rtems_version[1]
548            self.args.append('--target=%s-rtems%s' % (ab[0], rtems_version))
549            self.args.append('--with-rtems-bsp=%s' % (ab[1]))
550
551def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
552    """
553    Copy the defaults, get the host specific values and merge them overriding
554    any matching defaults, then create an options object to handle the command
555    line merging in any command line overrides. Finally post process the
556    command line.
557    """
558
559    global host_windows
560
561    #
562    # The path to this command.
563    #
564    command_path = path.dirname(args[0])
565    if len(command_path) == 0:
566        command_path = '.'
567
568    #
569    # The command line contains the base defaults object all build objects copy
570    # and modify by loading a configuration.
571    #
572    o = command_line(args,
573                     optargs,
574                     macros.macros(name = defaults,
575                                   sbdir = command_path),
576                     command_path)
577
578    overrides = None
579    if os.name == 'nt':
580        try:
581            import windows
582            overrides = windows.load()
583            host_windows = True
584        except:
585            raise error.general('failed to load Windows host support')
586    elif os.name == 'posix':
587        uname = os.uname()
588        try:
589            if uname[0].startswith('CYGWIN_NT'):
590                import windows
591                overrides = windows.load()
592            elif uname[0] == 'Darwin':
593                import darwin
594                overrides = darwin.load()
595            elif uname[0] == 'FreeBSD':
596                import freebsd
597                overrides = freebsd.load()
598            elif uname[0] == 'NetBSD':
599                import netbsd
600                overrides = netbsd.load()
601            elif uname[0] == 'Linux':
602                import linux
603                overrides = linux.load()
604            elif uname[0] == 'SunOS':
605                import solaris
606                overrides = solaris.load()
607        except:
608            raise error.general('failed to load %s host support' % (uname[0]))
609    else:
610        raise error.general('unsupported host type; please add')
611    if overrides is None:
612        raise error.general('no hosts defaults found; please add')
613    for k in overrides:
614        o.defaults[k] = overrides[k]
615
616    o.sb_git()
617    o.rtems_options()
618    o.process()
619    o.post_process()
620
621    return o
622
623def run(args):
624    try:
625        _opts = load(args = args, defaults = 'defaults.mc')
626        log.notice('RTEMS Source Builder - Defaults, v%s' % (version.str()))
627        _opts.log_info()
628        log.notice('Options:')
629        log.notice(str(_opts))
630        log.notice('Defaults:')
631        log.notice(str(_opts.defaults))
632        log.notice('with-opt1: %r' % (_opts.with_arg('opt1')))
633        log.notice('without-opt2: %r' % (_opts.with_arg('opt2')))
634    except error.general, gerr:
635        print gerr
636        sys.exit(1)
637    except error.internal, ierr:
638        print ierr
639        sys.exit(1)
640    except error.exit, eerr:
641        pass
642    except KeyboardInterrupt:
643        _notice(opts, 'abort: user terminated')
644        sys.exit(1)
645    sys.exit(0)
646
647if __name__ == '__main__':
648    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.