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

4.104.114.9
Last change on this file since dfdc129 was dfdc129, checked in by Chris Johns <chrisj@…>, on Apr 30, 2013 at 1:19:09 AM

Add user private macro loading.

  • 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 '--jobs                 : Run with specified number of jobs, default: num CPUs.'
195        print '--host                 : Set the host triplet'
196        print '--build                : Set the build triplet'
197        print '--target               : Set the target triplet'
198        print '--prefix path          : Tools build prefix, ie where they are installed'
199        print '--topdir path          : Top of the build tree, default is $PWD'
200        print '--configdir path       : Path to the configuration directory, default: ./config'
201        print '--builddir path        : Path to the build directory, default: ./build'
202        print '--sourcedir path       : Path to the source directory, default: ./source'
203        print '--tmppath path         : Path to the temp directory, default: ./tmp'
204        print '--macros file[,[file]  : Macro format files to load after the defaults'
205        print '--log file             : Log file where all build out is written too'
206        print '--url url[,url]        : URL to look for source'
207        print '--no-download          : Disable the source downloader'
208        print '--no-install           : Do not install the packages to the prefix'
209        print '--targetcflags flags   : List of C flags for the target code'
210        print '--targetcxxflags flags : List of C++ flags for the target code'
211        print '--libstdcxxflags flags : List of C++ flags to build the target libstdc++ code'
212        print '--with-<label>         : Add the --with-<label> to the build'
213        print '--without-<label>      : Add the --without-<label> to the build'
214        print '--regression           : Set --no-install, --keep-going and --always-clean'
215        if self.optargs:
216            for a in self.optargs:
217                print '%-22s : %s' % (a, self.optargs[a])
218        raise error.exit()
219
220    def process(self):
221        arg = 0
222        while arg < len(self.args):
223            a = self.args[arg]
224            if a == '-?':
225                self.help()
226            elif a.startswith('--'):
227                los = a.split('=')
228                lo = los[0]
229                if lo in self._long_opts:
230                    long_opt = self._long_opts[lo]
231                    if len(los) == 1:
232                        if long_opt[2]:
233                            if arg == len(self.args) - 1:
234                                raise error.general('option requires a parameter: %s' % (lo))
235                            arg += 1
236                            value = self.args[arg]
237                        else:
238                            value = None
239                    else:
240                        value = '='.join(los[1:])
241                    long_opt[1](lo, long_opt[0], value)
242            else:
243                self.opts['params'].append(a)
244            arg += 1
245
246    def post_process(self):
247        # Handle the log first.
248        log.default = log.log(self.logfiles())
249        if self.trace():
250            log.tracing = True
251        if self.quiet():
252            log.quiet = True
253        # Must have a host
254        if self.defaults['_host'] == self.defaults['nil']:
255            raise error.general('host not set')
256        # Manage the regression option
257        if self.opts['regression'] != '0':
258            self.opts['no-install'] = '1'
259            self.defaults['_no_install'] = '1'
260            self.opts['keep-going'] = '1'
261            self.defaults['_keep_going'] = '1'
262            self.opts['always-clean'] = '1'
263            self.defaults['_always_clean'] = '1'
264        # Handle the jobs for make
265        if '_ncpus' not in self.defaults:
266            raise error.general('host number of CPUs not set')
267        ncpus = self.jobs(self.defaults['_ncpus'])
268        if ncpus > 1:
269            self.defaults['_smp_mflags'] = '-j %d' % (ncpus)
270        else:
271            self.defaults['_smp_mflags'] = self.defaults['nil']
272        # Load user macro files
273        um = self.user_macros()
274        if um:
275            checked = path.exists(um)
276            if False in checked:
277                raise error.general('macro file not found: %s' % (um[checked.index(False)]))
278            for m in um:
279                self.defaults.load(m)
280        # Check if the user has a private set of macros to load
281        if 'RSB_MACROS' in os.environ:
282            if path.exists(os.environ['RSB_MACROS']):
283                self.defaults.load(os.environ['RSB_MACROS'])
284        if 'HOME' in os.environ:
285            rsb_macros = path.join(os.environ['HOME'], '.rsb_macros')
286            if path.exists(rsb_macros):
287                self.defaults.load(rsb_macros)
288
289    def sb_git(self):
290        repo = git.repo(self.defaults.expand('%{_sbdir}'), self)
291        if repo.valid():
292            repo_valid = '1'
293            repo_head = repo.head()
294            repo_clean = repo.clean()
295            repo_id = repo_head
296            if not repo_clean:
297                repo_id += '-modified'
298            repo_mail = repo.email()
299        else:
300            repo_valid = '0'
301            repo_head = '%{nil}'
302            repo_clean = '%{nil}'
303            repo_id = 'no-repo'
304            repo_mail = None
305        self.defaults['_sbgit_valid'] = repo_valid
306        self.defaults['_sbgit_head']  = repo_head
307        self.defaults['_sbgit_clean'] = str(repo_clean)
308        self.defaults['_sbgit_id']    = repo_id
309        if repo_mail is not None:
310            self.defaults['_sbgit_mail'] = repo_mail
311
312    def command(self):
313        return path.join(self.command_path, self.command_name)
314
315    def force(self):
316        return self.opts['force'] != '0'
317
318    def dry_run(self):
319        return self.opts['dry-run'] != '0'
320
321    def set_dry_run(self):
322        self.opts['dry-run'] = '1'
323
324    def quiet(self):
325        return self.opts['quiet'] != '0'
326
327    def trace(self):
328        return self.opts['trace'] != '0'
329
330    def warn_all(self):
331        return self.opts['warn-all'] != '0'
332
333    def keep_going(self):
334        return self.opts['keep-going'] != '0'
335
336    def no_clean(self):
337        return self.opts['no-clean'] != '0'
338
339    def always_clean(self):
340        return self.opts['always-clean'] != '0'
341
342    def no_install(self):
343        return self.opts['no-install'] != '0'
344
345    def user_macros(self):
346        #
347        # Return something even if it does not exist.
348        #
349        if self.opts['macros'] is None:
350            return None
351        um = []
352        configs = self.defaults.expand('%{_configdir}').split(':')
353        for m in self.opts['macros'].split(','):
354            if path.exists(m):
355                um += [m]
356            else:
357                # Get the expanded config macros then check them.
358                cm = path.expand(m, configs)
359                ccm = path.exists(cm)
360                if True in ccm:
361                    # Pick the first found
362                    um += [cm[ccm.index(True)]]
363                else:
364                    um += [m]
365        return um if len(um) else None
366
367    def jobs(self, cpus):
368        cpus = int(cpus)
369        if self.opts['jobs'] == 'none':
370            cpus = 0
371        elif self.opts['jobs'] == 'max':
372            pass
373        elif self.opts['jobs'] == 'half':
374            cpus = cpus / 2
375        else:
376            ok = False
377            try:
378                i = int(self.opts['jobs'])
379                cpus = i
380                ok = True
381            except:
382                pass
383            if not ok:
384                try:
385                    f = float(self.opts['jobs'])
386                    cpus = f * cpus
387                    ok = True
388                except:
389                    pass
390                if not ok:
391                    raise error.internal('bad jobs option: %s' % (self.opts['jobs']))
392        if cpus <= 0:
393            cpu = 1
394        return cpus
395
396    def params(self):
397        return self.opts['params']
398
399    def get_arg(self, arg):
400        if self.optargs is None or arg not in self.optargs:
401            raise error.internal('bad arg: %s' % (arg))
402        for a in self.args:
403            sa = a.split('=')
404            if sa[0].startswith(arg):
405                return sa
406        return None
407
408    def get_config_files(self, config):
409        #
410        # Convert to shell paths and return shell paths.
411        #
412        # @fixme should this use a passed in set of defaults and not
413        #        not the initial set of values ?
414        #
415        config = path.shell(config)
416        if '*' in config or '?' in config:
417            print config
418            configdir = path.dirname(config)
419            configbase = path.basename(config)
420            if len(configbase) == 0:
421                configbase = '*'
422            if not configbase.endswith('.cfg'):
423                configbase = configbase + '.cfg'
424            if len(configdir) == 0:
425                configdir = self.macros.expand(self.defaults['_configdir'])
426            configs = []
427            for cp in configdir.split(':'):
428                hostconfigdir = path.host(cp)
429                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
430                    configs += path.shell(f)
431        else:
432            configs = [config]
433        return configs
434
435    def config_files(self):
436        configs = []
437        for config in self.opts['params']:
438            configs.extend(self.get_config_files(config))
439        return configs
440
441    def logfiles(self):
442        if 'log' in self.opts and self.opts['log'] is not None:
443            return self.opts['log'].split(',')
444        return ['stdout']
445
446    def urls(self):
447        if self.opts['url'] is not None:
448            return self.opts['url'].split(',')
449        return None
450
451    def download_disabled(self):
452        return self.opts['no-download'] != '0'
453
454def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
455    """
456    Copy the defaults, get the host specific values and merge them overriding
457    any matching defaults, then create an options object to handle the command
458    line merging in any command line overrides. Finally post process the
459    command line.
460    """
461
462    #
463    # The path to this command.
464    #
465    command_path = path.dirname(args[0])
466    if len(command_path) == 0:
467        command_path = '.'
468
469    #
470    # The command line contains the base defaults object all build objects copy
471    # and modify by loading a configuration.
472    #
473    o = command_line(args,
474                     optargs,
475                     macros.macros(name = defaults,
476                                   sbdir = command_path),
477                     command_path)
478
479    overrides = None
480    if os.name == 'nt':
481        import windows
482        overrides = windows.load()
483    elif os.name == 'posix':
484        uname = os.uname()
485        try:
486            if uname[0].startswith('CYGWIN_NT'):
487                import windows
488                overrides = windows.load()
489            elif uname[0] == 'Darwin':
490                import darwin
491                overrides = darwin.load()
492            elif uname[0] == 'FreeBSD':
493                import freebsd
494                overrides = freebsd.load()
495            elif uname[0] == 'Linux':
496                import linux
497                overrides = linux.load()
498        except:
499            pass
500    else:
501        raise error.general('unsupported host type; please add')
502    if overrides is None:
503        raise error.general('no hosts defaults found; please add')
504    for k in overrides:
505        o.defaults[k] = overrides[k]
506
507    o.sb_git()
508    o.process()
509    o.post_process()
510
511    return o
512
513def run(args):
514    try:
515        _opts = load(args = args)
516        print 'Options:'
517        print _opts
518        print 'Defaults:'
519        print _opts.defaults
520    except error.general, gerr:
521        print gerr
522        sys.exit(1)
523    except error.internal, ierr:
524        print ierr
525        sys.exit(1)
526    except error.exit, eerr:
527        pass
528    except KeyboardInterrupt:
529        _notice(opts, 'abort: user terminated')
530        sys.exit(1)
531    sys.exit(0)
532
533if __name__ == '__main__':
534    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.