source: rtems-tools/rtemstoolkit/options.py @ 2b82b80

4.11
Last change on this file since 2b82b80 was 2b82b80, checked in by Gedare Bloom <gedare@…>, on 03/20/18 at 17:01:31

4.11: fix hosts defaults loading for linux systems.

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