diff --git a/docs/markdown/snippets/configure_file_overwrite_warning.md b/docs/markdown/snippets/configure_file_overwrite_warning.md new file mode 100644 index 000000000..550407dc1 --- /dev/null +++ b/docs/markdown/snippets/configure_file_overwrite_warning.md @@ -0,0 +1,39 @@ +## Meson warns if two calls to configure_file() write to the same file + +If two calls to [`configure_file()`](#Reference-manual.md#configure_file) +write to the same file Meson will print a `WARNING:` message during +configuration. For example: +```meson +project('configure_file', 'cpp') + +configure_file( + input: 'a.in', + output: 'out', + command: ['./foo.sh'] + ) +configure_file( + input: 'a.in', + output: 'out', + command: ['./foo.sh'] +) + +``` + +This will output: + +``` +The Meson build system +Version: 0.47.0.dev1 +Source dir: /path/to/srctree +Build dir: /path/to/buildtree +Build type: native build +Project name: configure_file +Project version: undefined +Build machine cpu family: x86_64 +Build machine cpu: x86_64 +Configuring out with command +WARNING: Output file out for configure_file overwritten. First time written in line 3 now in line 8 +Configuring out with command +Build targets in project: 0 +Found ninja-1.8.2 at /usr/bin/ninja +``` diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 2c54eaeca..94f7bfefd 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1868,6 +1868,7 @@ class Interpreter(InterpreterBase): self.global_args_frozen = False # implies self.project_args_frozen self.subprojects = {} self.subproject_stack = [] + self.configure_file_outputs = {} # Passed from the outside, only used in subprojects. if default_project_options: self.default_project_options = default_project_options.copy() @@ -3453,8 +3454,16 @@ root and issuing %s. raise InterpreterException('@INPUT@ used as command argument, but no input file specified.') # Validate output output = kwargs['output'] + ofile_rpath = os.path.join(self.subdir, output) if not isinstance(output, str): raise InterpreterException('Output file name must be a string') + if ofile_rpath in self.configure_file_outputs: + mesonbuildfile = os.path.join(self.subdir, 'meson.build') + current_call = "{}:{}".format(mesonbuildfile, self.current_lineno) + first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath]) + mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call) + else: + self.configure_file_outputs[ofile_rpath] = self.current_lineno if ifile_abs: values = mesonlib.get_filenames_templates_dict([ifile_abs], None) outputs = mesonlib.substitute_values([output], values) diff --git a/run_unittests.py b/run_unittests.py index f4e95a302..dd109aae2 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -2413,6 +2413,10 @@ recommended as it is not supported on some platforms''') self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*") self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in") + # Warnings for configuration files that are overwritten. + self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites") + self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites") + self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites") # No warnings about empty configuration data objects passed to files with substitutions self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in") self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in") diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 8c375c19d..d7beeb1d3 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -193,15 +193,15 @@ configure_file( configuration : configuration_data() ) -# Test that passing an empty configuration_data() object to a file with -# @FOO@ substitutions does not print the warning. +# test that passing an empty configuration_data() object to a file with +# @foo@ substitutions does not print the warning. configure_file( input: 'nosubst-nocopy2.txt.in', output: 'nosubst-nocopy2.txt', configuration : configuration_data() ) -# Test that passing a configured file object to test() works, and that passing +# test that passing a configured file object to test() works, and that passing # an empty configuration_data() object to a file that leads to no substitutions # prints a warning (see unit tests) test_file = configure_file( @@ -210,6 +210,25 @@ test_file = configure_file( configuration: configuration_data() ) +# Test that overwriting an existing file creates a warning. +configure_file( + input: 'test.py.in', + output: 'double_output.txt', + configuration: conf +) +configure_file( + input: 'test.py.in', + output: 'double_output.txt', + configuration: conf +) + +# Test that the same file name in a different subdir will not create a warning +configure_file( + input: 'test.py.in', + output: 'no_write_conflict.txt', + configuration: conf +) + test('configure-file', test_file) cdata = configuration_data() diff --git a/test cases/common/16 configure file/subdir/meson.build b/test cases/common/16 configure file/subdir/meson.build index d802c1d86..146b7b66c 100644 --- a/test cases/common/16 configure file/subdir/meson.build +++ b/test cases/common/16 configure file/subdir/meson.build @@ -17,3 +17,22 @@ configure_file(input : '../dummy.dat', output : 'config2-3.h', command : [found_script, '@INPUT@', '@OUTPUT@']) run_command(check_file, join_paths(meson.current_build_dir(), 'config2-3.h')) + +# Test that overwriting an existing file creates a warning. +configure_file( + input: '../test.py.in', + output: 'double_output2.txt', + configuration: conf +) +configure_file( + input: '../test.py.in', + output: 'double_output2.txt', + configuration: conf +) + +# Test that the same file name in a different subdir will not create a warning +configure_file( + input: '../test.py.in', + output: 'no_write_conflict.txt', + configuration: conf +)