blob: 0226e79fb3d83c4fab44c8d5aa0f12ea53500d8e [file] [log] [blame]
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Implement fuchsia_prebuilt_package() rule."""
load("//fuchsia/private:providers.bzl", "FuchsiaPackageInfo")
load("//fuchsia/private:package_publishing.bzl", "package_repo_path_from_label", "publish_package")
load("//fuchsia/private/workflows:fuchsia_task_publish.bzl", "fuchsia_task_publish")
_COMPONENT_VALIDATION_SCRIPT = """
components=$($FAR list --archive=$OUTPUT_DIR/meta.far | grep -F meta/$COMPONENT.cm | wc -l)
if [ $components -eq 0 ]; then
echo
echo "Component '$COMPONENT' is not included in the package!"
echo
exit 1
fi
touch $STAMP
"""
def _relative_file_name(ctx, filename):
return ctx.label.name + "_expanded/" + filename
def _validate_components_and_drivers(ctx, outdir):
far = ctx.toolchains["@rules_fuchsia//fuchsia:toolchain"].far
deps = []
for component in ctx.attr.components + ctx.attr.drivers:
stamp_file = ctx.actions.declare_file(_relative_file_name(ctx, component + "_stamp"))
# NOTE: outdir is a ctx.actions.declare_directory() path, so declare
# it as an input, even though only the `meta.far` inside it is used.
# (There is no way to create a File() instance from it).
#
# This ensures the action that populates the directory is always run
# properly before the run_shell() one below.
ctx.actions.run_shell(
inputs = [outdir, far],
outputs = [stamp_file],
command = _COMPONENT_VALIDATION_SCRIPT,
env = {
"FAR": far.path,
"COMPONENT": component,
"OUTPUT_DIR": outdir.path,
"STAMP": stamp_file.path,
},
progress_message = "Validating the component %s" % component,
)
deps.append(stamp_file)
return deps
def _fuchsia_prebuilt_package_impl(ctx):
sdk = ctx.toolchains["@rules_fuchsia//fuchsia:toolchain"]
far_archive = ctx.files.archive[0]
# Technical note: `pm expand` below will populate its output directory
# with multiple files whose name are content hashes and cannot be known in
# advance, so use ctx.actions.declare_directory() to declare an output
# directory for the expansion. This tells Bazel that all files present
# in the directory after the command execution are outputs, and should
# be copied from the sandbox to the corresponding final output location
# in the output_base directory (otherwise, they would disappear once the
# sandbox is destroyed).
#
# The top-level directory for this target will be computed from
# `${label}_expanded/`, which, for a label like `//package/foo:bar`
# will expand to something like this (relative to the sandbox execroot):
#
# `bazel-out/aarch64-opt/bin/package/foo/bar/bar_expanded`
#
# Inside this TARGET_OUT_DIR, the following is generated:
#
# $TARGET_OUT_DIR/
# content/
# A directory that contains the expanded content from the
# prebuilt package, as well as a `package_manifest.json` file
# that lists all entries, where source paths appear relative
# to the execroot too, e.g.:
#
# "blobs": [
# {
# "source_path": "bazel-out/aarch64-opt/bin/package/foo/bar/bar_expanded/_content/meta.far",
# "path": "meta/",
# "merkle": "d0d73e04d89e393b71f2280831421ebe279e247265e25714c71fdc8961928822",
# "size": 94288,
# },
# ...
#
# rebased_package_manifest.json
# A version of _content/package_manifest.json that contains
# source paths that are relative to an alternative `artifacts_base_path`
# value. However, since the default value for this argument is just '.',
# it will have the same content as `_content/package_manifest.json` in
# most cases.
#
# Note that this file _cannot_ be inside `_content`, as Bazel
# would complain otherwise, as it is generated by a different action
# than the one that generated `_content/`.
#
# <component>_stamp
# For each component listed in ctx.attr.components or
# ctx.attr.drivers, a stamp file generated by the action that
# verifies it belongs to the package.
#
output_dir = ctx.actions.declare_directory(_relative_file_name(ctx, "content"))
output_files = [output_dir]
output_files += _validate_components_and_drivers(ctx, output_dir)
# extract the package
ctx.actions.run(
executable = sdk.pm,
arguments = [
"-o",
output_dir.path,
"-r",
"fuchsia.com",
"expand",
far_archive.path,
],
inputs = [far_archive],
outputs = [
output_dir,
],
mnemonic = "FuchsiaPmExpand",
progress_message = "expanding package for %{label}",
)
# rebase paths in package manifest
rebased_package_manifest_json = ctx.actions.declare_file(_relative_file_name(ctx, "rebased_package_manifest.json"))
ctx.actions.run(
outputs = [rebased_package_manifest_json],
inputs = [output_dir],
executable = ctx.executable._rebase_package_manifest,
arguments = [
"--package-manifest",
output_dir.path + "/package_manifest.json",
"--updated-package-manifest",
rebased_package_manifest_json.path,
"--relative-base",
ctx.attr.artifacts_base_path,
],
)
output_files.append(rebased_package_manifest_json)
# Attempt to publish if told to do so
repo_path = package_repo_path_from_label(ctx.attr._package_repo_path)
if repo_path:
stamp_file = publish_package(ctx, sdk.pm, repo_path, [rebased_package_manifest_json])
output_files.append(stamp_file)
return [
DefaultInfo(files = depset(output_files)),
FuchsiaPackageInfo(
package_manifest = rebased_package_manifest_json,
far_file = ctx.files.archive,
components = ctx.attr.components,
drivers = ctx.attr.drivers,
),
]
_fuchsia_prebuilt_package = rule(
doc = """Provides access to a fuchsia package from a prebuilt package archive (.far).
""",
implementation = _fuchsia_prebuilt_package_impl,
toolchains = ["@rules_fuchsia//fuchsia:toolchain"],
attrs = {
"archive": attr.label(
doc = "The fuchsia archive",
allow_single_file = True,
mandatory = True,
),
"components": attr.string_list(
doc = "components of this driver",
default = [],
),
"drivers": attr.string_list(
doc = "drivers of this driver",
default = [],
),
"_package_repo_path": attr.label(
doc = "The command line flag used to publish packages.",
default = "//fuchsia:package_repo",
),
"artifacts_base_path": attr.string(
doc = "Artifacts base directories that items in config files are relative to.",
default = ".",
),
"_rebase_package_manifest": attr.label(
default = "//fuchsia/tools:rebase_package_manifest",
executable = True,
cfg = "exec",
),
},
)
def fuchsia_prebuilt_package(*, name, archive, components = [], drivers = [], artifacts_base_path = ".", **kwargs):
_fuchsia_prebuilt_package(
name = name,
archive = archive,
components = components,
drivers = drivers,
artifacts_base_path = artifacts_base_path,
**kwargs
)
fuchsia_task_publish(
name = "%s.publish" % name,
packages = [name],
**kwargs
)