blob: f130f19ef9fbb246ca968b2c147e633a2a2649a9 [file] [log] [blame] [edit]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
def _compute_slot_key(package):
"""
Computes a slot key from BinaryPackageInfo.
A slot key is a string that uniquely describes a package slot occuppied by a
package, in the form "<category>/<package-name>:<main-slot>@<sysroot>".
Two packages with the same slot key can't be installed at the same time on
a system.
Args:
package: BinaryPackageInfo: Describes a package.
Returns:
A slot key.
"""
main_slot = package.slot.split("/")[0]
return "%s/%s:%s@%s" % (
package.category,
package.package_name,
main_slot,
package.contents.sysroot,
)
def install_deps(
ctx,
output_prefix,
board,
sdk,
overlays,
portage_configs,
install_set,
executable_action_wrapper,
executable_fast_install_packages,
progress_message,
full_vdb):
"""
Creates an action which builds file system layers in which the build dependencies are installed.
Args:
ctx: ctx: A context object passed to the rule implementation.
output_prefix: str: A file name prefix to prepend to output files
defined in this function.
board: str: The target board name to install dependencies for. If it is
non-empty, packages are installed to the corresponding sysroot
(ROOT="/build/<board>"). If it is an empty string, packages are
installed to the host (ROOT="/").
sdk: SDKInfo: The provider describing the base file system layers.
overlays: OverlaySetInfo: Overlays providing packages.
portage_configs: list[File]: Tarballs containing portage config.
install_set: Depset[BinaryPackageInfo]: Binary package targets to
install. This depset must be closed over transitive runtime
dependencies; that is, if the depset contains a package X, it must
also contain all transitive dependencies of the package X. Also,
This depset's to_list() must return packages in a valid installation
order, i.e. a package's runtime dependencies are fully satisfied by
packages that appear before it.
executable_action_wrapper: File: An executable file of action_wrapper.
executable_fast_install_packages: File: An executable file of
fast_install_packages.
progress_message: str: Progress message for the installation action.
full_vdb: bool: If true, the layers will contain a full vdb. Set this
to true when building an SDK tarball, or an image where `emerge`
could be invoked.
Returns:
struct where:
sparse_layers: list[File]: Files representing file system layers.
The layers contains full contents, and a sparse vdb.
log_file: File: Log file generated when building the layers.
trace_file: File: Trace file generated when building the layers.
"""
sysroot = "/build/%s" % board if board else "/"
slot_key_to_package = {} # dict[str, BinaryPackageInfo]
# Inspect already installed packages.
for package in sdk.packages.to_list():
slot_key = _compute_slot_key(package)
conflicting_package = slot_key_to_package.get(slot_key)
if conflicting_package:
fail(
("Slot conflict: cannot install %s when %s has been already " +
"installed with the same slot key %s") % (
package.file.path,
conflicting_package.file.path,
slot_key,
),
)
else:
slot_key_to_package[slot_key] = package
# Create a list of packages to install.
install_list = []
for package in install_set.to_list():
slot_key = _compute_slot_key(package)
conflicting_package = slot_key_to_package.get(slot_key)
if conflicting_package:
# Skip identical packages.
if package.partial.path != conflicting_package.partial.path:
fail(
("Slot conflict: cannot install %s and %s that have " +
"the same slot key %s") % (
package.file.path,
conflicting_package.file.path,
slot_key,
),
)
else:
install_list.append(package)
slot_key_to_package[slot_key] = package
output_log_file = ctx.actions.declare_file("%s.log" % output_prefix)
output_profile_file = ctx.actions.declare_file(
"%s.profile.json" % output_prefix,
)
outputs = [output_log_file, output_profile_file]
args = ctx.actions.args()
args.add_all([
"--log",
output_log_file,
"--profile",
output_profile_file,
executable_fast_install_packages,
])
args.add("--root-dir=%s" % sysroot)
if not full_vdb:
args.add("--drop-revision")
input_layers = sdk.layers + overlays.layers + portage_configs
args.add_all(
input_layers,
format_each = "--layer=%s",
expand_directories = False,
)
inputs = input_layers[:]
layers = []
for i, package in enumerate(install_list):
package_output_prefix = "%s.%d" % (output_prefix, i)
output_preinst = ctx.actions.declare_directory(
"%s.preinst" % package_output_prefix,
)
output_postinst = ctx.actions.declare_directory(
"%s.postinst" % package_output_prefix,
)
if full_vdb:
installed_layer = package.contents.full.installed
staged_layer = package.contents.full.staged
else:
installed_layer = package.contents.internal.installed
staged_layer = package.contents.internal.staged
if package.contents.sysroot != sysroot:
fail(
("Requested to install %s/%s-%s to %s, but its installed " +
"contents layers were generated only for %s") % (
package.category,
package.package_name,
package.version,
sysroot,
package.contents.sysroot,
),
)
args.add_joined(
"--install",
[
package.partial,
installed_layer,
staged_layer,
output_preinst,
output_postinst,
],
join_with = ",",
expand_directories = False,
)
inputs.extend([
package.partial,
installed_layer,
staged_layer,
])
outputs.extend([output_preinst, output_postinst])
layers.extend([
output_preinst,
installed_layer,
output_postinst,
])
actual_progress_message = progress_message.replace(
"{dep_count}",
str(len(install_list)),
)
ctx.actions.run(
inputs = depset(inputs),
outputs = outputs,
executable = executable_action_wrapper,
tools = [executable_fast_install_packages],
arguments = [args],
execution_requirements = {
# Disable sandbox to avoid creating a symlink forest.
# This does not affect hermeticity since ebuild runs in a container.
"no-sandbox": "",
# Send SIGTERM instead of SIGKILL on user interruption.
"supports-graceful-termination": "",
},
mnemonic = "InstallDeps",
progress_message = actual_progress_message,
)
return struct(
layers = layers,
log_file = output_log_file,
trace_file = output_profile_file,
)