Skip to content

Commit

Permalink
Update buildtype from debug and optimization
Browse files Browse the repository at this point in the history
By adding a flag to indicate whether an option was explicitly set or
not, it is now possible to update the value of `buildtype` from the
values of `debug` and `optimization`, without modifying options that are
explicitly set.

Fixes mesonbuild#11645
Fixes mesonbuild#5814 (maybe this one was already fixed, but at least a test
proves it works)
  • Loading branch information
bruchar1 committed Jan 4, 2024
1 parent e4bbc63 commit d871b79
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 44 deletions.
99 changes: 55 additions & 44 deletions mesonbuild/coredata.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@

backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none']
genvslitelist = ['vs2022']
buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']
buildtypemap = {
'plain': {'debug': False, 'optimization': 'plain'},
'debug': {'debug': True, 'optimization': '0'},
'debugoptimized': {'debug': True, 'optimization': '2'},
'release': {'debug': False, 'optimization': '3'},
'minsize': {'debug': True, 'optimization': 's'},
'custom': {},
}

DEFAULT_YIELDING = False

Expand All @@ -77,7 +84,7 @@
def get_genvs_default_buildtype_list() -> list[str]:
# just debug, debugoptimized, and release for now
# but this should probably be configurable through some extra option, alongside --genvslite.
return buildtypelist[1:-2]
return list(buildtypemap)[1:-2]


class MesonVersionMismatchException(MesonException):
Expand All @@ -102,6 +109,7 @@ def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]
self.yielding = yielding
self.deprecated = deprecated
self.readonly = False
self.is_default = False

def listify(self, value: T.Any) -> T.List[T.Any]:
return [value]
Expand All @@ -116,9 +124,10 @@ def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bo
def validate_value(self, value: T.Any) -> _T:
raise RuntimeError('Derived option class did not override validate_value.')

def set_value(self, newvalue: T.Any) -> bool:
def set_value(self, newvalue: T.Any, is_default: bool = False) -> bool:
oldvalue = getattr(self, 'value', None)
self.value = self.validate_value(newvalue)
self.is_default = is_default
return self.value != oldvalue

class UserStringOption(UserOption[str]):
Expand Down Expand Up @@ -813,56 +822,32 @@ def clear_cache(self) -> None:
def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]]:
result: T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]] = []
value = self.options[OptionKey('buildtype')].value
if value == 'plain':
opt = 'plain'
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'
options = buildtypemap[value]
if not options:
return []

actual_opt = self.options[OptionKey('optimization')].value
actual_debug = self.options[OptionKey('debug')].value
if actual_opt != opt:
result.append(('optimization', actual_opt, opt))
if actual_debug != debug:
result.append(('debug', actual_debug, debug))
if actual_opt != options['optimization']:
result.append(('optimization', actual_opt, options['optimization']))
if actual_debug != options['debug']:
result.append(('debug', actual_debug, options['debug']))
return result

def _set_others_from_buildtype(self, value: str) -> bool:
dirty = False

if value == 'plain':
opt = 'plain'
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'
options = buildtypemap[value]
if not options:
return False

dirty |= self.options[OptionKey('optimization')].set_value(opt)
dirty |= self.options[OptionKey('debug')].set_value(debug)
optimization_opt = self.options[OptionKey('optimization')]
if optimization_opt.is_default:
dirty |= optimization_opt.set_value(options['optimization'], is_default=True)

debug_opt = self.options[OptionKey('debug')]
if debug_opt.is_default:
dirty |= debug_opt.set_value(options['debug'], is_default=True)

return dirty

Expand Down Expand Up @@ -953,6 +938,8 @@ def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '', f
if not self.is_cross_build():
dirty |= self.copy_build_options_from_regular_ones()

dirty |= self.update_alias_options()

return dirty

def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], subproject: str, env: 'Environment') -> None:
Expand Down Expand Up @@ -993,6 +980,27 @@ def set_default_options(self, default_options: T.MutableMapping[OptionKey, str],

self.set_options(options, subproject=subproject, first_invocation=env.first_invocation)

def update_buildtype_alias(self) -> bool:
dirty = False

buildtype_opt = self.options[OptionKey('buildtype')]
if buildtype_opt.is_default:
lookup = {
'debug': self.get_option(OptionKey('debug')),
'optimization': self.get_option(OptionKey('optimization')),
}
try:
buildtype_value = next(k for k, v in buildtypemap.items() if v == lookup)
except StopIteration:
buildtype_value = 'custom'

dirty = buildtype_opt.set_value(buildtype_value, is_default=True)

return dirty

def update_alias_options(self) -> bool:
return self.update_buildtype_alias()

def add_compiler_options(self, options: 'MutableKeyedOptionDictType', lang: str, for_machine: MachineChoice,
env: 'Environment') -> None:
for k, o in options.items():
Expand Down Expand Up @@ -1264,13 +1272,16 @@ def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yield

def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U:
"""Create an instance of opt_type and return it."""
is_default = False
if value is None:
is_default = True
value = self.prefixed_default(name, prefix)
keywords = {'yielding': self.yielding, 'value': value}
if self.choices:
keywords['choices'] = self.choices
o = self.opt_type(self.description, **keywords)
o.readonly = self.readonly
o.is_default = is_default
return o

def _argparse_action(self) -> T.Optional[str]:
Expand Down Expand Up @@ -1356,7 +1367,7 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi
choices=genvslitelist)
),
(OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug',
choices=buildtypelist)),
choices=list(buildtypemap))),
(OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)),
Expand Down
8 changes: 8 additions & 0 deletions test cases/unit/122 default options/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
project(
'empty project',
default_options : [
'buildtype=release',
'optimization=2',
'b_ndebug=if-release'
]
)
56 changes: 56 additions & 0 deletions unittests/platformagnostictests.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,59 @@ def test_error_configuring_subdir(self):
self.assertIn('first statement must be a call to project()', out)
# provide guidance diagnostics by finding a file whose first AST statement is project()
self.assertIn(f'Did you mean to run meson from the directory: "{testdir}"?', out)

def test_buildtype_from_debug_and_optimization(self):
"""Issue #11645"""
testdir = os.path.join(self.unit_test_dir, '116 empty project')

configurations = [
('plain', ['-Ddebug=false', '-Doptimization=plain']),
('debug', ['-Ddebug=true', '-Doptimization=0']),
('debugoptimized', ['-Ddebug=true', '-Doptimization=2']),
('release', ['-Ddebug=false', '-Doptimization=3']),
('minsize', ['-Ddebug=true', '-Doptimization=s']),
('custom', ['-Ddebug=false', '-Doptimization=0']),

('debug', []),
('debugoptimized', ['-Doptimization=2']),
('custom', ['-Doptimization=1']),
('custom', ['-Ddebug=false']),

# ensure buildtype is kept when explicitly set
('debug', ['-Ddebug=false', '-Doptimization=3', '-Dbuildtype=debug']),
]

for buildtype, extra_args in configurations:
self.new_builddir()
self.init(testdir, extra_args=extra_args, allow_fail=False)
self.assertEqual(buildtype, self.getconf('buildtype'))

def test_debug_and_optimization_from_buildtype(self):
testdir = os.path.join(self.unit_test_dir, '116 empty project')

configurations = [
(['-Dbuildtype=plain'], False, 'plain'),
(['-Dbuildtype=debug'], True, '0'),
(['-Dbuildtype=debugoptimized'], True, '2'),
(['-Dbuildtype=release'], False, '3'),
(['-Dbuildtype=minsize'], True, 's'),
(['-Dbuildtype=custom'], True, '0'), # keep default values for debug and optimization

# ensure values can still be overrided
(['-Dbuildtype=debug', '-Ddebug=False', '-Doptimization=3'], False, '3'),
]

for extra_args, debug, optimization in configurations:
self.new_builddir()
self.init(testdir, extra_args=extra_args, allow_fail=False)
self.assertEqual(debug, self.getconf('debug'))
self.assertEqual(optimization, self.getconf('optimization'))

def test_buildtype_and_optimization_from_project_default_options(self):
""" Issue #5814 """
testdir = os.path.join(self.unit_test_dir, '122 default options')
self.init(testdir, allow_fail=False)

self.assertEqual(False, self.getconf('debug'))
self.assertEqual('2', self.getconf('optimization'))
self.assertEqual('release', self.getconf('buildtype'))

0 comments on commit d871b79

Please sign in to comment.