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

4.104.114.95
Last change on this file since fbb39e7 was fbb39e7, checked in by Chris Johns <chrisj@…>, on 04/30/13 at 03:22:28

Add options help.

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