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

4.104.114.95
Last change on this file since cb12e48 was cb12e48, checked in by Chris Johns <chrisj@…>, on 04/09/13 at 03:51:43

Refactor defaults, macros and options.

To support building snapshots and pre-release source the defaults
has been refactored. The defaults have been moved to a stand alone
file and a macros.py module added. This modile abstracts the
old default dictionary turning it into a class. The macros
class can load macros from a file therefore the defaults have
been moved to a stand alone file.

The use of defaults has been removed from the project. The only
case where it is used in the options where the defaults are read
from a file. Macros are used everywhere now.

The defaults.py has been moved to the option.py and the separate
options and defaults values has been moved to a new pattern. When
constructing an object that needs macros and options if the macros
passed in is None the defaults from the options are used. This makes
it clear when the defaults are being used or when a modified set of
macros is being used.

The macros class support maps. The default is 'global' and where all
the defaults reside and where configuratiion file changes end up.
Maps allow macros to be read from a file and override the values
being maintained in the 'global' map. Reading a macro first checks
the map and if not present checks the 'global' map.

The addition of maps to the macros provides the base to support
snapshots and pre-release testing with standard configurations.
This functionality needs to be added. It works by letting to
specify a snapshot with:

source0: none, override, 'my-dist.tar.bz2'

and it will be used rather the value from the standard configuration.
With a build set you need to also specify the package these macros
are for. The maps provide this.

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