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

4.104.95
Last change on this file since d3fa158 was d3fa158, checked in by Chris Johns <chrisj@…>, on 03/10/16 at 05:19:58

sb: Add a download option --with-release-url/--without-release-url.

The default without the option is to prepend the release URL to the URL
list to download from the RTEMS FTP site first if the RSB is released. This
option can force the RTEMS FTP to be tried first when not a release,
or you can supply a different URL to download from or you can request
no RTEMS URL is tried first. Eg:

--with-release-url
--with-release-url=file://opt/local/cache
--without-release-url

Move the RTEMS release URL to the default.mc file. Change the URL
to the RTEMS FTP server and do not use the https method of access.

The option's with_arg call was cleaned up to make sense.

Remove the log's raw output of an extra space.

Some download error message formating was cleaned up.

Closes #2636.

  • 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, default = 'not-found'):
464        # the default if there is no option for without.
465        result = default
466        for pre in ['with', 'without']:
467            arg_str = '--%s-%s' % (pre, label)
468            arg_label = '%s_%s' % (pre, label)
469            arg = self.parse_args(arg_str, error = False, extra = False)
470            if arg is not None:
471                if arg[1] is  None:
472                    result = 'yes'
473                else:
474                    result = arg[1]
475                break
476        return [arg_label, result]
477
478    def get_config_files(self, config):
479        #
480        # Convert to shell paths and return shell paths.
481        #
482        # @fixme should this use a passed in set of defaults and not
483        #        not the initial set of values ?
484        #
485        config = path.shell(config)
486        if '*' in config or '?' in config:
487            print(config)
488            configdir = path.dirname(config)
489            configbase = path.basename(config)
490            if len(configbase) == 0:
491                configbase = '*'
492            if not configbase.endswith('.cfg'):
493                configbase = configbase + '.cfg'
494            if len(configdir) == 0:
495                configdir = self.macros.expand(self.defaults['_configdir'])
496            configs = []
497            for cp in configdir.split(':'):
498                hostconfigdir = path.host(cp)
499                for f in glob.glob(os.path.join(hostconfigdir, configbase)):
500                    configs += path.shell(f)
501        else:
502            configs = [config]
503        return configs
504
505    def config_files(self):
506        configs = []
507        for config in self.opts['params']:
508            configs.extend(self.get_config_files(config))
509        return configs
510
511    def logfiles(self):
512        if 'log' in self.opts and self.opts['log'] is not None:
513            return self.opts['log'].split(',')
514        return ['rsb-log-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))]
515
516    def urls(self):
517        if self.opts['url'] is not None:
518            return self.opts['url'].split(',')
519        return None
520
521    def download_disabled(self):
522        return self.opts['no-download'] != '0'
523
524    def disable_install(self):
525        self.opts['no-install'] = '1'
526
527    def info(self):
528        s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep)
529        s += ' Python: %s' % (sys.version.replace('\n', ''))
530        return s
531
532    def log_info(self):
533        log.output(self.info())
534
535    def rtems_options(self):
536        # Check for RTEMS specific helper options.
537        rtems_tools = self.parse_args('--rtems-tools')
538        if rtems_tools is not None:
539            if self.get_arg('--with-tools') is not None:
540                raise error.general('--rtems-tools and --with-tools cannot be used together')
541            self.args.append('--with-tools=%s' % (rtems_tools[1]))
542        rtems_arch_bsp = self.parse_args('--rtems-bsp')
543        if rtems_arch_bsp is not None:
544            if self.get_arg('--target') is not None:
545                raise error.general('--rtems-bsp and --target cannot be used together')
546            ab = rtems_arch_bsp[1].split('/')
547            if len(ab) != 2:
548                raise error.general('invalid --rtems-bsp option')
549            rtems_version = self.parse_args('--rtems-version')
550            if rtems_version is None:
551                rtems_version = '%d.%d' % (version.major, version.minor)
552            else:
553                rtems_version = rtems_version[1]
554            self.args.append('--target=%s-rtems%s' % (ab[0], rtems_version))
555            self.args.append('--with-rtems-bsp=%s' % (ab[1]))
556
557def load(args, optargs = None, defaults = '%{_sbdir}/defaults.mc'):
558    """
559    Copy the defaults, get the host specific values and merge them overriding
560    any matching defaults, then create an options object to handle the command
561    line merging in any command line overrides. Finally post process the
562    command line.
563    """
564
565    global host_windows
566    global host_posix
567
568    #
569    # The path to this command.
570    #
571    command_path = path.dirname(args[0])
572    if len(command_path) == 0:
573        command_path = '.'
574
575    #
576    # The command line contains the base defaults object all build objects copy
577    # and modify by loading a configuration.
578    #
579    o = command_line(args,
580                     optargs,
581                     macros.macros(name = defaults,
582                                   sbdir = command_path),
583                     command_path)
584
585    overrides = None
586    if os.name == 'nt':
587        try:
588            import windows
589            overrides = windows.load()
590            host_windows = True
591            host_posix = False
592        except:
593            raise error.general('failed to load Windows host support')
594    elif os.name == 'posix':
595        uname = os.uname()
596        try:
597            if uname[0].startswith('MINGW64_NT'):
598                import windows
599                overrides = windows.load()
600                host_windows = True
601            elif uname[0].startswith('CYGWIN_NT'):
602                import windows
603                overrides = windows.load()
604            elif uname[0] == 'Darwin':
605                import darwin
606                overrides = darwin.load()
607            elif uname[0] == 'FreeBSD':
608                import freebsd
609                overrides = freebsd.load()
610            elif uname[0] == 'NetBSD':
611                import netbsd
612                overrides = netbsd.load()
613            elif uname[0] == 'Linux':
614                import linux
615                overrides = linux.load()
616            elif uname[0] == 'SunOS':
617                import solaris
618                overrides = solaris.load()
619        except error.general as ge:
620            raise error.general('failed to load %s host support: %s' % (uname[0], ge))
621        except:
622            raise error.general('failed to load %s host support' % (uname[0]))
623    else:
624        raise error.general('unsupported host type; please add')
625    if overrides is None:
626        raise error.general('no hosts defaults found; please add')
627    for k in overrides:
628        o.defaults[k] = overrides[k]
629
630    o.sb_released()
631    o.sb_git()
632    o.rtems_options()
633    o.process()
634    o.post_process()
635
636    #
637    # Load the release hashes
638    #
639    version.load_release_hashes(o.defaults)
640
641    return o
642
643def run(args):
644    try:
645        _opts = load(args = args, defaults = 'defaults.mc')
646        log.notice('RTEMS Source Builder - Defaults, %s' % (version.str()))
647        _opts.log_info()
648        log.notice('Options:')
649        log.notice(str(_opts))
650        log.notice('Defaults:')
651        log.notice(str(_opts.defaults))
652        log.notice('with-opt1: %r' % (_opts.with_arg('opt1')))
653        log.notice('without-opt2: %r' % (_opts.with_arg('opt2')))
654    except error.general as gerr:
655        print(gerr)
656        sys.exit(1)
657    except error.internal as ierr:
658        print(ierr)
659        sys.exit(1)
660    except error.exit as eerr:
661        pass
662    except KeyboardInterrupt:
663        _notice(opts, 'abort: user terminated')
664        sys.exit(1)
665    sys.exit(0)
666
667if __name__ == '__main__':
668    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.