source: rtems-source-builder/source-builder/sb/options.py @ 03df294

4.9
Last change on this file since 03df294 was 03df294, checked in by Sebastian Huber <sebastian.huber@…>, on 01/17/19 at 10:32:45

sb: Change default prefix

Use OS prefix + "rtems" + $rtems_version as the default prefix to
automatically separate different RTEMS versions.

Close #3681.

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