source: rtems-source-builder/source-builder/sb/options.py @ 85b46b8

4.104.114.95
Last change on this file since 85b46b8 was 85b46b8, checked in by Chris Johns <chrisj@…>, on 10/31/14 at 04:10:31

sb: Provide a default log if none is provided.

  • Property mode set to 100644
File size: 21.2 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-2013 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
301    def sb_git(self):
302        repo = git.repo(self.defaults.expand('%{_sbdir}'), self)
303        if repo.valid():
304            repo_valid = '1'
305            repo_head = repo.head()
306            repo_clean = not repo.dirty()
307            repo_remotes = '%{nil}'
308            remotes = repo.remotes()
309            if 'origin' in remotes:
310                repo_remotes = '%s/origin' % (remotes['origin']['url'])
311            repo_id = repo_head
312            if not repo_clean:
313                repo_id += '-modified'
314            repo_mail = repo.email()
315        else:
316            repo_valid = '0'
317            repo_head = '%{nil}'
318            repo_clean = '%{nil}'
319            repo_remotes = '%{nil}'
320            repo_id = 'no-repo'
321            repo_mail = None
322        self.defaults['_sbgit_valid'] = repo_valid
323        self.defaults['_sbgit_head']  = repo_head
324        self.defaults['_sbgit_clean'] = str(repo_clean)
325        self.defaults['_sbgit_remotes'] = str(repo_remotes)
326        self.defaults['_sbgit_id']    = repo_id
327        if repo_mail is not None:
328            self.defaults['_sbgit_mail'] = repo_mail
329
330    def command(self):
331        return path.join(self.command_path, self.command_name)
332
333    def force(self):
334        return self.opts['force'] != '0'
335
336    def dry_run(self):
337        return self.opts['dry-run'] != '0'
338
339    def set_dry_run(self):
340        self.opts['dry-run'] = '1'
341
342    def quiet(self):
343        return self.opts['quiet'] != '0'
344
345    def trace(self):
346        return self.opts['trace'] != '0'
347
348    def warn_all(self):
349        return self.opts['warn-all'] != '0'
350
351    def keep_going(self):
352        return self.opts['keep-going'] != '0'
353
354    def no_clean(self):
355        return self.opts['no-clean'] != '0'
356
357    def always_clean(self):
358        return self.opts['always-clean'] != '0'
359
360    def no_install(self):
361        return self.opts['no-install'] != '0'
362
363    def user_macros(self):
364        #
365        # Return something even if it does not exist.
366        #
367        if self.opts['macros'] is None:
368            return None
369        um = []
370        configs = self.defaults.expand('%{_configdir}').split(':')
371        for m in self.opts['macros'].split(','):
372            if path.exists(m):
373                um += [m]
374            else:
375                # Get the expanded config macros then check them.
376                cm = path.expand(m, configs)
377                ccm = path.exists(cm)
378                if True in ccm:
379                    # Pick the first found
380                    um += [cm[ccm.index(True)]]
381                else:
382                    um += [m]
383        return um if len(um) else None
384
385    def jobs(self, cpus):
386        cpus = int(cpus)
387        if self.opts['jobs'] == 'none':
388            cpus = 0
389        elif self.opts['jobs'] == 'max':
390            pass
391        elif self.opts['jobs'] == 'half':
392            cpus = cpus / 2
393        else:
394            ok = False
395            try:
396                i = int(self.opts['jobs'])
397                cpus = i
398                ok = True
399            except:
400                pass
401            if not ok:
402                try:
403                    f = float(self.opts['jobs'])
404                    cpus = f * cpus
405                    ok = True
406                except:
407                    pass
408                if not ok:
409                    raise error.internal('bad jobs option: %s' % (self.opts['jobs']))
410        if cpus <= 0:
411            cpu = 1
412        return cpus
413
414    def params(self):
415        return self.opts['params']
416
417    def get_arg(self, arg):
418        if self.optargs is None or arg not in self.optargs:
419            raise error.internal('bad arg: %s' % (arg))
420        for a in self.args:
421            sa = a.split('=')
422            if sa[0].startswith(arg):
423                return sa
424        return None
425
426    def get_config_files(self, config):
427        #
428        # Convert to shell paths and return shell paths.
429        #
430        # @fixme should this use a passed in set of defaults and not
431        #        not the initial set of values ?
432        #
433        config = path.shell(config)
434        if '*' in config or '?' in config:
435            print config
436            configdir = path.dirname(config)
437            configbase = path.basename(config)
438            if len(configbase) == 0:
439                configbase = '*'
440            if not configbase.endswith('.cfg'):
441                configbase = configbase + '.cfg'
442            if len(configdir) == 0:
443                configdir = self.macros.expand(self.defaults['_configdir'])
444            configs = []
445            for cp in configdir.split(':'):
446                hostconfigdir = path.host(cp)
447                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
448                    configs += path.shell(f)
449        else:
450            configs = [config]
451        return configs
452
453    def config_files(self):
454        configs = []
455        for config in self.opts['params']:
456            configs.extend(self.get_config_files(config))
457        return configs
458
459    def logfiles(self):
460        if 'log' in self.opts and self.opts['log'] is not None:
461            return self.opts['log'].split(',')
462        return ['rsb-log-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))]
463
464    def urls(self):
465        if self.opts['url'] is not None:
466            return self.opts['url'].split(',')
467        return None
468
469    def download_disabled(self):
470        return self.opts['no-download'] != '0'
471
472    def info(self):
473        s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep)
474        s += ' Python: %s' % (sys.version.replace('\n', ''))
475        return s
476
477    def log_info(self):
478        log.output(self.info())
479
480def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
481    """
482    Copy the defaults, get the host specific values and merge them overriding
483    any matching defaults, then create an options object to handle the command
484    line merging in any command line overrides. Finally post process the
485    command line.
486    """
487
488    global host_windows
489
490    #
491    # The path to this command.
492    #
493    command_path = path.dirname(args[0])
494    if len(command_path) == 0:
495        command_path = '.'
496
497    #
498    # The command line contains the base defaults object all build objects copy
499    # and modify by loading a configuration.
500    #
501    o = command_line(args,
502                     optargs,
503                     macros.macros(name = defaults,
504                                   sbdir = command_path),
505                     command_path)
506
507    overrides = None
508    if os.name == 'nt':
509        try:
510            import windows
511            overrides = windows.load()
512            host_windows = True
513        except:
514            raise error.general('failed to load Windows host support')
515    elif os.name == 'posix':
516        uname = os.uname()
517        try:
518            if uname[0].startswith('CYGWIN_NT'):
519                import windows
520                overrides = windows.load()
521            elif uname[0] == 'Darwin':
522                import darwin
523                overrides = darwin.load()
524            elif uname[0] == 'FreeBSD':
525                import freebsd
526                overrides = freebsd.load()
527            elif uname[0] == 'NetBSD':
528                import netbsd
529                overrides = netbsd.load()
530            elif uname[0] == 'Linux':
531                import linux
532                overrides = linux.load()
533            elif uname[0] == 'SunOS':
534                import solaris
535                overrides = solaris.load()
536        except:
537            raise error.general('failed to load %s host support' % (uname[0]))
538    else:
539        raise error.general('unsupported host type; please add')
540    if overrides is None:
541        raise error.general('no hosts defaults found; please add')
542    for k in overrides:
543        o.defaults[k] = overrides[k]
544
545    o.sb_git()
546    o.process()
547    o.post_process()
548
549    return o
550
551def run(args):
552    try:
553        _opts = load(args = args, defaults = 'defaults.mc')
554        log.notice('RTEMS Source Builder - Defaults, v%s' % (version.str()))
555        _opts.log_info()
556        log.notice('Options:')
557        log.notice(str(_opts))
558        log.notice('Defaults:')
559        log.notice(str(_opts.defaults))
560    except error.general, gerr:
561        print gerr
562        sys.exit(1)
563    except error.internal, ierr:
564        print ierr
565        sys.exit(1)
566    except error.exit, eerr:
567        pass
568    except KeyboardInterrupt:
569        _notice(opts, 'abort: user terminated')
570        sys.exit(1)
571    sys.exit(0)
572
573if __name__ == '__main__':
574    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.