Fix consistency in variables kwarg

Share common code to extract the `variables` kwarg in
declare_dependency() and pkg.generate().
This commit is contained in:
Xavier Claessens 2020-10-15 09:56:08 -04:00 committed by Jussi Pakkanen
parent 2e80c52129
commit bcf369ea3c
11 changed files with 58 additions and 31 deletions

View File

@ -49,7 +49,8 @@ keyword arguments.
generated file. The strings must be in the form `name=value` and may generated file. The strings must be in the form `name=value` and may
reference other pkgconfig variables, reference other pkgconfig variables,
e.g. `datadir=${prefix}/share`. The names `prefix`, `libdir` and e.g. `datadir=${prefix}/share`. The names `prefix`, `libdir` and
`includedir` are reserved and may not be used. `includedir` are reserved and may not be used. *Since 0.56.0* it can also be a
dictionary.
- `version` a string describing the version of this library, used to set the - `version` a string describing the version of this library, used to set the
`Version:` field. (*since 0.46.0*) Defaults to the project version if unspecified. `Version:` field. (*since 0.46.0*) Defaults to the project version if unspecified.
- `d_module_versions` a list of module version flags used when compiling - `d_module_versions` a list of module version flags used when compiling

View File

@ -427,7 +427,7 @@ keyword arguments:
- `version`: the version of this dependency, such as `1.2.3` - `version`: the version of this dependency, such as `1.2.3`
- `variables` *(since 0.54.0)*: a dictionary of arbitrary strings, this is meant to be used - `variables` *(since 0.54.0)*: a dictionary of arbitrary strings, this is meant to be used
in subprojects where special variables would be provided via cmake or in subprojects where special variables would be provided via cmake or
pkg-config. pkg-config. *since 0.56.0* it can also be a list of `'key=value'` strings.
### dependency() ### dependency()

View File

@ -0,0 +1,12 @@
## Consistency between `declare_dependency()` and `pkgconfig.generate()` variables
The `variables` keyword argument in `declare_dependency()` used to only support
dictionary and `pkgconfig.generate()` only list of strings. They now both support
dictionary and list of strings in the format `'name=value'`. This makes easier
to share a common set of variables for both:
```meson
vars = {'foo': 'bar'}
dep = declare_dependency(..., variables: vars)
pkg.generate(..., variables: vars)
```

View File

@ -2680,6 +2680,32 @@ class Interpreter(InterpreterBase):
def func_files(self, node, args, kwargs): def func_files(self, node, args, kwargs):
return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args] return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args]
# Used by declare_dependency() and pkgconfig.generate()
def extract_variables(self, kwargs, argname='variables', list_new=False, dict_new=False):
variables = kwargs.get(argname, {})
if isinstance(variables, dict):
if dict_new and variables:
FeatureNew.single_use('variables as dictionary', '0.56.0', self.subproject)
else:
varlist = mesonlib.stringlistify(variables)
if list_new:
FeatureNew.single_use('variables as list of strings', '0.56.0', self.subproject)
variables = {}
for v in varlist:
try:
(key, value) = v.split('=', 1)
except ValueError:
raise InterpreterException('Variable {!r} must have a value separated by equals sign.'.format(v))
variables[key.strip()] = value.strip()
for k, v in variables.items():
if not k or not v:
raise InterpreterException('Empty variable name or value')
if any(c.isspace() for c in k):
raise InterpreterException('Invalid whitespace in variable name "{}"'.format(k))
if not isinstance(v, str):
raise InterpreterException('variables values must be strings.')
return variables
@FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole']) @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole'])
@FeatureNewKwargs('declare_dependency', '0.54.0', ['variables']) @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables'])
@permittedKwargs(permitted_kwargs['declare_dependency']) @permittedKwargs(permitted_kwargs['declare_dependency'])
@ -2696,12 +2722,7 @@ class Interpreter(InterpreterBase):
deps = unholder(extract_as_list(kwargs, 'dependencies')) deps = unholder(extract_as_list(kwargs, 'dependencies'))
compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) compile_args = mesonlib.stringlistify(kwargs.get('compile_args', []))
link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) link_args = mesonlib.stringlistify(kwargs.get('link_args', []))
variables = kwargs.get('variables', {}) variables = self.extract_variables(kwargs, list_new=True)
if not isinstance(variables, dict):
raise InterpreterException('variables must be a dict.')
if not all(isinstance(v, str) for v in variables.values()):
# Because that is how they will come from pkg-config and cmake
raise InterpreterException('variables values be strings.')
final_deps = [] final_deps = []
for d in deps: for d in deps:
try: try:

View File

@ -513,31 +513,17 @@ class PkgConfigModule(ExtensionModule):
deps.remove_dups() deps.remove_dups()
def parse_variable_list(stringlist): def parse_variable_list(vardict):
reserved = ['prefix', 'libdir', 'includedir'] reserved = ['prefix', 'libdir', 'includedir']
variables = [] variables = []
for var in stringlist: for name, value in vardict.items():
# foo=bar=baz is ('foo', 'bar=baz')
l = var.split('=', 1)
if len(l) < 2:
raise mesonlib.MesonException('Invalid variable "{}". Variables must be in \'name=value\' format'.format(var))
name, value = l[0].strip(), l[1].strip()
if not name or not value:
raise mesonlib.MesonException('Invalid variable "{}". Variables must be in \'name=value\' format'.format(var))
# Variable names must not contain whitespaces
if any(c.isspace() for c in name):
raise mesonlib.MesonException('Invalid whitespace in assignment "{}"'.format(var))
if name in reserved: if name in reserved:
raise mesonlib.MesonException('Variable "{}" is reserved'.format(name)) raise mesonlib.MesonException('Variable "{}" is reserved'.format(name))
variables.append((name, value)) variables.append((name, value))
return variables return variables
variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('variables', []))) variables = self.interpreter.extract_variables(kwargs, dict_new=True)
variables = parse_variable_list(variables)
pcfile = filebase + '.pc' pcfile = filebase + '.pc'
pkgroot = kwargs.get('install_dir', default_install_dir) pkgroot = kwargs.get('install_dir', default_install_dir)
@ -552,7 +538,9 @@ class PkgConfigModule(ExtensionModule):
version, pcfile, conflicts, variables, version, pcfile, conflicts, variables,
False, dataonly) False, dataonly)
res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot) res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot)
variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('uninstalled_variables', []))) variables = self.interpreter.extract_variables(kwargs, argname='uninstalled_variables', dict_new=True)
variables = parse_variable_list(variables)
pcfile = filebase + '-uninstalled.pc' pcfile = filebase + '-uninstalled.pc'
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables, version, pcfile, conflicts, variables,

View File

@ -6040,6 +6040,7 @@ class LinuxlikeTests(BasePlatformTests):
self.assertTrue(libhello_nolib.found()) self.assertTrue(libhello_nolib.found())
self.assertEqual(libhello_nolib.get_link_args(), []) self.assertEqual(libhello_nolib.get_link_args(), [])
self.assertEqual(libhello_nolib.get_compile_args(), []) self.assertEqual(libhello_nolib.get_compile_args(), [])
self.assertEqual(libhello_nolib.get_pkgconfig_variable('foo', {}), 'bar')
def test_pkgconfig_gen_deps(self): def test_pkgconfig_gen_deps(self):
''' '''

View File

@ -59,3 +59,6 @@ idep = declare_dependency()
assert(idep.get_variable(pkgconfig : 'foo', cmake : 'foo', configtool : 'foo', assert(idep.get_variable(pkgconfig : 'foo', cmake : 'foo', configtool : 'foo',
default_value : default) == default, default_value : default) == default,
'something went wrong with an InternalDependency with no variables.') 'something went wrong with an InternalDependency with no variables.')
idep = declare_dependency(variables : ['foo=value'])
assert(idep.get_variable(internal: 'foo') == 'value')

View File

@ -64,7 +64,8 @@ pkgg.generate(
name : 'libhello_nolib', name : 'libhello_nolib',
description : 'A minimalistic pkgconfig file.', description : 'A minimalistic pkgconfig file.',
version : libver, version : libver,
dataonly: true dataonly: true,
variables : {'foo': 'bar'},
) )
# Regression test for 2 cases: # Regression test for 2 cases:

View File

@ -1,7 +1,7 @@
{ {
"stdout": [ "stdout": [
{ {
"line": "test cases/failing/47 pkgconfig variables zero length/meson.build:8:5: ERROR: Invalid variable \"=value\". Variables must be in 'name=value' format" "line": "test cases/failing/47 pkgconfig variables zero length/meson.build:8:5: ERROR: Empty variable name or value"
} }
] ]
} }

View File

@ -1,7 +1,7 @@
{ {
"stdout": [ "stdout": [
{ {
"line": "test cases/failing/48 pkgconfig variables zero length value/meson.build:8:5: ERROR: Invalid variable \"key=\". Variables must be in 'name=value' format" "line": "test cases/failing/48 pkgconfig variables zero length value/meson.build:8:5: ERROR: Empty variable name or value"
} }
] ]
} }

View File

@ -1,7 +1,7 @@
{ {
"stdout": [ "stdout": [
{ {
"line": "test cases/failing/49 pkgconfig variables not key value/meson.build:8:5: ERROR: Invalid variable \"this_should_be_key_value\". Variables must be in 'name=value' format" "line": "test cases/failing/49 pkgconfig variables not key value/meson.build:8:5: ERROR: Variable 'this_should_be_key_value' must have a value separated by equals sign."
} }
] ]
} }