dependencies: allow get_variable to expose files from subprojects

There are somewhat common, reasonable and legitimate use cases for a
dependency to provide data files installed to /usr which are used as
command inputs. When getting a dependency from a subproject, however,
the attempt to directly construct an input file from a subproject
results in a sandbox violation. This means not all dependencies can be
wrapped as a subproject.

One example is wayland-protocols XML files which get scanned and used to
produce C source files.

Teach Meson to recognize when a string path is the result of fetching a
dep.get_variable(), and special case this to be exempt from subproject
violations.

A requirement of this is that the file must be installed by
install_data() or install_subdir() because otherwise it is not actually
representative of what a pkg-config dependency would provide.
pull/10039/head
Eli Schwartz 3 years ago
parent b55349c2e9
commit 0e3ed2f655
No known key found for this signature in database
GPG Key ID: CEB167EFB5722BD6
  1. 28
      mesonbuild/interpreter/interpreter.py
  2. 5
      mesonbuild/interpreter/interpreterobjects.py
  3. 8
      mesonbuild/interpreter/primitives/__init__.py
  4. 12
      mesonbuild/interpreter/primitives/string.py
  5. 13
      test cases/common/251 subproject dependency variables/meson.build
  6. 26
      test cases/common/251 subproject dependency variables/subprojects/subfiles/meson.build
  7. 1
      test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir/foo.c
  8. 1
      test cases/common/251 subproject dependency variables/subprojects/subfiles/subdir2/foo.c
  9. 7
      test cases/common/251 subproject dependency variables/test.json
  10. 25
      test cases/failing/123 subproject sandbox violation/meson.build
  11. 1
      test cases/failing/123 subproject sandbox violation/meson_options.txt
  12. 0
      test cases/failing/123 subproject sandbox violation/subprojects/subproj1/file.txt
  13. 4
      test cases/failing/123 subproject sandbox violation/subprojects/subproj1/meson.build
  14. 5
      test cases/failing/123 subproject sandbox violation/subprojects/subproj1/nested/meson.build
  15. 0
      test cases/failing/123 subproject sandbox violation/subprojects/subproj2/file.txt
  16. 7
      test cases/failing/123 subproject sandbox violation/subprojects/subproj2/meson.build
  17. 0
      test cases/failing/123 subproject sandbox violation/subprojects/subproj2/nested/meson.build
  18. 15
      test cases/failing/123 subproject sandbox violation/test.json

@ -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,
@ -2716,7 +2717,12 @@ 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]
ret = os.path.join(*parts).replace('\\', '/')
if isinstance(parts[0], P_OBJ.DependencyVariableString):
return P_OBJ.DependencyVariableString(ret)
else:
return ret
def run(self) -> None:
super().run()
@ -2759,6 +2765,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 norm.is_absolute() and '..' not in norm.parts and validate_installable_file(norm):
return
norm = Path(srcdir, subdir, fname).resolve()
if os.path.isdir(norm):
inputtype = 'directory'

@ -21,6 +21,7 @@ from ..interpreterbase import (
typed_pos_args, typed_kwargs, typed_operator,
noArgsFlattening, noPosargs, noKwargs, unholder_return,
flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
from ..interpreter.primitives import DependencyVariableString
from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW
from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram
@ -483,14 +484,14 @@ class DependencyHolder(ObjectHolder[Dependency]):
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)
return self.held_object.get_variable(
return DependencyVariableString(self.held_object.get_variable(
cmake=kwargs['cmake'] or default_varname,
pkgconfig=kwargs['pkgconfig'] or default_varname,
configtool=kwargs['configtool'] or default_varname,
internal=kwargs['internal'] or default_varname,
default_value=kwargs['default_value'],
pkgconfig_define=kwargs['pkgconfig_define'],
)
))
@FeatureNew('dependency.include_type', '0.52.0')
@noPosargs

@ -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
)

@ -179,3 +179,15 @@ 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) -> DependencyVariableString:
return DependencyVariableString(super().op_div(other))

@ -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'
)

@ -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,
)

@ -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" }
]
}

@ -0,0 +1,25 @@
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'
if get_option('failmode') == 'parent-dir'
mustfail = sub1_mustfail
elif get_option('failmode') == 'not-installed'
mustfail = sub2_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@'
],
)

@ -0,0 +1 @@
option('failmode', type: 'combo', choices: ['parent-dir', 'not-installed'])

@ -0,0 +1,4 @@
project('subproj1')
install_data('file.txt')
subdir('nested')

@ -0,0 +1,5 @@
d = declare_dependency(
variables: [
'dir=@0@'.format(meson.current_source_dir()),
]
)

@ -0,0 +1,7 @@
project('subproj1')
d = declare_dependency(
variables: [
'dir=@0@'.format(meson.current_source_dir()),
]
)

@ -0,0 +1,15 @@
{
"matrix": {
"options": {
"failmode": [
{ "val": "not-installed" },
{ "val": "parent-dir" }
]
}
},
"stdout": [
{
"line": "test cases/failing/123 subproject sandbox violation/meson.build:19:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject."
}
]
}
Loading…
Cancel
Save