Add general override functionality. Closes #3001.

overrides
Jussi Pakkanen 4 years ago
parent 1730778974
commit f8529b3621
  1. 1
      docs/markdown/Builtin-options.md
  2. 3
      mesonbuild/backend/backends.py
  3. 94
      mesonbuild/coredata.py
  4. 59
      mesonbuild/environment.py
  5. 4
      mesonbuild/interpreter/interpreter.py
  6. 2
      mesonbuild/mintro.py
  7. 31
      run_unittests.py
  8. 7
      test cases/unit/96 overriding/meson.build
  9. 6
      test cases/unit/96 overriding/overrides.txt
  10. 6
      test cases/unit/96 overriding/subprojects/othersub/meson.build
  11. 1
      test cases/unit/96 overriding/subprojects/othersub/other.c
  12. 5
      test cases/unit/96 overriding/subprojects/somesub/meson.build
  13. 1
      test cases/unit/96 overriding/subprojects/somesub/some.c
  14. 1
      test cases/unit/96 overriding/toplevel.c

@ -87,6 +87,7 @@ machine](#specifying-options-per-machine) section for details.
| werror | false | Treat warnings as errors | no | yes |
| wrap_mode {default, nofallback,<br>nodownload, forcefallback, nopromote} | default | Wrap mode to use | no | no |
| force_fallback_for | [] | Force fallback for those dependencies | no | no |
| override_file | '' | Path to file containing option overrides | no | no |
<a name="build-type-options"></a> For setting optimization levels and
toggling debug, you can either set the `buildtype` option, or you can

@ -263,6 +263,9 @@ class Backend:
return OptionOverrideProxy(comp_override, comp_reg)
def get_option_for_target(self, option_name: 'OptionKey', target: build.BuildTarget):
potential = self.environment.overrides.value_if_overridden(option_name, target.subproject)
if potential is not None:
return potential
if option_name in target.option_overrides_base:
override = target.option_overrides_base[option_name]
return self.environment.coredata.validate_option_value(option_name, override)

@ -49,6 +49,19 @@ default_yielding = False
# Can't bind this near the class method it seems, sadly.
_T = T.TypeVar('_T')
BUILDTYPE_2_DEBUG = {'plain': False,
'debug': True,
'debugoptimized': True,
'release': False,
'minsize': True,
}
BUILDTYPE_2_OPTIMIZATION = {'plain': '0',
'debug':'0',
'debugoptimized':'2',
'release': '3',
'minsize':'s',
}
class MesonVersionMismatchException(MesonException):
'''Build directory generated with Meson version is incompatible with current version'''
@ -94,6 +107,27 @@ class UserStringOption(UserOption[str]):
raise MesonException('Value "%s" for string option is not a string.' % str(value))
return value
class UserFilePathOption(UserOption[str]):
def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
super().__init__(description, None, yielding)
self.path_must_exist = True
self.set_value(value)
def validate_value(self, value: T.Any) -> str:
if not isinstance(value, str):
raise MesonException('Value "%s" for file path option is not a string.' % str(value))
if not value:
return value
# Store absolute paths, because the original Meson command
# might not have been run from the build root.
if not os.path.isabs(value):
value = os.path.join(os.getcwd(), value)
if not self.path_must_exist:
return value
if not os.path.exists(value):
raise MesonException(f'File path "{value}" for does not exist on the file system.')
return value
class UserBooleanOption(UserOption[bool]):
def __init__(self, description: str, value, yielding: T.Optional[bool] = None) -> None:
super().__init__(description, [True, False], yielding)
@ -374,6 +408,7 @@ class DependencyCache:
def clear(self) -> None:
self.__cache.clear()
# Can't bind this near the class method it seems, sadly.
_V = T.TypeVar('_V')
@ -603,24 +638,10 @@ class CoreData:
def get_nondefault_buildtype_args(self):
result= []
value = self.options[OptionKey('buildtype')].value
if value == 'plain':
opt = '0'
debug = False
elif value == 'debug':
opt = '0'
debug = True
elif value == 'debugoptimized':
opt = '2'
debug = True
elif value == 'release':
opt = '3'
debug = False
elif value == 'minsize':
opt = 's'
debug = True
else:
assert(value == 'custom')
if value == 'custom':
return []
opt = BUILDTYPE_2_OPTIMIZATION[value]
debug = BUILDTYPE_2_DEBUG[value]
actual_opt = self.options[OptionKey('optimization')].value
actual_debug = self.options[OptionKey('debug')].value
if actual_opt != opt:
@ -831,7 +852,7 @@ class MachineFileParser():
def __init__(self, filenames: T.List[str]) -> None:
self.parser = CmdLineFileParser()
self.constants = {'True': True, 'False': False}
self.sections = {}
self.sections = {}
self.parser.read(filenames)
@ -1124,24 +1145,25 @@ BUILTIN_DIR_OPTIONS: 'KeyedOptionDictType' = OrderedDict([
])
BUILTIN_CORE_OPTIONS: 'KeyedOptionDictType' = OrderedDict([
(OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')),
(OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist)),
(OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug',
choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])),
(OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Debug', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)),
(OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)),
(OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')),
(OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])),
(OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['0', 'g', '1', '2', '3', 's'])),
(OptionKey('stdsplit'), BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)),
(OptionKey('strip'), BuiltinOption(UserBooleanOption, 'Strip targets on install', False)),
(OptionKey('unity'), BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])),
(OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))),
(OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3'], yielding=False)),
(OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)),
(OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])),
(OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')),
(OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist)),
(OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug',
choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])),
(OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Debug', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)),
(OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)),
(OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')),
(OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])),
(OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['0', 'g', '1', '2', '3', 's'])),
(OptionKey('override_file'), BuiltinOption(UserFilePathOption, 'Override file path', '')),
(OptionKey('stdsplit'), BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)),
(OptionKey('strip'), BuiltinOption(UserBooleanOption, 'Strip targets on install', False)),
(OptionKey('unity'), BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])),
(OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))),
(OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3'], yielding=False)),
(OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)),
(OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])),
(OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])),
])

@ -560,6 +560,45 @@ def search_version(text: str) -> str:
return 'unknown version'
class OptionOverrides:
def __init__(self):
self.subproject_overrides = {} # Overrides option value in all subprojects but not the main project.
self.option_overrides = {}
def add_subprojects_override(self, key, value):
self.subproject_overrides[key] = value
def value_if_overridden(self, option, subproject): # FIXME add target as an argument?
if subproject == '':
return None
if isinstance(option, str):
option_name = option
else:
option_name = option.name
option_key = (subproject, option_name)
if option_key in self.option_overrides:
return self.option_overrides[option_key]
# There was no specifc override for this option. However
# overriding the build type implicitly sets debug and
# optimization. Thus we
if option_name == 'optimization':
buildtype = self.value_if_overridden('buildtype', subproject)
if buildtype is None:
return None
return coredata.BUILDTYPE_2_OPTIMIZATION[buildtype]
elif option_name == 'debug':
buildtype = self.value_if_overridden('buildtype', subproject)
if buildtype is None:
return None
return coredata.BUILDTYPE_2_DEBUG[buildtype]
if option_name not in self.subproject_overrides:
return None
return self.subproject_overrides[option_name]
def add_option_override(self, subproject, key, value):
self.option_overrides[(subproject, key)] = value
class Environment:
private_dir = 'meson-private'
log_dir = 'meson-logs'
@ -742,6 +781,8 @@ class Environment:
self.default_cmake = ['cmake']
self.default_pkgconfig = ['pkg-config']
self.wrap_resolver = None
self.overrides = OptionOverrides()
self.init_overrides()
def _load_machine_file_options(self, config: 'ConfigParser', properties: Properties, machine: MachineChoice) -> None:
"""Read the contents of a Machine file and put it in the options store."""
@ -2185,3 +2226,21 @@ class Environment:
if not self.need_exe_wrapper():
return EmptyExternalProgram()
return self.exe_wrapper
def init_overrides(self) -> None:
k = OptionKey('override_file')
override_file = self.options.get(k, None)
if override_file:
if not os.path.exists(override_file):
raise mesonlib.MesonException(f'Override file {override_file} does not exist.')
sections = coredata.MachineFileParser(override_file).sections
for section_name, entries in coredata.MachineFileParser(override_file).sections.items():
if section_name == 'options_subprojects':
for k, v in entries.items():
self.overrides.add_subprojects_override(k, v)
elif section_name.startswith('options_'):
subproject_name = section_name.split('_', 1)[1]
for k, v in entries.items():
self.overrides.add_option_override(subproject_name, k, v)
else:
raise mesonlib.MesonException(f'Unknown section "{section_name}" in override file.')

@ -858,6 +858,10 @@ external dependencies (including libraries) must go to "dependencies".''')
def get_option_internal(self, optname: str):
key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
maybe_v = self.environment.overrides.value_if_overridden(optname, self.subproject)
if maybe_v is not None:
return maybe_v
if not key.is_project():
for opts in [self.coredata.options, compilers.base_options]:
v = opts.get(key)

@ -256,6 +256,8 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s
typestr = 'integer'
elif isinstance(opt, cdata.UserArrayOption):
typestr = 'array'
elif isinstance(opt, cdata.UserFilePathOption):
typestr = 'filepath'
else:
raise RuntimeError("Unknown option type")
optdict['type'] = typestr

@ -1720,7 +1720,8 @@ class BasePlatformTests(unittest.TestCase):
self.common_test_dir = os.path.join(src_root, 'test cases/common')
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
self.unit_test_rel_dir = 'test cases/unit'
self.unit_test_dir = os.path.join(src_root, self.unit_test_rel_dir)
self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite')
self.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike')
self.objc_test_dir = os.path.join(src_root, 'test cases/objc')
@ -4654,6 +4655,7 @@ class AllPlatformTests(BasePlatformTests):
('boolean', bool, []),
('integer', int, []),
('array', list, []),
('filepath', str, []),
]
buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test']
@ -5705,6 +5707,33 @@ class AllPlatformTests(BasePlatformTests):
link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
self.assertEqual(sorted(link_args), sorted(['-flto']))
def test_overrides(self):
d = '96 overriding'
testdir = os.path.join(self.unit_test_dir, d)
# Use a relative path to the override file to test that path
# preservation works.
override_file = os.path.join(self.unit_test_rel_dir, d, 'overrides.txt')
self.init(testdir, extra_args=['-Doverride_file='+ override_file])
self.build()
file_found = False
for i in self.introspect('--buildoptions'):
if i['name'] == 'override_file':
self.assertTrue(os.path.isabs(i['value']))
file_found = True
self.assertTrue(file_found, 'Override file missing from build options.')
for c in self.get_compdb():
if 'toplevel.c' in c['file']:
self.assertNotIn('-O1', c['command'])
self.assertNotIn('-O2', c['command'])
elif 'other.c' in c['file']:
self.assertNotIn('-O2', c['command'])
self.assertIn('-O1', c['command'])
elif 'some.c' in c['file']:
self.assertNotIn('-O1', c['command'])
self.assertIn('-O2', c['command'])
else:
self.assertTrue(False, 'Unknown input file {} in compdb'.format(c['file']))
class FailureTests(BasePlatformTests):
'''
Tests that test failure conditions. Build files here should be dynamically

@ -0,0 +1,7 @@
project('mainprog', 'c')
assert(get_option('buildtype') == 'debug')
subproject('somesub')
subproject('othersub')
executable('toplevel', 'toplevel.c')

@ -0,0 +1,6 @@
[options_subprojects]
buildtype = 'debugoptimized'
[options_othersub]
buildtype = 'release'
optimization = '1'

@ -0,0 +1,6 @@
project('othersub', 'c')
assert(get_option('buildtype') == 'release')
assert(get_option('optimization') == '1')
executable('other', 'other.c')

@ -0,0 +1 @@
int main(int argc, char **argv) { (void) argc; (void)argv; return 0;}

@ -0,0 +1,5 @@
project('somesub', 'c')
assert(get_option('buildtype') == 'debugoptimized')
executable('some', 'some.c')

@ -0,0 +1 @@
int main(int argc, char **argv) { (void) argc; (void)argv; return 0;}

@ -0,0 +1 @@
int main(int argc, char **argv) { (void) argc; (void)argv; return 0;}
Loading…
Cancel
Save