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

4.104.114.95
Last change on this file since ba0008c was ba0008c, checked in by Chris Johns <chrisj@…>, on 03/20/15 at 02:57:27

sb: Do not accept an extra separate option for --with/--without.

The --with/--without that does not have an extra option was
consuming an extra option on the command line.

  • 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# 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        if self.optargs:
225            for a in self.optargs:
226                print '%-22s : %s' % (a, self.optargs[a])
227        raise error.exit()
228
229    def process(self):
230        arg = 0
231        while arg < len(self.args):
232            a = self.args[arg]
233            if a == '-?':
234                self.help()
235            elif a.startswith('--'):
236                los = a.split('=')
237                lo = los[0]
238                if lo in self._long_opts:
239                    long_opt = self._long_opts[lo]
240                    if len(los) == 1:
241                        if long_opt[2]:
242                            if arg == len(self.args) - 1:
243                                raise error.general('option requires a parameter: %s' % (lo))
244                            arg += 1
245                            value = self.args[arg]
246                        else:
247                            value = None
248                    else:
249                        value = '='.join(los[1:])
250                    long_opt[1](lo, long_opt[0], value)
251            else:
252                self.opts['params'].append(a)
253            arg += 1
254
255    def post_process(self):
256        # Handle the log first.
257        log.default = log.log(self.logfiles())
258        if self.trace():
259            log.tracing = True
260        if self.quiet():
261            log.quiet = True
262        # Must have a host
263        if self.defaults['_host'] == self.defaults['nil']:
264            raise error.general('--host not set')
265        # Must have a host
266        if self.defaults['_build'] == self.defaults['nil']:
267            raise error.general('--build not set')
268        # Manage the regression option
269        if self.opts['regression'] != '0':
270            self.opts['no-install'] = '1'
271            self.defaults['_no_install'] = '1'
272            self.opts['keep-going'] = '1'
273            self.defaults['_keep_going'] = '1'
274            self.opts['always-clean'] = '1'
275            self.defaults['_always_clean'] = '1'
276        # Handle the jobs for make
277        if '_ncpus' not in self.defaults:
278            raise error.general('host number of CPUs not set')
279        ncpus = self.jobs(self.defaults['_ncpus'])
280        if ncpus > 1:
281            self.defaults['_smp_mflags'] = '-j %d' % (ncpus)
282        else:
283            self.defaults['_smp_mflags'] = self.defaults['nil']
284        # Load user macro files
285        um = self.user_macros()
286        if um:
287            checked = path.exists(um)
288            if False in checked:
289                raise error.general('macro file not found: %s' % (um[checked.index(False)]))
290            for m in um:
291                self.defaults.load(m)
292        # Check if the user has a private set of macros to load
293        if 'RSB_MACROS' in os.environ:
294            if path.exists(os.environ['RSB_MACROS']):
295                self.defaults.load(os.environ['RSB_MACROS'])
296        if 'HOME' in os.environ:
297            rsb_macros = path.join(os.environ['HOME'], '.rsb_macros')
298            if path.exists(rsb_macros):
299                self.defaults.load(rsb_macros)
300        # If a Cxc build disable installing.
301        if self.canadian_cross():
302            self.opts['no-install'] = '1'
303            self.defaults['_no_install'] = '1'
304
305    def sb_git(self):
306        repo = git.repo(self.defaults.expand('%{_sbdir}'), self)
307        if repo.valid():
308            repo_valid = '1'
309            repo_head = repo.head()
310            repo_clean = not repo.dirty()
311            repo_remotes = '%{nil}'
312            remotes = repo.remotes()
313            if 'origin' in remotes:
314                repo_remotes = '%s/origin' % (remotes['origin']['url'])
315            repo_id = repo_head
316            if not repo_clean:
317                repo_id += '-modified'
318            repo_mail = repo.email()
319        else:
320            repo_valid = '0'
321            repo_head = '%{nil}'
322            repo_clean = '%{nil}'
323            repo_remotes = '%{nil}'
324            repo_id = 'no-repo'
325            repo_mail = None
326        self.defaults['_sbgit_valid'] = repo_valid
327        self.defaults['_sbgit_head']  = repo_head
328        self.defaults['_sbgit_clean'] = str(repo_clean)
329        self.defaults['_sbgit_remotes'] = str(repo_remotes)
330        self.defaults['_sbgit_id']    = repo_id
331        if repo_mail is not None:
332            self.defaults['_sbgit_mail'] = repo_mail
333
334    def command(self):
335        return path.join(self.command_path, self.command_name)
336
337    def force(self):
338        return self.opts['force'] != '0'
339
340    def dry_run(self):
341        return self.opts['dry-run'] != '0'
342
343    def set_dry_run(self):
344        self.opts['dry-run'] = '1'
345
346    def quiet(self):
347        return self.opts['quiet'] != '0'
348
349    def trace(self):
350        return self.opts['trace'] != '0'
351
352    def warn_all(self):
353        return self.opts['warn-all'] != '0'
354
355    def keep_going(self):
356        return self.opts['keep-going'] != '0'
357
358    def no_clean(self):
359        return self.opts['no-clean'] != '0'
360
361    def always_clean(self):
362        return self.opts['always-clean'] != '0'
363
364    def no_install(self):
365        return self.opts['no-install'] != '0'
366
367    def canadian_cross(self):
368        _host = self.defaults.expand('%{_host}')
369        _build = self.defaults.expand('%{_build}')
370        _target = self.defaults.expand('%{_target}')
371        if len(_target):
372            return len(_host) and len(_build) and (_target) and \
373                _host != _build and _host != _target
374        return len(_host) and len(_build) and _host != _build
375
376    def user_macros(self):
377        #
378        # Return something even if it does not exist.
379        #
380        if self.opts['macros'] is None:
381            return None
382        um = []
383        configs = self.defaults.expand('%{_configdir}').split(':')
384        for m in self.opts['macros'].split(','):
385            if path.exists(m):
386                um += [m]
387            else:
388                # Get the expanded config macros then check them.
389                cm = path.expand(m, configs)
390                ccm = path.exists(cm)
391                if True in ccm:
392                    # Pick the first found
393                    um += [cm[ccm.index(True)]]
394                else:
395                    um += [m]
396        return um if len(um) else None
397
398    def jobs(self, cpus):
399        cpus = int(cpus)
400        if self.opts['jobs'] == 'none':
401            cpus = 0
402        elif self.opts['jobs'] == 'max':
403            pass
404        elif self.opts['jobs'] == 'half':
405            cpus = cpus / 2
406        else:
407            ok = False
408            try:
409                i = int(self.opts['jobs'])
410                cpus = i
411                ok = True
412            except:
413                pass
414            if not ok:
415                try:
416                    f = float(self.opts['jobs'])
417                    cpus = f * cpus
418                    ok = True
419                except:
420                    pass
421                if not ok:
422                    raise error.internal('bad jobs option: %s' % (self.opts['jobs']))
423        if cpus <= 0:
424            cpu = 1
425        return cpus
426
427    def params(self):
428        return self.opts['params']
429
430    def parse_args(self, arg, error = True, extra = True):
431        for a in range(0, len(self.args)):
432            if self.args[a].startswith(arg):
433                lhs = None
434                rhs = None
435                if '=' in self.args[a]:
436                    eqs = self.args[a].split('=')
437                    lhs = eqs[0]
438                    if len(eqs) > 2:
439                        rhs = '='.join(eqs[1:])
440                    else:
441                        rhs = eqs[1]
442                elif extra:
443                    lhs = self.args[a]
444                    a += 1
445                    if a < len(self.args):
446                        rhs = self.args[a]
447                return [lhs, rhs]
448            a += 1
449        return None
450
451    def get_arg(self, arg):
452        if self.optargs is None or arg not in self.optargs:
453            raise error.internal('bad arg: %s' % (arg))
454        return self.parse_args(arg)
455
456    def with_arg(self, label):
457        for pre in ['with', 'without']:
458            arg_str = '--%s-%s' % (pre, label)
459            arg_label = '%s_%s' % (pre, label)
460            arg = self.parse_args(arg_str, error = False, extra = False)
461            if arg is not None:
462                if arg[1] is  None:
463                    result = 'yes'
464                else:
465                    result = arg[1]
466                break
467            if pre == 'with':
468                result = 'yes'
469            else:
470                result = 'no'
471        return [arg_label, result]
472
473    def get_config_files(self, config):
474        #
475        # Convert to shell paths and return shell paths.
476        #
477        # @fixme should this use a passed in set of defaults and not
478        #        not the initial set of values ?
479        #
480        config = path.shell(config)
481        if '*' in config or '?' in config:
482            print config
483            configdir = path.dirname(config)
484            configbase = path.basename(config)
485            if len(configbase) == 0:
486                configbase = '*'
487            if not configbase.endswith('.cfg'):
488                configbase = configbase + '.cfg'
489            if len(configdir) == 0:
490                configdir = self.macros.expand(self.defaults['_configdir'])
491            configs = []
492            for cp in configdir.split(':'):
493                hostconfigdir = path.host(cp)
494                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
495                    configs += path.shell(f)
496        else:
497            configs = [config]
498        return configs
499
500    def config_files(self):
501        configs = []
502        for config in self.opts['params']:
503            configs.extend(self.get_config_files(config))
504        return configs
505
506    def logfiles(self):
507        if 'log' in self.opts and self.opts['log'] is not None:
508            return self.opts['log'].split(',')
509        return ['rsb-log-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))]
510
511    def urls(self):
512        if self.opts['url'] is not None:
513            return self.opts['url'].split(',')
514        return None
515
516    def download_disabled(self):
517        return self.opts['no-download'] != '0'
518
519    def info(self):
520        s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep)
521        s += ' Python: %s' % (sys.version.replace('\n', ''))
522        return s
523
524    def log_info(self):
525        log.output(self.info())
526
527def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
528    """
529    Copy the defaults, get the host specific values and merge them overriding
530    any matching defaults, then create an options object to handle the command
531    line merging in any command line overrides. Finally post process the
532    command line.
533    """
534
535    global host_windows
536
537    #
538    # The path to this command.
539    #
540    command_path = path.dirname(args[0])
541    if len(command_path) == 0:
542        command_path = '.'
543
544    #
545    # The command line contains the base defaults object all build objects copy
546    # and modify by loading a configuration.
547    #
548    o = command_line(args,
549                     optargs,
550                     macros.macros(name = defaults,
551                                   sbdir = command_path),
552                     command_path)
553
554    overrides = None
555    if os.name == 'nt':
556        try:
557            import windows
558            overrides = windows.load()
559            host_windows = True
560        except:
561            raise error.general('failed to load Windows host support')
562    elif os.name == 'posix':
563        uname = os.uname()
564        try:
565            if uname[0].startswith('CYGWIN_NT'):
566                import windows
567                overrides = windows.load()
568            elif uname[0] == 'Darwin':
569                import darwin
570                overrides = darwin.load()
571            elif uname[0] == 'FreeBSD':
572                import freebsd
573                overrides = freebsd.load()
574            elif uname[0] == 'NetBSD':
575                import netbsd
576                overrides = netbsd.load()
577            elif uname[0] == 'Linux':
578                import linux
579                overrides = linux.load()
580            elif uname[0] == 'SunOS':
581                import solaris
582                overrides = solaris.load()
583        except:
584            raise error.general('failed to load %s host support' % (uname[0]))
585    else:
586        raise error.general('unsupported host type; please add')
587    if overrides is None:
588        raise error.general('no hosts defaults found; please add')
589    for k in overrides:
590        o.defaults[k] = overrides[k]
591
592    o.sb_git()
593    o.process()
594    o.post_process()
595
596    return o
597
598def run(args):
599    try:
600        _opts = load(args = args, defaults = 'defaults.mc')
601        log.notice('RTEMS Source Builder - Defaults, v%s' % (version.str()))
602        _opts.log_info()
603        log.notice('Options:')
604        log.notice(str(_opts))
605        log.notice('Defaults:')
606        log.notice(str(_opts.defaults))
607        log.notice('with-opt1: %r' % (_opts.with_arg('opt1')))
608        log.notice('without-opt2: %r' % (_opts.with_arg('opt2')))
609    except error.general, gerr:
610        print gerr
611        sys.exit(1)
612    except error.internal, ierr:
613        print ierr
614        sys.exit(1)
615    except error.exit, eerr:
616        pass
617    except KeyboardInterrupt:
618        _notice(opts, 'abort: user terminated')
619        sys.exit(1)
620    sys.exit(0)
621
622if __name__ == '__main__':
623    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.