From 56c9e95b04b51def7443a514e5021fa7b70fe8c8 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sat, 4 Apr 2020 17:39:39 -0400 Subject: Implicit dependency fallback when a subproject wrap or dir exists --- docs/markdown/Reference-manual.md | 4 +++- docs/markdown/snippets/implicit_fallback.md | 9 +++++++++ mesonbuild/interpreter.py | 8 ++++++++ run_unittests.py | 5 +++++ test cases/common/102 subproject subdir/meson.build | 4 ++++ .../102 subproject subdir/subprojects/sub_implicit/meson.build | 4 ++++ test cases/linuxlike/5 dependency versions/meson.build | 4 ++-- 7 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 docs/markdown/snippets/implicit_fallback.md create mode 100644 test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index dad8c12..293e41f 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -456,7 +456,9 @@ arguments: *(since 0.54.0)* `'subproj_dep'` argument can be omitted in the case the subproject used `meson.override_dependency('dependency_name', subproj_dep)`. In that case, the `fallback` keyword argument can be a single string instead - of a list of 2 strings. + of a list of 2 strings. *Since 0.55.0* the `fallback` keyword argument can be + omitted when there is a wrap file or a directory with the same `dependency_name`, + and subproject used `meson.override_dependency('dependency_name', subproj_dep)`. - `language` *(since 0.42.0)*: defines what language-specific dependency to find if it's available for multiple languages. - `method`: defines the way the dependency is detected, the default is diff --git a/docs/markdown/snippets/implicit_fallback.md b/docs/markdown/snippets/implicit_fallback.md new file mode 100644 index 0000000..87003d0 --- /dev/null +++ b/docs/markdown/snippets/implicit_fallback.md @@ -0,0 +1,9 @@ +## Implicit dependency fallback + +`dependency('foo')` now automatically fallback if the dependency is not found on +the system but a subproject wrap file or directory exists with the same name. + +That means that simply adding `subprojects/foo.wrap` is enough to add fallback +to any `dependency('foo')` call. It is however requires that the subproject call +`meson.override_dependency('foo', foo_dep)` to specify which dependency object +should be used for `foo`. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 487bdd6..053db12 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3549,6 +3549,14 @@ external dependencies (including libraries) must go to "dependencies".''') return self.notfound_dependency() has_fallback = 'fallback' in kwargs + if not has_fallback and name: + # Add an implicit fallback if we have a wrap file or a directory with the same name. + subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) + wrap_, directory = wrap.get_directory(subproject_dir_abs, name) + if wrap_ or os.path.exists(os.path.join(subproject_dir_abs, directory)): + kwargs['fallback'] = name + has_fallback = True + if 'default_options' in kwargs and not has_fallback: mlog.warning('The "default_options" keyworg argument does nothing without a "fallback" keyword argument.', location=self.current_node) diff --git a/run_unittests.py b/run_unittests.py index c4978c2..2f9fb7f 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4081,6 +4081,11 @@ recommended as it is not supported on some platforms''') 'version': '1.0' }, { + 'descriptive_name': 'sub_implicit', + 'name': 'sub_implicit', + 'version': '1.0', + }, + { 'descriptive_name': 'sub-novar', 'name': 'sub_novar', 'version': '1.0', diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index 8299a37..46a2bce 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -25,3 +25,7 @@ dependency('sub-novar', fallback : 'sub_novar') # Verify a subproject can force a dependency to be not-found d = dependency('sub-notfound', fallback : 'sub_novar', required : false) assert(not d.found(), 'Dependency should be not-found') + +# Verify that implicit fallback works because subprojects/sub_implicit directory exists +d = dependency('sub_implicit') +assert(d.found(), 'Should implicitly fallback') diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build new file mode 100644 index 0000000..613bd05 --- /dev/null +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build @@ -0,0 +1,4 @@ +project('sub_implicit', 'c', version : '1.0') + +dep = declare_dependency() +meson.override_dependency('sub_implicit', dep) diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build index 94f424d..164e679 100644 --- a/test cases/linuxlike/5 dependency versions/meson.build +++ b/test cases/linuxlike/5 dependency versions/meson.build @@ -31,10 +31,10 @@ dependency('somebrokenlib', version : '>=1.0', required : false) # Search for an external dependency that won't be found, but must later be # found via fallbacks -somelibnotfound = dependency('somelib', required : false) +somelibnotfound = dependency('somelib1', required : false) assert(somelibnotfound.found() == false, 'somelibnotfound was found?') # Find internal dependency without version -somelibver = dependency('somelib', +somelibver = dependency('somelib1', fallback : ['somelibnover', 'some_dep']) assert(somelibver.type_name() == 'internal', 'somelibver should be of type "internal", not ' + somelibver.type_name()) # Find an internal dependency again with the same name and a specific version -- cgit v1.1 From 2a7f72885ff0623a0a625efb5ffeca6299fc4cf7 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sat, 4 Apr 2020 23:56:33 -0400 Subject: wrap: Add 'provide' section --- docs/markdown/Reference-manual.md | 4 +- docs/markdown/Wrap-dependency-system-manual.md | 32 ++++++++++++++- docs/markdown/snippets/implicit_fallback.md | 7 ++++ mesonbuild/interpreter.py | 18 ++++---- mesonbuild/wrap/wrap.py | 48 ++++++++++++++++++++-- .../common/102 subproject subdir/meson.build | 11 +++++ .../subprojects/sub_implicit.wrap | 5 +++ .../subprojects/sub_implicit/meson.build | 4 ++ .../subprojects/s2/subprojects/athing.wrap | 3 +- 9 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 293e41f..9bca74b 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -458,7 +458,9 @@ arguments: In that case, the `fallback` keyword argument can be a single string instead of a list of 2 strings. *Since 0.55.0* the `fallback` keyword argument can be omitted when there is a wrap file or a directory with the same `dependency_name`, - and subproject used `meson.override_dependency('dependency_name', subproj_dep)`. + and subproject registered the dependency using + `meson.override_dependency('dependency_name', subproj_dep)`, or when the wrap + file has `dependency_name` in its `[provide]` section. - `language` *(since 0.42.0)*: defines what language-specific dependency to find if it's available for multiple languages. - `method`: defines the way the dependency is detected, the default is diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index e59a6be..cb7c6d6 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -105,7 +105,7 @@ of downloading the file, even if `--wrap-mode` option is set to valid value (such as a git tag) for the VCS's `checkout` command, or (for git) `head` to track upstream's default branch. Required. -## Specific to wrap-git +### Specific to wrap-git - `depth` - shallowly clone the repository to X number of commits. Note that git always allow shallowly cloning branches, but in order to clone commit ids shallowly, the server must support @@ -138,6 +138,36 @@ put them somewhere where you can download them. Meson build patches are only supported for wrap-file mode. When using wrap-git, the repository must contain all Meson build definitions. +## `provide` section + +*Since *0.55.0* + +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. Each entry in the format `dependency_name = variable_name`, +where `dependency_name` usually match the corresponding pkg-config name and +`variable_name` is the name of a variable defined in the subproject that should +be returned for that dependency. In the case the subproject uses +`meson.override_dependency('foo', foo_dep)` the `variable_name` can be left empty +in the wrap file. + +For example `glib.wrap` provides `glib-2.0`, `gobject-2.0` and `gio-2.0`. A wrap +file for glib would look like: +```ini +[wrap-git] +url=https://gitlab.gnome.org/GNOME/glib.git +revision=glib-2-62 + +[provide] +glib-2.0=glib_dep +gobject-2.0=gobject_dep +gio-2.0=gio_dep +``` + +With such wrap file, `dependency('glib-2.0')` will automatically fallback to use +`glib.wrap` and return `glib_dep` variable from the subproject. + ## 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 87003d0..bad1c71 100644 --- a/docs/markdown/snippets/implicit_fallback.md +++ b/docs/markdown/snippets/implicit_fallback.md @@ -7,3 +7,10 @@ That means that simply adding `subprojects/foo.wrap` is enough to add fallback to any `dependency('foo')` call. It is however requires that the subproject call `meson.override_dependency('foo', foo_dep)` to specify which dependency object should be used for `foo`. + +## Wrap file `provide` section + +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). diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 053db12..7c55932 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2779,10 +2779,9 @@ external dependencies (including libraries) must go to "dependencies".''') self.subproject_dir, dirname)) return subproject - subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) - r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode'), current_subproject=self.subproject) + r = self.environment.wrap_resolver try: - resolved = r.resolve(dirname, method) + resolved = r.resolve(dirname, method, self.subproject) except wrap.WrapException as e: subprojdir = os.path.join(self.subproject_dir, r.directory) if isinstance(e, wrap.WrapNotFoundException): @@ -2798,7 +2797,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise e subdir = os.path.join(self.subproject_dir, resolved) - subdir_abs = os.path.join(subproject_dir_abs, resolved) + subdir_abs = os.path.join(self.environment.get_source_dir(), subdir) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True @@ -3062,6 +3061,10 @@ external dependencies (including libraries) must go to "dependencies".''') self.subproject_dir = spdirname self.build.subproject_dir = self.subproject_dir + if not self.is_subproject(): + wrap_mode = self.coredata.get_builtin_option('wrap_mode') + subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) + self.environment.wrap_resolver = wrap.Resolver(subproject_dir_abs, wrap_mode) self.build.projects[self.subproject] = proj_name mlog.log('Project name:', mlog.bold(proj_name)) @@ -3551,10 +3554,9 @@ external dependencies (including libraries) must go to "dependencies".''') has_fallback = 'fallback' in kwargs if not has_fallback and name: # Add an implicit fallback if we have a wrap file or a directory with the same name. - subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) - wrap_, directory = wrap.get_directory(subproject_dir_abs, name) - if wrap_ or os.path.exists(os.path.join(subproject_dir_abs, directory)): - kwargs['fallback'] = name + provider = self.environment.wrap_resolver.find_provider(name) + if provider: + kwargs['fallback'] = provider has_fallback = True if 'default_options' in kwargs and not has_fallback: diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 689fb4f..63ee349 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -111,6 +111,10 @@ class PackageDefinition: self.config.read(fname) except configparser.Error: raise WrapException('Failed to parse {}'.format(self.basename)) + self.parse_wrap_section() + self.parse_provide_section() + + def parse_wrap_section(self): if len(self.config.sections()) < 1: raise WrapException('Missing sections in {}'.format(self.basename)) self.wrap_section = self.config.sections()[0] @@ -120,6 +124,11 @@ class PackageDefinition: self.type = self.wrap_section[5:] self.values = dict(self.config[self.wrap_section]) + def parse_provide_section(self): + self.provide = {self.name: None} + if self.config.has_section('provide'): + self.provide.update(self.config['provide']) + def get(self, key: str) -> str: try: return self.values[key] @@ -145,17 +154,48 @@ def get_directory(subdir_root: str, packagename: str): return wrap, directory class Resolver: - def __init__(self, subdir_root: str, wrap_mode=WrapMode.default, current_subproject: str = ''): + def __init__(self, subdir_root: str, wrap_mode=WrapMode.default): self.wrap_mode = wrap_mode self.subdir_root = subdir_root - self.current_subproject = current_subproject self.cachedir = os.path.join(self.subdir_root, 'packagecache') self.filesdir = os.path.join(self.subdir_root, 'packagefiles') + self.wraps = {} # type: T.Dict[str, T.Tuple[T.Optional[PackageDefinition], T.Optional[str]]] + self.load_wraps() - def resolve(self, packagename: str, method: str) -> str: + def load_wraps(self): + if not os.path.isdir(self.subdir_root): + return + # Load wrap files upfront + for f in os.listdir(self.subdir_root): + if f.endswith('.wrap'): + packagename = f[:-5] + wrap, directory = get_directory(self.subdir_root, packagename) + for k in wrap.provide.keys(): + self.wraps[k] = (wrap, directory) + elif os.path.isdir(os.path.join(self.subdir_root, f)): + # Keep it in the case we have dirs with no corresponding wrap file. + self.wraps.setdefault(f, (None, f)) + + def find_provider(self, packagename: str): + # Return value is in the same format as fallback kwarg: + # ['subproject_name', 'variable_name'], or 'subproject_name'. + wrap, directory = self.wraps.get(packagename, (None, None)) + if wrap: + dep_var = wrap.provide[packagename] + if dep_var: + return [wrap.name, dep_var] + return wrap.name + return directory + + def resolve(self, packagename: str, method: str, current_subproject: str = '') -> str: + self.current_subproject = current_subproject self.packagename = packagename - self.wrap, self.directory = get_directory(self.subdir_root, self.packagename) + self.wrap, self.directory = self.wraps.get(packagename, (None, self.packagename)) + if self.wrap and packagename != self.wrap.name: + m = 'subproject() must not be called by the name of a dependency it provides. Expecting {!r} but got {!r}.' + raise WrapException(m.format(self.wrap.name, packagename)) self.dirname = os.path.join(self.subdir_root, self.directory) + meson_file = os.path.join(self.dirname, 'meson.build') cmake_file = os.path.join(self.dirname, 'CMakeLists.txt') diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index 46a2bce..bc202a3 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -29,3 +29,14 @@ assert(not d.found(), 'Dependency should be not-found') # Verify that implicit fallback works because subprojects/sub_implicit directory exists d = dependency('sub_implicit') assert(d.found(), 'Should implicitly fallback') + +# Verify that implicit fallback works because sub_implicit.wrap has +# `sub_implicit_provide1=` and the subproject overrides sub_implicit_provide1. +d = dependency('sub_implicit_provide1') +assert(d.found(), 'Should implicitly fallback') + +# Verify that implicit fallback works because sub_implicit.wrap has +# `sub_implicit_provide2=sub_implicit_provide2_dep` and does not override +# sub_implicit_provide2. +d = dependency('sub_implicit_provide2') +assert(d.found(), 'Should implicitly fallback') diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap new file mode 100644 index 0000000..c14fff0 --- /dev/null +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap @@ -0,0 +1,5 @@ +[wrap-file] + +[provide] +sub_implicit_provide1= +sub_implicit_provide2=sub_implicit_provide2_dep diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build index 613bd05..64374d3 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build @@ -2,3 +2,7 @@ project('sub_implicit', 'c', version : '1.0') dep = declare_dependency() meson.override_dependency('sub_implicit', dep) +meson.override_dependency('sub_implicit_provide1', dep) + +# This one is not overriden but the wrap file tells the variable name to use. +sub_implicit_provide2_dep = dep diff --git a/test cases/unit/12 promote/subprojects/s2/subprojects/athing.wrap b/test cases/unit/12 promote/subprojects/s2/subprojects/athing.wrap index 09ba4e8..11b2178 100644 --- a/test cases/unit/12 promote/subprojects/s2/subprojects/athing.wrap +++ b/test cases/unit/12 promote/subprojects/s2/subprojects/athing.wrap @@ -1,2 +1 @@ -The contents of this wrap file are never evaluated so they -can be anything. +[wrap-file] -- cgit v1.1 From 71804e56eb3612eabc51887fe4d46961684a3ecc Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 13 Apr 2020 18:12:43 -0400 Subject: wrap: Add special 'dependency_names' key in [provide] section The value for that key must be a coma separated list of dependecy names provided by that subproject, when no variable name is needed because the subproject uses override_dependency(). --- docs/markdown/Wrap-dependency-system-manual.md | 22 ++++++++++++++++++---- mesonbuild/wrap/wrap.py | 14 +++++++++++++- .../common/102 subproject subdir/meson.build | 2 +- .../subprojects/sub_implicit.wrap | 4 ++-- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index cb7c6d6..b927944 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -145,12 +145,15 @@ wrap-git, the repository must contain all Meson build definitions. 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. Each entry in the format `dependency_name = variable_name`, +argument is given. It is recommended for subprojects to call +`meson.override_dependency('foo', foo_dep)`, dependency name can then be added into +the special `dependency_names` entry which takes coma separated list of dependency +names. For backward compatibility with subprojects that does not call +`meson.override_dependency()`, the variable name can be provided in the wrap file +with entries in the format `dependency_name = variable_name`, where `dependency_name` usually match the corresponding pkg-config name and `variable_name` is the name of a variable defined in the subproject that should -be returned for that dependency. In the case the subproject uses -`meson.override_dependency('foo', foo_dep)` the `variable_name` can be left empty -in the wrap file. +be returned for that dependency. For example `glib.wrap` provides `glib-2.0`, `gobject-2.0` and `gio-2.0`. A wrap file for glib would look like: @@ -165,6 +168,17 @@ gobject-2.0=gobject_dep gio-2.0=gio_dep ``` +Alternatively, when using a recent enough version of glib that uses +`meson.override_dependency()`: +```ini +[wrap-git] +url=https://gitlab.gnome.org/GNOME/glib.git +revision=glib-2-62 + +[provide] +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. diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 63ee349..d645c2c 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -127,7 +127,19 @@ class PackageDefinition: def parse_provide_section(self): self.provide = {self.name: None} if self.config.has_section('provide'): - self.provide.update(self.config['provide']) + for k, v in self.config['provide'].items(): + if k == 'dependency_names': + # A coma separated list of dependency names that does not + # need a variable name + names = {n.strip(): None for n in v.split(',')} + self.provide.update(names) + continue + if not v: + m = ('Empty dependency variable name for {!r} in {}. ' + 'If the subproject uses meson.override_dependency() ' + 'it can be added in the "dependency_names" special key.') + raise WrapException(m.format(k, self.basename)) + self.provide[k] = v def get(self, key: str) -> str: try: diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index bc202a3..6faff75 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -31,7 +31,7 @@ d = dependency('sub_implicit') assert(d.found(), 'Should implicitly fallback') # Verify that implicit fallback works because sub_implicit.wrap has -# `sub_implicit_provide1=` and the subproject overrides sub_implicit_provide1. +# `dependency_names=sub_implicit_provide1` and the subproject overrides sub_implicit_provide1. d = dependency('sub_implicit_provide1') assert(d.found(), 'Should implicitly fallback') diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap index c14fff0..e668a8d 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap @@ -1,5 +1,5 @@ [wrap-file] [provide] -sub_implicit_provide1= -sub_implicit_provide2=sub_implicit_provide2_dep +dependency_names = sub_implicit_provide1 +sub_implicit_provide2 = sub_implicit_provide2_dep -- cgit v1.1 From 288d1ae5a5de13c8844635023caf27378df4919b Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Wed, 15 Apr 2020 16:42:21 -0400 Subject: wrap: Do not fallback implicitly on optional dependency This fix the following common pattern, we don't want to implicitly fallback on the first line: foo_dep = dependency('foo', required: false) if not foo_dep.found() foo_dep = cc.find_library('foo', required : false) if not foo_dep.found() foo_dep = dependency('foo', fallback: 'foo') endif endif --- mesonbuild/interpreter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 7c55932..89c9daa 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3552,8 +3552,11 @@ external dependencies (including libraries) must go to "dependencies".''') return self.notfound_dependency() has_fallback = 'fallback' in kwargs - if not has_fallback and name: - # Add an implicit fallback if we have a wrap file or a directory with the same name. + if not has_fallback and name and required: + # Add an implicit fallback if we have a wrap file or a directory with the same name, + # but only if this dependency is required. It is common to first check for a pkg-config, + # then fallback to use find_library() and only afterward check again the dependency + # with a fallback. provider = self.environment.wrap_resolver.find_provider(name) if provider: kwargs['fallback'] = provider -- cgit v1.1 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 From fba796cf1312715b8b97dcb80a4b5c200332b2e8 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Fri, 29 May 2020 11:36:06 -0400 Subject: Fix typo: coma->comma --- docs/markdown/Wrap-dependency-system-manual.md | 2 +- mesonbuild/wrap/wrap.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index dd8595b..c96c73a 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -148,7 +148,7 @@ 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. It is recommended for subprojects to call `meson.override_dependency('foo', foo_dep)`, dependency name can then be added into -the special `dependency_names` entry which takes coma separated list of dependency +the special `dependency_names` entry which takes comma separated list of dependency names. For backward compatibility with subprojects that does not call `meson.override_dependency()`, the variable name can be provided in the wrap file with entries in the format `dependency_name = variable_name`, diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 19e2175..1a6399d 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -130,13 +130,13 @@ class PackageDefinition: if self.config.has_section('provide'): for k, v in self.config['provide'].items(): if k == 'dependency_names': - # A coma separated list of dependency names that does not + # A comma separated list of dependency names that does not # need a variable name 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 + # A comma separated list of program names names = {n.strip(): None for n in v.split(',')} self.provide_programs += names continue -- cgit v1.1 From 13316f99feaa9831146f6456ce11916042a871cb Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Thu, 4 Jun 2020 14:35:18 -0400 Subject: wrap: Refactor to split wraps dictionary into 3 separate dicts It makes the code cleaner to have 3 separate dictionaries for packagename, dependency and programs. --- mesonbuild/interpreter.py | 2 +- mesonbuild/mdist.py | 2 +- mesonbuild/wrap/wrap.py | 107 ++++++++++++++++++++++++---------------------- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index e616d85..3e64a67 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3590,7 +3590,7 @@ external dependencies (including libraries) must go to "dependencies".''') # but only if this dependency is required. It is common to first check for a pkg-config, # then fallback to use find_library() and only afterward check again the dependency # with a fallback. - provider = self.environment.wrap_resolver.find_provider(name) + provider = self.environment.wrap_resolver.find_dep_provider(name) if provider: kwargs['fallback'] = provider has_fallback = True diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py index 5ab0ad4..9d94ace 100644 --- a/mesonbuild/mdist.py +++ b/mesonbuild/mdist.py @@ -259,7 +259,7 @@ def run(options): if options.include_subprojects: subproject_dir = os.path.join(src_root, b.subproject_dir) for sub in b.subprojects: - _, directory = wrap.get_directory(subproject_dir, sub) + directory = wrap.get_directory(subproject_dir, sub) subprojects.append(os.path.join(b.subproject_dir, directory)) extra_meson_args.append('-Dwrap_mode=nodownload') diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 1a6399d..536b8ae 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -103,9 +103,22 @@ class WrapNotFoundException(WrapException): class PackageDefinition: def __init__(self, fname: str): - self.filename = fname + self.type = None + self.values = {} # type: T.Dict[str, str] + self.provided_deps = {} # type: T.Dict[str, T.Optional[str]] + self.provided_programs = [] # type: T.List[str] self.basename = os.path.basename(fname) - self.name = self.basename[:-5] + self.name = self.basename + if self.name.endswith('.wrap'): + self.name = self.name[:-5] + self.provided_deps[self.name] = None + if fname.endswith('.wrap'): + self.parse_wrap(fname) + self.directory = self.values.get('directory', self.name) + if os.path.dirname(self.directory): + raise WrapException('Directory key must be a name and not a path') + + def parse_wrap(self, fname: str): try: self.config = configparser.ConfigParser(interpolation=None) self.config.read(fname) @@ -125,27 +138,25 @@ class PackageDefinition: self.values = dict(self.config[self.wrap_section]) 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': # A comma separated list of dependency names that does not # need a variable name names = {n.strip(): None for n in v.split(',')} - self.provide.update(names) + self.provided_deps.update(names) continue if k == 'program_names': # A comma separated list of program names - names = {n.strip(): None for n in v.split(',')} - self.provide_programs += names + names = [n.strip() for n in v.split(',')] + self.provided_programs += names continue if not v: m = ('Empty dependency variable name for {!r} in {}. ' 'If the subproject uses meson.override_dependency() ' 'it can be added in the "dependency_names" special key.') raise WrapException(m.format(k, self.basename)) - self.provide[k] = v + self.provided_deps[k] = v def get(self, key: str) -> str: try: @@ -154,22 +165,12 @@ class PackageDefinition: m = 'Missing key {!r} in {}' raise WrapException(m.format(key, self.basename)) -def load_wrap(subdir_root: str, packagename: str) -> PackageDefinition: +def get_directory(subdir_root: str, packagename: str) -> str: fname = os.path.join(subdir_root, packagename + '.wrap') if os.path.isfile(fname): - return PackageDefinition(fname) - return None - -def get_directory(subdir_root: str, packagename: str): - directory = packagename - # We always have to load the wrap file, if it exists, because it could - # override the default directory name. - wrap = load_wrap(subdir_root, packagename) - if wrap and 'directory' in wrap.values: - directory = wrap.get('directory') - if os.path.dirname(directory): - raise WrapException('Directory key must be a name and not a path') - return wrap, directory + wrap = PackageDefinition(fname) + return wrap.directory + return packagename class Resolver: def __init__(self, subdir_root: str, wrap_mode=WrapMode.default): @@ -177,49 +178,60 @@ class Resolver: self.subdir_root = subdir_root self.cachedir = os.path.join(self.subdir_root, 'packagecache') self.filesdir = os.path.join(self.subdir_root, 'packagefiles') - self.wraps = {} # type: T.Dict[str, T.Tuple[T.Optional[PackageDefinition], T.Optional[str]]] + self.wraps = {} # type: T.Dict[str, PackageDefinition] + self.provided_deps = {} # type: T.Dict[str, PackageDefinition] + self.provided_programs = {} # type: T.Dict[str, PackageDefinition] self.load_wraps() def load_wraps(self): if not os.path.isdir(self.subdir_root): return - # Load wrap files upfront for f in os.listdir(self.subdir_root): - if f.endswith('.wrap'): - packagename = f[:-5] - wrap, directory = get_directory(self.subdir_root, packagename) - for k in wrap.provide.keys(): - self.wraps[k] = (wrap, directory) - elif os.path.isdir(os.path.join(self.subdir_root, f)): - # Keep it in the case we have dirs with no corresponding wrap file. - self.wraps.setdefault(f, (None, f)) - - def find_provider(self, packagename: str): + fname = os.path.join(self.subdir_root, f) + # Ignore not .wrap files, and reserved directories. + if (os.path.isfile(fname) and not fname.endswith('.wrap')) or \ + f in ['packagecache', 'packagefiles']: + continue + wrap = PackageDefinition(fname) + # We could have added a dummy package definition for the directory, + # replace it now with the proper wrap. This happens if we already + # downloaded the subproject into 'foo-1.0' directory and we now found + # 'foo.wrap' file. + if wrap.directory in self.wraps: + del self.wraps[wrap.directory] + self.wraps[wrap.name] = wrap + for k in wrap.provided_deps.keys(): + self.provided_deps[k] = wrap + for k in wrap.provided_programs: + self.provided_programs[k] = wrap + + def find_dep_provider(self, packagename: str): # Return value is in the same format as fallback kwarg: # ['subproject_name', 'variable_name'], or 'subproject_name'. - wrap, directory = self.wraps.get(packagename, (None, None)) + wrap = self.provided_deps.get(packagename) if wrap: - dep_var = wrap.provide[packagename] + dep_var = wrap.provided_deps.get(packagename) if dep_var: return [wrap.name, dep_var] return wrap.name - return directory + return None 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 + wrap = self.provided_programs.get(name) + if wrap: + return wrap.name return None def resolve(self, packagename: str, method: str, current_subproject: str = '') -> str: self.current_subproject = current_subproject self.packagename = packagename - self.wrap, self.directory = self.wraps.get(packagename, (None, self.packagename)) - if self.wrap and packagename != self.wrap.name: - m = 'subproject() must not be called by the name of a dependency it provides. Expecting {!r} but got {!r}.' - raise WrapException(m.format(self.wrap.name, packagename)) + self.directory = packagename + self.wrap = self.wraps.get(packagename) + if not self.wrap: + m = 'Subproject directory not found and {}.wrap file not found' + raise WrapNotFoundException(m.format(self.packagename)) + self.directory = self.wrap.directory self.dirname = os.path.join(self.subdir_root, self.directory) meson_file = os.path.join(self.dirname, 'meson.build') @@ -241,11 +253,6 @@ class Resolver: if not os.path.isdir(self.dirname): raise WrapException('Path already exists but is not a directory') else: - # A wrap file is required to download - if not self.wrap: - m = 'Subproject directory not found and {}.wrap file not found' - raise WrapNotFoundException(m.format(self.packagename)) - if self.wrap.type == 'file': self.get_file() else: -- cgit v1.1 From 41aefd2145bf287c9c07139a7334845f8c03374c Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Fri, 12 Jun 2020 11:45:26 -0400 Subject: wrap: Abort if multiple wraps provide the same dep or program --- mesonbuild/wrap/wrap.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 536b8ae..e0c7a04 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -186,23 +186,35 @@ class Resolver: def load_wraps(self): if not os.path.isdir(self.subdir_root): return - for f in os.listdir(self.subdir_root): - fname = os.path.join(self.subdir_root, f) - # Ignore not .wrap files, and reserved directories. - if (os.path.isfile(fname) and not fname.endswith('.wrap')) or \ - f in ['packagecache', 'packagefiles']: + root, dirs, files = next(os.walk(self.subdir_root)) + for i in files: + if not i.endswith('.wrap'): continue + fname = os.path.join(self.subdir_root, i) wrap = PackageDefinition(fname) - # We could have added a dummy package definition for the directory, - # replace it now with the proper wrap. This happens if we already - # downloaded the subproject into 'foo-1.0' directory and we now found - # 'foo.wrap' file. - if wrap.directory in self.wraps: - del self.wraps[wrap.directory] self.wraps[wrap.name] = wrap + if wrap.directory in dirs: + dirs.remove(wrap.directory) + # Add dummy package definition for directories not associated with a wrap file. + for i in dirs: + if i in ['packagecache', 'packagefiles']: + continue + fname = os.path.join(self.subdir_root, i) + wrap = PackageDefinition(fname) + self.wraps[wrap.name] = wrap + + for wrap in self.wraps.values(): for k in wrap.provided_deps.keys(): + if k in self.provided_deps: + prev_wrap = self.provided_deps[k] + m = 'Multiple wrap files provide {!r} dependency: {} and {}' + raise WrapException(m.format(k, wrap.basename, prev_wrap.basename)) self.provided_deps[k] = wrap for k in wrap.provided_programs: + if k in self.provided_programs: + prev_wrap = self.provided_programs[k] + m = 'Multiple wrap files provide {!r} program: {} and {}' + raise WrapException(m.format(k, wrap.basename, prev_wrap.basename)) self.provided_programs[k] = wrap def find_dep_provider(self, packagename: str): -- cgit v1.1 From 95c3fee47d75ef493865b3ffbcea7989ef246287 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Fri, 12 Jun 2020 11:57:25 -0400 Subject: wrap: Update doc to give dependency_names example first --- docs/markdown/Wrap-dependency-system-manual.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index c96c73a..0ff304d 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -156,28 +156,28 @@ where `dependency_name` usually match the corresponding pkg-config name and `variable_name` is the name of a variable defined in the subproject that should be returned for that dependency. -For example `glib.wrap` provides `glib-2.0`, `gobject-2.0` and `gio-2.0`. A wrap -file for glib would look like: +For example when using a recent enough version of glib that uses +`meson.override_dependency()` to override `glib-2.0`, `gobject-2.0` and `gio-2.0`, +a wrap file would look like: ```ini [wrap-git] url=https://gitlab.gnome.org/GNOME/glib.git revision=glib-2-62 [provide] -glib-2.0=glib_dep -gobject-2.0=gobject_dep -gio-2.0=gio_dep +dependency_names = glib-2.0, gobject-2.0, gio-2.0 ``` -Alternatively, when using a recent enough version of glib that uses -`meson.override_dependency()`: +With older version of glib dependency variable names need to be specified: ```ini [wrap-git] url=https://gitlab.gnome.org/GNOME/glib.git revision=glib-2-62 [provide] -dependency_names = glib-2.0, gobject-2.0, gio-2.0 +glib-2.0=glib_dep +gobject-2.0=gobject_dep +gio-2.0=gio_dep ``` With such wrap file, `dependency('glib-2.0')` will automatically fallback to use -- cgit v1.1 From f7a07ee91a77f68e27b1cf60f5ffcd3296f15b7b Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 23 Jun 2020 21:34:54 -0400 Subject: interpreter: Already configured fallback should be used for optional dep --- mesonbuild/interpreter.py | 8 +++++--- test cases/common/102 subproject subdir/meson.build | 7 +++++++ .../common/102 subproject subdir/subprojects/sub_implicit.wrap | 1 + .../102 subproject subdir/subprojects/sub_implicit/meson.build | 3 +++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 3e64a67..7896f51 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3585,13 +3585,15 @@ external dependencies (including libraries) must go to "dependencies".''') return self.notfound_dependency() has_fallback = 'fallback' in kwargs - if not has_fallback and name and required: + if not has_fallback and name: # Add an implicit fallback if we have a wrap file or a directory with the same name, # but only if this dependency is required. It is common to first check for a pkg-config, # then fallback to use find_library() and only afterward check again the dependency - # with a fallback. + # with a fallback. If the fallback has already been configured then we have to use it + # even if the dependency is not required. provider = self.environment.wrap_resolver.find_dep_provider(name) - if provider: + dirname = mesonlib.listify(provider)[0] + if provider and (required or dirname in self.subprojects): kwargs['fallback'] = provider has_fallback = True diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index 6faff75..93093bf 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -40,3 +40,10 @@ assert(d.found(), 'Should implicitly fallback') # sub_implicit_provide2. d = dependency('sub_implicit_provide2') assert(d.found(), 'Should implicitly fallback') + +# sub_implicit.wrap provides glib-2.0 and we already configured that subproject, +# so we must not return the system dependency here. Using glib-2.0 here because +# some CI runners have it installed. +d = dependency('glib-2.0', required : false) +assert(d.found()) +assert(d.type_name() == 'internal') diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap index e668a8d..6f2dab6 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap @@ -1,5 +1,6 @@ [wrap-file] [provide] +glib-2.0 = glib_dep dependency_names = sub_implicit_provide1 sub_implicit_provide2 = sub_implicit_provide2_dep diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build index 64374d3..24609ae 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/meson.build @@ -6,3 +6,6 @@ meson.override_dependency('sub_implicit_provide1', dep) # This one is not overriden but the wrap file tells the variable name to use. sub_implicit_provide2_dep = dep + +# This one is not overriden but the wrap file tells the variable name to use. +glib_dep = dep -- cgit v1.1 From 7c90639078650d9457db21a024d437cbb90452c1 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 23 Jun 2020 22:03:43 -0400 Subject: interpreter: Don't abort if dep isn't required and sub didn't override --- mesonbuild/interpreter.py | 8 ++++++-- test cases/common/102 subproject subdir/meson.build | 7 +++++++ .../common/102 subproject subdir/subprojects/sub_implicit.wrap | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 7896f51..12d6cde 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3489,8 +3489,12 @@ external dependencies (including libraries) must go to "dependencies".''') raise DependencyException(m.format(display_name)) return DependencyHolder(cached_dep, self.subproject) else: - m = 'Subproject {} did not override dependency {}' - raise DependencyException(m.format(subproj_path, display_name)) + if required: + m = 'Subproject {} did not override dependency {}' + raise DependencyException(m.format(subproj_path, display_name)) + mlog.log('Dependency', mlog.bold(display_name), 'from subproject', + mlog.bold(subproj_path), 'found:', mlog.red('NO')) + return self.notfound_dependency() if subproject.found(): self.verify_fallback_consistency(dirname, varname, cached_dep) dep = self.subprojects[dirname].get_variable_method([varname], {}) diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index 93093bf..a891ca9 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -47,3 +47,10 @@ assert(d.found(), 'Should implicitly fallback') d = dependency('glib-2.0', required : false) assert(d.found()) assert(d.type_name() == 'internal') + +# sub_implicit.wrap provides gobject-2.0 and we already configured that subproject, +# so we must not return the system dependency here. But since the subproject did +# not override that dependency and its not required, not-found should be returned. +# Using gobject-2.0 here because some CI runners have it installed. +d = dependency('gobject-2.0', required : false) +assert(not d.found()) diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap index 6f2dab6..a809c43 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap +++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit.wrap @@ -2,5 +2,5 @@ [provide] glib-2.0 = glib_dep -dependency_names = sub_implicit_provide1 +dependency_names = sub_implicit_provide1, gobject-2.0 sub_implicit_provide2 = sub_implicit_provide2_dep -- cgit v1.1 From 576493982da325a739f04e5455ef0643b49d94f1 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 29 Jun 2020 15:18:14 -0400 Subject: wrap: Add failing unit test for fallback consistency Dependency 'foo' is overriden with 'foo_dep' so using fallback variable name 'bar_dep' should abort. --- test cases/failing/106 fallback consistency/meson.build | 3 +++ test cases/failing/106 fallback consistency/subprojects/foo.wrap | 6 ++++++ .../failing/106 fallback consistency/subprojects/foo/meson.build | 6 ++++++ test cases/failing/106 fallback consistency/test.json | 7 +++++++ 4 files changed, 22 insertions(+) create mode 100644 test cases/failing/106 fallback consistency/meson.build create mode 100644 test cases/failing/106 fallback consistency/subprojects/foo.wrap create mode 100644 test cases/failing/106 fallback consistency/subprojects/foo/meson.build create mode 100644 test cases/failing/106 fallback consistency/test.json diff --git a/test cases/failing/106 fallback consistency/meson.build b/test cases/failing/106 fallback consistency/meson.build new file mode 100644 index 0000000..1b007f5 --- /dev/null +++ b/test cases/failing/106 fallback consistency/meson.build @@ -0,0 +1,3 @@ +project('fallback consistency') + +dependency('foo') diff --git a/test cases/failing/106 fallback consistency/subprojects/foo.wrap b/test cases/failing/106 fallback consistency/subprojects/foo.wrap new file mode 100644 index 0000000..28055d9 --- /dev/null +++ b/test cases/failing/106 fallback consistency/subprojects/foo.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_url = http://host.invalid/foo.tar.gz +source_filename = foo.tar.gz + +[provide] +foo = bar_dep diff --git a/test cases/failing/106 fallback consistency/subprojects/foo/meson.build b/test cases/failing/106 fallback consistency/subprojects/foo/meson.build new file mode 100644 index 0000000..fb58a4a --- /dev/null +++ b/test cases/failing/106 fallback consistency/subprojects/foo/meson.build @@ -0,0 +1,6 @@ +project('sub') + +foo_dep = declare_dependency() +meson.override_dependency('foo', foo_dep) + +bar_dep = declare_dependency() diff --git a/test cases/failing/106 fallback consistency/test.json b/test cases/failing/106 fallback consistency/test.json new file mode 100644 index 0000000..af1a429 --- /dev/null +++ b/test cases/failing/106 fallback consistency/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/106 fallback consistency/meson.build:3:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'bar_dep'" + } + ] +} -- cgit v1.1