Skip to content

Commit

Permalink
Add new array type option
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dcbaker committed Nov 28, 2017
1 parent 703f5f3 commit e9aa293
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 11 deletions.
5 changes: 4 additions & 1 deletion docs/markdown/Build-options.md
Expand Up @@ -16,9 +16,10 @@ 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
This demonstrates the four 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,
Expand All @@ -29,6 +30,8 @@ name.

These options are accessed in Meson code with the `get_option` function.

The array type is new in 0.44.0

```meson
optval = get_option('opt_name')
```
Expand Down
17 changes: 17 additions & 0 deletions 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
```
33 changes: 23 additions & 10 deletions mesonbuild/coredata.py
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions mesonbuild/optinterpreter.py
Expand Up @@ -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[0])
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:
Expand Down
47 changes: 47 additions & 0 deletions run_unittests.py
Expand Up @@ -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):
'''
Expand Down
9 changes: 9 additions & 0 deletions test cases/common/47 options/meson.build
Expand Up @@ -12,6 +12,15 @@ 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 (default >1 element)')
endif

if get_option('array_opt2') != ['one']
error('Incorrect value for array option (default 1 element)')
endif

# If the default changes, update test cases/unit/13 reconfigure
if get_option('b_lto') != false
error('Incorrect value in base option.')
Expand Down
2 changes: 2 additions & 0 deletions test cases/common/47 options/meson_options.txt
@@ -1,3 +1,5 @@
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'])
option('array_opt2', type : 'array', choices : ['one', 'two', 'three'], value : 'one')
15 changes: 15 additions & 0 deletions 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')
20 changes: 20 additions & 0 deletions 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'],
)

0 comments on commit e9aa293

Please sign in to comment.