From 6dea1777743fe26173d9c674b0870790a7caae92 Mon Sep 17 00:00:00 2001 From: David Fort Date: Wed, 21 Feb 2018 10:43:56 +0100 Subject: [PATCH] add support for cmakedefine in configure_file() The added format argument for configure_file allows to specify the kind of file that is treated. It defaults to 'meson', but can also have the 'cmake' or 'cmake@' value to treat config.h.in files in the cmake format with #cmakedefine statements. --- docs/markdown/Reference-manual.md | 7 ++++- mesonbuild/interpreter.py | 14 +++++++-- mesonbuild/mesonlib.py | 29 ++++++++++++++----- .../common/16 configure file/config7.h.in | 16 ++++++++++ .../common/16 configure file/meson.build | 12 ++++++++ test cases/common/16 configure file/prog7.c | 10 +++++++ 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 test cases/common/16 configure file/config7.h.in create mode 100644 test cases/common/16 configure file/prog7.c diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index d98fc1989..0d3d1aa90 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -186,7 +186,12 @@ These are all the supported keyword arguments: `output`. Available since v0.41.0. - `command` as explained above, if specified, Meson does not create the file itself but rather runs the specified command, which allows - you to do fully custom file generation + you to do fully custom file generation. +- `format` *(added 0.46.0)* the format of defines. It defaults to `meson`, and so substitutes +`#mesondefine` statements and variables surrounded by `@` characters, you can also use `cmake` +to replace `#cmakedefine` statements and variables with the `${variable}` syntax. Finally you can use +`cmake@` in which case substitutions will apply on `#cmakedefine` statements and variables with +the `@variable@` syntax. - `input` the input file name. If it's not specified in configuration mode, all the variables in the `configuration:` object (see above) are written to the `output:` file. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 6e3b86426..6ecd28569 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1463,7 +1463,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'}, 'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'}, 'build_target': known_build_target_kwargs, - 'configure_file': {'input', 'output', 'configuration', 'command', 'install_dir', 'capture', 'install'}, + 'configure_file': {'input', 'output', 'configuration', 'command', 'install_dir', 'capture', 'install', 'format'}, 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, 'dependency': {'default_options', 'fallback', 'language', 'main', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'link_whole', 'version'}, @@ -2885,6 +2885,16 @@ root and issuing %s. if 'command' not in kwargs: raise InterpreterException('"capture" keyword requires "command" keyword.') + if 'format' in kwargs: + format = kwargs['format'] + if not isinstance(format, str): + raise InterpreterException('"format" keyword must be a string.') + else: + format = 'meson' + + if format not in ('meson', 'cmake', 'cmake@'): + raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".') + # Validate input inputfile = None ifile_abs = None @@ -2924,7 +2934,7 @@ root and issuing %s. if inputfile is not None: os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) missing_variables = mesonlib.do_conf_file(ifile_abs, ofile_abs, - conf.held_object) + conf.held_object, format) if missing_variables: var_list = ", ".join(map(repr, sorted(missing_variables))) mlog.warning( diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index a076e3e12..9b00c2891 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -541,8 +541,13 @@ def has_path_sep(name, sep='/\\'): return True return False -def do_replacement(regex, line, confdata): +def do_replacement(regex, line, format, confdata): missing_variables = set() + start_tag = '@' + backslash_tag = '\\@' + if format == 'cmake': + start_tag = '${' + backslash_tag = '\\${' def variable_replace(match): # Pairs of escape characters before '@' or '\@' @@ -550,8 +555,8 @@ def do_replacement(regex, line, confdata): num_escapes = match.end(0) - match.start(0) return '\\' * (num_escapes // 2) # Single escape character and '@' - elif match.group(0) == '\\@': - return '@' + elif match.group(0) == backslash_tag: + return start_tag # Template variable to be replaced else: varname = match.group(1) @@ -591,7 +596,7 @@ def do_mesondefine(line, confdata): raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) -def do_conf_file(src, dst, confdata): +def do_conf_file(src, dst, confdata, format): try: with open(src, encoding='utf-8') as f: data = f.readlines() @@ -599,14 +604,24 @@ def do_conf_file(src, dst, confdata): raise MesonException('Could not read input file %s: %s' % (src, str(e))) # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define # Also allow escaping '@' with '\@' - regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + if format in ['meson', 'cmake@']: + regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + elif format == 'cmake': + regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') + else: + raise MesonException('Format "{}" not handled'.format(format)) + + search_token = '#mesondefine' + if format != 'meson': + search_token = '#cmakedefine' + result = [] missing_variables = set() for line in data: - if line.startswith('#mesondefine'): + if line.startswith(search_token): line = do_mesondefine(line, confdata) else: - line, missing = do_replacement(regex, line, confdata) + line, missing = do_replacement(regex, line, format, confdata) missing_variables.update(missing) result.append(line) dst_tmp = dst + '~' diff --git a/test cases/common/16 configure file/config7.h.in b/test cases/common/16 configure file/config7.h.in new file mode 100644 index 000000000..edd0bb3fe --- /dev/null +++ b/test cases/common/16 configure file/config7.h.in @@ -0,0 +1,16 @@ +/* No escape */ +#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}" + +/* Check escape character outside variables */ +#define MESSAGE5 "\\ ${ \${ \\\\${ \\\\\${" diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 71a256340..5c3a1a593 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -137,3 +137,15 @@ cfile = configure_file(input : 'config.h.in', output : 'do_not_get_installed.h', install_dir : '', configuration : conf) + +# Test escaping with cmake format +conf7 = configuration_data() +conf7.set('var1', 'foo') +conf7.set('var2', 'bar') +configure_file( + input : 'config7.h.in', + output : '@BASENAME@', + format : 'cmake', + configuration : conf7 +) +test('test7', executable('prog7', 'prog7.c')) diff --git a/test cases/common/16 configure file/prog7.c b/test cases/common/16 configure file/prog7.c new file mode 100644 index 000000000..0bb7d1320 --- /dev/null +++ b/test cases/common/16 configure file/prog7.c @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char **argv) { + return strcmp(MESSAGE1, "foo") + || strcmp(MESSAGE2, "${var1}") + || strcmp(MESSAGE3, "\\foo") + || strcmp(MESSAGE4, "\\${var1}") + || strcmp(MESSAGE5, "\\ ${ ${ \\${ \\${"); +}