source: rtems-tools/rtemstoolkit/options.py @ 04a5204

4.105
Last change on this file since 04a5204 was 04a5204, checked in by Sebastian Huber <sebastian.huber@…>, on 11/12/15 at 10:15:23

Python 3 compatibility

  • Property mode set to 100644
File size: 20.8 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2014 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# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31#
32# Determine the defaults and load the specific file.
33#
34
35import copy
36import glob
37import pprint
38import re
39import os
40import string
41
42import error
43import execute
44import git
45import log
46import macros
47import path
48import sys
49
50import version
51
52basepath = 'tb'
53
54#
55# Save the host state.
56#
57host_windows = False
58
59class command_line(object):
60    """Process the command line in a common way for all Tool Builder commands."""
61
62    def __init__(self, base_path = None, argv = None, optargs = None,
63                 defaults = None, long_opts = None, long_opts_help = None,
64                 command_path = None, log_default = None):
65
66        if argv is None:
67            return
68
69        global basepath
70
71        if long_opts == None:
72            raise error.general('No options provided')
73
74        basepath = base_path
75
76        if log_default is not None and type(log_default) is not list:
77            raise error.general('log default is a list')
78        self.log_default = log_default
79
80        self.long_opts = {
81            # key                 macro                handler            param  defs       init
82            '--jobs'           : ('_jobs',             self._lo_jobs,     True,  'default', True),
83            '--log'            : ('_logfile',          self._lo_string,   True,  None,      False),
84            '--macros'         : ('_macros',           self._lo_string,   True,  None,      False),
85            '--force'          : ('_force',            self._lo_bool,     False, '0',       True),
86            '--quiet'          : ('_quiet',            self._lo_bool,     False, '0',       True),
87            '--trace'          : ('_trace',            self._lo_bool,     False, '0',       True),
88            '--dry-run'        : ('_dry_run',          self._lo_bool,     False, '0',       True),
89            '--warn-all'       : ('_warn_all',         self._lo_bool,     False, '0',       True),
90            '--no-clean'       : ('_no_clean',         self._lo_bool,     False, '0',       True),
91            '--keep-going'     : ('_keep_going',       self._lo_bool,     False, '0',       True),
92            '--always-clean'   : ('_always_clean',     self._lo_bool,     False, '0',       True),
93            '--no-install'     : ('_no_install',       self._lo_bool,     False, '0',       True),
94            '--help'           : (None,                self._lo_help,     False, None,      False)
95        }
96        self.long_opts_help = {
97            '--force':                      'Force the build to proceed',
98            '--quiet':                      'Quiet output (not used)',
99            '--trace':                      'Trace the execution',
100            '--dry-run':                    'Do everything but actually run the build',
101            '--warn-all':                   'Generate warnings',
102            '--no-clean':                   'Do not clean up the build tree',
103            '--always-clean':               'Always clean the build tree, even with an error',
104            '--keep-going':                 'Do not stop on an error.',
105            '--jobs=[0..n,none,half,full]': 'Run with specified number of jobs, default: num CPUs.',
106            '--macros file[,file]':         'Macro format files to load after the defaults',
107            '--log file':                   'Log file where all build output is written to',
108        }
109        self.opts = { 'params' : [] }
110        self.command_path = command_path
111        self.command_name = path.basename(argv[0])
112        self.argv = argv
113        self.args = argv[1:]
114        self.optargs = optargs
115        self.defaults = defaults
116        for lo in self.long_opts:
117            self.opts[lo[2:]] = self.long_opts[lo][3]
118            if self.long_opts[lo][4]:
119                self.defaults[self.long_opts[lo][0]] = ('none', 'none', self.long_opts[lo][3])
120        for lo in long_opts:
121            if lo in self.long_opts:
122                raise error.general('suplicate option: %s' % (lo))
123            self.opts[lo[2:]] = long_opts[lo][3]
124            if long_opts[lo][4]:
125                self.defaults[long_opts[lo][0]] = ('none', 'none', long_opts[lo][3])
126            if long_opts[lo][1] == 'int':
127                handler = self._lo_int
128            elif long_opts[lo][1] == 'string':
129                handler = self._lo_string
130            elif long_opts[lo][1] == 'path':
131                hanlder = self._lo_path
132            elif long_opts[lo][1] == 'jobs':
133                handler = self._lo_jobs
134            elif long_opts[lo][1] == 'bool':
135                handler = self._lo_bool
136            elif long_opts[lo][1] == 'triplet':
137                handler = self._lo_triplets
138            else:
139                raise error.general('invalid option handler: %s: %s' % (lo, long_opts[lo][1]))
140            self.long_opts[lo] = (long_opts[lo][0], handler, long_opts[lo][2],
141                                   long_opts[lo][3], long_opts[lo][4])
142            if lo not in long_opts_help:
143                raise error.general('no help for option: %s' % (lo))
144            self.long_opts_help[lo] = long_opts_help[lo]
145
146    def __copy__(self):
147        new = type(self)()
148        #new.__dict__.update(self.__dict__)
149        new.opts = copy.copy(self.opts)
150        new.command_path = copy.copy(self.command_path)
151        new.command_name = copy.copy(self.command_name)
152        new.argv = self.argv
153        new.args = self.args
154        new.optargs = copy.copy(self.optargs)
155        new.defaults = copy.copy(self.defaults)
156        new.long_opts = copy.copy(self.long_opts)
157        return new
158
159    def __str__(self):
160        def _dict(dd):
161            s = ''
162            ddl = dd.keys()
163            ddl.sort()
164            for d in ddl:
165                s += '  ' + d + ': ' + str(dd[d]) + '\n'
166            return s
167
168        s = 'command: ' + self.command() + \
169            '\nargs: ' + str(self.args) + \
170            '\nopts:\n' + _dict(self.opts)
171
172        return s
173
174    def _lo_int(self, opt, macro, value):
175        if value is None:
176            raise error.general('option requires a value: %s' % (opt))
177        try:
178            num = int(value)
179        except:
180            raise error.general('option conversion to int failed: %s' % (opt))
181        self.opts[opt[2:]] = value
182        self.defaults[macro] = value
183
184    def _lo_string(self, opt, macro, value):
185        if value is None:
186            raise error.general('option requires a value: %s' % (opt))
187        self.opts[opt[2:]] = value
188        self.defaults[macro] = value
189
190    def _lo_path(self, opt, macro, value):
191        if value is None:
192            raise error.general('option requires a path: %s' % (opt))
193        value = path.abspath(value)
194        self.opts[opt[2:]] = value
195        self.defaults[macro] = value
196
197    def _lo_jobs(self, opt, macro, value):
198        if value is None:
199            raise error.general('option requires a value: %s' % (opt))
200        ok = False
201        if value in ['max', 'none', 'half']:
202            ok = True
203        else:
204            try:
205                i = int(value)
206                ok = True
207            except:
208                pass
209            if not ok:
210                try:
211                    f = float(value)
212                    ok = True
213                except:
214                    pass
215        if not ok:
216            raise error.general('invalid jobs option: %s' % (value))
217        self.defaults[macro] = value
218        self.opts[opt[2:]] = value
219
220    def _lo_bool(self, opt, macro, value):
221        if value is not None:
222            raise error.general('option does not take a value: %s' % (opt))
223        self.opts[opt[2:]] = '1'
224        self.defaults[macro] = '1'
225
226    def _lo_triplets(self, opt, macro, value):
227        #
228        # This is a target triplet. Run it past config.sub to make make sure it
229        # is ok. The target triplet is 'cpu-vendor-os'.
230        #
231        e = execute.capture_execution()
232        config_sub = path.join(self.command_path,
233                               basepath, 'config.sub')
234        exit_code, proc, output = e.shell(config_sub + ' ' + value)
235        if exit_code == 0:
236            value = output
237        self.defaults[macro] = ('triplet', 'none', value)
238        self.opts[opt[2:]] = value
239        _cpu = macro + '_cpu'
240        _arch = macro + '_arch'
241        _vendor = macro + '_vendor'
242        _os = macro + '_os'
243        _arch_value = ''
244        _vendor_value = ''
245        _os_value = ''
246        dash = value.find('-')
247        if dash >= 0:
248            _arch_value = value[:dash]
249            value = value[dash + 1:]
250        dash = value.find('-')
251        if dash >= 0:
252            _vendor_value = value[:dash]
253            value = value[dash + 1:]
254        if len(value):
255            _os_value = value
256        self.defaults[_cpu]    = _arch_value
257        self.defaults[_arch]   = _arch_value
258        self.defaults[_vendor] = _vendor_value
259        self.defaults[_os]     = _os_value
260
261    def _lo_help(self, opt, macro, value):
262        self.help()
263
264    def _help_indent(self):
265        indent = 0
266        if self.optargs:
267            for o in self.optargs:
268                if len(o) > indent:
269                    indent = len(o)
270        for o in self.long_opts_help:
271            if len(o) > indent:
272                indent = len(o)
273        return indent
274
275    def help(self):
276        print('%s: [options] [args]' % (self.command_name))
277        print('RTEMS Tools Project (c) 2012-2015 Chris Johns')
278        print('Options and arguments:')
279        opts = self.long_opts_help.keys()
280        if self.optargs:
281            opts += self.optargs.keys()
282        indent = self._help_indent()
283        for o in sorted(opts):
284            if o in self.long_opts_help:
285                h = self.long_opts_help[o]
286            elif self.optargs:
287                h = self.optargs[o]
288            else:
289                raise error.general('invalid help data: %s' %(o))
290            print('%-*s : %s' % (indent, o, h))
291        raise error.exit()
292
293    def process(self):
294        arg = 0
295        while arg < len(self.args):
296            a = self.args[arg]
297            if a == '-?':
298                self.help()
299            elif a.startswith('--'):
300                los = a.split('=')
301                lo = los[0]
302                if lo in self.long_opts:
303                    long_opt = self.long_opts[lo]
304                    if len(los) == 1:
305                        if long_opt[2]:
306                            if arg == len(self.args) - 1:
307                                raise error.general('option requires a parameter: %s' % (lo))
308                            arg += 1
309                            value = self.args[arg]
310                        else:
311                            value = None
312                    else:
313                        value = '='.join(los[1:])
314                    long_opt[1](lo, long_opt[0], value)
315            else:
316                self.opts['params'].append(a)
317            arg += 1
318
319    def post_process(self):
320        # Handle the log first.
321        log.default = log.log(self.logfiles())
322        if self.trace():
323            log.tracing = True
324        if self.quiet():
325            log.quiet = True
326        # Handle the jobs for make
327        if '_ncpus' not in self.defaults:
328            raise error.general('host number of CPUs not set')
329        ncpus = self.jobs(self.defaults['_ncpus'])
330        if ncpus > 1:
331            self.defaults['_smp_mflags'] = '-j %d' % (ncpus)
332        else:
333            self.defaults['_smp_mflags'] = self.defaults['nil']
334        # Load user macro files
335        um = self.user_macros()
336        if um:
337            checked = path.exists(um)
338            if False in checked:
339                raise error.general('macro file not found: %s' % (um[checked.index(False)]))
340            for m in um:
341                self.defaults.load(m)
342        # Check if the user has a private set of macros to load
343        if 'RSB_MACROS' in os.environ:
344            if path.exists(os.environ['RSB_MACROS']):
345                self.defaults.load(os.environ['RSB_MACROS'])
346        if 'HOME' in os.environ:
347            rsb_macros = path.join(os.environ['HOME'], '.rsb_macros')
348            if path.exists(rsb_macros):
349                self.defaults.load(rsb_macros)
350
351    def local_git(self):
352        repo = git.repo(self.defaults.expand('%{_rtdir}'), self)
353        if repo.valid():
354            repo_valid = '1'
355            repo_head = repo.head()
356            repo_clean = repo.clean()
357            repo_id = repo_head
358            if not repo_clean:
359                repo_id += '-modified'
360            repo_mail = repo.email()
361        else:
362            repo_valid = '0'
363            repo_head = '%{nil}'
364            repo_clean = '%{nil}'
365            repo_id = 'no-repo'
366            repo_mail = None
367        self.defaults['_local_git_valid'] = repo_valid
368        self.defaults['_local_git_head']  = repo_head
369        self.defaults['_local_git_clean'] = str(repo_clean)
370        self.defaults['_local_git_id']    = repo_id
371        if repo_mail is not None:
372            self.defaults['_localgit_mail'] = repo_mail
373
374    def command(self):
375        return path.join(self.command_path, self.command_name)
376
377    def force(self):
378        return self.opts['force'] != '0'
379
380    def dry_run(self):
381        return self.opts['dry-run'] != '0'
382
383    def set_dry_run(self):
384        self.opts['dry-run'] = '1'
385
386    def quiet(self):
387        return self.opts['quiet'] != '0'
388
389    def trace(self):
390        return self.opts['trace'] != '0'
391
392    def warn_all(self):
393        return self.opts['warn-all'] != '0'
394
395    def keep_going(self):
396        return self.opts['keep-going'] != '0'
397
398    def no_clean(self):
399        return self.opts['no-clean'] != '0'
400
401    def always_clean(self):
402        return self.opts['always-clean'] != '0'
403
404    def no_install(self):
405        return self.opts['no-install'] != '0'
406
407    def user_macros(self):
408        #
409        # Return something even if it does not exist.
410        #
411        if self.opts['macros'] is None:
412            return None
413        um = []
414        configs = self.defaults.expand('%{_configdir}').split(':')
415        for m in self.opts['macros'].split(','):
416            if path.exists(m):
417                um += [m]
418            else:
419                # Get the expanded config macros then check them.
420                cm = path.expand(m, configs)
421                ccm = path.exists(cm)
422                if True in ccm:
423                    # Pick the first found
424                    um += [cm[ccm.index(True)]]
425                else:
426                    um += [m]
427        return um if len(um) else None
428
429    def jobs(self, cpus):
430        try:
431            cpus = int(cpus)
432        except:
433            raise error.general('invalid host cpu value')
434        opt_jobs = self.opts['jobs']
435        if opt_jobs == 'default':
436            _jobs = self.defaults.get_value('jobs')
437            if _jobs is not None:
438                if _jobs == 'none':
439                    cpus = 0
440                elif _jobs == 'max':
441                    pass
442                elif _jobs == 'half':
443                    cpus = cpus / 2
444                else:
445                    try:
446                        cpus = int(_jobs)
447                    except:
448                        raise error.general('invalid %%{jobs} value: %s' % (_jobs))
449            else:
450                opt_jobs = 'max'
451        if opt_jobs != 'default':
452            if opt_jobs == 'none':
453                cpus = 0
454            elif opt_jobs == 'max':
455                pass
456            elif opt_jobs == 'half':
457                cpus = cpus / 2
458            else:
459                ok = False
460                try:
461                    i = int(opt_jobs)
462                    cpus = i
463                    ok = True
464                except:
465                    pass
466                if not ok:
467                    try:
468                        f = float(opt_jobs)
469                        cpus = f * cpus
470                        ok = True
471                    except:
472                        pass
473                    if not ok:
474                        raise error.internal('bad jobs option: %s' % (opt_jobs))
475        if cpus <= 0:
476            cpu = 1
477        return cpus
478
479    def params(self):
480        return self.opts['params']
481
482    def get_args(self):
483        for arg in self.args:
484            yield arg
485
486    def find_arg(self, arg):
487        if self.optargs is None or arg not in self.optargs:
488            raise error.internal('bad arg: %s' % (arg))
489        for a in self.args:
490            sa = a.split('=')
491            if sa[0].startswith(arg):
492                return sa
493        return None
494
495    def logfiles(self):
496        if 'log' in self.opts and self.opts['log'] is not None:
497            log = self.opts['log'].split(',')
498        elif self.log_default is None:
499            log = ['stdout']
500        else:
501            log = self.log_default
502        return log
503
504    def urls(self):
505        if self.opts['url'] is not None:
506            return self.opts['url'].split(',')
507        return None
508
509    def log_info(self):
510        log.output(' Command Line: %s' % (' '.join(self.argv)))
511        log.output(' Python: %s' % (sys.version.replace('\n', '')))
512
513def load(opts):
514    """
515    Copy the defaults, get the host specific values and merge them overriding
516    any matching defaults, then create an options object to handle the command
517    line merging in any command line overrides. Finally post process the
518    command line.
519    """
520
521    global host_windows
522
523    overrides = None
524    if os.name == 'nt':
525        try:
526            import windows
527            overrides = windows.load()
528            host_windows = True
529        except:
530            raise error.general('failed to load Windows host support')
531    elif os.name == 'posix':
532        uname = os.uname()
533        try:
534            if uname[0].startswith('CYGWIN_NT'):
535                import windows
536                overrides = windows.load()
537            elif uname[0] == 'Darwin':
538                import darwin
539                overrides = darwin.load()
540            elif uname[0] == 'FreeBSD':
541                import freebsd
542                overrides = freebsd.load()
543            elif uname[0] == 'NetBSD':
544                import netbsd
545                overrides = netbsd.load()
546            elif uname[0] == 'Linux':
547                import linux
548                overrides = linux.load()
549        except:
550            raise error.general('failed to load %s host support' % (uname[0]))
551    else:
552        raise error.general('unsupported host type; please add')
553    if overrides is None:
554        raise error.general('no hosts defaults found; please add')
555    for k in overrides:
556        opts.defaults[k] = overrides[k]
557
558    opts.local_git()
559    opts.process()
560    opts.post_process()
561
562def run(args):
563    try:
564        long_opts = {
565            # key              macro         handler   param  defs   init
566            '--test-path'  : ('_test_path',  'path',   True,  None,  False),
567            '--test-jobs'  : ('_test_jobs',  'jobs',   True,  'max', True),
568            '--test-bool'  : ('_test_bool',  'bool',   False, '0',   True)
569        }
570        opts = command_line(base_path = '.',
571                            argv = args,
572                            optargs = None,
573                            defaults = macros.macros(),
574                            long_opts = long_opts,
575                            command_path = '.')
576        load(opts)
577        log.notice('RTEMS Tools Project - Defaults, v%s' % (version.str()))
578        opts.log_info()
579        log.notice('Options:')
580        log.notice(str(opts))
581        log.notice('Defaults:')
582        log.notice(str(opts.defaults))
583    except error.general as gerr:
584        print(gerr)
585        sys.exit(1)
586    except error.internal as ierr:
587        print(ierr)
588        sys.exit(1)
589    except error.exit:
590        pass
591    except KeyboardInterrupt:
592        _notice(opts, 'abort: user terminated')
593        sys.exit(1)
594    sys.exit(0)
595
596if __name__ == '__main__':
597    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.