Allow setting project options from cross or native files

This allows adding a `[project options]` section to a cross or native file
that contains the options defined for a project in it's meson_option.txt
file.
pull/6597/head
Dylan Baker 5 years ago
parent cc201a5396
commit a6164ca5a8
  1. 20
      docs/markdown/Machine-files.md
  2. 37
      docs/markdown/snippets/project_options_in_machine_files.md
  3. 10
      mesonbuild/coredata.py
  4. 19
      mesonbuild/environment.py
  5. 81
      run_unittests.py
  6. 1
      test cases/unit/75 user options for subproject/.gitignore
  7. 3
      test cases/unit/75 user options for subproject/meson.build

@ -12,6 +12,7 @@ The following sections are allowed:
- binaries
- paths
- properties
- project options
### constants
@ -166,6 +167,25 @@ section may contain random key value pairs accessed using the
The properties section can contain any variable you like, and is accessed via
`meson.get_external_property`, or `meson.get_cross_property`.
### Project specific options
*New in 0.54.0*
Being able to set project specific options in a native or cross files can be
done using the `[project options]` section of the specific file (if doing a
cross build the options from the native file will be ignored)
For setting options in supbprojects use the `<subproject>:project options`
section instead.
```ini
[project options]
build-tests = true
[zlib:project options]
build-tests = false
```
## Loading multiple machine files
Native files allow layering (cross files can be layered since meson 0.52.0).

@ -0,0 +1,37 @@
## Project options can be set in native or cross files
A new set of sections has been added to the cross and native files, `[project
options]` and `[<subproject_name>:project options]`, where `subproject_name`
is the name of a subproject. Any options that are allowed in the project can
be set from this section. They have the lowest precedent, and will be
overwritten by command line arguments.
```meson
option('foo', type : 'string', value : 'foo')
```
```ini
[project options]
foo = 'other val'
```
```console
meson build --native-file my.ini
```
Will result in the option foo having the value `other val`,
```console
meson build --native-file my.ini -Dfoo='different val'
```
Will result in the option foo having the value `different val`,
Subproject options are assigned like this:
```ini
[zlib:project options]
foo = 'some val'
```

@ -377,6 +377,7 @@ class CoreData:
host_cache = DependencyCache(self.builtins_per_machine, MachineChoice.BUILD)
self.deps = PerMachine(build_cache, host_cache) # type: PerMachine[DependencyCache]
self.compiler_check_cache = OrderedDict()
# Only to print a warning if it changes between Meson invocations.
self.config_files = self.__load_config_files(options, scratch_dir, 'native')
self.builtin_options_libdir_cross_fixup()
@ -734,7 +735,7 @@ class CoreData:
if not self.is_cross_build():
self.copy_build_options_from_regular_ones()
def set_default_options(self, default_options, subproject, env):
def set_default_options(self, default_options: T.Mapping[str, str], subproject: str, env: 'Environment') -> None:
# Warn if the user is using two different ways of setting build-type
# options that override each other
if 'buildtype' in env.cmd_line_options and \
@ -755,6 +756,13 @@ class CoreData:
k = subproject + ':' + k
cmd_line_options[k] = v
# load the values for user options out of the appropriate machine file,
# then overload the command line
for k, v in env.user_options.get(subproject, {}).items():
if subproject:
k = '{}:{}'.format(subproject, k)
cmd_line_options[k] = v
# Override project default_options using conf files (cross or native)
for k, v in env.paths.host:
if v is not None:

@ -553,6 +553,9 @@ class Environment:
# architecture, just the build and host architectures
paths = PerMachineDefaultable()
# We only need one of these as project options are not per machine
user_options = {}
## Setup build machine defaults
# Will be fully initialized later using compilers later.
@ -565,12 +568,26 @@ class Environment:
## Read in native file(s) to override build machine configuration
def load_user_options():
for section in config.keys():
if section.endswith('project options'):
if ':' in section:
project = section.split(':')[0]
else:
project = ''
user_options[project] = config.get(section, {})
if self.coredata.config_files is not None:
config = coredata.parse_machine_files(self.coredata.config_files)
binaries.build = BinaryTable(config.get('binaries', {}))
paths.build = Directories(**config.get('paths', {}))
properties.build = Properties(config.get('properties', {}))
# Don't run this if there are any cross files, we don't want to use
# the native values if we're doing a cross build
if not self.coredata.cross_files:
load_user_options()
## Read in cross file(s) to override host machine configuration
if self.coredata.cross_files:
@ -582,6 +599,7 @@ class Environment:
if 'target_machine' in config:
machines.target = MachineInfo.from_literal(config['target_machine'])
paths.host = Directories(**config.get('paths', {}))
load_user_options()
## "freeze" now initialized configuration, and "save" to the class.
@ -589,6 +607,7 @@ class Environment:
self.binaries = binaries.default_missing()
self.properties = properties.default_missing()
self.paths = paths.default_missing()
self.user_options = user_options
exe_wrapper = self.lookup_binary_entry(MachineChoice.HOST, 'exe_wrapper')
if exe_wrapper is not None:

@ -7672,6 +7672,9 @@ class NativeFileTests(BasePlatformTests):
for section, entries in values.items():
f.write('[{}]\n'.format(section))
for k, v in entries.items():
if isinstance(v, bool):
f.write("{}={}\n".format(k, v))
else:
f.write("{}='{}'\n".format(k, v))
return filename
@ -7996,6 +7999,54 @@ class NativeFileTests(BasePlatformTests):
self.init(testcase, extra_args=['--native-file', config])
self.build()
def test_user_options(self):
testcase = os.path.join(self.common_test_dir, '43 options')
for opt, value in [('testoption', 'some other val'), ('other_one', True),
('combo_opt', 'one'), ('array_opt', ['two']),
('integer_opt', 0)]:
config = self.helper_create_native_file({'project options': {opt: value}})
with self.assertRaises(subprocess.CalledProcessError) as cm:
self.init(testcase, extra_args=['--native-file', config])
self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
def test_user_options_command_line_overrides(self):
testcase = os.path.join(self.common_test_dir, '43 options')
config = self.helper_create_native_file({'project options': {'other_one': True}})
self.init(testcase, extra_args=['--native-file', config, '-Dother_one=false'])
def test_user_options_subproject(self):
testcase = os.path.join(self.unit_test_dir, '75 user options for subproject')
s = os.path.join(testcase, 'subprojects')
if not os.path.exists(s):
os.mkdir(s)
s = os.path.join(s, 'sub')
if not os.path.exists(s):
sub = os.path.join(self.common_test_dir, '43 options')
shutil.copytree(sub, s)
for opt, value in [('testoption', 'some other val'), ('other_one', True),
('combo_opt', 'one'), ('array_opt', ['two']),
('integer_opt', 0)]:
config = self.helper_create_native_file({'project options': {'sub:{}'.format(opt): value}})
with self.assertRaises(subprocess.CalledProcessError) as cm:
self.init(testcase, extra_args=['--native-file', config])
self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
def test_option_bool(self):
# Bools are allowed to be unquoted
testcase = os.path.join(self.common_test_dir, '1 trivial')
config = self.helper_create_native_file({'built-in options': {'werror': True}})
self.init(testcase, extra_args=['--native-file', config])
configuration = self.introspect('--buildoptions')
for each in configuration:
# Test that no-per subproject options are inherited from the parent
if 'werror' in each['name']:
self.assertEqual(each['value'], True)
break
else:
self.fail('Did not find werror in build options?')
class CrossFileTests(BasePlatformTests):
@ -8005,6 +8056,11 @@ class CrossFileTests(BasePlatformTests):
This is mainly aimed to testing overrides from cross files.
"""
def setUp(self):
super().setUp()
self.current_config = 0
self.current_wrapper = 0
def _cross_file_generator(self, *, needs_exe_wrapper: bool = False,
exe_wrapper: T.Optional[T.List[str]] = None) -> str:
if is_windows():
@ -8133,6 +8189,21 @@ class CrossFileTests(BasePlatformTests):
self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
self.wipe()
def helper_create_cross_file(self, values):
"""Create a config file as a temporary file.
values should be a nested dictionary structure of {section: {key:
value}}
"""
filename = os.path.join(self.builddir, 'generated{}.config'.format(self.current_config))
self.current_config += 1
with open(filename, 'wt') as f:
for section, entries in values.items():
f.write('[{}]\n'.format(section))
for k, v in entries.items():
f.write("{}='{}'\n".format(k, v))
return filename
def test_cross_file_dirs(self):
testcase = os.path.join(self.unit_test_dir, '60 native file override')
self.init(testcase, default_args=False,
@ -8189,6 +8260,16 @@ class CrossFileTests(BasePlatformTests):
'-Ddef_sharedstatedir=sharedstatebar',
'-Ddef_sysconfdir=sysconfbar'])
def test_user_options(self):
# This is just a touch test for cross file, since the implementation
# shares code after loading from the files
testcase = os.path.join(self.common_test_dir, '43 options')
config = self.helper_create_cross_file({'project options': {'testoption': 'some other value'}})
with self.assertRaises(subprocess.CalledProcessError) as cm:
self.init(testcase, extra_args=['--native-file', config])
self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
class TAPParserTests(unittest.TestCase):
def assert_test(self, events, **kwargs):
if 'explanation' not in kwargs:

@ -0,0 +1,3 @@
project('user option for subproject')
p = subproject('sub')
Loading…
Cancel
Save