diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index c831169c523d..38241e4d7857 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -1182,24 +1182,21 @@ 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]]: missing_variables: T.Set[str] = set() - if variable_format == 'cmake': - start_tag = '${' - backslash_tag = '\\${' - else: - start_tag = '@' - backslash_tag = '\\@' def variable_replace(match: T.Match[str]) -> str: - # Pairs of escape characters before '@' or '\@' + # Pairs of escape characters before '@', '\@', '${' or '\${' if match.group(0).endswith('\\'): num_escapes = match.end(0) - match.start(0) return '\\' * (num_escapes // 2) - # Single escape character and '@' - elif match.group(0) == backslash_tag: - return start_tag - # Template variable to be replaced + # Handle cmake escaped \${} tags + elif variable_format == 'cmake' and match.group(0) == '\\${': + return '${' + # \@escaped\@ variables + elif match.groupdict().get('escaped') is not None: + return match.group('escaped')[1:-2]+'@' else: - varname = match.group(1) + # Template variable to be replaced + varname = match.group('variable') var_str = '' if varname in confdata: var, _ = confdata.get(varname) @@ -1280,11 +1277,23 @@ def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: 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 - # Also allow escaping '@' with '\@' if variable_format in {'meson', 'cmake@'}: - regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + # Also allow escaping pairs of '@' with '\@' + regex = re.compile(r''' + (?:\\\\)+(?=\\?@) # Matches multiple backslashes followed by an @ symbol + | # OR + @(?P[-a-zA-Z0-9_]+)@ # Matches a variable enclosed in @ symbols and captures the variable name + | # OR + (?P\\@[-a-zA-Z0-9_]+\\@) # Matches an escaped variable enclosed in @ symbols + ''', re.VERBOSE) else: - regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') + regex = re.compile(r''' + (?:\\\\)+(?=\\?\$) # Matches multiple backslashes followed by a dollar sign + | # OR + \\\${ # Matches a backslash followed by a dollar sign and an opening curly brace + | # OR + \${(?P[-a-zA-Z0-9_]+)} # Matches a variable enclosed in curly braces and captures the variable name + ''', re.VERBOSE) return regex def do_conf_str(src: str, data: T.List[str], confdata: 'ConfigurationData', diff --git a/test cases/common/14 configure file/config6.h.in b/test cases/common/14 configure file/config6.h.in index 9719f8715210..2d6deb520915 100644 --- a/test cases/common/14 configure file/config6.h.in +++ b/test cases/common/14 configure file/config6.h.in @@ -1,19 +1,16 @@ /* No escape */ #define MESSAGE1 "@var1@" -/* Single escape means no replace */ -#define MESSAGE2 "\@var1@" +/* Escaped whole variable */ +#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@" - -/* Check escaped variable does not overlap following variable */ -#define MESSAGE5 "\@var1@var2@" +/* Pairs of escapes and then an escaped variable */ +#define MESSAGE4 "\\\\\@var1\@" /* Check escape character outside variables */ -#define MESSAGE6 "\\ @ \@ \\\\@ \\\\\@" +#define MESSAGE5 "\\ @ \\\\@" diff --git a/test cases/common/14 configure file/dummy-perl.pl.in b/test cases/common/14 configure file/dummy-perl.pl.in new file mode 100644 index 000000000000..a758c19b41de --- /dev/null +++ b/test cases/common/14 configure file/dummy-perl.pl.in @@ -0,0 +1,8 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +my @array = qw(1 2); +use Data::Dumper; +say Dumper(\@array); diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 90a468f5e191..e7ad96188d32 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -297,6 +297,15 @@ configure_file( configuration: conf ) +# Do not mangle (e.g.) perl files when input legitimately contains '\@' +configure_file( + input: 'dummy-perl.pl.in', + output: 'dummy-perl.pl', + configuration: conf, +) +run_command('cmp', files('dummy-perl.pl.in'), + join_paths(meson.current_build_dir(), 'dummy-perl.pl'), check: true) + test('configure-file', test_file) # Dictionaries diff --git a/test cases/common/14 configure file/prog6.c b/test cases/common/14 configure file/prog6.c index 57f55860515c..9284f841de2b 100644 --- a/test cases/common/14 configure file/prog6.c +++ b/test cases/common/14 configure file/prog6.c @@ -1,11 +1,17 @@ #include #include +#include int main(void) { + fprintf(stderr, "MESSAGE1 %s\n", MESSAGE1); + fprintf(stderr, "MESSAGE2 %s\n", MESSAGE2); + fprintf(stderr, "MESSAGE3 %s\n", MESSAGE3); + fprintf(stderr, "MESSAGE4 %s\n", MESSAGE4); + fprintf(stderr, "MESSAGE5 %s\n", MESSAGE5); + return strcmp(MESSAGE1, "foo") || strcmp(MESSAGE2, "@var1@") || strcmp(MESSAGE3, "\\foo") || strcmp(MESSAGE4, "\\@var1@") - || strcmp(MESSAGE5, "@var1bar") - || strcmp(MESSAGE6, "\\ @ @ \\@ \\@"); + || strcmp(MESSAGE5, "\\ @ \\@"); } diff --git a/test cases/common/14 configure file/subdir/meson.build b/test cases/common/14 configure file/subdir/meson.build index 98b672c05804..743218545343 100644 --- a/test cases/common/14 configure file/subdir/meson.build +++ b/test cases/common/14 configure file/subdir/meson.build @@ -35,4 +35,4 @@ configure_file( input: '../test.py.in', output: 'no_write_conflict.txt', configuration: conf -) +) \ No newline at end of file