diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2018-05-13 10:36:58 -0400 |
---|---|---|
committer | Nirbheek Chauhan <nirbheek.chauhan@gmail.com> | 2018-06-06 20:02:37 +0000 |
commit | 7c4736d27f4c5d7844a44addc0305e4354440074 (patch) | |
tree | 19aa2290f1f9b40c4ef543bda84285a96d9db45c /mesonbuild | |
parent | b38452636cc25ffd38d379645607f1563de59d80 (diff) | |
download | meson-7c4736d27f4c5d7844a44addc0305e4354440074.zip meson-7c4736d27f4c5d7844a44addc0305e4354440074.tar.gz meson-7c4736d27f4c5d7844a44addc0305e4354440074.tar.bz2 |
Convert args.projectoptions into a dict
This simplifies a lot of code, and centralize "key=value" parsing in a
single place.
Unknown command line options becomes an hard error instead of
merely printing warning message. It has been warning it would become an
hard error for a while now. This has exceptions though, any
unknown option starting with "<lang>_" or "b_" are ignored because they
depend on which languages gets added and which compiler gets selected.
Also any option for unknown subproject are ignored because they depend
on which subproject actually gets built.
Also write more command line parsing tests. "19 bad command line
options" is removed because bad cmd line option became hard error and
it's covered with new tests in "30 command line".
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/coredata.py | 41 | ||||
-rw-r--r-- | mesonbuild/environment.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 142 | ||||
-rw-r--r-- | mesonbuild/optinterpreter.py | 45 |
4 files changed, 75 insertions, 155 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index d0521b6..2d44b99 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -301,18 +301,11 @@ class CoreData: args = [key] + builtin_options[key][1:-1] + [value] self.builtins[key] = builtin_options[key][0](*args) - def init_backend_options(self, backend_name, options): + def init_backend_options(self, backend_name): if backend_name == 'ninja': self.backend_options['backend_max_links'] = UserIntegerOption('backend_max_links', 'Maximum number of linker processes to run or 0 for no limit', 0, None, 0) - for o in options: - key, value = o.split('=', 1) - if not key.startswith('backend_'): - continue - if key not in self.backend_options: - raise MesonException('Unknown backend option %s' % key) - self.backend_options[key].set_value(value) def get_builtin_option(self, optname): if optname in self.builtins: @@ -497,27 +490,6 @@ def register_builtin_arguments(parser): parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", help='Set the value of an option, can be used several times to set multiple options.') -def filter_builtin_options(args): - """Filter out any builtin arguments passed as -- instead of -D. - - Error if an argument is passed with -- and -D - """ - for name in builtin_options: - cmdline_name = get_builtin_option_cmdline_name(name) - # Chekc if user passed -Doption=value or --option=value - has_dashdash = hasattr(args, name) - has_dashd = any([a.startswith('{}='.format(name)) for a in args.projectoptions]) - - # Passing both is ambigous, abort - if has_dashdash and has_dashd: - raise MesonException( - 'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name)) - - # Pretend --option never existed - if has_dashdash: - args.projectoptions.append('{}={}'.format(name, getattr(args, name))) - delattr(args, name) - def create_options_dict(options): result = {} for o in options: @@ -529,9 +501,18 @@ def create_options_dict(options): return result def parse_cmd_line_options(args): - filter_builtin_options(args) args.cmd_line_options = create_options_dict(args.projectoptions) + # Merge builtin options set with --option into the dict. + for name in builtin_options: + value = getattr(args, name, None) + if value is not None: + if name in args.cmd_line_options: + cmdline_name = get_builtin_option_cmdline_name(name) + raise MesonException( + 'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name)) + args.cmd_line_options[name] = value + delattr(args, name) builtin_options = { 'buildtype': [UserComboOption, 'Build type to use.', ['plain', 'debug', 'debugoptimized', 'release', 'minsize'], 'debug'], diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 9b252a2..074bd75 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -288,7 +288,7 @@ class Environment: self.cross_info = CrossBuildInfo(self.coredata.cross_file) else: self.cross_info = None - self.cmd_line_options = options + self.cmd_line_options = options.cmd_line_options # List of potential compilers. if mesonlib.is_windows(): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index ffe942f..86c55e4 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1755,7 +1755,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, class Interpreter(InterpreterBase): def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects', - modules = None, default_project_options=[]): + modules = None, default_project_options=None): super().__init__(build.environment.get_source_dir(), subdir) self.an_unpicklable_object = mesonlib.an_unpicklable_object self.build = build @@ -1781,7 +1781,11 @@ class Interpreter(InterpreterBase): self.global_args_frozen = False # implies self.project_args_frozen self.subprojects = {} self.subproject_stack = [] - self.default_project_options = default_project_options[:] # Passed from the outside, only used in subprojects. + # Passed from the outside, only used in subprojects. + if default_project_options: + self.default_project_options = default_project_options.copy() + else: + self.default_project_options = {} self.build_func_dict() # build_def_files needs to be defined before parse_project is called self.build_def_files = [os.path.join(self.subdir, environment.build_filename)] @@ -2106,6 +2110,8 @@ external dependencies (including libraries) must go to "dependencies".''') return self.do_subproject(dirname, kwargs) def do_subproject(self, dirname, kwargs): + default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) + default_options = coredata.create_options_dict(default_options) if dirname == '': raise InterpreterException('Subproject dir name must not be empty.') if dirname[0] == '.': @@ -2142,7 +2148,7 @@ external dependencies (including libraries) must go to "dependencies".''') with mlog.nested(): mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir, - self.modules, mesonlib.stringlistify(kwargs.get('default_options', []))) + self.modules, default_options) subi.subprojects = self.subprojects subi.subproject_stack = self.subproject_stack + [dirname] @@ -2206,53 +2212,34 @@ to directly access options of other subprojects.''') raise InterpreterException('configuration_data takes no arguments') return ConfigurationDataHolder() - def parse_default_options(self, default_options): - default_options = listify(default_options) - for option in default_options: - if not isinstance(option, str): - mlog.debug(option) - raise InterpreterException('Default options must be strings') - if '=' not in option: - raise InterpreterException('All default options must be of type key=value.') - key, value = option.split('=', 1) - if coredata.is_builtin_option(key): - if self.subproject != '': - continue # Only the master project is allowed to set global options. - newoptions = [option] + self.environment.cmd_line_options.projectoptions - self.environment.cmd_line_options.projectoptions = newoptions - else: - # Option values set with subproject() default_options override those - # set in project() default_options. - pref = key + '=' - for i in self.default_project_options: - if i.startswith(pref): - option = i - break - # If we are in a subproject, add the subproject prefix to option - # name. - if self.subproject != '': - option = self.subproject + ':' + option - newoptions = [option] + self.environment.cmd_line_options.projectoptions - self.environment.cmd_line_options.projectoptions = newoptions - # Add options that are only in default_options. - for defopt in self.default_project_options: - key, value = defopt.split('=') - pref = key + '=' - for i in default_options: - if i.startswith(pref): - break - else: - defopt = self.subproject + ':' + defopt - newoptions = [defopt] + self.environment.cmd_line_options.projectoptions - self.environment.cmd_line_options.projectoptions = newoptions - - def set_builtin_options(self): - # Create a dict containing only builtin options, then use - # coredata.set_options() because it already has code to set the prefix - # option first to sanitize all other options. - options = coredata.create_options_dict(self.environment.cmd_line_options.projectoptions) - options = {k: v for k, v in options.items() if coredata.is_builtin_option(k)} - self.coredata.set_options(options) + def set_options(self, default_options): + # Set default options as if they were passed to the command line. + # Subprojects can only define default for user options. + for k, v in default_options.items(): + if self.subproject: + if optinterpreter.is_invalid_name(k): + continue + k = self.subproject + ':' + k + self.environment.cmd_line_options.setdefault(k, v) + + # Create a subset of cmd_line_options, keeping only options for this + # subproject. Also take builtin options if it's the main project. + # Language and backend specific options will be set later when adding + # languages and setting the backend (builtin options must be set first + # to know which backend we'll use). + options = {} + for k, v in self.environment.cmd_line_options.items(): + if self.subproject: + if not k.startswith(self.subproject + ':'): + continue + elif k not in coredata.get_builtin_options(): + if ':' in k: + continue + if optinterpreter.is_invalid_name(k): + continue + options[k] = v + + self.coredata.set_options(options, self.subproject) def set_backend(self): # The backend is already set when parsing subprojects @@ -2282,7 +2269,10 @@ to directly access options of other subprojects.''') else: raise InterpreterException('Unknown backend "%s".' % backend) - self.coredata.init_backend_options(backend, self.environment.cmd_line_options.projectoptions) + self.coredata.init_backend_options(backend) + + options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')} + self.coredata.set_options(options) @stringArgs @permittedKwargs(permitted_kwargs['project']) @@ -2293,20 +2283,20 @@ to directly access options of other subprojects.''') proj_langs = args[1:] if ':' in proj_name: raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) - default_options = kwargs.get('default_options', []) - if self.environment.first_invocation and (len(default_options) > 0 or - len(self.default_project_options) > 0): - self.parse_default_options(default_options) - if not self.is_subproject(): - self.build.project_name = proj_name - self.set_builtin_options() + if os.path.exists(self.option_file): - oi = optinterpreter.OptionInterpreter(self.subproject, - self.build.environment.cmd_line_options.projectoptions, - ) + oi = optinterpreter.OptionInterpreter(self.subproject) oi.process(self.option_file) self.coredata.merge_user_options(oi.options) + + default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) + default_options = coredata.create_options_dict(default_options) + default_options.update(self.default_project_options) + self.set_options(default_options) self.set_backend() + + if not self.is_subproject(): + self.build.project_name = proj_name self.active_projectname = proj_name self.project_version = kwargs.get('version', 'undefined') if self.build.project_version is None: @@ -2450,17 +2440,14 @@ to directly access options of other subprojects.''') cross_comp.sanity_check(self.environment.get_scratch_dir(), self.environment) self.coredata.cross_compilers[lang] = cross_comp new_options.update(cross_comp.get_options()) + optprefix = lang + '_' - for i in new_options: - if not i.startswith(optprefix): - raise InterpreterException('Internal error, %s has incorrect prefix.' % i) - cmd_prefix = i + '=' - for cmd_arg in self.environment.cmd_line_options.projectoptions: - if cmd_arg.startswith(cmd_prefix): - value = cmd_arg.split('=', 1)[1] - new_options[i].set_value(value) - new_options.update(self.coredata.compiler_options) - self.coredata.compiler_options = new_options + for k, o in new_options.items(): + if not k.startswith(optprefix): + raise InterpreterException('Internal error, %s has incorrect prefix.' % k) + if k in self.environment.cmd_line_options: + o.set_value(self.environment.cmd_line_options[k]) + self.coredata.compiler_options.setdefault(k, o) # Unlike compiler and linker flags, preprocessor flags are not in # compiler_options because they are not visible to user. @@ -2510,19 +2497,14 @@ to directly access options of other subprojects.''') def add_base_options(self, compiler): enabled_opts = [] - proj_opt = self.environment.cmd_line_options.projectoptions for optname in compiler.base_options: if optname in self.coredata.base_options: continue oobj = compilers.base_options[optname] - for po in proj_opt: - if po.startswith(optname + '='): - opt, value = po.split('=', 1) - oobj.set_value(value) - if oobj.value: - enabled_opts.append(opt) - break - self.coredata.base_options[optname] = oobj + if optname in self.environment.cmd_line_options: + oobj.set_value(self.environment.cmd_line_options[optname]) + enabled_opts.append(optname) + self.coredata. base_options[optname] = oobj self.emit_base_options_warnings(enabled_opts) def program_from_cross_file(self, prognames, silent=False): diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 7455c48..4207f45 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -15,7 +15,6 @@ import os, re import functools -from . import mlog from . import mparser from . import coredata from . import mesonlib @@ -125,48 +124,9 @@ option_types = {'string': StringParser, } class OptionInterpreter: - def __init__(self, subproject, command_line_options): + def __init__(self, subproject): self.options = {} self.subproject = subproject - self.sbprefix = subproject + ':' - self.cmd_line_options = {} - for o in command_line_options: - if self.subproject != '': # Strip the beginning. - # Ignore options that aren't for this subproject - if not o.startswith(self.sbprefix): - continue - try: - (key, value) = o.split('=', 1) - except ValueError: - raise OptionException('Option {!r} must have a value separated by equals sign.'.format(o)) - # Ignore subproject options if not fetching subproject options - if self.subproject == '' and ':' in key: - continue - self.cmd_line_options[key] = value - - def get_bad_options(self): - subproj_len = len(self.subproject) - if subproj_len > 0: - subproj_len += 1 - retval = [] - # The options need to be sorted (e.g. here) to get consistent - # error messages (on all platforms) which is required by some test - # cases that check (also) the order of these options. - for option in sorted(self.cmd_line_options): - if option in list(self.options) + forbidden_option_names: - continue - if any(option[subproj_len:].startswith(p) for p in forbidden_prefixes): - continue - retval += [option] - return retval - - def check_for_bad_options(self): - bad = self.get_bad_options() - if bad: - sub = 'In subproject {}: '.format(self.subproject) if self.subproject else '' - mlog.warning( - '{}Unknown command line options: "{}"\n' - 'This will become a hard error in a future Meson release.'.format(sub, ', '.join(bad))) def process(self, option_file): try: @@ -187,7 +147,6 @@ class OptionInterpreter: e.colno = cur.colno e.file = os.path.join('meson_options.txt') raise e - self.check_for_bad_options() def reduce_single(self, arg): if isinstance(arg, str): @@ -243,6 +202,4 @@ class OptionInterpreter: opt = option_types[opt_type](opt_name, kwargs.pop('description', ''), kwargs) if opt.description == '': opt.description = opt_name - if opt_name in self.cmd_line_options: - opt.set_value(self.cmd_line_options[opt_name]) self.options[opt_name] = opt |