aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2020-10-14 19:40:34 +0300
committerGitHub <noreply@github.com>2020-10-14 19:40:34 +0300
commit30d78f39812a0585a27e24ab44ef4e9da1f12574 (patch)
tree437f8b5d8e63e900f5b41923dcd33fc3950fe4f9
parent8b8a610ea4ed632759b831606723d1cafa8920d7 (diff)
parent3a0182378612bc764667328805b11bc92db696c2 (diff)
downloadmeson-30d78f39812a0585a27e24ab44ef4e9da1f12574.zip
meson-30d78f39812a0585a27e24ab44ef4e9da1f12574.tar.gz
meson-30d78f39812a0585a27e24ab44ef4e9da1f12574.tar.bz2
Merge pull request #6968 from xclaesse/auto-promote
Merge wraps from subprojects into wraps from main project
-rw-r--r--docs/markdown/Builtin-options.md2
-rw-r--r--docs/markdown/Subprojects.md7
-rw-r--r--docs/markdown/Using-wraptool.md21
-rw-r--r--docs/markdown/snippets/subsubproject.md13
-rw-r--r--mesonbuild/interpreter.py277
-rwxr-xr-xmesonbuild/msubprojects.py2
-rw-r--r--mesonbuild/wrap/__init__.py2
-rw-r--r--mesonbuild/wrap/wrap.py86
-rwxr-xr-xrun_unittests.py66
-rw-r--r--test cases/common/102 subproject subdir/meson.build11
-rw-r--r--test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/meson.build3
-rw-r--r--test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/packagefiles/subsubsub-1.0.zipbin0 -> 455 bytes
-rw-r--r--test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap4
-rw-r--r--test cases/failing/16 extract from subproject/test.json2
14 files changed, 309 insertions, 187 deletions
diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md
index febcc8a..afc1cde 100644
--- a/docs/markdown/Builtin-options.md
+++ b/docs/markdown/Builtin-options.md
@@ -79,7 +79,7 @@ for details.
| unity_size {>=2} | 4 | Unity file block size | no | no |
| warning_level {0, 1, 2, 3} | 1 | Set the warning level. From 0 = none to 3 = highest | no | yes |
| werror | false | Treat warnings as errors | no | yes |
-| wrap_mode {default, nofallback,<br>nodownload, forcefallback} | default | Wrap mode to use | no | no |
+| wrap_mode {default, nofallback,<br>nodownload, forcefallback, nopromote} | default | Wrap mode to use | no | no |
| force_fallback_for | [] | Force fallback for those dependencies | no | no |
<a name="build-type-options"></a>
diff --git a/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md
index 7e17afa..e8adc96 100644
--- a/docs/markdown/Subprojects.md
+++ b/docs/markdown/Subprojects.md
@@ -258,6 +258,13 @@ the following command-line options:
`glib-2.0` must also be forced to fallback, in this case with
`--force-fallback-for=glib,gsteamer`.
+* **--wrap-mode=nopromote**
+
+ *Since 0.56.0* Meson will automatically use wrap files found in subprojects
+ and copy them into the main project. That new behavior can be disabled by
+ passing `--wrap-mode=nopromote`. In that case only wraps found in the main
+ project will be used.
+
## `meson subprojects` command
*Since 0.49.0*
diff --git a/docs/markdown/Using-wraptool.md b/docs/markdown/Using-wraptool.md
index f6023e8..ffa8309 100644
--- a/docs/markdown/Using-wraptool.md
+++ b/docs/markdown/Using-wraptool.md
@@ -76,24 +76,3 @@ straightforward:
Wraptool can do other things besides these. Documentation for these
can be found in the command line help, which can be accessed by
`meson wrap --help`.
-
-## Promoting dependencies
-
-Meson will only search for subprojects from the top level
-`subprojects` directory. If you have subprojects that themselves have
-subprojects, you must transfer them to the top level. This can be done
-by going to your source root and issuing a promotion command.
-
- meson wrap promote projname
-
-This will cause Meson to go through your entire project tree, find an
-embedded subproject and copy it to the top level.
-
-If there are multiple embedded copies of a subproject, Meson will not
-try to guess which one you want. Instead it will print all the
-possibilities. You can then manually select which one to promote by
-writing it out fully.
-
- meson wrap promote subprojects/s1/subprojects/projname
-
-This functionality was added in Meson release 0.45.0.
diff --git a/docs/markdown/snippets/subsubproject.md b/docs/markdown/snippets/subsubproject.md
new file mode 100644
index 0000000..77f4a0d
--- /dev/null
+++ b/docs/markdown/snippets/subsubproject.md
@@ -0,0 +1,13 @@
+## Wraps from subprojects are automatically promoted
+
+It is not required to promote wrap files for subprojects into the main project
+any more. When configuring a subproject, meson will look for any wrap file or
+directory in the subproject's `subprojects/` directory and add them into the
+global list of available subprojects, to be used by any future `subproject()`
+call or `dependency()` fallback. If a subproject with the same name already exists,
+the new wrap file or directory is ignored. That means that the main project can
+always override any subproject's wrap files by providing their own, it also means
+the ordering in which subprojects are configured matters, if 2 subprojects provide
+foo.wrap only the one from the first subproject to be configured will be used.
+
+This new behavior can be disabled by passing `--wrap-mode=nopromote`.
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index f25a6f3..ad6f04e 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -1010,15 +1010,14 @@ class Test(InterpreterObject):
class SubprojectHolder(InterpreterObject, ObjectHolder):
- def __init__(self, subinterpreter, subproject_dir, name, warnings=0, disabled_feature=None,
+ def __init__(self, subinterpreter, subdir, warnings=0, disabled_feature=None,
exception=None):
InterpreterObject.__init__(self)
ObjectHolder.__init__(self, subinterpreter)
- self.name = name
self.warnings = warnings
self.disabled_feature = disabled_feature
self.exception = exception
- self.subproject_dir = subproject_dir
+ self.subdir = PurePath(subdir).as_posix()
self.methods.update({'get_variable': self.get_variable_method,
'found': self.found_method,
})
@@ -1037,8 +1036,7 @@ class SubprojectHolder(InterpreterObject, ObjectHolder):
if len(args) < 1 or len(args) > 2:
raise InterpreterException('Get_variable takes one or two arguments.')
if not self.found():
- raise InterpreterException('Subproject "%s/%s" disabled can\'t get_variable on it.' % (
- self.subproject_dir, self.name))
+ raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir))
varname = args[0]
if not isinstance(varname, str):
raise InterpreterException('Get_variable first argument must be a string.')
@@ -2171,7 +2169,7 @@ class MesonMain(InterpreterObject):
self.interpreter.environment.build_dir)
if not os.path.exists(abspath):
raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
- exe = OverrideProgram(abspath)
+ exe = OverrideProgram(name, abspath)
if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)):
raise InterpreterException('Second argument must be an external program or executable.')
self.interpreter.add_find_program_override(name, exe)
@@ -2840,84 +2838,75 @@ external dependencies (including libraries) must go to "dependencies".''')
def func_subproject(self, nodes, args, kwargs):
if len(args) != 1:
raise InterpreterException('Subproject takes exactly one argument')
- dirname = args[0]
- return self.do_subproject(dirname, 'meson', kwargs)
+ subp_name = args[0]
+ return self.do_subproject(subp_name, 'meson', kwargs)
- def disabled_subproject(self, dirname, disabled_feature=None, exception=None):
- sub = SubprojectHolder(None, self.subproject_dir, dirname,
+ def disabled_subproject(self, subp_name, disabled_feature=None, exception=None):
+ sub = SubprojectHolder(None, os.path.join(self.subproject_dir, subp_name),
disabled_feature=disabled_feature, exception=exception)
- self.subprojects[dirname] = sub
+ self.subprojects[subp_name] = sub
return sub
- def get_subproject(self, dirname):
- sub = self.subprojects.get(dirname)
+ def get_subproject(self, subp_name):
+ sub = self.subprojects.get(subp_name)
if sub and sub.found():
return sub
return None
- def do_subproject(self, dirname: str, method: str, kwargs):
+ def do_subproject(self, subp_name: str, method: str, kwargs):
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
- mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
- return self.disabled_subproject(dirname, disabled_feature=feature)
+ mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
+ return self.disabled_subproject(subp_name, disabled_feature=feature)
default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
default_options = coredata.create_options_dict(default_options)
- if dirname == '':
- raise InterpreterException('Subproject dir name must not be empty.')
- if dirname[0] == '.':
- raise InterpreterException('Subproject dir name must not start with a period.')
- if '..' in dirname:
+ if subp_name == '':
+ raise InterpreterException('Subproject name must not be empty.')
+ if subp_name[0] == '.':
+ raise InterpreterException('Subproject name must not start with a period.')
+ if '..' in subp_name:
raise InterpreterException('Subproject name must not contain a ".." path segment.')
- if os.path.isabs(dirname):
+ if os.path.isabs(subp_name):
raise InterpreterException('Subproject name must not be an absolute path.')
- if has_path_sep(dirname):
+ if has_path_sep(subp_name):
mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.',
location=self.current_node)
- if dirname in self.subproject_stack:
- fullstack = self.subproject_stack + [dirname]
+ if subp_name in self.subproject_stack:
+ fullstack = self.subproject_stack + [subp_name]
incpath = ' => '.join(fullstack)
raise InvalidCode('Recursive include of subprojects: %s.' % incpath)
- if dirname in self.subprojects:
- subproject = self.subprojects[dirname]
+ if subp_name in self.subprojects:
+ subproject = self.subprojects[subp_name]
if required and not subproject.found():
- raise InterpreterException('Subproject "%s/%s" required but not found.' % (
- self.subproject_dir, dirname))
+ raise InterpreterException('Subproject "%s" required but not found.' % (subproject.subdir))
return subproject
r = self.environment.wrap_resolver
try:
- resolved = r.resolve(dirname, method, self.subproject)
+ subdir = r.resolve(subp_name, method, self.subproject)
except wrap.WrapException as e:
- subprojdir = os.path.join(self.subproject_dir, r.directory)
- if isinstance(e, wrap.WrapNotFoundException):
- # if the reason subproject execution failed was because
- # the directory doesn't exist, try to give some helpful
- # advice if it's a nested subproject that needs
- # promotion...
- self.print_nested_info(dirname)
if not required:
mlog.log(e)
- mlog.log('Subproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)')
- return self.disabled_subproject(dirname, exception=e)
+ mlog.log('Subproject ', mlog.bold(subp_name), 'is buildable:', mlog.red('NO'), '(disabling)')
+ return self.disabled_subproject(subp_name, exception=e)
raise e
- subdir = os.path.join(self.subproject_dir, 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
mlog.log()
with mlog.nested():
- mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n')
+ mlog.log('Executing subproject', mlog.bold(subp_name), 'method', mlog.bold(method), '\n')
try:
if method == 'meson':
- return self._do_subproject_meson(dirname, subdir, default_options, kwargs)
+ return self._do_subproject_meson(subp_name, subdir, default_options, kwargs)
elif method == 'cmake':
- return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, kwargs)
+ return self._do_subproject_cmake(subp_name, subdir, subdir_abs, default_options, kwargs)
else:
- raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname))
+ raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, subp_name))
# Invalid code is always an error
except InvalidCode:
raise
@@ -2927,18 +2916,18 @@ external dependencies (including libraries) must go to "dependencies".''')
# Suppress the 'ERROR:' prefix because this exception is not
# fatal and VS CI treat any logs with "ERROR:" as fatal.
mlog.exception(e, prefix=mlog.yellow('Exception:'))
- mlog.log('\nSubproject', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)')
- return self.disabled_subproject(dirname, exception=e)
+ mlog.log('\nSubproject', mlog.bold(subdir), 'is buildable:', mlog.red('NO'), '(disabling)')
+ return self.disabled_subproject(subp_name, exception=e)
raise e
- def _do_subproject_meson(self, dirname, subdir, default_options, kwargs, ast=None, build_def_files=None):
+ def _do_subproject_meson(self, subp_name, subdir, default_options, kwargs, ast=None, build_def_files=None):
with mlog.nested():
new_build = self.build.copy()
- subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
+ subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir,
self.modules, default_options, ast=ast)
subi.subprojects = self.subprojects
- subi.subproject_stack = self.subproject_stack + [dirname]
+ subi.subproject_stack = self.subproject_stack + [subp_name]
current_active = self.active_projectname
current_warnings_counter = mlog.log_warnings_counter
mlog.log_warnings_counter = 0
@@ -2946,7 +2935,7 @@ external dependencies (including libraries) must go to "dependencies".''')
subi_warnings = mlog.log_warnings_counter
mlog.log_warnings_counter = current_warnings_counter
- mlog.log('Subproject', mlog.bold(dirname), 'finished.')
+ mlog.log('Subproject', mlog.bold(subp_name), 'finished.')
mlog.log()
@@ -2954,22 +2943,21 @@ external dependencies (including libraries) must go to "dependencies".''')
pv = subi.project_version
wanted = kwargs['version']
if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
- raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted))
+ raise InterpreterException('Subproject %s version is %s but %s required.' % (subp_name, pv, wanted))
self.active_projectname = current_active
self.subprojects.update(subi.subprojects)
- self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname,
- warnings=subi_warnings)
+ self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings)
# Duplicates are possible when subproject uses files from project root
if build_def_files:
self.build_def_files = list(set(self.build_def_files + build_def_files))
else:
self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
self.build.merge(subi.build)
- self.build.subprojects[dirname] = subi.project_version
+ self.build.subprojects[subp_name] = subi.project_version
self.summary.update(subi.summary)
- return self.subprojects[dirname]
+ return self.subprojects[subp_name]
- def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwargs):
+ def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, kwargs):
with mlog.nested():
new_build = self.build.copy()
prefix = self.coredata.builtins['prefix'].value
@@ -3007,7 +2995,7 @@ external dependencies (including libraries) must go to "dependencies".''')
mlog.cmd_ci_include(meson_filename)
mlog.log()
- result = self._do_subproject_meson(dirname, subdir, default_options, kwargs, ast, cm_int.bs_files)
+ result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files)
result.cm_interpreter = cm_int
mlog.log()
@@ -3157,8 +3145,11 @@ external dependencies (including libraries) must go to "dependencies".''')
'license': proj_license}
if self.subproject in self.build.projects:
raise InvalidCode('Second call to project().')
- if not self.is_subproject() and 'subproject_dir' in kwargs:
- spdirname = kwargs['subproject_dir']
+
+ # spdirname is the subproject_dir for this project, relative to self.subdir.
+ # self.subproject_dir is the subproject_dir for the main project, relative to top source dir.
+ spdirname = kwargs.get('subproject_dir')
+ if spdirname:
if not isinstance(spdirname, str):
raise InterpreterException('Subproject_dir must be a string')
if os.path.isabs(spdirname):
@@ -3167,13 +3158,21 @@ external dependencies (including libraries) must go to "dependencies".''')
raise InterpreterException('Subproject_dir must not begin with a period.')
if '..' in spdirname:
raise InterpreterException('Subproject_dir must not contain a ".." segment.')
- self.subproject_dir = spdirname
-
+ if not self.is_subproject():
+ self.subproject_dir = spdirname
+ else:
+ spdirname = 'subprojects'
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)
+
+ # Load wrap files from this (sub)project.
+ wrap_mode = self.coredata.get_builtin_option('wrap_mode')
+ if not self.is_subproject() or wrap_mode != WrapMode.nopromote:
+ subdir = os.path.join(self.subdir, spdirname)
+ r = wrap.Resolver(self.environment.get_source_dir(), subdir, wrap_mode)
+ if self.is_subproject():
+ self.environment.wrap_resolver.merge_wraps(r)
+ else:
+ self.environment.wrap_resolver = r
self.build.projects[self.subproject] = proj_name
mlog.log('Project name:', mlog.bold(proj_name))
@@ -3376,7 +3375,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return ExternalProgramHolder(prog, self.subproject)
return None
- def program_from_system(self, args, search_dirs, silent=False):
+ def program_from_system(self, args, search_dirs, extra_info):
# Search for scripts relative to current subdir.
# Do not cache found programs because find_program('foobar')
# might give different results when run from different source dirs.
@@ -3399,9 +3398,10 @@ external dependencies (including libraries) must go to "dependencies".''')
'files, not {!r}'.format(exename))
extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
extra_search_dirs=extra_search_dirs,
- silent=silent)
+ silent=True)
progobj = ExternalProgramHolder(extprog, self.subproject)
if progobj.found():
+ extra_info.append('({})'.format(' '.join(progobj.get_command())))
return progobj
def program_from_overrides(self, command_names, extra_info):
@@ -3459,7 +3459,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if not is_found:
mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(version), 'but need:',
- mlog.bold(', '.join(["'{}'".format(e) for e in not_found])))
+ mlog.bold(', '.join(["'{}'".format(e) for e in not_found])), *extra_info)
if required:
m = 'Invalid version of program, need {!r} {!r} found {!r}.'
raise InterpreterException(m.format(progobj.get_name(), not_found, version))
@@ -3485,7 +3485,7 @@ external dependencies (including libraries) must go to "dependencies".''')
progobj = self.program_from_file_for(for_machine, args)
if progobj is None:
- progobj = self.program_from_system(args, search_dirs, silent=True)
+ progobj = self.program_from_system(args, search_dirs, extra_info)
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
@@ -3582,8 +3582,8 @@ external dependencies (including libraries) must go to "dependencies".''')
def notfound_dependency(self):
return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
- def verify_fallback_consistency(self, dirname, varname, cached_dep):
- subi = self.get_subproject(dirname)
+ def verify_fallback_consistency(self, subp_name, varname, cached_dep):
+ subi = self.get_subproject(subp_name)
if not cached_dep or not varname or not subi or not cached_dep.found():
return
dep = subi.get_variable_method([varname], {})
@@ -3591,63 +3591,77 @@ external dependencies (including libraries) must go to "dependencies".''')
m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}'
raise DependencyException(m.format(varname))
- def get_subproject_dep(self, name, display_name, dirname, varname, kwargs):
+ def get_subproject_dep(self, name, display_name, subp_name, varname, kwargs):
required = kwargs.get('required', True)
wanted = mesonlib.stringlistify(kwargs.get('version', []))
- subproj_path = os.path.join(self.subproject_dir, dirname)
dep = self.notfound_dependency()
+
+ # Verify the subproject is found
+ subproject = self.subprojects.get(subp_name)
+ if not subproject or not subproject.found():
+ mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
+ mlog.blue('(subproject failed to configure)'))
+ if required:
+ m = 'Subproject {} failed to configure for dependency {}'
+ raise DependencyException(m.format(subproject.subdir, display_name))
+ return dep
+
+ extra_info = []
try:
- subproject = self.subprojects[dirname]
+ # Check if the subproject overridden the dependency
_, cached_dep = self._find_cached_dep(name, display_name, kwargs)
- if varname is None:
- # Assuming the subproject overridden the dependency we want
- if cached_dep:
- if required and not cached_dep.found():
- m = 'Dependency {!r} is not satisfied'
- raise DependencyException(m.format(display_name))
- return DependencyHolder(cached_dep, self.subproject)
- else:
- 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], {})
+ if cached_dep:
+ if varname:
+ self.verify_fallback_consistency(subp_name, varname, cached_dep)
+ if required and not cached_dep.found():
+ m = 'Dependency {!r} is not satisfied'
+ raise DependencyException(m.format(display_name))
+ return DependencyHolder(cached_dep, self.subproject)
+ elif varname is None:
+ mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
+ if required:
+ m = 'Subproject {} did not override dependency {}'
+ raise DependencyException(m.format(subproject.subdir, display_name))
+ return self.notfound_dependency()
+ else:
+ # The subproject did not override the dependency, but we know the
+ # variable name to take.
+ dep = subproject.get_variable_method([varname], {})
except InvalidArguments:
- pass
+ # This is raised by get_variable_method() if varname does no exist
+ # in the subproject. Just add the reason in the not-found message
+ # that will be printed later.
+ extra_info.append(mlog.blue('(Variable {!r} not found)'.format(varname)))
if not isinstance(dep, DependencyHolder):
raise InvalidCode('Fetched variable {!r} in the subproject {!r} is '
- 'not a dependency object.'.format(varname, dirname))
+ 'not a dependency object.'.format(varname, subp_name))
if not dep.found():
+ mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), *extra_info)
if required:
raise DependencyException('Could not find dependency {} in subproject {}'
- ''.format(varname, dirname))
- # If the dependency is not required, don't raise an exception
- mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
- mlog.bold(subproj_path), 'found:', mlog.red('NO'))
+ ''.format(varname, subp_name))
return dep
found = dep.held_object.get_version()
if not self.check_version(wanted, found):
- if required:
- raise DependencyException('Version {} of subproject dependency {} already '
- 'cached, requested incompatible version {} for '
- 'dep {}'.format(found, dirname, wanted, display_name))
-
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
- mlog.bold(subproj_path), 'found:', mlog.red('NO'),
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found), 'but need:',
mlog.bold(', '.join(["'{}'".format(e) for e in wanted])))
+ if required:
+ raise DependencyException('Version {} of subproject dependency {} already '
+ 'cached, requested incompatible version {} for '
+ 'dep {}'.format(found, subp_name, wanted, display_name))
return self.notfound_dependency()
found = mlog.normal_cyan(found) if found else None
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
- mlog.bold(subproj_path), 'found:', mlog.green('YES'), found)
+ mlog.bold(subproject.subdir), 'found:', mlog.green('YES'), found)
return dep
def _handle_featurenew_dependencies(self, name):
@@ -3733,8 +3747,8 @@ external dependencies (including libraries) must go to "dependencies".''')
provider = self.environment.wrap_resolver.find_dep_provider(name)
if not provider and allow_fallback is True:
raise InvalidArguments('Fallback wrap or subproject not found for dependency \'%s\'' % name)
- dirname = mesonlib.listify(provider)[0]
- if provider and (allow_fallback is True or required or self.get_subproject(dirname)):
+ subp_name = mesonlib.listify(provider)[0]
+ if provider and (allow_fallback is True or required or self.get_subproject(subp_name)):
fallback = provider
if 'default_options' in kwargs and not fallback:
@@ -3752,8 +3766,8 @@ external dependencies (including libraries) must go to "dependencies".''')
identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs)
if cached_dep:
if fallback:
- dirname, varname = self.get_subproject_infos(fallback)
- self.verify_fallback_consistency(dirname, varname, cached_dep)
+ subp_name, varname = self.get_subproject_infos(fallback)
+ self.verify_fallback_consistency(subp_name, varname, cached_dep)
if required and not cached_dep.found():
m = 'Dependency {!r} was already checked and was not found'
raise DependencyException(m.format(display_name))
@@ -3762,16 +3776,16 @@ external dependencies (including libraries) must go to "dependencies".''')
if fallback:
# If the dependency has already been configured, possibly by
# a higher level project, try to use it first.
- dirname, varname = self.get_subproject_infos(fallback)
- if self.get_subproject(dirname):
- return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
+ subp_name, varname = self.get_subproject_infos(fallback)
+ if self.get_subproject(subp_name):
+ return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs)
wrap_mode = self.coredata.get_builtin_option('wrap_mode')
force_fallback_for = self.coredata.get_builtin_option('force_fallback_for')
force_fallback = (force_fallback or
wrap_mode == WrapMode.forcefallback or
name in force_fallback_for or
- dirname in force_fallback_for)
+ subp_name in force_fallback_for)
if name != '' and (not fallback or not force_fallback):
self._handle_featurenew_dependencies(name)
@@ -3798,23 +3812,6 @@ external dependencies (including libraries) must go to "dependencies".''')
def func_disabler(self, node, args, kwargs):
return Disabler()
- def print_nested_info(self, dependency_name):
- message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' +
- 'To use it in the current project, promote it by going in the project source\n'
- 'root and issuing']
- sprojs = mesonlib.detect_subprojects('subprojects', self.source_root)
- if dependency_name not in sprojs:
- return
- found = sprojs[dependency_name]
- if len(found) > 1:
- message.append('one of the following commands:')
- else:
- message.append('the following command:')
- command_templ = '\nmeson wrap promote {}'
- for l in found:
- message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:])))
- mlog.warning(*message, location=self.current_node)
-
def get_subproject_infos(self, fbinfo):
fbinfo = mesonlib.stringlistify(fbinfo)
if len(fbinfo) == 1:
@@ -3825,13 +3822,13 @@ external dependencies (including libraries) must go to "dependencies".''')
return fbinfo
def dependency_fallback(self, name, display_name, fallback, kwargs):
- dirname, varname = self.get_subproject_infos(fallback)
+ subp_name, varname = self.get_subproject_infos(fallback)
required = kwargs.get('required', True)
# Explicitly listed fallback preferences for specific subprojects
# take precedence over wrap-mode
force_fallback_for = self.coredata.get_builtin_option('force_fallback_for')
- if name in force_fallback_for or dirname in force_fallback_for:
+ if name in force_fallback_for or subp_name in force_fallback_for:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject')
elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback:
@@ -3852,8 +3849,8 @@ external dependencies (including libraries) must go to "dependencies".''')
'default_options': kwargs.get('default_options', []),
'required': required,
}
- self.do_subproject(dirname, 'meson', sp_kwargs)
- return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
+ self.do_subproject(subp_name, 'meson', sp_kwargs)
+ return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs)
@FeatureNewKwargs('executable', '0.42.0', ['implib'])
@permittedKwargs(permitted_kwargs['executable'])
@@ -4709,11 +4706,11 @@ This will probably not work.
Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value),
location=self.current_node)
- def evaluate_subproject_info(self, path_from_source_root, subproject_dirname):
+ def evaluate_subproject_info(self, path_from_source_root, subproject_dir):
depth = 0
subproj_name = ''
segs = PurePath(path_from_source_root).parts
- segs_spd = PurePath(subproject_dirname).parts
+ segs_spd = PurePath(subproject_dir).parts
while segs and segs[0] == segs_spd[0]:
if len(segs_spd) == 1:
subproj_name = segs[1]
@@ -4926,14 +4923,8 @@ This will become a hard error in the future.''', location=self.current_node)
# Only permit object extraction from the same subproject
def validate_extraction(self, buildtarget: InterpreterObject) -> None:
- if not self.subdir.startswith(self.subproject_dir):
- if buildtarget.subdir.startswith(self.subproject_dir):
- raise InterpreterException('Tried to extract objects from a subproject target.')
- else:
- if not buildtarget.subdir.startswith(self.subproject_dir):
- raise InterpreterException('Tried to extract objects from the main project from a subproject.')
- if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
- raise InterpreterException('Tried to extract objects from a different subproject.')
+ if self.subproject != buildtarget.subproject:
+ raise InterpreterException('Tried to extract objects from a different subproject.')
def is_subproject(self):
return self.subproject != ''
diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py
index b628a47..20639cb 100755
--- a/mesonbuild/msubprojects.py
+++ b/mesonbuild/msubprojects.py
@@ -364,7 +364,7 @@ def run(options):
if not os.path.isdir(subprojects_dir):
mlog.log('Directory', mlog.bold(src_dir), 'does not seem to have subprojects.')
return 0
- r = Resolver(subprojects_dir)
+ r = Resolver(src_dir, 'subprojects')
if options.subprojects:
wraps = [wrap for name, wrap in r.wraps.items() if name in options.subprojects]
else:
diff --git a/mesonbuild/wrap/__init__.py b/mesonbuild/wrap/__init__.py
index 1771146..653f42a 100644
--- a/mesonbuild/wrap/__init__.py
+++ b/mesonbuild/wrap/__init__.py
@@ -40,6 +40,7 @@ string_to_value = {'default': 1,
'nofallback': 2,
'nodownload': 3,
'forcefallback': 4,
+ 'nopromote': 5,
}
class WrapMode(Enum):
@@ -47,6 +48,7 @@ class WrapMode(Enum):
nofallback = 2
nodownload = 3
forcefallback = 4
+ nopromote = 5
def __str__(self) -> str:
return self.name
diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py
index a0a4801..a4e0b25 100644
--- a/mesonbuild/wrap/wrap.py
+++ b/mesonbuild/wrap/wrap.py
@@ -26,6 +26,7 @@ import subprocess
import sys
import configparser
import typing as T
+import textwrap
from .._pathlib import Path
from . import WrapMode
@@ -97,25 +98,47 @@ class PackageDefinition:
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
- if self.name.endswith('.wrap'):
- self.name = self.name[:-5]
+ self.has_wrap = self.basename.endswith('.wrap')
+ self.name = self.basename[:-5] if self.has_wrap else self.basename
+ self.directory = self.name
self.provided_deps[self.name] = None
- if fname.endswith('.wrap'):
- self.parse_wrap(fname)
+ if self.has_wrap:
+ self.parse_wrap()
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')
if self.type and self.type not in ALL_TYPES:
raise WrapException('Unknown wrap type {!r}'.format(self.type))
+ self.filesdir = os.path.join(os.path.dirname(self.filename), 'packagefiles')
- def parse_wrap(self, fname: str) -> None:
+ def parse_wrap(self) -> None:
try:
self.config = configparser.ConfigParser(interpolation=None)
- self.config.read(fname)
+ self.config.read(self.filename)
except configparser.Error:
raise WrapException('Failed to parse {}'.format(self.basename))
self.parse_wrap_section()
+ if self.type == 'redirect':
+ # [wrap-redirect] have a `filename` value pointing to the real wrap
+ # file we should parse instead. It must be relative to the current
+ # wrap file location and must be in the form foo/subprojects/bar.wrap.
+ dirname = Path(self.filename).parent
+ fname = Path(self.values['filename'])
+ for i, p in enumerate(fname.parts):
+ if i % 2 == 0:
+ if p == '..':
+ raise WrapException('wrap-redirect filename cannot contain ".."')
+ else:
+ if p != 'subprojects':
+ raise WrapException('wrap-redirect filename must be in the form foo/subprojects/bar.wrap')
+ if fname.suffix != '.wrap':
+ raise WrapException('wrap-redirect filename must be a .wrap file')
+ fname = dirname / fname
+ if not fname.is_file():
+ raise WrapException('wrap-redirect filename does not exist')
+ self.filename = str(fname)
+ self.parse_wrap()
+ return
self.parse_provide_section()
def parse_wrap_section(self) -> None:
@@ -164,11 +187,12 @@ def get_directory(subdir_root: str, packagename: str) -> str:
return packagename
class Resolver:
- def __init__(self, subdir_root: str, wrap_mode: WrapMode = WrapMode.default) -> None:
+ def __init__(self, source_dir: str, subdir: str, wrap_mode: WrapMode = WrapMode.default) -> None:
+ self.source_dir = source_dir
+ self.subdir = subdir
self.wrap_mode = wrap_mode
- self.subdir_root = subdir_root
+ self.subdir_root = os.path.join(source_dir, subdir)
self.cachedir = os.path.join(self.subdir_root, 'packagecache')
- self.filesdir = os.path.join(self.subdir_root, 'packagefiles')
self.wraps = {} # type: T.Dict[str, PackageDefinition]
self.provided_deps = {} # type: T.Dict[str, PackageDefinition]
self.provided_programs = {} # type: T.Dict[str, PackageDefinition]
@@ -208,6 +232,14 @@ class Resolver:
raise WrapException(m.format(k, wrap.basename, prev_wrap.basename))
self.provided_programs[k] = wrap
+ def merge_wraps(self, other_resolver: 'Resolver') -> None:
+ for k, v in other_resolver.wraps.items():
+ self.wraps.setdefault(k, v)
+ for k, v in other_resolver.provided_deps.items():
+ self.provided_deps.setdefault(k, v)
+ for k, v in other_resolver.provided_programs.items():
+ self.provided_programs.setdefault(k, v)
+
def find_dep_provider(self, packagename: str) -> T.Optional[T.Union[str, T.List[str]]]:
# Return value is in the same format as fallback kwarg:
# ['subproject_name', 'variable_name'], or 'subproject_name'.
@@ -235,7 +267,29 @@ class Resolver:
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)
+
+ if self.wrap.has_wrap:
+ # We have a .wrap file, source code will be placed into main
+ # project's subproject_dir even if the wrap file comes from another
+ # subproject.
+ self.dirname = os.path.join(self.subdir_root, self.directory)
+ # Check if the wrap comes from the main project.
+ main_fname = os.path.join(self.subdir_root, self.wrap.basename)
+ if self.wrap.filename != main_fname:
+ rel = os.path.relpath(self.wrap.filename, self.source_dir)
+ mlog.log('Using', mlog.bold(rel))
+ # Write a dummy wrap file in main project that redirect to the
+ # wrap we picked.
+ with open(main_fname, 'w') as f:
+ f.write(textwrap.dedent('''\
+ [wrap-redirect]
+ filename = {}
+ '''.format(os.path.relpath(self.wrap.filename, self.subdir_root))))
+ else:
+ # No wrap file, it's a dummy package definition for an existing
+ # directory. Use the source code in place.
+ self.dirname = self.wrap.filename
+ rel_path = os.path.relpath(self.dirname, self.source_dir)
meson_file = os.path.join(self.dirname, 'meson.build')
cmake_file = os.path.join(self.dirname, 'CMakeLists.txt')
@@ -245,9 +299,9 @@ class Resolver:
# The directory is there and has meson.build? Great, use it.
if method == 'meson' and os.path.exists(meson_file):
- return self.directory
+ return rel_path
if method == 'cmake' and os.path.exists(cmake_file):
- return self.directory
+ return rel_path
# Check if the subproject is a git submodule
self.resolve_git_submodule()
@@ -276,7 +330,7 @@ class Resolver:
if method == 'cmake' and not os.path.exists(cmake_file):
raise WrapException('Subproject exists but has no CMakeLists.txt file')
- return self.directory
+ return rel_path
def check_can_download(self) -> None:
# Don't download subproject data based on wrap file if requested.
@@ -487,7 +541,7 @@ class Resolver:
else:
from ..interpreterbase import FeatureNew
FeatureNew('Local wrap patch files without {}_url'.format(what), '0.55.0').use(self.current_subproject)
- path = Path(self.filesdir) / filename
+ path = Path(self.wrap.filesdir) / filename
if not path.exists():
raise WrapException('File "{}" does not exist'.format(path))
@@ -511,7 +565,7 @@ class Resolver:
from ..interpreterbase import FeatureNew
FeatureNew('patch_directory', '0.55.0').use(self.current_subproject)
patch_dir = self.wrap.values['patch_directory']
- src_dir = os.path.join(self.filesdir, patch_dir)
+ src_dir = os.path.join(self.wrap.filesdir, patch_dir)
if not os.path.isdir(src_dir):
raise WrapException('patch directory does not exists: {}'.format(patch_dir))
self.copy_tree(src_dir, self.dirname)
diff --git a/run_unittests.py b/run_unittests.py
index 22e7cdc..f6adcee 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -69,6 +69,8 @@ import mesonbuild.modules.pkgconfig
from mesonbuild.mtest import TAPParser, TestResult
+from mesonbuild.wrap.wrap import PackageDefinition, WrapException
+
from run_tests import (
Backend, FakeBuild, FakeCompilerOptions,
ensure_backend_detects_changes, exe_suffix, get_backend_commands,
@@ -4183,6 +4185,16 @@ recommended as it is not supported on some platforms''')
'name': 'sub_novar',
'version': '1.0',
},
+ {
+ 'descriptive_name': 'subsub',
+ 'name': 'subsub',
+ 'version': 'undefined'
+ },
+ {
+ 'descriptive_name': 'subsubsub',
+ 'name': 'subsubsub',
+ 'version': 'undefined'
+ },
]
}
res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name'])
@@ -5156,6 +5168,52 @@ recommended as it is not supported on some platforms''')
out = self.init(testdir)
self.assertNotRegex(out, r'WARNING')
+ def test_wrap_redirect(self):
+ redirect_wrap = os.path.join(self.builddir, 'redirect.wrap')
+ real_wrap = os.path.join(self.builddir, 'foo/subprojects/real.wrap')
+ os.makedirs(os.path.dirname(real_wrap))
+
+ # Invalid redirect, filename must have .wrap extension
+ with open(redirect_wrap, 'w') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/subprojects/real.wrapper
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be a .wrap file'):
+ PackageDefinition(redirect_wrap)
+
+ # Invalid redirect, filename cannot be in parent directory
+ with open(redirect_wrap, 'w') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = ../real.wrap
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename cannot contain ".."'):
+ PackageDefinition(redirect_wrap)
+
+ # Invalid redirect, filename must be in foo/subprojects/real.wrap
+ with open(redirect_wrap, 'w') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/real.wrap
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be in the form foo/subprojects/bar.wrap'):
+ wrap = PackageDefinition(redirect_wrap)
+
+ # Correct redirect
+ with open(redirect_wrap, 'w') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/subprojects/real.wrap
+ '''))
+ with open(real_wrap, 'w') as f:
+ f.write(textwrap.dedent('''
+ [wrap-git]
+ url = http://invalid
+ '''))
+ wrap = PackageDefinition(redirect_wrap)
+ self.assertEqual(wrap.get('url'), 'http://invalid')
+
class FailureTests(BasePlatformTests):
'''
Tests that test failure conditions. Build files here should be dynamically
@@ -5365,16 +5423,16 @@ class FailureTests(BasePlatformTests):
correct message when the fallback subproject is found but the
variable inside it is not.
4. A fallback dependency is found from the subproject parsed in (3)
- 5. The correct message is outputted when the .wrap file is missing for
- a sub-subproject.
+ 5. A wrap file from a subproject is used but fails because it does not
+ contain required keys.
'''
tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables')
out = self.init(tdir, inprocess=True)
self.assertRegex(out, r"Subproject directory not found and .*nosubproj.wrap.* file not found")
self.assertRegex(out, r'Function does not take positional arguments.')
- self.assertRegex(out, r'WARNING:.* Dependency .*subsubproject.* not found but it is available in a sub-subproject.')
- self.assertRegex(out, r'Subproject directory not found and .*subsubproject.wrap.* file not found')
+ self.assertRegex(out, r'Dependency .*somenotfounddep.* from subproject .*subprojects/somesubproj.* found: .*NO.*')
self.assertRegex(out, r'Dependency .*zlibproxy.* from subproject .*subprojects.*somesubproj.* found: .*YES.*')
+ self.assertRegex(out, r'Missing key .*source_filename.* in subsubproject.wrap')
def test_exception_exit_status(self):
'''
diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build
index a891ca9..36e48a7 100644
--- a/test cases/common/102 subproject subdir/meson.build
+++ b/test cases/common/102 subproject subdir/meson.build
@@ -54,3 +54,14 @@ assert(d.type_name() == 'internal')
# Using gobject-2.0 here because some CI runners have it installed.
d = dependency('gobject-2.0', required : false)
assert(not d.found())
+
+# Verify that implicit fallback works because subprojects/sub_implicit/subprojects/subsub
+# directory exists.
+d = dependency('subsub')
+assert(d.found(), 'Should be able to fallback to sub-subproject')
+
+# Verify that implicit fallback works because
+# subprojects/sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap
+# file exists.
+d = dependency('subsubsub')
+assert(d.found(), 'Should be able to fallback to sub-sub-subproject')
diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/meson.build b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/meson.build
new file mode 100644
index 0000000..18e2cea
--- /dev/null
+++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/meson.build
@@ -0,0 +1,3 @@
+project('subsub')
+
+meson.override_dependency('subsub', declare_dependency())
diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/packagefiles/subsubsub-1.0.zip b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/packagefiles/subsubsub-1.0.zip
new file mode 100644
index 0000000..dfb7576
--- /dev/null
+++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/packagefiles/subsubsub-1.0.zip
Binary files differ
diff --git a/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap
new file mode 100644
index 0000000..6567ed0
--- /dev/null
+++ b/test cases/common/102 subproject subdir/subprojects/sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap
@@ -0,0 +1,4 @@
+[wrap-file]
+directory = subsubsub-1.0
+source_filename = subsubsub-1.0.zip
+source_hash = c073a96b7251937e53216578f6f03d91b84816618a0f1ce3ecfb867beddf1498
diff --git a/test cases/failing/16 extract from subproject/test.json b/test cases/failing/16 extract from subproject/test.json
index 78d45a5..2e32904 100644
--- a/test cases/failing/16 extract from subproject/test.json
+++ b/test cases/failing/16 extract from subproject/test.json
@@ -1,7 +1,7 @@
{
"stdout": [
{
- "line": "test cases/failing/16 extract from subproject/meson.build:6:0: ERROR: Tried to extract objects from a subproject target."
+ "line": "test cases/failing/16 extract from subproject/meson.build:6:0: ERROR: Tried to extract objects from a different subproject."
}
]
}