Skip to content

Commit 852a580

Browse files
bonzinijpakkane
authored andcommitted
options: fix option ordering
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 5ab871b commit 852a580

File tree

4 files changed

+106
-25
lines changed

4 files changed

+106
-25
lines changed

mesonbuild/interpreter/interpreter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,8 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str
12071207
self.coredata.optstore.initialize_from_subproject_call(self.subproject,
12081208
self.invoker_method_default_options,
12091209
self.project_default_options,
1210-
self.user_defined_options.cmd_line_options)
1210+
self.user_defined_options.cmd_line_options,
1211+
self.environment.options)
12111212
self.coredata.initialized_subprojects.add(self.subproject)
12121213

12131214
if not self.is_subproject():

mesonbuild/options.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,30 +1305,31 @@ def initialize_from_top_level_project_call(self,
13051305
if not self.is_cross and key.is_for_build():
13061306
continue
13071307
if key.subproject:
1308-
self.augments[key] = valstr
1308+
# do apply project() default_options for subprojects here, because
1309+
# they have low priority
1310+
self.pending_options[key] = valstr
13091311
else:
13101312
# Setting a project option with default_options
13111313
# should arguably be a hard error; the default
13121314
# value of project option should be set in the option
13131315
# file, not in the project call.
13141316
self.set_option_maybe_root(key, valstr, True)
1317+
1318+
# ignore subprojects for now for machine file and command line
1319+
# options; they are applied later
13151320
for key, valstr in machine_file_options.items():
13161321
# Due to backwards compatibility we ignore all build-machine options
13171322
# when building natively.
13181323
if not self.is_cross and key.is_for_build():
13191324
continue
1320-
if key.subproject:
1321-
self.augments[key] = valstr
1322-
else:
1325+
if not key.subproject:
13231326
self.set_option_maybe_root(key, valstr, True)
13241327
for key, valstr in cmd_line_options.items():
13251328
# Due to backwards compatibility we ignore all build-machine options
13261329
# when building natively.
13271330
if not self.is_cross and key.is_for_build():
13281331
continue
1329-
if key.subproject:
1330-
self.augments[key] = valstr
1331-
else:
1332+
if not key.subproject:
13321333
self.set_option_maybe_root(key, valstr, True)
13331334

13341335
def accept_as_pending_option(self, key: OptionKey, known_subprojects: T.Optional[T.Container[str]] = None,
@@ -1362,26 +1363,37 @@ def initialize_from_subproject_call(self,
13621363
subproject: str,
13631364
spcall_default_options: OptionDict,
13641365
project_default_options: OptionDict,
1365-
cmd_line_options: OptionDict) -> None:
1366+
cmd_line_options: OptionDict,
1367+
machine_file_options: OptionDict) -> None:
1368+
# pick up pending per-project settings from the toplevel project() invocation
1369+
options = {k: v for k, v in self.pending_options.items() if k.subproject == subproject}
1370+
1371+
# apply project() and subproject() default_options
13661372
for key, valstr in itertools.chain(project_default_options.items(), spcall_default_options.items()):
13671373
if key.subproject is None:
13681374
key = key.evolve(subproject=subproject)
13691375
elif key.subproject == subproject:
13701376
without_subp = key.evolve(subproject=None)
13711377
raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"')
1372-
# If the key points to a project option, set the value from that.
1373-
# Otherwise set an augment.
1374-
if key in self.project_options:
1375-
self.set_option(key, valstr, True)
1376-
else:
1377-
self.pending_options.pop(key, None)
1378-
self.augments[key] = valstr
1379-
# Check for pending options
1380-
for key, valstr in cmd_line_options.items():
1381-
if key.subproject != subproject:
1382-
continue
1378+
options[key] = valstr
1379+
1380+
# then global settings from machine file and command line
1381+
for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()):
1382+
if key.subproject is None:
1383+
subp_key = key.evolve(subproject=subproject)
1384+
self.pending_options.pop(subp_key, None)
1385+
options.pop(subp_key, None)
1386+
1387+
# then finally per project augments from machine file and command line
1388+
for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()):
1389+
if key.subproject == subproject:
1390+
options[key] = valstr
1391+
1392+
# merge everything that has been computed above, while giving self.augments priority
1393+
for key, valstr in options.items():
13831394
self.pending_options.pop(key, None)
1384-
if key in self.options:
1395+
valstr = self.augments.pop(key, valstr)
1396+
if key in self.project_options:
13851397
self.set_option(key, valstr, True)
13861398
else:
13871399
self.augments[key] = valstr

unittests/machinefiletests.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -563,12 +563,18 @@ def test_builtin_options_machinefile_overrides_subproject(self):
563563
check = cm.exception.stdout
564564
self.assertIn(check, 'Parent should override default_library')
565565

566-
def test_builtin_options_machinefile_global_loses_over_subproject(self):
567-
# The buildfile says subproject(... default_library: static), ensure that it overrides the machinefile
568-
# FIXME: Should it??
566+
def test_builtin_options_machinefile_global_overrides_subproject(self):
567+
# The buildfile says subproject(... default_library: static), ensure that's overridden
569568
testcase = os.path.join(self.common_test_dir, '223 persubproject options')
570569
config = self.helper_create_native_file({'built-in options': {'default_library': 'both'}})
571-
self.init(testcase, extra_args=['--native-file', config])
570+
571+
with self.assertRaises((RuntimeError, subprocess.CalledProcessError)) as cm:
572+
self.init(testcase, extra_args=['--native-file', config])
573+
if isinstance(cm, RuntimeError):
574+
check = str(cm.exception)
575+
else:
576+
check = cm.exception.stdout
577+
self.assertIn(check, 'Parent should override default_library')
572578

573579
def test_builtin_options_compiler_properties(self):
574580
# the properties section can have lang_args, and those need to be

unittests/optiontests.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,68 @@ def test_subproject_nonexistent(self):
240240
self.assertFalse(optstore.accept_as_pending_option(OptionKey('foo', subproject='found'), subprojects))
241241
self.assertTrue(optstore.accept_as_pending_option(OptionKey('foo', subproject='whatisthis'), subprojects))
242242

243+
def test_subproject_cmdline_override_global(self):
244+
name = 'optimization'
245+
subp = 'subp'
246+
new_value = '0'
247+
248+
optstore = OptionStore(False)
249+
prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr')
250+
optstore.add_system_option('prefix', prefix)
251+
o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])
252+
optstore.add_system_option(name, o)
253+
254+
toplevel_proj_default = {OptionKey(name): 's'}
255+
subp_proj_default = {OptionKey(name): '3'}
256+
cmd_line = {OptionKey(name): new_value}
257+
258+
optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {})
259+
optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {})
260+
self.assertEqual(optstore.get_value_for(name, subp), new_value)
261+
self.assertEqual(optstore.get_value_for(name), new_value)
262+
263+
def test_subproject_cmdline_override_global_and_augment(self):
264+
name = 'optimization'
265+
subp = 'subp'
266+
global_value = 's'
267+
new_value = '0'
268+
269+
optstore = OptionStore(False)
270+
prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr')
271+
optstore.add_system_option('prefix', prefix)
272+
o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])
273+
optstore.add_system_option(name, o)
274+
275+
toplevel_proj_default = {OptionKey(name): '1'}
276+
subp_proj_default = {OptionKey(name): '3'}
277+
cmd_line = {OptionKey(name): global_value, OptionKey(name, subproject=subp): new_value}
278+
279+
optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {})
280+
optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {})
281+
self.assertEqual(optstore.get_value_for(name, subp), new_value)
282+
self.assertEqual(optstore.get_value_for(name), global_value)
283+
284+
def test_subproject_cmdline_override_toplevel(self):
285+
name = 'default_library'
286+
subp = 'subp'
287+
toplevel_value = 'both'
288+
subp_value = 'static'
289+
290+
optstore = OptionStore(False)
291+
prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr')
292+
optstore.add_system_option('prefix', prefix)
293+
o = UserComboOption(name, 'Kind of library', 'both', choices=['shared', 'static', 'both'])
294+
optstore.add_system_option(name, o)
295+
296+
toplevel_proj_default = {OptionKey(name): 'shared'}
297+
subp_proj_default = {OptionKey(name): subp_value}
298+
cmd_line = {OptionKey(name, subproject=''): toplevel_value}
299+
300+
optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {})
301+
optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {})
302+
self.assertEqual(optstore.get_value_for(name, subp), subp_value)
303+
self.assertEqual(optstore.get_value_for(name), toplevel_value)
304+
243305
def test_deprecated_nonstring_value(self):
244306
# TODO: add a lot more deprecated option tests
245307
optstore = OptionStore(False)

0 commit comments

Comments
 (0)