summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/utils/universal.py143
-rw-r--r--test cases/common/14 configure file/config7.h.in17
-rw-r--r--test cases/common/14 configure file/prog7.c12
3 files changed, 107 insertions, 65 deletions
diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py
index bf08fd7a2..4b656a670 100644
--- a/mesonbuild/utils/universal.py
+++ b/mesonbuild/utils/universal.py
@@ -1240,7 +1240,7 @@ def do_replacement(regex: T.Pattern[str], line: 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)
+ return do_replacement_cmake(line, variable_format == 'cmake@', confdata)
else:
raise MesonException('Invalid variable format')
@@ -1275,44 +1275,92 @@ def do_replacement_meson(regex: T.Pattern[str], line: str,
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,
+def do_replacement_cmake(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:
- # Pairs of escape characters before '@', '\@', '${' or '\${'
- if match.group(0).endswith('\\'):
- num_escapes = match.end(0) - match.start(0)
- return '\\' * (num_escapes // 2)
- # Handle cmake escaped \${} tags
- elif not at_only and match.group(0) == '\\${':
- return '${'
- # \@escaped\@ variables
- elif match.groupdict().get('escaped') is not None:
- return match.group('escaped')[1:-2]+'@'
+ character_regex = re.compile(r'''
+ [^a-zA-Z0-9_/.+\-]
+ ''', re.VERBOSE)
+
+ def variable_get(varname: str) -> str:
+ var_str = ''
+ if varname in confdata:
+ var, _ = confdata.get(varname)
+ if isinstance(var, str):
+ var_str = var
+ elif isinstance(var, bool):
+ var_str = str(int(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:
- # Template variable to be replaced
- varname = match.group('variable')
- if not varname:
- varname = match.group('cmake_variable')
-
- var_str = ''
- if varname in confdata:
- var, _ = confdata.get(varname)
- if isinstance(var, str):
- var_str = var
- elif isinstance(var, bool):
- var_str = str(int(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}'
+ missing_variables.add(varname)
+ return var_str
+
+ def parse_line(line: str) -> str:
+ index = 0
+ while len(line) > index:
+ if line[index] == '@':
+ next_at = line.find("@", index+1)
+ if next_at > index+1:
+ varname = line[index+1:next_at]
+ match = character_regex.search(varname)
+
+ # at substituion doesn't occur if they key isn't valid
+ # however it also doesn't raise an error
+ if not match:
+ value = variable_get(varname)
+ line = line[:index] + value + line[next_at+1:]
+
+ elif not at_only and line[index:index+2] == '${':
+ bracket_count = 1
+ end_bracket = index + 2
+ try:
+ while bracket_count > 0:
+ if line[end_bracket:end_bracket+2] == "${":
+ end_bracket += 2
+ bracket_count += 1
+ elif line[end_bracket] == "}":
+ end_bracket += 1
+ bracket_count -= 1
+ elif line[end_bracket] in {"@", "\n"}:
+ # these aren't valid variable characters
+ # but they are inconsequential at this point
+ end_bracket += 1
+ elif character_regex.search(line[end_bracket]):
+ invalid_character = line[end_bracket]
+ variable = line[index+2:end_bracket]
+ msg = f'Found invalid character {invalid_character!r}' \
+ f' in variable {variable!r}'
+ raise MesonException(msg)
+ else:
+ end_bracket += 1
+ except IndexError:
+ msg = f'Found incomplete variable {line[index:-1]!r}'
raise MesonException(msg)
- else:
- missing_variables.add(varname)
- return var_str
- return re.sub(regex, variable_replace, line), missing_variables
+
+ if bracket_count == 0:
+ varname = parse_line(line[index+2:end_bracket-1])
+ match = character_regex.search(varname)
+ if match:
+ invalid_character = line[end_bracket-2]
+ variable = line[index+2:end_bracket-3]
+ msg = f'Found invalid character {invalid_character!r}' \
+ f' in variable {variable!r}'
+ raise MesonException(msg)
+
+ value = variable_get(varname)
+ line = line[:index] + value + line[end_bracket:]
+
+ index += 1
+
+ return line
+
+ return parse_line(line), missing_variables
def do_define_meson(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData',
subproject: T.Optional[SubProject] = None) -> str:
@@ -1341,7 +1389,7 @@ def do_define_meson(regex: T.Pattern[str], line: str, confdata: 'ConfigurationDa
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,
+def do_define_cmake(line: str, confdata: 'ConfigurationData', at_only: bool,
subproject: T.Optional[SubProject] = None) -> str:
cmake_bool_define = 'cmakedefine01' in line
@@ -1381,12 +1429,12 @@ def do_define_cmake(regex: T.Pattern[str], line: str, confdata: 'ConfigurationDa
result = get_cmake_define(line, confdata)
result = f'#define {varname} {result}'.strip() + '\n'
- result, _ = do_replacement_cmake(regex, result, at_only, confdata)
+ result, _ = do_replacement_cmake(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
- if variable_format in {'meson', 'cmake@'}:
+ if variable_format == 'meson':
# Also allow escaping pairs of '@' with '\@'
regex = re.compile(r'''
(?:\\\\)+(?=\\?@) # Matches multiple backslashes followed by an @ symbol
@@ -1395,17 +1443,13 @@ def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'm
| # OR
(?P<escaped>\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols
''', re.VERBOSE)
- else:
+ elif variable_format == 'cmake@':
regex = re.compile(r'''
- (?:\\\\)+(?=\\?(\$|@)) # Match multiple backslashes followed by a dollar sign or an @ symbol
- | # OR
- \\\${ # Match a backslash followed by a dollar sign and an opening curly brace
- | # OR
- \${(?P<cmake_variable>[-a-zA-Z0-9_]*)} # Match a variable enclosed in curly braces and capture the variable name
- | # OR
(?<!\\)@(?P<variable>[-a-zA-Z0-9_]+)@ # Match a variable enclosed in @ symbols and capture the variable name; no matches beginning with '\@'
- | # OR
- (?P<escaped>\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols
+ ''', re.VERBOSE)
+ elif variable_format == "cmake":
+ regex = re.compile(r'''
+ \${(?P<variable>[-a-zA-Z0-9_]*)} # Match a variable enclosed in curly braces and capture the variable name
''', re.VERBOSE)
return regex
@@ -1453,8 +1497,6 @@ def do_conf_str_cmake(src: str, data: T.List[str], confdata: 'ConfigurationData'
if at_only:
variable_format = 'cmake@'
- regex = get_variable_regex(variable_format)
-
search_token = 'cmakedefine'
result: T.List[str] = []
@@ -1466,11 +1508,12 @@ def do_conf_str_cmake(src: str, data: T.List[str], confdata: 'ConfigurationData'
stripped_line = line.lstrip()
if len(stripped_line) >= 2 and stripped_line[0] == '#' and stripped_line[1:].lstrip().startswith(search_token):
confdata_useless = False
- line = do_define_cmake(regex, line, confdata, at_only, subproject)
+
+ line = do_define_cmake(line, confdata, at_only, subproject)
else:
if '#mesondefine' in line:
raise MesonException(f'Format error in {src}: saw "{line.strip()}" when format set to "{variable_format}"')
- line, missing = do_replacement_cmake(regex, line, at_only, confdata)
+ line, missing = do_replacement_cmake(line, at_only, confdata)
missing_variables.update(missing)
if missing:
confdata_useless = False
diff --git a/test cases/common/14 configure file/config7.h.in b/test cases/common/14 configure file/config7.h.in
index edd0bb3fe..5180c2fba 100644
--- a/test cases/common/14 configure file/config7.h.in
+++ b/test cases/common/14 configure file/config7.h.in
@@ -1,16 +1,11 @@
-/* No escape */
+/* cmake substitions cannot be escaped */
#define MESSAGE1 "${var1}"
-
-/* Single escape means no replace */
#define MESSAGE2 "\${var1}"
-
-/* Replace pairs of escapes before '@' or '\@' with escape characters
- * (note we have to double number of pairs due to C string escaping)
- */
#define MESSAGE3 "\\\\${var1}"
-
-/* Pairs of escapes and then single escape to avoid replace */
#define MESSAGE4 "\\\\\${var1}"
+#define MESSAGE5 "@var1@"
+#define MESSAGE6 "\\@var1@"
+#define MESSAGE7 "\\\\@var1@"
-/* Check escape character outside variables */
-#define MESSAGE5 "\\ ${ \${ \\\\${ \\\\\${"
+/* backslash is an invalid variable character */
+#define MESSAGE8 "@var1\@"
diff --git a/test cases/common/14 configure file/prog7.c b/test cases/common/14 configure file/prog7.c
index 802bc46e5..900522c14 100644
--- a/test cases/common/14 configure file/prog7.c
+++ b/test cases/common/14 configure file/prog7.c
@@ -3,8 +3,12 @@
int main(void) {
return strcmp(MESSAGE1, "foo")
- || strcmp(MESSAGE2, "${var1}")
- || strcmp(MESSAGE3, "\\foo")
- || strcmp(MESSAGE4, "\\${var1}")
- || strcmp(MESSAGE5, "\\ ${ ${ \\${ \\${");
+ || strcmp(MESSAGE2, "\foo")
+ || strcmp(MESSAGE3, "\\\\foo")
+ || strcmp(MESSAGE4, "\\\\\foo")
+ || strcmp(MESSAGE5, "foo")
+ || strcmp(MESSAGE6, "\\foo")
+ || strcmp(MESSAGE7, "\\\\foo")
+ || strcmp(MESSAGE8, "@var1\@")
+ || 0;
}