source: rtems-tools/rtemstoolkit/options.py @ 7f11670

Last change on this file since 7f11670 was 7f11670, checked in by Chris Johns <chrisj@…>, on Oct 11, 2017 at 7:08:31 PM

rtemstoolkit: Fix error message in options.

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