From dc1431379ce18cbdedbc67d9e1289ac8564e362e Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Mon, 9 Dec 2024 12:06:47 +0100 Subject: [PATCH] separate mesondefine and cmakedefine logic --- mesonbuild/utils/universal.py | 170 +++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 45 deletions(-) diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 88d8e1f891c7..76a6df8da642 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -1173,6 +1173,46 @@ def join_args(args: T.Iterable[str]) -> str: def do_replacement(regex: T.Pattern[str], line: str, variable_format: Literal['meson', 'cmake', 'cmake@'], confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]: + if variable_format == 'meson': + return do_replacement_meson(regex, line, confdata) + elif variable_format in {'cmake', 'cmake@'}: + return do_replacement_cmake(regex, line, variable_format == 'cmake@', confdata) + else: + raise MesonException('Invalid variable format') + +def do_replacement_meson(regex: T.Pattern[str], line: str, + confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]: + missing_variables: T.Set[str] = set() + + def variable_replace(match: T.Match[str]) -> str: + # Pairs of escape characters before '@', '\@', '${' or '\${' + if match.group(0).endswith('\\'): + num_escapes = match.end(0) - match.start(0) + return '\\' * (num_escapes // 2) + # \@escaped\@ variables + elif match.groupdict().get('escaped') is not None: + return match.group('escaped')[1:-2]+'@' + else: + # Template variable to be replaced + varname = match.group('variable') + var_str = '' + if varname in confdata: + var, _ = confdata.get(varname) + if isinstance(var, str): + var_str = var + elif isinstance(var, int): + var_str = str(var) + else: + msg = f'Tried to replace variable {varname!r} value with ' \ + f'something other than a string or int: {var!r}' + raise MesonException(msg) + else: + missing_variables.add(varname) + return var_str + return re.sub(regex, variable_replace, line), missing_variables + +def do_replacement_cmake(regex: T.Pattern[str], line: str, at_only: bool, + confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]: missing_variables: T.Set[str] = set() def variable_replace(match: T.Match[str]) -> str: @@ -1181,7 +1221,7 @@ def variable_replace(match: T.Match[str]) -> str: num_escapes = match.end(0) - match.start(0) return '\\' * (num_escapes // 2) # Handle cmake escaped \${} tags - elif variable_format == 'cmake' and match.group(0) == '\\${': + elif not at_only and match.group(0) == '\\${': return '${' # \@escaped\@ variables elif match.groupdict().get('escaped') is not None: @@ -1194,7 +1234,7 @@ def variable_replace(match: T.Match[str]) -> str: var, _ = confdata.get(varname) if isinstance(var, str): var_str = var - elif variable_format.startswith("cmake") and isinstance(var, bool): + elif isinstance(var, bool): var_str = str(int(var)) elif isinstance(var, int): var_str = str(var) @@ -1207,11 +1247,36 @@ def variable_replace(match: T.Match[str]) -> str: return var_str return re.sub(regex, variable_replace, line), missing_variables -def do_define(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', - variable_format: Literal['meson', 'cmake', 'cmake@'], subproject: T.Optional[SubProject] = None) -> str: - cmake_bool_define = False - if variable_format != "meson": - cmake_bool_define = "cmakedefine01" in line +def do_define_meson(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', + subproject: T.Optional[SubProject] = None) -> str: + + arr = line.split() + if len(arr) != 2: + raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip()) + + varname = arr[1] + try: + v, _ = confdata.get(varname) + except KeyError: + return '/* #undef %s */\n' % varname + + if isinstance(v, str): + result = f'#define {varname} {v}'.strip() + '\n' + result, _ = do_replacement_meson(regex, result, confdata) + return result + elif isinstance(v, bool): + if v: + return '#define %s\n' % varname + else: + return '#undef %s\n' % varname + elif isinstance(v, int): + return '#define %s %d\n' % (varname, v) + else: + raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) + +def do_define_cmake(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', at_only: bool, + subproject: T.Optional[SubProject] = None) -> str: + cmake_bool_define = 'cmakedefine01' in line def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: arr = line.split() @@ -1230,12 +1295,10 @@ def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: return ' '.join(define_value) arr = line.split() - if len(arr) != 2: - if variable_format == 'meson': - raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip()) - elif subproject is not None: - from ..interpreterbase.decorators import FeatureNew - FeatureNew.single_use('cmakedefine without exactly two tokens', '0.54.1', subproject) + + if len(arr) != 2 and subproject is not None: + from ..interpreterbase.decorators import FeatureNew + FeatureNew.single_use('cmakedefine without exactly two tokens', '0.54.1', subproject) varname = arr[1] try: @@ -1246,26 +1309,13 @@ def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: else: return '/* #undef %s */\n' % varname - if isinstance(v, str) or variable_format != "meson": - if variable_format == 'meson': - result = v - else: - if not cmake_bool_define and not v: - return '/* #undef %s */\n' % varname + if not cmake_bool_define and not v: + return '/* #undef %s */\n' % varname - result = get_cmake_define(line, confdata) - result = f'#define {varname} {result}'.strip() + '\n' - result, _ = do_replacement(regex, result, variable_format, confdata) - return result - elif isinstance(v, bool): - if v: - return '#define %s\n' % varname - else: - return '#undef %s\n' % varname - elif isinstance(v, int): - return '#define %s %d\n' % (varname, v) - else: - raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) + result = get_cmake_define(line, confdata) + result = f'#define {varname} {result}'.strip() + '\n' + result, _ = do_replacement_cmake(regex, result, at_only, confdata) + return result def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'meson') -> T.Pattern[str]: # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define @@ -1291,20 +1341,50 @@ def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'm def do_conf_str(src: str, data: T.List[str], confdata: 'ConfigurationData', variable_format: Literal['meson', 'cmake', 'cmake@'], subproject: T.Optional[SubProject] = None) -> T.Tuple[T.List[str], T.Set[str], bool]: - def line_is_valid(line: str, variable_format: str) -> bool: - if variable_format == 'meson': + if variable_format == 'meson': + return do_conf_str_meson(src, data, confdata, subproject) + elif variable_format in {'cmake', 'cmake@'}: + return do_conf_str_cmake(src, data, confdata, variable_format == 'cmake@', subproject) + else: + raise MesonException('Invalid variable format') + +def do_conf_str_meson(src: str, data: T.List[str], confdata: 'ConfigurationData', + subproject: T.Optional[SubProject] = None) -> T.Tuple[T.List[str], T.Set[str], bool]: + + regex = get_variable_regex('meson') + + search_token = '#mesondefine' + + result: T.List[str] = [] + missing_variables: T.Set[str] = set() + # Detect when the configuration data is empty and no tokens were found + # during substitution so we can warn the user to use the `copy:` kwarg. + confdata_useless = not confdata.keys() + for line in data: + if line.lstrip().startswith(search_token): + confdata_useless = False + line = do_define_meson(regex, line, confdata, subproject) + else: if '#cmakedefine' in line: - return False - else: # cmake format - if '#mesondefine' in line: - return False - return True + raise MesonException(f'Format error in {src}: saw "{line.strip()}" when format set to "meson"') + line, missing = do_replacement_meson(regex, line, confdata) + missing_variables.update(missing) + if missing: + confdata_useless = False + result.append(line) + + return result, missing_variables, confdata_useless + +def do_conf_str_cmake(src: str, data: T.List[str], confdata: 'ConfigurationData', at_only: bool, + subproject: T.Optional[SubProject] = None) -> T.Tuple[T.List[str], T.Set[str], bool]: + + variable_format: Literal['cmake', 'cmake@'] = 'cmake' + if at_only: + variable_format = 'cmake@' regex = get_variable_regex(variable_format) - search_token = '#mesondefine' - if variable_format != 'meson': - search_token = '#cmakedefine' + search_token = '#cmakedefine' result: T.List[str] = [] missing_variables: T.Set[str] = set() @@ -1314,11 +1394,11 @@ def line_is_valid(line: str, variable_format: str) -> bool: for line in data: if line.lstrip().startswith(search_token): confdata_useless = False - line = do_define(regex, line, confdata, variable_format, subproject) + line = do_define_cmake(regex, line, confdata, at_only, subproject) else: - if not line_is_valid(line, variable_format): + if '#mesondefine' in line: raise MesonException(f'Format error in {src}: saw "{line.strip()}" when format set to "{variable_format}"') - line, missing = do_replacement(regex, line, variable_format, confdata) + line, missing = do_replacement_cmake(regex, line, at_only, confdata) missing_variables.update(missing) if missing: confdata_useless = False