diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2022-05-01 00:01:03 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-01 00:01:03 +0300 |
commit | bba588d8b03a9125bf5c4faaad31b70d39242b68 (patch) | |
tree | 63afdf87d9a5bfbb0953170ac89a46bedb02710d | |
parent | b30d04f52b5e13bf0c1047439465997184869c09 (diff) | |
parent | c181f2c70bd606f0100f74844fcf9697be16c217 (diff) | |
download | meson-bba588d8b03a9125bf5c4faaad31b70d39242b68.zip meson-bba588d8b03a9125bf5c4faaad31b70d39242b68.tar.gz meson-bba588d8b03a9125bf5c4faaad31b70d39242b68.tar.bz2 |
Merge pull request #10039 from eli-schwartz/wayland-protocols-subproject-files
dependencies: allow get_variable to expose files from subprojects
26 files changed, 223 insertions, 23 deletions
diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index 15da929..a3e5a00 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -90,6 +90,28 @@ following will happen: If 'default_value' was provided that value will be returned, if 'default_value' was not provided then an error will be raised. +## Dependencies that provide resource files + +Sometimes a dependency provides installable files which other projects then +need to use. For example, wayland-protocols XML files. + +```meson +foo_dep = dependency('foo') +foo_datadir = foo_dep.get_variable('pkgdatadir') +custom_target( + 'foo-generated.c', + input: foo_datadir / 'prototype.xml', + output: 'foo-generated.c', + command: [generator, '@INPUT@', '@OUTPUT@'] +) +``` + +*Since 0.63.0* these actually work as expected, even when they come from a +(well-formed) internal dependency. This only works when treating the files to +be obtained as interchangeable with a system dependency -- e.g. only public +files may be used, and leaving the directory pointed to by the dependency is +not allowed. + # Declaring your own You can declare your own dependency objects that can be used diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 7b85159..1242af7 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -216,7 +216,7 @@ class Dependency(HoldableObject): def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, default_value: T.Optional[str] = None, - pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + pkgconfig_define: T.Optional[T.List[str]] = None) -> str: if default_value is not None: return default_value raise DependencyException(f'No default provided for dependency {self!r}, which is not pkg-config, cmake, or config-tool based.') @@ -232,7 +232,7 @@ class InternalDependency(Dependency): libraries: T.List[T.Union['BuildTarget', 'CustomTarget']], whole_libraries: T.List[T.Union['BuildTarget', 'CustomTarget']], sources: T.Sequence[T.Union['FileOrString', 'CustomTarget', StructuredSources]], - ext_deps: T.List[Dependency], variables: T.Dict[str, T.Any], + ext_deps: T.List[Dependency], variables: T.Dict[str, str], d_module_versions: T.List[str], d_import_dirs: T.List['IncludeDirs']): super().__init__(DependencyTypeName('internal'), {}) self.version = version @@ -301,16 +301,10 @@ class InternalDependency(Dependency): def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, default_value: T.Optional[str] = None, - pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + pkgconfig_define: T.Optional[T.List[str]] = None) -> str: val = self.variables.get(internal, default_value) if val is not None: - # TODO: Try removing this assert by better typing self.variables - if isinstance(val, str): - return val - if isinstance(val, list): - for i in val: - assert isinstance(i, str) - return val + return val raise DependencyException(f'Could not get an internal variable and no default provided for {self!r}') def generate_link_whole_dependency(self) -> Dependency: diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index e9a4aa3..1ae7071 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -627,17 +627,23 @@ class CMakeDependency(ExternalDependency): def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, default_value: T.Optional[str] = None, - pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + pkgconfig_define: T.Optional[T.List[str]] = None) -> str: if cmake and self.traceparser is not None: try: v = self.traceparser.vars[cmake] except KeyError: pass else: - if len(v) == 1: - return v[0] - elif v: - return v + # CMake does NOT have a list datatype. We have no idea whether + # anything is a string or a string-separated-by-; Internally, + # we treat them as the latter and represent everything as a + # list, because it is convenient when we are mostly handling + # imported targets, which have various properties that are + # actually lists. + # + # As a result we need to convert them back to strings when grabbing + # raw variables the user requested. + return ';'.join(v) if default_value is not None: return default_value raise DependencyException(f'Could not get cmake variable and no default provided for {self!r}') diff --git a/mesonbuild/dependencies/configtool.py b/mesonbuild/dependencies/configtool.py index 7dccee4..3e8f212 100644 --- a/mesonbuild/dependencies/configtool.py +++ b/mesonbuild/dependencies/configtool.py @@ -155,7 +155,7 @@ class ConfigToolDependency(ExternalDependency): def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, default_value: T.Optional[str] = None, - pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + pkgconfig_define: T.Optional[T.List[str]] = None) -> str: if configtool: # In the not required case '' (empty string) will be returned if the # variable is not found. Since '' is a valid value to return we diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index e3f6e5c..62a7e39 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -485,7 +485,7 @@ class PkgConfigDependency(ExternalDependency): def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, default_value: T.Optional[str] = None, - pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + pkgconfig_define: T.Optional[T.List[str]] = None) -> str: if pkgconfig: try: return self.get_pkgconfig_variable(pkgconfig, pkgconfig_define or [], default_value) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d5e7dbd..6058838 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -420,6 +420,7 @@ class Interpreter(InterpreterBase, HoldableObject): bool: P_OBJ.BooleanHolder, str: P_OBJ.StringHolder, P_OBJ.MesonVersionString: P_OBJ.MesonVersionStringHolder, + P_OBJ.DependencyVariableString: P_OBJ.DependencyVariableStringHolder, # Meson types mesonlib.File: OBJ.FileHolder, @@ -669,6 +670,18 @@ class Interpreter(InterpreterBase, HoldableObject): d_module_versions = extract_as_list(kwargs, 'd_module_versions') d_import_dirs = self.extract_incdirs(kwargs, 'd_import_dirs') final_deps = [] + srcdir = Path(self.environment.source_dir) + # convert variables which refer to an -uninstalled.pc style datadir + for k, v in variables.items(): + try: + p = Path(v) + except ValueError: + continue + else: + if not self.is_subproject() and srcdir / self.subproject_dir in p.parents: + continue + if p.is_absolute() and p.is_dir() and srcdir / self.root_subdir in p.resolve().parents: + variables[k] = P_OBJ.DependencyVariableString(v) for d in deps: if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): raise InterpreterException('Dependencies must be external deps') @@ -2721,7 +2734,13 @@ external dependencies (including libraries) must go to "dependencies".''') @typed_pos_args('join_paths', varargs=str, min_varargs=1) @noKwargs def func_join_paths(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> str: - return os.path.join(*args[0]).replace('\\', '/') + parts = args[0] + other = os.path.join('', *parts[1:]).replace('\\', '/') + ret = os.path.join(*parts).replace('\\', '/') + if isinstance(parts[0], P_OBJ.DependencyVariableString) and '..' not in other: + return P_OBJ.DependencyVariableString(ret) + else: + return ret def run(self) -> None: super().run() @@ -2763,6 +2782,26 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey # declare_dependency). def validate_within_subproject(self, subdir, fname): srcdir = Path(self.environment.source_dir) + builddir = Path(self.environment.build_dir) + if isinstance(fname, P_OBJ.DependencyVariableString): + def validate_installable_file(fpath: Path) -> bool: + installablefiles: T.Set[Path] = set() + for d in self.build.data: + for s in d.sources: + installablefiles.add(Path(s.absolute_path(srcdir, builddir))) + installabledirs = [str(Path(srcdir, s.source_subdir)) for s in self.build.install_dirs] + if fpath in installablefiles: + return True + for d in installabledirs: + if str(fpath).startswith(d): + return True + return False + + norm = Path(fname) + # variables built from a dep.get_variable are allowed to refer to + # subproject files, as long as they are scheduled to be installed. + if validate_installable_file(norm): + return norm = Path(srcdir, subdir, fname).resolve() if os.path.isdir(norm): inputtype = 'directory' diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index a07d548..e59ba6b 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -479,7 +479,7 @@ class DependencyHolder(ObjectHolder[Dependency]): KwargInfo('default_value', (str, NoneType)), KwargInfo('pkgconfig_define', ContainerTypeInfo(list, str, pairs=True), default=[], listify=True), ) - def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.DependencyGetVariable') -> T.Union[str, T.List[str]]: + def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.DependencyGetVariable') -> str: default_varname = args[0] if default_varname is not None: FeatureNew('Positional argument to dependency.get_variable()', '0.58.0').use(self.subproject, self.current_node) diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py index b4fe621..1874d0d 100644 --- a/mesonbuild/interpreter/primitives/__init__.py +++ b/mesonbuild/interpreter/primitives/__init__.py @@ -10,6 +10,8 @@ __all__ = [ 'StringHolder', 'MesonVersionString', 'MesonVersionStringHolder', + 'DependencyVariableString', + 'DependencyVariableStringHolder', ] from .array import ArrayHolder @@ -17,4 +19,8 @@ from .boolean import BooleanHolder from .dict import DictHolder from .integer import IntegerHolder from .range import RangeHolder -from .string import StringHolder, MesonVersionString, MesonVersionStringHolder +from .string import ( + StringHolder, + MesonVersionString, MesonVersionStringHolder, + DependencyVariableString, DependencyVariableStringHolder +) diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py index 9129303..1fd6e92 100644 --- a/mesonbuild/interpreter/primitives/string.py +++ b/mesonbuild/interpreter/primitives/string.py @@ -179,3 +179,18 @@ class MesonVersionStringHolder(StringHolder): def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: self.interpreter.tmp_meson_version = args[0] return version_compare(self.held_object, args[0]) + +# These special subclasses of string exist to cover the case where a dependency +# exports a string variable interchangeable with a system dependency. This +# matters because a dependency can only have string-type get_variable() return +# values. If at any time dependencies start supporting additional variable +# types, this class could be deprecated. +class DependencyVariableString(str): + pass + +class DependencyVariableStringHolder(StringHolder): + def op_div(self, other: str) -> T.Union[str, DependencyVariableString]: + ret = super().op_div(other) + if '..' in other: + return ret + return DependencyVariableString(ret) diff --git a/test cases/common/251 subproject dependency variables/meson.build b/test cases/common/251 subproject dependency variables/meson.build new file mode 100644 index 0000000..6abcc16 --- /dev/null +++ b/test cases/common/251 subproject dependency variables/meson.build @@ -0,0 +1,13 @@ +project('subproject dependency variables', 'c') + +subfiles_dep = subproject('subfiles').get_variable('files_dep') + +executable( + 'foo', + join_paths(subfiles_dep.get_variable('pkgdatadir'), 'foo.c') +) + +executable( + 'foo2', + subfiles_dep.get_variable('pkgdatadir2') / 'foo.c' +) diff --git a/test cases/common/251 subproject dependency variables/subprojects/subfiles/meson.build b/test cases/common/251 subproject dependency variables/subprojects/subfiles/meson.build new file mode 100644 index 0000000..0c63bac --- /dev/null +++ b/test cases/common/251 subproject dependency variables/subprojects/subfiles/meson.build @@ -0,0 +1,26 @@ +project('dependency variable resource') + +files_dep = declare_dependency( + variables: [ + 'pkgdatadir=@0@/subdir'.format(meson.current_source_dir()), + 'pkgdatadir2=@0@/subdir2'.format(meson.current_source_dir()), + ] +) + +install_data('subdir/foo.c', install_dir: get_option('datadir') / 'subdir') +install_subdir('subdir2', install_dir: get_option('datadir')) + +import('pkgconfig').generate( + name: 'depvar_resource', + description: 'Get a resource file from pkgconfig or a subproject', + version: '0.1', + variables: [ + 'pkgdatadir=${datadir}/subdir', + 'pkgdatadir2=${datadir}/subdir2', + ], + uninstalled_variables: [ + 'pkgdatadir=@0@/subdir'.format(meson.current_source_dir()), + 'pkgdatadir2=@0@/subdir2'.format(meson.current_source_dir()), + ], + dataonly: true, +) diff --git a/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir/foo.c b/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir/foo.c new file mode 100644 index 0000000..78f2de1 --- /dev/null +++ b/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir/foo.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir2/foo.c b/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir2/foo.c new file mode 100644 index 0000000..78f2de1 --- /dev/null +++ b/test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir2/foo.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/test cases/common/251 subproject dependency variables/test.json b/test cases/common/251 subproject dependency variables/test.json new file mode 100644 index 0000000..dfd348c --- /dev/null +++ b/test cases/common/251 subproject dependency variables/test.json @@ -0,0 +1,7 @@ +{ + "installed": [ + { "type": "file", "file": "usr/share/pkgconfig/depvar_resource.pc" }, + { "type": "file", "file": "usr/share/subdir/foo.c" }, + { "type": "file", "file": "usr/share/subdir2/foo.c" } + ] +} diff --git a/test cases/failing/123 subproject sandbox violation/meson.build b/test cases/failing/123 subproject sandbox violation/meson.build new file mode 100644 index 0000000..d41994c --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/meson.build @@ -0,0 +1,34 @@ +project('subproject-sandbox-violation') + +sub1_d = subproject('subproj1').get_variable('d') +sub1_mustfail = sub1_d.get_variable('dir') / '..' / 'file.txt' + +sub2_d = subproject('subproj2').get_variable('d') +sub2_mustfail = sub2_d.get_variable('dir') / 'file.txt' + +main_d = declare_dependency( + variables: [ + 'dir=@0@'.format(meson.current_source_dir()), + ] +) +main_mustfail = main_d.get_variable('dir') / 'subprojects/subproj3/file.txt' + +if get_option('failmode') == 'parent-dir' + mustfail = sub1_mustfail +elif get_option('failmode') == 'not-installed' + mustfail = sub2_mustfail +elif get_option('failmode') == 'root-subdir' + mustfail = main_mustfail +endif + +custom_target( + 'mustfail', + input: mustfail, + output: 'file.txt', + command: [ + 'python3', '-c', + 'import os; shutil.copy(sys.argv[1], sys.argv[2])', + '@INPUT@', + '@OUTPUT@' + ], +) diff --git a/test cases/failing/123 subproject sandbox violation/meson_options.txt b/test cases/failing/123 subproject sandbox violation/meson_options.txt new file mode 100644 index 0000000..e7b782d --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/meson_options.txt @@ -0,0 +1 @@ +option('failmode', type: 'combo', choices: ['parent-dir', 'not-installed', 'root-subdir']) diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/file.txt b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/file.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/file.txt diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/meson.build b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/meson.build new file mode 100644 index 0000000..bd33bf3 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/meson.build @@ -0,0 +1,4 @@ +project('subproj1') + +install_data('file.txt') +subdir('nested') diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/nested/meson.build b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/nested/meson.build new file mode 100644 index 0000000..038c139 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj1/nested/meson.build @@ -0,0 +1,5 @@ +d = declare_dependency( + variables: [ + 'dir=@0@'.format(meson.current_source_dir()), + ] +) diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/file.txt b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/file.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/file.txt diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/meson.build b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/meson.build new file mode 100644 index 0000000..a6032aa --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/meson.build @@ -0,0 +1,7 @@ +project('subproj1') + +d = declare_dependency( + variables: [ + 'dir=@0@'.format(meson.current_source_dir()), + ] +) diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/nested/meson.build b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/nested/meson.build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj2/nested/meson.build diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/file.txt b/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/file.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/file.txt diff --git a/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/meson.build b/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/meson.build new file mode 100644 index 0000000..c4fa64c --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/subprojects/subproj3/meson.build @@ -0,0 +1,3 @@ +project('subproj2') + +install_data('file.txt') diff --git a/test cases/failing/123 subproject sandbox violation/test.json b/test cases/failing/123 subproject sandbox violation/test.json new file mode 100644 index 0000000..88ea620 --- /dev/null +++ b/test cases/failing/123 subproject sandbox violation/test.json @@ -0,0 +1,16 @@ +{ + "matrix": { + "options": { + "failmode": [ + { "val": "not-installed" }, + { "val": "parent-dir" }, + { "val": "root-subdir" } + ] + } + }, + "stdout": [ + { + "line": "test cases/failing/123 subproject sandbox violation/meson.build:24:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject." + } + ] +} diff --git a/test cases/unit/63 cmake parser/meson.build b/test cases/unit/63 cmake parser/meson.build index 061bab0..472561d 100644 --- a/test cases/unit/63 cmake parser/meson.build +++ b/test cases/unit/63 cmake parser/meson.build @@ -12,8 +12,8 @@ assert(dep.get_variable(cmake : 'VAR_WITH_SPACES_PS') == 'With Spaces', 'set(PAR assert(dep.get_variable(cmake : 'VAR_THAT_IS_UNSET', default_value : 'sentinal') == 'sentinal', 'set() to unset is incorrect') assert(dep.get_variable(cmake : 'CACHED_STRING_NS') == 'foo', 'set(CACHED) without spaces is incorrect') assert(dep.get_variable(cmake : 'CACHED_STRING_WS') == 'foo bar', 'set(CACHED STRING) with spaces is incorrect') -assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_NS') == ['foo', 'bar'], 'set(CACHED STRING) without spaces is incorrect') -assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_WS') == ['foo', 'foo bar', 'bar'], 'set(CACHED STRING[]) with spaces is incorrect') +assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_NS') == 'foo;bar', 'set(CACHED STRING) without spaces is incorrect') +assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_WS') == 'foo;foo bar;bar', 'set(CACHED STRING[]) with spaces is incorrect') # We don't support this, so it should be unset. -assert(dep.get_variable(cmake : 'ENV{var}', default_value : 'sentinal') == 'sentinal', 'set(ENV) should be ignored')
\ No newline at end of file +assert(dep.get_variable(cmake : 'ENV{var}', default_value : 'sentinal') == 'sentinal', 'set(ENV) should be ignored') |