From c9351ce30c03d107279090da7825096951a705d3 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 29 Sep 2017 10:07:29 -0700 Subject: [PATCH] Add new array type option This exposes the already existing UserStringArrayOption class through the meson_options.txt. The intention is to provide a way for projects to take list/array type arguments and validate that all of the elements in that array are valid without using complex looping constructrs. --- docs/markdown/Build-options.md | 37 +++++++++++---- docs/markdown/snippets/option-array-type.md | 17 +++++++ mesonbuild/coredata.py | 33 +++++++++---- mesonbuild/optinterpreter.py | 16 +++++++ run_unittests.py | 47 +++++++++++++++++++ .../common/166 array option/meson.build | 17 +++++++ .../common/166 array option/meson_options.txt | 19 ++++++++ test cases/common/47 options/meson.build | 5 ++ .../common/47 options/meson_options.txt | 1 + test cases/unit/18 array option/meson.build | 15 ++++++ .../unit/18 array option/meson_options.txt | 20 ++++++++ 11 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 docs/markdown/snippets/option-array-type.md create mode 100644 test cases/common/166 array option/meson.build create mode 100644 test cases/common/166 array option/meson_options.txt create mode 100644 test cases/unit/18 array option/meson.build create mode 100644 test cases/unit/18 array option/meson_options.txt diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index cb7b19e25b1c..f05eb7b904c8 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -16,18 +16,37 @@ Here is a simple option file. option('someoption', type : 'string', value : 'optval', description : 'An option') option('other_one', type : 'boolean', value : false) option('combo_opt', type : 'combo', choices : ['one', 'two', 'three'], value : 'three') +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) ``` -This demonstrates the three basic option types and their usage. String -option is just a free form string and a boolean option is, -unsurprisingly, true or false. The combo option can have any value -from the strings listed in argument `choices`. If `value` is not set, -it defaults to empty string for strings, `true` for booleans or the -first element in a combo. You can specify `description`, which is a -free form piece of text describing the option. It defaults to option -name. +All types allow a `description` value to be set describing the option, if no +option is set then the name of the option will be used instead. -These options are accessed in Meson code with the `get_option` function. +### Strings + +The string type is a free form string. If the default value is not set then an +empty string will be used as the default. + +### Booleans + +Booleans may have values of either `true` or `false`. If not default value is +supplied then `true` will be used as the default. + +### Combos + +A combo allows any one of the values in the `choices` parameter to be selected. +If no default value is set then the first value will be the default. + +### Arrays + +Arrays allow one or more of the values in the `choices` parameter to be selected. +If the `value` parameter is unset then the values of `choices` will be used as +the default. + +This type is new in version 0.44.0 + + +## Using build options ```meson optval = get_option('opt_name') diff --git a/docs/markdown/snippets/option-array-type.md b/docs/markdown/snippets/option-array-type.md new file mode 100644 index 000000000000..f073dc1aa7fd --- /dev/null +++ b/docs/markdown/snippets/option-array-type.md @@ -0,0 +1,17 @@ +# An array type for user options + +Previously to have an option that took more than one value a string value would +have to be created and split, but validating this was difficult. A new array type +has been added to the meson_options.txt for this case. It works like a 'combo', but +allows more than one option to be passed. When used on the command line (with -D), +values are passed as a comma separated list. + +```meson +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one']) +``` + +These can be overwritten on the command line, + +```meson +meson _build -Darray_opt=two,three +``` diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 401211a6b2ef..af5a19aa599e 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -127,24 +127,37 @@ def validate_value(self, value): class UserStringArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', [])) - self.set_value(value) - - def validate(self, value): - if isinstance(value, str): - if not value.startswith('['): - raise MesonException('Valuestring does not define an array: ' + value) - newvalue = ast.literal_eval(value) + self.set_value(value, user_input=False) + + def validate(self, value, user_input): + # User input is for options defined on the command line (via -D + # options). Users should put their input in as a comma separated + # string, but for defining options in meson_options.txt the format + # should match that of a combo + if not user_input: + if isinstance(value, str): + if not value.startswith('['): + raise MesonException('Valuestring does not define an array: ' + value) + newvalue = ast.literal_eval(value) + else: + newvalue = value else: - newvalue = value + assert isinstance(value, str) + newvalue = [v.strip() for v in value.split(',')] if not isinstance(newvalue, list): raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) + if self.choices: + bad = [x for x in newvalue if x not in self.choices] + if bad: + raise MesonException('Options "{}" are not in allowed choices: "{}"'.format( + ', '.join(bad), ', '.join(self.choices))) return newvalue - def set_value(self, newvalue): - self.value = self.validate(newvalue) + def set_value(self, newvalue, user_input=True): + self.value = self.validate(newvalue, user_input) def validate_value(self, value): self.validate(value) diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 01dd036e391c..22a263e729cd 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -84,9 +84,25 @@ def ComboParser(name, description, kwargs): raise OptionException('Combo choice elements must be strings.') return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0])) +@permitted_kwargs({'value', 'choices'}) +def string_array_parser(name, description, kwargs): + if 'choices' not in kwargs: + raise OptionException('Array option missing "choices" keyword.') + choices = kwargs['choices'] + if not isinstance(choices, list): + raise OptionException('Array choices must be an array.') + for i in choices: + if not isinstance(i, str): + raise OptionException('Array choice elements must be strings.') + value = kwargs.get('value', choices) + if not isinstance(value, list): + raise OptionException('Array choices must be passed as an array.') + return coredata.UserStringArrayOption(name, description, value, choices=choices) + option_types = {'string': StringParser, 'boolean': BooleanParser, 'combo': ComboParser, + 'array': string_array_parser, } class OptionInterpreter: diff --git a/run_unittests.py b/run_unittests.py index 91a71ce0a2f7..42f4e1908c3c 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1562,6 +1562,53 @@ def test_pkgconfig_gen_escaping(self): cargs = ['-I' + incdir.as_posix()] self.assertEqual(foo_dep.get_compile_args(), cargs) + def test_array_option_change(self): + def get_opt(): + opts = self.introspect('--buildoptions') + for x in opts: + if x.get('name') == 'list': + return x + raise Exception(opts) + + expected = { + 'name': 'list', + 'description': 'list', + 'type': 'stringarray', + 'value': ['foo', 'bar'], + } + tdir = os.path.join(self.unit_test_dir, '18 array option') + self.init(tdir) + original = get_opt() + self.assertDictEqual(original, expected) + + expected['value'] = ['oink', 'boink'] + self.setconf('-Dlist=oink,boink') + changed = get_opt() + self.assertEqual(changed, expected) + + def test_array_option_bad_change(self): + def get_opt(): + opts = self.introspect('--buildoptions') + for x in opts: + if x.get('name') == 'list': + return x + raise Exception(opts) + + expected = { + 'name': 'list', + 'description': 'list', + 'type': 'stringarray', + 'value': ['foo', 'bar'], + } + tdir = os.path.join(self.unit_test_dir, '18 array option') + self.init(tdir) + original = get_opt() + self.assertDictEqual(original, expected) + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dlist=bad') + changed = get_opt() + self.assertDictEqual(changed, expected) + class FailureTests(BasePlatformTests): ''' diff --git a/test cases/common/166 array option/meson.build b/test cases/common/166 array option/meson.build new file mode 100644 index 000000000000..bfcde7ce09a8 --- /dev/null +++ b/test cases/common/166 array option/meson.build @@ -0,0 +1,17 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project('stringarray default options') + +assert(get_option('array') == ['foo', 'bar'], 'Default value for array is not equal to choices') diff --git a/test cases/common/166 array option/meson_options.txt b/test cases/common/166 array option/meson_options.txt new file mode 100644 index 000000000000..7ed0ac14d5cf --- /dev/null +++ b/test cases/common/166 array option/meson_options.txt @@ -0,0 +1,19 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +option( + 'array', + type : 'array', + choices : ['foo', 'bar'], +) diff --git a/test cases/common/47 options/meson.build b/test cases/common/47 options/meson.build index 2a764f036f50..863703c0ac6a 100644 --- a/test cases/common/47 options/meson.build +++ b/test cases/common/47 options/meson.build @@ -12,6 +12,11 @@ if get_option('combo_opt') != 'combo' error('Incorrect value to combo option.') endif +if get_option('array_opt') != ['one', 'two'] + message(get_option('array_opt')) + error('Incorrect value for array option') +endif + # If the default changes, update test cases/unit/13 reconfigure if get_option('b_lto') != false error('Incorrect value in base option.') diff --git a/test cases/common/47 options/meson_options.txt b/test cases/common/47 options/meson_options.txt index 653dd75f9188..8978d44f5967 100644 --- a/test cases/common/47 options/meson_options.txt +++ b/test cases/common/47 options/meson_options.txt @@ -1,3 +1,4 @@ option('testoption', type : 'string', value : 'optval', description : 'An option to do something') option('other_one', type : 'boolean', value : false) option('combo_opt', type : 'combo', choices : ['one', 'two', 'combo'], value : 'combo') +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) diff --git a/test cases/unit/18 array option/meson.build b/test cases/unit/18 array option/meson.build new file mode 100644 index 000000000000..2b44181fbda1 --- /dev/null +++ b/test cases/unit/18 array option/meson.build @@ -0,0 +1,15 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project('array option test') diff --git a/test cases/unit/18 array option/meson_options.txt b/test cases/unit/18 array option/meson_options.txt new file mode 100644 index 000000000000..0ccdcc4ad119 --- /dev/null +++ b/test cases/unit/18 array option/meson_options.txt @@ -0,0 +1,20 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +option( + 'list', + type : 'array', + value : ['foo', 'bar'], + choices : ['foo', 'bar', 'oink', 'boink'], +)