source: rtems-source-builder/source-builder/sb/options.py @ 8268ba6

4.11
Last change on this file since 8268ba6 was 8268ba6, checked in by Chris Johns <chrisj@…>, on Mar 8, 2016 at 11:17:09 AM

sb: Report platform import errors.

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