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

4.11
Last change on this file since c07ee80 was c07ee80, checked in by Chris Johns <chrisj@…>, on 12/11/15 at 05:22:00

VERSION is an INI format file.

VERSION is an INI format file with 2 sections:

  1. version The version of the release. It contains: release = version-string
  2. hashes A list of hashes for packages that are formed when creating a release. A hash entry is: file-name = hash-type checksum

The approach means we do not need to hold hash values in configuration files
which need to be updated when a release is made. The release scripts can
generate the hashes when creating the release.

  • Property mode set to 100644
File size: 24.5 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-2015 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        print '--rtems-tools path     : Path to an install RTEMS tool set'
225        print '--rtems-bsp arc/bsp    : Standard RTEMS architecure and BSP specifier'
226        print '--rtems-version ver    : The RTEMS major/minor version string'
227        if self.optargs:
228            for a in self.optargs:
229                print '%-22s : %s' % (a, self.optargs[a])
230        raise error.exit()
231
232    def process(self):
233        arg = 0
234        while arg < len(self.args):
235            a = self.args[arg]
236            if a == '-?':
237                self.help()
238            elif a.startswith('--'):
239                los = a.split('=')
240                lo = los[0]
241                if lo in self._long_opts:
242                    long_opt = self._long_opts[lo]
243                    if len(los) == 1:
244                        if long_opt[2]:
245                            if arg == len(self.args) - 1:
246                                raise error.general('option requires a parameter: %s' % (lo))
247                            arg += 1
248                            value = self.args[arg]
249                        else:
250                            value = None
251                    else:
252                        value = '='.join(los[1:])
253                    long_opt[1](lo, long_opt[0], value)
254            else:
255                self.opts['params'].append(a)
256            arg += 1
257
258    def post_process(self):
259        # Handle the log first.
260        log.default = log.log(self.logfiles())
261        if self.trace():
262            log.tracing = True
263        if self.quiet():
264            log.quiet = True
265        # Must have a host
266        if self.defaults['_host'] == self.defaults['nil']:
267            raise error.general('--host not set')
268        # Must have a host
269        if self.defaults['_build'] == self.defaults['nil']:
270            raise error.general('--build not set')
271        # Manage the regression option
272        if self.opts['regression'] != '0':
273            self.opts['no-install'] = '1'
274            self.defaults['_no_install'] = '1'
275            self.opts['keep-going'] = '1'
276            self.defaults['_keep_going'] = '1'
277            self.opts['always-clean'] = '1'
278            self.defaults['_always_clean'] = '1'
279        # Handle the jobs for make
280        if '_ncpus' not in self.defaults:
281            raise error.general('host number of CPUs not set')
282        ncpus = self.jobs(self.defaults['_ncpus'])
283        if ncpus > 1:
284            self.defaults['_smp_mflags'] = '-j %d' % (ncpus)
285        else:
286            self.defaults['_smp_mflags'] = self.defaults['nil']
287        # Load user macro files
288        um = self.user_macros()
289        if um:
290            checked = path.exists(um)
291            if False in checked:
292                raise error.general('macro file not found: %s' % (um[checked.index(False)]))
293            for m in um:
294                self.defaults.load(m)
295        # Check if the user has a private set of macros to load
296        if 'RSB_MACROS' in os.environ:
297            if path.exists(os.environ['RSB_MACROS']):
298                self.defaults.load(os.environ['RSB_MACROS'])
299        if 'HOME' in os.environ:
300            rsb_macros = path.join(os.environ['HOME'], '.rsb_macros')
301            if path.exists(rsb_macros):
302                self.defaults.load(rsb_macros)
303
304    def sb_released(self):
305        if version.released():
306            self.defaults['rsb_released'] = '1'
307        self.defaults['rsb_version'] = version.str()
308
309    def sb_git(self):
310        repo = git.repo(self.defaults.expand('%{_sbdir}'), self)
311        if repo.valid():
312            repo_valid = '1'
313            repo_head = repo.head()
314            repo_clean = not repo.dirty()
315            repo_remotes = '%{nil}'
316            remotes = repo.remotes()
317            if 'origin' in remotes:
318                repo_remotes = '%s/origin' % (remotes['origin']['url'])
319            repo_id = repo_head
320            if not repo_clean:
321                repo_id += '-modified'
322            repo_mail = repo.email()
323        else:
324            repo_valid = '0'
325            repo_head = '%{nil}'
326            repo_clean = '%{nil}'
327            repo_remotes = '%{nil}'
328            repo_id = 'no-repo'
329            repo_mail = None
330        self.defaults['_sbgit_valid'] = repo_valid
331        self.defaults['_sbgit_head']  = repo_head
332        self.defaults['_sbgit_clean'] = str(repo_clean)
333        self.defaults['_sbgit_remotes'] = str(repo_remotes)
334        self.defaults['_sbgit_id']    = repo_id
335        if repo_mail is not None:
336            self.defaults['_sbgit_mail'] = repo_mail
337
338    def command(self):
339        return path.join(self.command_path, self.command_name)
340
341    def force(self):
342        return self.opts['force'] != '0'
343
344    def dry_run(self):
345        return self.opts['dry-run'] != '0'
346
347    def set_dry_run(self):
348        self.opts['dry-run'] = '1'
349
350    def quiet(self):
351        return self.opts['quiet'] != '0'
352
353    def trace(self):
354        return self.opts['trace'] != '0'
355
356    def warn_all(self):
357        return self.opts['warn-all'] != '0'
358
359    def keep_going(self):
360        return self.opts['keep-going'] != '0'
361
362    def no_clean(self):
363        return self.opts['no-clean'] != '0'
364
365    def always_clean(self):
366        return self.opts['always-clean'] != '0'
367
368    def no_install(self):
369        return self.opts['no-install'] != '0'
370
371    def canadian_cross(self):
372        _host = self.defaults.expand('%{_host}')
373        _build = self.defaults.expand('%{_build}')
374        _target = self.defaults.expand('%{_target}')
375        if len(_target):
376            return len(_host) and len(_build) and (_target) and \
377                _host != _build and _host != _target
378        return len(_host) and len(_build) and _host != _build
379
380    def user_macros(self):
381        #
382        # Return something even if it does not exist.
383        #
384        if self.opts['macros'] is None:
385            return None
386        um = []
387        configs = self.defaults.expand('%{_configdir}').split(':')
388        for m in self.opts['macros'].split(','):
389            if path.exists(m):
390                um += [m]
391            else:
392                # Get the expanded config macros then check them.
393                cm = path.expand(m, configs)
394                ccm = path.exists(cm)
395                if True in ccm:
396                    # Pick the first found
397                    um += [cm[ccm.index(True)]]
398                else:
399                    um += [m]
400        return um if len(um) else None
401
402    def jobs(self, cpus):
403        cpus = int(cpus)
404        if self.opts['jobs'] == 'none':
405            cpus = 0
406        elif self.opts['jobs'] == 'max':
407            pass
408        elif self.opts['jobs'] == 'half':
409            cpus = cpus / 2
410        else:
411            ok = False
412            try:
413                i = int(self.opts['jobs'])
414                cpus = i
415                ok = True
416            except:
417                pass
418            if not ok:
419                try:
420                    f = float(self.opts['jobs'])
421                    cpus = f * cpus
422                    ok = True
423                except:
424                    pass
425                if not ok:
426                    raise error.internal('bad jobs option: %s' % (self.opts['jobs']))
427        if cpus <= 0:
428            cpu = 1
429        return cpus
430
431    def params(self):
432        return self.opts['params']
433
434    def parse_args(self, arg, error = True, extra = True):
435        for a in range(0, len(self.args)):
436            if self.args[a].startswith(arg):
437                lhs = None
438                rhs = None
439                if '=' in self.args[a]:
440                    eqs = self.args[a].split('=')
441                    lhs = eqs[0]
442                    if len(eqs) > 2:
443                        rhs = '='.join(eqs[1:])
444                    else:
445                        rhs = eqs[1]
446                elif extra:
447                    lhs = self.args[a]
448                    a += 1
449                    if a < len(self.args):
450                        rhs = self.args[a]
451                return [lhs, rhs]
452            a += 1
453        return None
454
455    def get_arg(self, arg):
456        if self.optargs is None or arg not in self.optargs:
457            return None
458        return self.parse_args(arg)
459
460    def with_arg(self, label):
461        for pre in ['with', 'without']:
462            arg_str = '--%s-%s' % (pre, label)
463            arg_label = '%s_%s' % (pre, label)
464            arg = self.parse_args(arg_str, error = False, extra = False)
465            if arg is not None:
466                if arg[1] is  None:
467                    result = 'yes'
468                else:
469                    result = arg[1]
470                break
471            if pre == 'with':
472                result = 'yes'
473            else:
474                result = 'no'
475        return [arg_label, result]
476
477    def get_config_files(self, config):
478        #
479        # Convert to shell paths and return shell paths.
480        #
481        # @fixme should this use a passed in set of defaults and not
482        #        not the initial set of values ?
483        #
484        config = path.shell(config)
485        if '*' in config or '?' in config:
486            print config
487            configdir = path.dirname(config)
488            configbase = path.basename(config)
489            if len(configbase) == 0:
490                configbase = '*'
491            if not configbase.endswith('.cfg'):
492                configbase = configbase + '.cfg'
493            if len(configdir) == 0:
494                configdir = self.macros.expand(self.defaults['_configdir'])
495            configs = []
496            for cp in configdir.split(':'):
497                hostconfigdir = path.host(cp)
498                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
499                    configs += path.shell(f)
500        else:
501            configs = [config]
502        return configs
503
504    def config_files(self):
505        configs = []
506        for config in self.opts['params']:
507            configs.extend(self.get_config_files(config))
508        return configs
509
510    def logfiles(self):
511        if 'log' in self.opts and self.opts['log'] is not None:
512            return self.opts['log'].split(',')
513        return ['rsb-log-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))]
514
515    def urls(self):
516        if self.opts['url'] is not None:
517            return self.opts['url'].split(',')
518        return None
519
520    def download_disabled(self):
521        return self.opts['no-download'] != '0'
522
523    def disable_install(self):
524        self.opts['no-install'] = '1'
525
526    def info(self):
527        s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep)
528        s += ' Python: %s' % (sys.version.replace('\n', ''))
529        return s
530
531    def log_info(self):
532        log.output(self.info())
533
534    def rtems_options(self):
535        # Check for RTEMS specific helper options.
536        rtems_tools = self.parse_args('--rtems-tools')
537        if rtems_tools is not None:
538            if self.get_arg('--with-tools') is not None:
539                raise error.general('--rtems-tools and --with-tools cannot be used together')
540            self.args.append('--with-tools=%s' % (rtems_tools[1]))
541        rtems_arch_bsp = self.parse_args('--rtems-bsp')
542        if rtems_arch_bsp is not None:
543            if self.get_arg('--target') is not None:
544                raise error.general('--rtems-bsp and --target cannot be used together')
545            ab = rtems_arch_bsp[1].split('/')
546            if len(ab) != 2:
547                raise error.general('invalid --rtems-bsp option')
548            rtems_version = self.parse_args('--rtems-version')
549            if rtems_version is None:
550                rtems_version = '%d.%d' % (version.major, version.minor)
551            else:
552                rtems_version = rtems_version[1]
553            self.args.append('--target=%s-rtems%s' % (ab[0], rtems_version))
554            self.args.append('--with-rtems-bsp=%s' % (ab[1]))
555
556def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
557    """
558    Copy the defaults, get the host specific values and merge them overriding
559    any matching defaults, then create an options object to handle the command
560    line merging in any command line overrides. Finally post process the
561    command line.
562    """
563
564    global host_windows
565
566    #
567    # The path to this command.
568    #
569    command_path = path.dirname(args[0])
570    if len(command_path) == 0:
571        command_path = '.'
572
573    #
574    # The command line contains the base defaults object all build objects copy
575    # and modify by loading a configuration.
576    #
577    o = command_line(args,
578                     optargs,
579                     macros.macros(name = defaults,
580                                   sbdir = command_path),
581                     command_path)
582
583    overrides = None
584    if os.name == 'nt':
585        try:
586            import windows
587            overrides = windows.load()
588            host_windows = True
589        except:
590            raise error.general('failed to load Windows host support')
591    elif os.name == 'posix':
592        uname = os.uname()
593        try:
594            if uname[0].startswith('CYGWIN_NT'):
595                import windows
596                overrides = windows.load()
597            elif uname[0] == 'Darwin':
598                import darwin
599                overrides = darwin.load()
600            elif uname[0] == 'FreeBSD':
601                import freebsd
602                overrides = freebsd.load()
603            elif uname[0] == 'NetBSD':
604                import netbsd
605                overrides = netbsd.load()
606            elif uname[0] == 'Linux':
607                import linux
608                overrides = linux.load()
609            elif uname[0] == 'SunOS':
610                import solaris
611                overrides = solaris.load()
612        except:
613            raise error.general('failed to load %s host support' % (uname[0]))
614    else:
615        raise error.general('unsupported host type; please add')
616    if overrides is None:
617        raise error.general('no hosts defaults found; please add')
618    for k in overrides:
619        o.defaults[k] = overrides[k]
620
621    o.sb_released()
622    o.sb_git()
623    o.rtems_options()
624    o.process()
625    o.post_process()
626
627    #
628    # Load the release hashes
629    #
630    version.load_release_hashes(o.defaults)
631
632    return o
633
634def run(args):
635    try:
636        _opts = load(args = args, defaults = 'defaults.mc')
637        log.notice('RTEMS Source Builder - Defaults, %s' % (version.str()))
638        _opts.log_info()
639        log.notice('Options:')
640        log.notice(str(_opts))
641        log.notice('Defaults:')
642        log.notice(str(_opts.defaults))
643        log.notice('with-opt1: %r' % (_opts.with_arg('opt1')))
644        log.notice('without-opt2: %r' % (_opts.with_arg('opt2')))
645    except error.general, gerr:
646        print gerr
647        sys.exit(1)
648    except error.internal, ierr:
649        print ierr
650        sys.exit(1)
651    except error.exit, eerr:
652        pass
653    except KeyboardInterrupt:
654        _notice(opts, 'abort: user terminated')
655        sys.exit(1)
656    sys.exit(0)
657
658if __name__ == '__main__':
659    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.