Skip to content

Commit

Permalink
Fix subprojects logic in cross build scenarios
Browse files Browse the repository at this point in the history
Where we cannot use the same subproject state for both machines.

We may not even need to use a subproject for both machines, for example
if the build machine has a dependency installed, and we only need to
fall back to a subproject for the host machine.

Fixes: mesonbuild#10947
  • Loading branch information
oleavr committed Feb 6, 2024
1 parent 7f80556 commit 20e8d28
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 31 deletions.
2 changes: 2 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,8 @@ def check_can_link_together(self, t: BuildTargetTypes) -> None:
links_with_rust_abi = isinstance(t, BuildTarget) and t.uses_rust_abi()
if not self.uses_rust() and links_with_rust_abi:
raise InvalidArguments(f'Try to link Rust ABI library {t.name!r} with a non-Rust target {self.name!r}')
if t.subproject and not self.environment.is_cross_build():
return
if self.for_machine is not t.for_machine and (not links_with_rust_abi or t.rust_crate_type != 'proc-macro'):
msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}'
if self.environment.is_cross_build():
Expand Down
23 changes: 14 additions & 9 deletions mesonbuild/interpreter/dependencyfallbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_
name = func_args[0]
cached_dep = self._get_cached_dep(name, kwargs)
if cached_dep:
self._verify_fallback_consistency(cached_dep)
self._verify_fallback_consistency(cached_dep, kwargs.get('native', False))
return cached_dep

def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
Expand All @@ -95,7 +95,7 @@ def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs
def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
subp_name = func_args[0]
varname = self.subproject_varname
if subp_name and self._get_subproject(subp_name):
if subp_name and self._get_subproject(subp_name, kwargs.get('native', False)):
return self._get_subproject_dep(subp_name, varname, kwargs)
return None

Expand Down Expand Up @@ -130,15 +130,17 @@ def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs
self.interpreter.do_subproject(subp_name, func_kwargs)
return self._get_subproject_dep(subp_name, varname, kwargs)

def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
sub = self.interpreter.subprojects.get(subp_name)
def _get_subproject(self, subp_name: str, native: bool) -> T.Optional[SubprojectHolder]:
sub = self.interpreter.find_subproject(subp_name, native)
if sub and sub.found():
return sub
return None

def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
native = kwargs.get('native', False)

# Verify the subproject is found
subproject = self._get_subproject(subp_name)
subproject = self._get_subproject(subp_name, native)
if not subproject:
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subp_name), 'found:', mlog.red('NO'),
Expand All @@ -160,7 +162,7 @@ def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs
# If we have cached_dep we did all the checks and logging already in
# self._get_cached_dep().
if cached_dep:
self._verify_fallback_consistency(cached_dep)
self._verify_fallback_consistency(cached_dep, native)
return cached_dep

# Legacy: Use the variable name if provided instead of relying on the
Expand Down Expand Up @@ -256,10 +258,12 @@ def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -
return None
return var_dep

def _verify_fallback_consistency(self, cached_dep: Dependency) -> None:
def _verify_fallback_consistency(self, cached_dep: Dependency, native: bool) -> None:
subp_name = self.subproject_name
if subp_name is None:
return
varname = self.subproject_varname
subproject = self._get_subproject(subp_name)
subproject = self._get_subproject(subp_name, native)
if subproject and varname:
var_dep = self._get_subproject_variable(subproject, varname)
if var_dep and cached_dep.found() and var_dep != cached_dep:
Expand Down Expand Up @@ -336,7 +340,8 @@ def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependen
subp_name, varname = self.wrap_resolver.find_dep_provider(name)
if subp_name:
self.forcefallback |= subp_name in force_fallback_for
if self.forcefallback or self.allow_fallback is True or required or self._get_subproject(subp_name):
if self.forcefallback or self.allow_fallback is True or required \
or self._get_subproject(subp_name, kwargs.get('native', False)):
self._subproject_impl(subp_name, varname)
break

Expand Down
57 changes: 35 additions & 22 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,18 +859,29 @@ def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: k
}
return self.do_subproject(args[0], kw)

def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None,
def find_subproject(self, subp_name: str, native: bool) -> Optional[SubprojectHolder]:
return self.subprojects.get(self._make_subproject_id(subp_name, native))

def _make_subproject_id(self, subp_name: str, native: bool) -> str:
if not self.coredata.is_cross_build():
return subp_name
tag = 'build' if native else 'host'
return ':'.join([subp_name, tag])

def disabled_subproject(self, subp_name: str, subp_id: str, disabled_feature: T.Optional[str] = None,
exception: T.Optional[Exception] = None) -> SubprojectHolder:
sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name),
disabled_feature=disabled_feature, exception=exception)
self.subprojects[subp_name] = sub
self.subprojects[subp_id] = sub
return sub

def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None) -> SubprojectHolder:
subp_id = self._make_subproject_id(subp_name, kwargs.get('native', False))

disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
return self.disabled_subproject(subp_name, disabled_feature=feature)
return self.disabled_subproject(subp_name, subp_id, disabled_feature=feature)

default_options = {k.evolve(subproject=subp_name): v for k, v in kwargs['default_options'].items()}

Expand All @@ -889,8 +900,8 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
fullstack = self.subproject_stack + [subp_name]
incpath = ' => '.join(fullstack)
raise InvalidCode(f'Recursive include of subprojects: {incpath}.')
if subp_name in self.subprojects:
subproject = self.subprojects[subp_name]
if subp_id in self.subprojects:
subproject = self.subprojects[subp_id]
if required and not subproject.found():
raise InterpreterException(f'Subproject "{subproject.subdir}" required but not found.')
if kwargs['version']:
Expand All @@ -907,7 +918,7 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
if not required:
mlog.log(e)
mlog.log('Subproject ', mlog.bold(subp_name), 'is buildable:', mlog.red('NO'), '(disabling)')
return self.disabled_subproject(subp_name, exception=e)
return self.disabled_subproject(subp_name, subp_id, exception=e)
raise e

os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
Expand All @@ -919,14 +930,14 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
m += ['method', mlog.bold(method)]
mlog.log(*m, '\n', nested=False)

methods_map: T.Dict[wrap.Method, T.Callable[[str, str, T.Dict[OptionKey, str, kwtypes.DoSubproject]], SubprojectHolder]] = {
methods_map: T.Dict[wrap.Method, T.Callable[[str, str, T.Dict[OptionKey, str, str, kwtypes.DoSubproject]], SubprojectHolder]] = {
'meson': self._do_subproject_meson,
'cmake': self._do_subproject_cmake,
'cargo': self._do_subproject_cargo,
}

try:
return methods_map[method](subp_name, subdir, default_options, kwargs)
return methods_map[method](subp_name, subp_id, subdir, default_options, kwargs)
# Invalid code is always an error
except InvalidCode:
raise
Expand All @@ -937,15 +948,17 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
# fatal and VS CI treat any logs with "ERROR:" as fatal.
mlog.exception(e, prefix=mlog.yellow('Exception:'))
mlog.log('\nSubproject', mlog.bold(subdir), 'is buildable:', mlog.red('NO'), '(disabling)')
return self.disabled_subproject(subp_name, exception=e)
return self.disabled_subproject(subp_name, subp_id, exception=e)
raise e

def _do_subproject_meson(self, subp_name: str, subdir: str,
def _do_subproject_meson(self, subp_name: str, subp_id: str, subdir: str,
default_options: T.Dict[OptionKey, str],
kwargs: kwtypes.DoSubproject,
ast: T.Optional[mparser.CodeBlockNode] = None,
build_def_files: T.Optional[T.List[str]] = None,
relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = None) -> SubprojectHolder:
subp_id = self._make_subproject_id(subp_name, kwargs.get('native', False))

with mlog.nested(subp_name):
if ast:
# Debug print the generated meson file
Expand All @@ -961,7 +974,7 @@ def _do_subproject_meson(self, subp_name: str, subdir: str,
mlog.cmd_ci_include(meson_filename)

new_build = self.build.copy()
subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir,
subi = Interpreter(new_build, self.backend, subp_id, subdir, self.subproject_dir,
default_options, ast=ast, is_translated=(ast is not None),
relaxations=relaxations,
user_defined_options=self.user_defined_options)
Expand Down Expand Up @@ -990,18 +1003,18 @@ def _do_subproject_meson(self, subp_name: str, subdir: str,
raise InterpreterException(f'Subproject {subp_name} version is {pv} but {wanted} required.')
self.active_projectname = current_active
self.subprojects.update(subi.subprojects)
self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings,
callstack=self.subproject_stack)
self.subprojects[subp_id] = SubprojectHolder(subi, subdir, warnings=subi_warnings,
callstack=self.subproject_stack)
# Duplicates are possible when subproject uses files from project root
if build_def_files:
self.build_def_files.update(build_def_files)
# We always need the subi.build_def_files, to propagate sub-sub-projects
self.build_def_files.update(subi.build_def_files)
self.build.merge(subi.build)
self.build.subprojects[subp_name] = subi.project_version
return self.subprojects[subp_name]
return self.subprojects[subp_id]

def _do_subproject_cmake(self, subp_name: str, subdir: str,
def _do_subproject_cmake(self, subp_name: str, subp_id: str, subdir: str,
default_options: T.Dict[OptionKey, str],
kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
from ..cmake import CMakeInterpreter
Expand All @@ -1018,7 +1031,7 @@ def _do_subproject_cmake(self, subp_name: str, subdir: str,
# Generate a meson ast and execute it with the normal do_subproject_meson
ast = cm_int.pretend_to_be_meson(options.target_options)
result = self._do_subproject_meson(
subp_name, subdir, default_options,
subp_name, subp_id, subdir, default_options,
kwargs, ast,
[str(f) for f in cm_int.bs_files],
relaxations={
Expand All @@ -1028,15 +1041,15 @@ def _do_subproject_cmake(self, subp_name: str, subdir: str,
result.cm_interpreter = cm_int
return result

def _do_subproject_cargo(self, subp_name: str, subdir: str,
def _do_subproject_cargo(self, subp_name: str, subp_id: str, subdir: str,
default_options: T.Dict[OptionKey, str],
kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
from .. import cargo
FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node)
with mlog.nested(subp_name):
ast = cargo.interpret(subp_name, subdir, self.environment)
return self._do_subproject_meson(
subp_name, subdir, default_options, kwargs, ast,
subp_name, subp_id, subdir, default_options, kwargs, ast,
# FIXME: Are there other files used by cargo interpreter?
[os.path.join(subdir, 'Cargo.toml')])

Expand Down Expand Up @@ -1372,7 +1385,7 @@ def summary_impl(self, section: str, values, kwargs: 'kwtypes.Summary') -> None:
def _print_summary(self) -> None:
# Add automatic 'Subprojects' section in main project.
all_subprojects = collections.OrderedDict()
for name, subp in sorted(self.subprojects.items()):
for subp_id, subp in sorted(self.subprojects.items()):
value = [subp.found()]
if subp.disabled_feature:
value += [f'Feature {subp.disabled_feature!r} disabled']
Expand All @@ -1383,7 +1396,7 @@ def _print_summary(self) -> None:
if subp.callstack:
stack = ' => '.join(subp.callstack)
value += [f'(from {stack})']
all_subprojects[name] = value
all_subprojects[subp_id] = value
if all_subprojects:
self.summary_impl('Subprojects', all_subprojects,
{'bool_yn': True,
Expand All @@ -1403,8 +1416,8 @@ def _print_summary(self) -> None:
# Print all summaries, main project last.
mlog.log('') # newline
main_summary = self.summary.pop('', None)
for subp_name, summary in sorted(self.summary.items()):
if self.subprojects[subp_name].found():
for subp_id, summary in sorted(self.summary.items()):
if self.subprojects[subp_id].found():
summary.dump()
if main_summary:
main_summary.dump()
Expand Down

0 comments on commit 20e8d28

Please sign in to comment.