diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index ea11f6001..9762c5d2d 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1529,7 +1529,9 @@ the following methods. specifies that whenever `find_program` is used to find a program named `progname`, Meson should not not look it up on the system but instead return `program`, which may either be the result of - `find_program` or `configure_file`. + `find_program`, `configure_file` or `executable`. + + If `program` is an `executable`, it cannot be used during configure. - `project_version()` returns the version string specified in `project` function call. diff --git a/docs/markdown/snippets/overrideexe.md b/docs/markdown/snippets/overrideexe.md new file mode 100644 index 000000000..59213c5cb --- /dev/null +++ b/docs/markdown/snippets/overrideexe.md @@ -0,0 +1,8 @@ +## More flexible `override_find_program()`. + +It is now possible to pass an `executable` to +`override_find_program()` if the overridden program is not used during +configure. + +This is particularly useful for fallback dependencies like Protobuf +that also provide a tool like protoc. diff --git a/mesonbuild/build.py b/mesonbuild/build.py index efe58f8f8..585a8d3fb 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1314,6 +1314,10 @@ class Executable(BuildTarget): # Only linkwithable if using export_dynamic self.is_linkwithable = self.export_dynamic + def description(self): + '''Human friendly description of the executable''' + return self.name + def type_suffix(self): return "@exe" diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index e375f102a..b369780c4 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1057,6 +1057,10 @@ class ExternalProgram: r = '<{} {!r} -> {!r}>' return r.format(self.__class__.__name__, self.name, self.command) + def description(self): + '''Human friendly description of the command''' + return ' '.join(self.command) + @staticmethod def from_cross_info(cross_info, name): if name not in cross_info.config['binaries']: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index dba3fc0af..9bd21e3aa 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -480,7 +480,7 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): return self.held_object.get_path() def found(self): - return self.held_object.found() + return isinstance(self.held_object, build.Executable) or self.held_object.found() def get_command(self): return self.held_object.get_command() @@ -1780,9 +1780,8 @@ class MesonMain(InterpreterObject): if not os.path.exists(abspath): raise InterpreterException('Tried to override %s with a file that does not exist.' % name) exe = dependencies.ExternalProgram(abspath) - if not isinstance(exe, dependencies.ExternalProgram): - # FIXME, make this work if the exe is an Executable target. - raise InterpreterException('Second argument must be an external program.') + if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)): + raise InterpreterException('Second argument must be an external program or executable.') self.interpreter.add_find_program_override(name, exe) @noPosargs @@ -2184,6 +2183,11 @@ external dependencies (including libraries) must go to "dependencies".''') 'or configure_file(), or a compiler object; not {!r}' if isinstance(cmd, ExternalProgramHolder): cmd = cmd.held_object + if isinstance(cmd, build.Executable): + progname = node.args.arguments[0].value + msg = 'Program {!r} was overridden with the compiled executable {!r}'\ + ' and therefore cannot be used during configuration' + raise InterpreterException(msg.format(progname, cmd.description())) elif isinstance(cmd, CompilerHolder): cmd = cmd.compiler.get_exelist()[0] prog = ExternalProgram(cmd, silent=True) @@ -2758,7 +2762,7 @@ external dependencies (including libraries) must go to "dependencies".''') exe = self.build.find_overrides[name] if not silent: mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), - '(overridden: %s)' % ' '.join(exe.command)) + '(overridden: %s)' % exe.description()) return ExternalProgramHolder(exe) return None diff --git a/run_unittests.py b/run_unittests.py index 94c1abaa9..d446a5480 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -3881,6 +3881,17 @@ endian = 'little' return raise RuntimeError('Could not find the rpath') + def test_override_with_exe_dep(self): + ''' + Test that we produce the correct dependencies when a program is overridden with an executable. + ''' + testdir = os.path.join(self.common_test_dir, '206 override with exe') + self.init(testdir) + with open(os.path.join(self.builddir, 'build.ninja')) as bfile: + for line in bfile: + if 'main1.c:' in line or 'main2.c:' in line: + self.assertIn('| subprojects/sub/foobar', line) + @skipIfNoPkgconfig def test_usage_external_library(self): ''' diff --git a/test cases/common/206 override with exe/main2.input b/test cases/common/206 override with exe/main2.input new file mode 100644 index 000000000..e69de29bb diff --git a/test cases/common/206 override with exe/meson.build b/test cases/common/206 override with exe/meson.build new file mode 100644 index 000000000..81f6c02b7 --- /dev/null +++ b/test cases/common/206 override with exe/meson.build @@ -0,0 +1,15 @@ +project('myexe', 'c') +sub = subproject('sub') +prog = find_program('foobar') +custom1 = custom_target('custom1', + build_by_default : true, + input : [], + output : 'main1.c', + command : [prog, '@OUTPUT@']) +gen = generator(prog, + output : '@BASENAME@.c', + arguments : ['@OUTPUT@']) +custom2 = gen.process('main2.input') + +executable('e1', custom1) +executable('e2', custom2) diff --git a/test cases/common/206 override with exe/subprojects/sub/foobar.c b/test cases/common/206 override with exe/subprojects/sub/foobar.c new file mode 100644 index 000000000..030ac49da --- /dev/null +++ b/test cases/common/206 override with exe/subprojects/sub/foobar.c @@ -0,0 +1,12 @@ +#include +#include + +int main(int argc, char* argv[]) { + FILE *f = fopen(argv[1], "w"); + const char msg[] = "int main(void) {return 0;}\n"; + size_t w = fwrite(msg, 1, sizeof(msg) - 1, f); + assert(w == sizeof(msg) - 1); + int r = fclose(f); + assert(r == 0); + return 0; +} diff --git a/test cases/common/206 override with exe/subprojects/sub/meson.build b/test cases/common/206 override with exe/subprojects/sub/meson.build new file mode 100644 index 000000000..1f186da0d --- /dev/null +++ b/test cases/common/206 override with exe/subprojects/sub/meson.build @@ -0,0 +1,3 @@ +project('sub', 'c') +foobar = executable('foobar', 'foobar.c', native : true) +meson.override_find_program('foobar', foobar) diff --git a/test cases/failing/81 override exe config/foo.c b/test cases/failing/81 override exe config/foo.c new file mode 100644 index 000000000..03b2213bb --- /dev/null +++ b/test cases/failing/81 override exe config/foo.c @@ -0,0 +1,3 @@ +int main(void) { + return 0; +} diff --git a/test cases/failing/81 override exe config/meson.build b/test cases/failing/81 override exe config/meson.build new file mode 100644 index 000000000..29a74166b --- /dev/null +++ b/test cases/failing/81 override exe config/meson.build @@ -0,0 +1,6 @@ +project('myexe', 'c') + +foo = executable('foo', 'foo.c') +meson.override_find_program('bar', foo) +bar = find_program('bar') +run_command(bar)