From f08eed37cb69ba0d793c0f1d086eaef7f25c2ea3 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 20 Apr 2020 19:14:49 -0400 Subject: find_program: Fallback if a wrap file provide the program name We don't need the legacy variable name system as for dependency() fallbacks because meson.override_find_program() is largely used already, so we can just rely on it. --- docs/markdown/Wrap-dependency-system-manual.md | 13 ++++ docs/markdown/snippets/implicit_fallback.md | 5 ++ mesonbuild/interpreter.py | 83 +++++++++++++++------- mesonbuild/wrap/wrap.py | 14 ++++ test cases/common/187 find override/meson.build | 3 + .../common/187 find override/subprojects/sub.wrap | 5 ++ .../187 find override/subprojects/sub/meson.build | 4 ++ 7 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 test cases/common/187 find override/subprojects/sub.wrap create mode 100644 test cases/common/187 find override/subprojects/sub/meson.build diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index b927944..dd8595b 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -71,6 +71,7 @@ revision = head - `directory` - name of the subproject root directory, defaults to the name of the wrap. Since *0.55.0* those can be used in all wrap types, they were previously reserved to `wrap-file`: + - `patch_url` - download url to retrieve an optional overlay archive - `patch_fallback_url` - fallback URL to be used when download from `patch_url` fails *Since: 0.55.0* - `patch_filename` - filename of the downloaded overlay archive @@ -182,6 +183,18 @@ dependency_names = glib-2.0, gobject-2.0, gio-2.0 With such wrap file, `dependency('glib-2.0')` will automatically fallback to use `glib.wrap` and return `glib_dep` variable from the subproject. +Programs can also be provided by wrap files, with the `program_names` key: +```ini +[wrap-git] +... + +[provide] +program_names = myprog, otherprog +``` + +With such wrap file, `find_program('myprog')` will automatically fallback to use +the subproject, assuming it uses `meson.override_find_program('myprog')`. + ## Using wrapped projects Wraps provide a convenient way of obtaining a project into your subproject directory. diff --git a/docs/markdown/snippets/implicit_fallback.md b/docs/markdown/snippets/implicit_fallback.md index bad1c71..3d5a833 100644 --- a/docs/markdown/snippets/implicit_fallback.md +++ b/docs/markdown/snippets/implicit_fallback.md @@ -14,3 +14,8 @@ Wrap files can define the dependencies it provides in the `[provide]` section. When a wrap file provides the dependency `foo` any call do `dependency('foo')` will automatically fallback to that subproject even if no `fallback` keyword argument is given. See [Wrap documentation](Wrap-dependency-system-manual.md#provide_section). + +## `find_program()` fallback + +When a program cannot be found on the system but a wrap file has its name in the +`[provide]` section, that subproject will be used as fallback. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 89c9daa..e616d85 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3251,7 +3251,7 @@ external dependencies (including libraries) must go to "dependencies".''') return success - def program_from_file_for(self, for_machine, prognames, silent): + def program_from_file_for(self, for_machine, prognames): for p in unholder(prognames): if isinstance(p, mesonlib.File): continue # Always points to a local (i.e. self generated) file. @@ -3290,15 +3290,13 @@ external dependencies (including libraries) must go to "dependencies".''') if progobj.found(): return progobj - def program_from_overrides(self, command_names, silent=False): + def program_from_overrides(self, command_names, extra_info): for name in command_names: if not isinstance(name, str): continue if name in self.build.find_overrides: exe = self.build.find_overrides[name] - if not silent: - mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), - '(overridden: %s)' % exe.description()) + extra_info.append(mlog.blue('(overriden)')) return ExternalProgramHolder(exe, self.subproject, self.backend) return None @@ -3316,40 +3314,75 @@ external dependencies (including libraries) must go to "dependencies".''') % name) self.build.find_overrides[name] = exe + def notfound_program(self, args): + return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) + # TODO update modules to always pass `for_machine`. It is bad-form to assume # the host machine. def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, required=True, silent=True, wanted='', search_dirs=None): - if not isinstance(args, list): - args = [args] + args = mesonlib.listify(args) - progobj = self.program_from_overrides(args, silent=silent) - if progobj is None: - progobj = self.program_from_file_for(for_machine, args, silent=silent) - if progobj is None: - progobj = self.program_from_system(args, search_dirs, silent=silent) - if progobj is None and args[0].endswith('python3'): - prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) - progobj = ExternalProgramHolder(prog, self.subproject) - if required and (progobj is None or not progobj.found()): - raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args)) + extra_info = [] + progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info) if progobj is None: - return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) - # Only store successful lookups - self.store_name_lookups(args) + progobj = self.notfound_program(args) + + if not progobj.found(): + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO')) + if required: + m = 'Program {!r} not found' + raise InterpreterException(m.format(progobj.get_name())) + return progobj + if wanted: version = progobj.get_version(self) is_found, not_found, found = mesonlib.version_compare_many(version, wanted) if not is_found: mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), - 'found {!r} but need:'.format(version), - ', '.join(["'{}'".format(e) for e in not_found])) + 'found', mlog.normal_cyan(version), 'but need:', + mlog.bold(', '.join(["'{}'".format(e) for e in not_found]))) if required: m = 'Invalid version of program, need {!r} {!r} found {!r}.' - raise InvalidArguments(m.format(progobj.get_name(), not_found, version)) - return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) + raise InterpreterException(m.format(progobj.get_name(), not_found, version)) + return self.notfound_program(args) + extra_info.insert(0, mlog.normal_cyan(version)) + + # Only store successful lookups + self.store_name_lookups(args) + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.green('YES'), *extra_info) return progobj + def program_lookup(self, args, for_machine, required, search_dirs, extra_info): + progobj = self.program_from_overrides(args, extra_info) + if progobj: + return progobj + + fallback = None + wrap_mode = self.coredata.get_builtin_option('wrap_mode') + if wrap_mode != WrapMode.nofallback: + fallback = self.environment.wrap_resolver.find_program_provider(args) + if fallback and wrap_mode == WrapMode.forcefallback: + return self.find_program_fallback(fallback, args, required, extra_info) + + progobj = self.program_from_file_for(for_machine, args) + if progobj is None: + progobj = self.program_from_system(args, search_dirs, silent=True) + if progobj is None and args[0].endswith('python3'): + prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) + progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None + if progobj is None and fallback and required: + progobj = self.find_program_fallback(fallback, args, required, extra_info) + + return progobj + + def find_program_fallback(self, fallback, args, required, extra_info): + mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', + mlog.bold(' '.join(args))) + sp_kwargs = { 'required': required } + self.do_subproject(fallback, 'meson', sp_kwargs) + return self.program_from_overrides(args, extra_info) + @FeatureNewKwargs('find_program', '0.53.0', ['dirs']) @FeatureNewKwargs('find_program', '0.52.0', ['version']) @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) @@ -3362,7 +3395,7 @@ external dependencies (including libraries) must go to "dependencies".''') disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled') - return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) + return self.notfound_program(args) search_dirs = extract_search_dirs(kwargs) wanted = mesonlib.stringlistify(kwargs.get('version', [])) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index d645c2c..19e2175 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -126,6 +126,7 @@ class PackageDefinition: def parse_provide_section(self): self.provide = {self.name: None} + self.provide_programs = [] if self.config.has_section('provide'): for k, v in self.config['provide'].items(): if k == 'dependency_names': @@ -134,6 +135,11 @@ class PackageDefinition: names = {n.strip(): None for n in v.split(',')} self.provide.update(names) continue + if k == 'program_names': + # A coma separated list of program names + names = {n.strip(): None for n in v.split(',')} + self.provide_programs += names + continue if not v: m = ('Empty dependency variable name for {!r} in {}. ' 'If the subproject uses meson.override_dependency() ' @@ -199,6 +205,14 @@ class Resolver: return wrap.name return directory + def find_program_provider(self, names: T.List[str]): + wraps = [i[0] for i in self.wraps.values()] + for name in names: + for wrap in wraps: + if wrap and name in wrap.provide_programs: + return wrap.name + return None + def resolve(self, packagename: str, method: str, current_subproject: str = '') -> str: self.current_subproject = current_subproject self.packagename = packagename diff --git a/test cases/common/187 find override/meson.build b/test cases/common/187 find override/meson.build index 3b8af80..b277459 100644 --- a/test cases/common/187 find override/meson.build +++ b/test cases/common/187 find override/meson.build @@ -10,3 +10,6 @@ if not gencodegen.found() endif subdir('otherdir') + +tool = find_program('sometool') +assert(tool.found()) diff --git a/test cases/common/187 find override/subprojects/sub.wrap b/test cases/common/187 find override/subprojects/sub.wrap new file mode 100644 index 0000000..17aa332 --- /dev/null +++ b/test cases/common/187 find override/subprojects/sub.wrap @@ -0,0 +1,5 @@ +[wrap-file] +directory = sub + +[provide] +program_names = sometool diff --git a/test cases/common/187 find override/subprojects/sub/meson.build b/test cases/common/187 find override/subprojects/sub/meson.build new file mode 100644 index 0000000..640f270 --- /dev/null +++ b/test cases/common/187 find override/subprojects/sub/meson.build @@ -0,0 +1,4 @@ +project('tools') + +exe = find_program('gencodegen') +meson.override_find_program('sometool', exe) -- cgit v1.1