blob: 2e0523f655a7abd256bdd3a85cc70dc44d135a6c [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.
"""
Implements generating and consuming interface libraries.
An interface library is a minimal representation of a library whose
implementation details are omitted. For example, we can generate an interface
library from a shared library object (.so) by trimming everything that are not
need by the linker, e.g. TEXT section in ELF. Interface libraries allow us to
avoid package rebuilds when a library package is updated without changing its
external interfaces.
"""
load("//bazel/portage/build_defs:common.bzl", "EbuildLibraryInfo", "compute_input_file_path")
load("@bazel_skylib//lib:paths.bzl", "paths")
def _format_input_file_arg(strip_prefix, file, use_runfiles):
return "--sysroot-file=%s=%s" % (file.path.removeprefix(strip_prefix), compute_input_file_path(file, use_runfiles))
def add_interface_library_args(input_targets, args, use_runfiles):
"""
Computes the arguments to pass to build_package to link interface libraries.
A build rule that consumes interface libraries should call this function
to get an input depset and arguments to pass to the program.
Args:
input_targets: list[Target]: A list of targets that provide interface
libraries. Typically it is from the shared_lib_deps attribute.
args: Args: An Args object where necessary arguments are added in order
to depend on the interface libraries.
use_runfiles: bool: Whether to refer to input file paths in relative to
execroot or runfiles directory. See compute_input_file_path for
details.
Returns:
Depset[File]: A depset representing interface library inputs.
"""
depsets = []
for input_target in input_targets:
lib_info = input_target[EbuildLibraryInfo]
deps = depset(transitive = [lib_info.headers, lib_info.pkg_configs, lib_info.shared_libs])
args.add_all(
deps,
allow_closure = True,
map_each = lambda file: _format_input_file_arg(lib_info.strip_prefix, file, use_runfiles),
)
depsets.append(deps)
return depset(transitive = depsets)
def _declare_interface_library_outputs(
ctx,
input_paths,
allowed_extensions,
output_base_dir,
args):
"""
Declares interface library outputs.
Args:
ctx: ctx: A context object passed to the rule implementation function.
input_paths: list[str]: A list of interface library file paths contained
in the binary package.
allowed_extensions: Optional[list[str]]: An optional list of allowed
extensions in interface libraries. This function will fail if the
given interface library file paths contain non-allowed extensions.
output_base_dir: str: The relative directory where interface library
outputs are saved.
args: Args: An Args object where necessary arguments are added in order
to extract interface libraries.
Returns:
list[File]: A list of files declared as outputs of the current rule.
"""
outputs = []
for input_path in input_paths:
if not paths.is_absolute(input_path):
fail("%s is not absolute" % input_path)
file_name = paths.basename(input_path)
_, extension = paths.split_extension(file_name)
if allowed_extensions and extension not in allowed_extensions:
fail("%s must be of file type %s, got %s" % (input_path, allowed_extensions, extension))
file = ctx.actions.declare_file(paths.join(output_base_dir, input_path[1:]))
outputs.append(file)
args.add("--output-file=%s=%s" % (input_path, file.path))
return outputs
def generate_interface_libraries(
ctx,
input_binary_package_file,
output_base_dir,
headers,
pkg_configs,
shared_libs,
static_libs,
extract_interface_executable,
action_wrapper_executable):
"""
Declares an action to generate interface libraries from a binary package.
A build rule that generates interface libraries should call this function
to get a list of interface library outputs and associated providers.
Args:
ctx: ctx: A context object passed to the rule implementation function.
input_binary_package_file: File: An input binary package file to extract
interface libraries from.
output_base_dir: str: The base directory where extracted interface
libraries are saved.
headers: list[str]: A list of header files in the binary package that
makes interface libraries. They must be absolute file paths.
pkg_configs: list[str]: A list of pkg-config files (.pc) in the binary
package that makes interface libraries. They must be absolute file
paths.
shared_libs: list[str]: A list of shared library files (.so) in the
binary package that makes interface libraries. They must be absolute
file paths.
static_libs: list[str]: A list of static library files (.a) in the
binary package that makes interface libraries. They must be absolute
file paths.
extract_interface_executable: File: The "extract_interface" executable
file.
action_wrapper_executable: File: The "action_wrapper" executable file.
Returns:
(outputs, providers) where:
outputs: list[File]: Generated interface library files.
providers: list[Provider]: A list of providers that should be
attached to the current build target.
"""
output_log = ctx.actions.declare_file(output_base_dir + ".extract_interface.log")
args = ctx.actions.args()
args.add_all([
"--log=%s" % output_log.path,
# TODO: Enable profiling.
extract_interface_executable,
"--binpkg=%s" % input_binary_package_file.path,
])
xpak_outputs = []
for xpak_file in ["NEEDED", "REQUIRES", "PROVIDES", "USE"]:
file = ctx.actions.declare_file(paths.join(output_base_dir, "xpak", xpak_file))
xpak_outputs.append(file)
args.add("--xpak=%s=?%s" % (xpak_file, file.path))
files_output_base_dir = paths.join(output_base_dir, "files")
header_outputs = _declare_interface_library_outputs(
ctx = ctx,
input_paths = headers,
allowed_extensions = [".h", ".hpp"],
output_base_dir = files_output_base_dir,
args = args,
)
pkg_config_outputs = _declare_interface_library_outputs(
ctx = ctx,
input_paths = pkg_configs,
allowed_extensions = [".pc"],
output_base_dir = files_output_base_dir,
args = args,
)
shared_lib_outputs = _declare_interface_library_outputs(
ctx = ctx,
input_paths = shared_libs,
allowed_extensions = None,
output_base_dir = files_output_base_dir,
args = args,
)
static_lib_outputs = _declare_interface_library_outputs(
ctx = ctx,
input_paths = static_libs,
allowed_extensions = [".a"],
output_base_dir = files_output_base_dir,
args = args,
)
files_outputs = header_outputs + pkg_config_outputs + shared_lib_outputs + static_lib_outputs
outputs = xpak_outputs + files_outputs + [output_log]
ctx.actions.run(
inputs = [input_binary_package_file],
outputs = outputs,
executable = action_wrapper_executable,
tools = [extract_interface_executable],
arguments = [args],
progress_message = "Generating interface libraries for %s" % paths.basename(input_binary_package_file.path),
)
providers = [
# TODO: Only generate EbuildLibraryInfo if we have files specified.
EbuildLibraryInfo(
# TODO: Fix the computation of strip_prefix. Currently this assumes
# that output_base_dir is exactly the same as the stem part of the
# binary package file name.
strip_prefix = paths.join(input_binary_package_file.path.rsplit(".", 1)[0], "files"),
headers = depset(header_outputs),
pkg_configs = depset(pkg_config_outputs),
shared_libs = depset(shared_lib_outputs),
static_libs = depset(static_lib_outputs),
),
# TODO: Create a CCInfo provider so we can start using rules_cc.
]
return outputs, providers