source: rtems-source-builder/source-builder/sb/options.py @ 348e498

4.104.114.95
Last change on this file since 348e498 was 348e498, checked in by Chris Johns <chrisj@…>, on 09/02/13 at 21:59:38

sb: Add options log_info.

Provide a log_info method for the options class to log the command line
and the python version string.

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