Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

separate cmakedefine and mesondefine logic #13208

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 125 additions & 45 deletions mesonbuild/utils/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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
Expand Down
Loading