blob: 090f8b25d11974143a95bad2c37639b8b13b0868 [file] [log] [blame]
# Copyright 2024 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.
"""Rules used to manage IDK -> SDK conversions."""
load("@fuchsia_build_config//:defs.bzl", "build_config")
load("@fuchsia_build_info//:args.bzl", "sdk_id", "target_cpu")
def _find_root_directory(files, suffix):
for f in files:
if f.path.endswith(suffix):
return f.path.removesuffix(suffix)
return None
# genrule() cannot deal with TreeArtifacts inputs or outputs
# so a custom rule is needed to invoke the validate_idk
# py_binary() target.
def _validate_idk(ctx):
output_file = ctx.actions.declare_file(ctx.label.name)
if len(ctx.files._schema_files) == 0:
fail("No schema files specified.")
schema_dir_path = ctx.files._schema_files[0].dirname
inputs = depset(
[ctx.file.json_validator, ctx.file.idk_directory_hash] + ctx.files._schema_files,
)
args = [
"--idk-directory",
ctx.attr.idk_directory,
"--schema-directory",
schema_dir_path,
"--json-validator-path",
ctx.file.json_validator.path,
"--stamp-file",
output_file.path,
]
ctx.actions.run(
mnemonic = "IDKVerify",
executable = ctx.executable._validate_idk_script,
arguments = args,
inputs = inputs,
outputs = [output_file],
execution_requirements = {
# We want the generated IDK to persist.
"no-sandbox": "1",
# A full IDK is currently about 4.7 GiB, and this
# action runs in a few seconds, so avoid remoting it.
"no-remote": "1",
"no-cache": "1",
},
)
return DefaultInfo(files = depset([output_file]))
validate_idk = rule(
doc = """Generate a merged IDK at build time from `ninja_root_build_dir`.""",
implementation = _validate_idk,
attrs = {
"idk_directory": attr.string(
doc = "Path to the IDK to validate.",
mandatory = True,
),
"idk_directory_hash": attr.label(
doc = "The hash file corresponding to `idk_directory`.",
mandatory = True,
allow_single_file = True,
),
"json_validator": attr.label(
doc = "The JSON validator executable for schema validation",
default = "//build/tools/json_validator:json_validator_valico",
allow_single_file = True,
cfg = "exec",
),
"_schema_files": attr.label(
doc = "The IDK schema files. They must all be in the same directory.",
default = "//build/sdk/meta:idk_schema_files",
),
"_validate_idk_script": attr.label(
default = "//build/sdk/generate_idk:validate_idk",
executable = True,
cfg = "exec",
),
},
)
# genrule() cannot deal with TreeArtifacts inputs or outputs
# so a custom rule is needed to invoke the generate_idk_bazel
# py_binary() target.
def _generate_merged_idk(ctx):
output_dir = ctx.actions.declare_directory(ctx.label.name)
if len(ctx.files._schema_files) == 0:
fail("No schema files specified.")
schema_dir_path = ctx.files._schema_files[0].dirname
_collection_relative_path = "sdk/exported/%s" % ctx.attr.collection_name
args = [
"--collection-relative-path",
_collection_relative_path,
"--output-directory",
output_dir.path,
"--schema-directory",
schema_dir_path,
"--json-validator-path",
ctx.file.json_validator.path,
"--host-arch",
build_config.host_target_triple,
"--release-version",
sdk_id,
]
inputs = depset(
[ctx.file.json_validator] + ctx.files._schema_files,
)
for cpu in ctx.attr.buildable_cpus:
args += [
"--target-arch",
cpu,
]
# TODO(https://fxbug.dev/413071161): Either standardize "_for_subbuilds" or
# allow the prefix to be specified.
subbuilds_dir_prefix = "idk_subbuild.%s_for_subbuilds" % ctx.attr.collection_name
root_build_dir = ctx.attr.ninja_root_build_dir
subbuild_dirs = []
for cpu in ctx.attr.buildable_cpus:
# Add subbuilds for the PLATFORM API level.
if cpu == target_cpu:
# There is no sub-build for the target CPU - use the collection in
# the main build directory.
subbuild_dirs.append(root_build_dir)
else:
subbuild_dirs.append("%s/%s-%s" % (root_build_dir, subbuilds_dir_prefix, cpu))
# Add subbuilds for individual API levels.
for api_level in ctx.attr.buildable_api_levels:
subbuild_dirs.append("%s/%s-api%s-%s" % (root_build_dir, subbuilds_dir_prefix, api_level, cpu))
for dir in subbuild_dirs:
args += [
"--subbuild-directory",
dir,
]
ctx.actions.run(
mnemonic = "IDKMerge",
executable = ctx.executable._idk_merge_script,
arguments = args,
inputs = inputs,
outputs = [output_dir],
execution_requirements = {
# We want the generated IDK to persist.
"no-sandbox": "1",
# A full IDK is currently about 4.7 GiB, and this
# action runs in a few seconds, so avoid remoting it.
"no-remote": "1",
"no-cache": "1",
},
)
return DefaultInfo(files = depset([output_dir]))
generate_merged_idk = rule(
doc = """Generate a merged IDK at build time from `ninja_root_build_dir`.""",
implementation = _generate_merged_idk,
attrs = {
"collection_name": attr.string(
doc = "Name of the collection to merge",
mandatory = True,
),
"buildable_cpus": attr.string_list(
doc = "List of CPU architectures for which the IDK build will provide build-time support",
mandatory = True,
),
"buildable_api_levels": attr.string_list(
doc = "List of API levels for which the IDK build will provide build-time support",
mandatory = True,
),
"ninja_root_build_dir": attr.string(
doc = "Path to the Ninja build root directory",
mandatory = True,
),
"json_validator": attr.label(
doc = "The JSON validator executable for schema validation",
default = "//build/tools/json_validator:json_validator_valico",
allow_single_file = True,
cfg = "exec",
),
"_schema_files": attr.label(
doc = "The IDK schema files. They must all be in the same directory.",
default = "//build/sdk/meta:idk_schema_files",
),
"_idk_merge_script": attr.label(
default = "//build/sdk/generate_idk:generate_idk_bazel",
executable = True,
cfg = "exec",
),
},
)
# genrule() cannot deal with TreeArtifacts inputs or outputs
# so a custom rule is needed to invoke the idk_to_bazel_sdk
# py_binary() target.
def _generate_bazel_sdk(ctx):
output_dir = ctx.actions.declare_directory(ctx.label.name)
inputs = [ctx.file._buildifier_tool]
if ctx.attr.idk_export_dir:
if ctx.attr.idk_export_label:
fail("Only define one of idk_export_dir or idk_export_label when calling this rule!")
input_idk_files = ctx.files.idk_export_dir
input_idk_path = _find_root_directory(input_idk_files, "/meta/manifest.json")
if not input_idk_path:
fail("IDK meta/manifest.json file missing from %s" % ctx.attr.idk_export_dir)
inputs.extend(input_idk_files)
elif ctx.attr.idk_export_label:
# When a build produces a single TreeArtifact, its DefaultInfo.files should be
# a depset that contains a single File item that covers the whole directory.
input_idk_depset = ctx.attr.idk_export_label[DefaultInfo].files
input_idk_files = input_idk_depset.to_list()
if len(input_idk_files) != 1:
fail("More than one file listed ad idk_export_label: %s" % ctx.attr.idk.export_label)
input_idk_path = input_idk_files[0].path
inputs = depset(inputs, transitive = [input_idk_depset])
else:
fail("Define idk_export_dir or idk_export_label when calling this rule!")
ctx.actions.run(
mnemonic = "IDKtoBazel",
executable = ctx.executable._idk_to_bazel_script,
arguments = [
"--input-idk",
input_idk_path,
"--output-sdk",
output_dir.path,
"--buildifier",
ctx.file._buildifier_tool.path,
],
inputs = inputs,
outputs = [output_dir],
execution_requirements = {
# The input IDK has more than 10,000 files in it, and should
# always be clean due to the way it is generated by Ninja, so
# do not use a sandbox to speed up this action.
"no-sandbox": "1",
# Similarly, a full IDK is currently about 4.7 GiB, and this
# action runs in a few seconds, so avoid remoting it.
"no-remote": "1",
"no-cache": "1",
},
)
return DefaultInfo(files = depset([output_dir]))
generate_bazel_sdk = rule(
doc = """Generate a Bazel SDK at build time from an input IDK directory. Setting either idk_export_dir or idk_export_label is mandatory.""",
implementation = _generate_bazel_sdk,
attrs = {
"idk_export_dir": attr.label(
doc = "Path to IDK export directory, must point to filegroup()",
allow_files = True,
),
"idk_export_label": attr.label(
doc = "Label to a target generating an IDK export directory as a TreeArtifact",
),
"_idk_to_bazel_script": attr.label(
default = "//build/bazel/bazel_sdk:idk_to_bazel_sdk",
executable = True,
cfg = "exec",
),
"_buildifier_tool": attr.label(
default = "//:prebuilt/third_party/buildifier/%s/buildifier" % build_config.host_tag,
allow_single_file = True,
),
},
)
# Similarly, a genrule() cannot be used here because some attributes can
# point to TreeArtifacts generated by generate_bazel_sdk().
def _compare_bazel_sdk_contents(ctx):
output_file = ctx.actions.declare_file(ctx.label.name)
def find_sdk_root_dir(ctx, attr_name):
attr_value = getattr(ctx.attr, attr_name)
if attr_value != None and DefaultInfo in attr_value:
files = attr_value[DefaultInfo].files.to_list()
if len(files) == 1:
# This is the case where the label points to a single
# TreeArtifact generated by generate_bazel_sdk().
return str(files[0])
# Otherwise, the label points to a filegroup() that
# contains lists many IDK files.
else:
# This is the case where the label points to a WORKSPACE.bazel
# file directly.
files = getattr(ctx.files, attr_name)
return _find_root_directory(files, "/WORKSPACE.bazel")
first_sdk_path = find_sdk_root_dir(ctx, "first_sdk")
if not first_sdk_path:
fail("Could not find WORKSPACE.bazel from: %s" % ctx.attr.first_sdk)
second_sdk_path = find_sdk_root_dir(ctx, "second_sdk")
if not second_sdk_path:
fail("Could not find WORKSPACE.bazel from: %s" % ctx.attr.second_sdk)
ctx.actions.run(
mnemonic = "CompareBazelSDKContents",
executable = ctx.executable._comparison_script,
arguments = [
"--first-sdk",
first_sdk_path,
"--second-sdk",
second_sdk_path,
"--stamp-file",
output_file.path,
],
inputs = ctx.files.first_sdk + ctx.files.second_sdk,
outputs = [output_file],
execution_requirements = {
# Disable sandboxing and remoting for the same reason as
# generate_bazel_sdk().
"no-sandbox": "1",
"no-remote": "1",
"no-cache": "1",
},
)
return DefaultInfo(files = depset([output_file]))
compare_bazel_sdk_contents = rule(
doc = """Compare the content of two Bazel SDK repositories.""",
implementation = _compare_bazel_sdk_contents,
attrs = {
"first_sdk": attr.label(
mandatory = True,
doc = """Label to first SDK target, as generated by generate_bazel_sdk(), or to a corresponding WORKSPACE.bazel file.""",
allow_files = True,
),
"second_sdk": attr.label(
doc = """Label to second SDK target, as generated by generate_bazel_sdk(), or to a corresponding WORKSPACE.bazel file.""",
mandatory = True,
allow_files = True,
),
"_comparison_script": attr.label(
default = "//build/bazel/bazel_sdk:compare_bazel_sdks",
executable = True,
cfg = "exec",
),
},
)