blob: ce8d428996df24ae7aa6894bb8461cfad0760e21 [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.
"""Defines a WORKSPACE rule for loading a version of clang."""
load("//fuchsia/workspace:utils.bzl", "normalize_os", "workspace_path")
load("//cipd:defs.bzl", "fetch_cipd_contents")
# Base URL for Fuchsia clang archives.
_CLANG_URL_TEMPLATE = "https://chrome-infra-packages.appspot.com/dl/fuchsia/third_party/clang/{os}-amd64/+/{tag}"
_LOCAL_FUCHSIA_PLATFORM_BUILD = "LOCAL_FUCHSIA_PLATFORM_BUILD"
_LOCAL_FUCHSIA_CLANG_DIR = "../../prebuilt/third_party/clang"
def _clang_url(os, tag):
# Return the URL of clang given an Operating System string and a CIPD tag.
return _CLANG_URL_TEMPLATE.format(os = os, tag = tag)
def _instantiate_local_archive(ctx):
# Extracts the clang from a local archive file.
ctx.report_progress("Extracting local clang archive")
ctx.extract(archive = ctx.attr.local_archive)
def _instantiate_from_local_dir(ctx, local_clang):
# buildifier: disable=print
# local_path can be either a string or Path object.
if type(local_clang) == type("str"):
local_clang = ctx.path(local_clang)
ctx.report_progress("Copying local clang from %s" % local_clang)
# Symlink top-level items from Clang prebuilt install to repository directory
# Note that this is possible because our C++ toolchain configuration redefine
# the "dependency_file" feature to use relative file paths.
for f in local_clang.readdir():
ctx.symlink(f, f.basename)
def _instantiate_from_local_fuchsia_tree(ctx):
# Copies clang prebuilt from a local Fuchsia platform tree.
local_fuchsia_dir = ctx.os.environ[_LOCAL_FUCHSIA_PLATFORM_BUILD]
local_clang = ctx.path("%s/%s" % (local_fuchsia_dir, _LOCAL_FUCHSIA_CLANG_DIR))
if not local_clang.exists:
fail("Cannot find clang prebuilt in local Fuchsia tree. Please ensure it exists: %s" % str(local_clang))
local_clang_archs = local_clang.readdir()
if len(local_clang_archs) != 1:
fail("Expected a single host architecture subdirectory in local clang: %s" % str(local_clang))
local_clang_arch = local_clang_archs[0]
_instantiate_from_local_dir(ctx, local_clang_arch)
def _fuchsia_clang_repository_impl(ctx):
# Pre-evaluate paths of templated output files so that the repository does not need to be
# re-fetched after potentially talking to the network
ctx.path("BUILD.bazel")
ctx.path("cc_toolchain_config.bzl")
crosstool_template = Label("//fuchsia/workspace/clang_templates:crosstool_template.BUILD")
toolchain_config_template = Label("//fuchsia/workspace/clang_templates:cc_toolchain_config_template.bzl")
ctx.path(crosstool_template)
ctx.path(toolchain_config_template)
# Hack to get the path to the sysroot directory, see
# https://github.com/bazelbuild/bazel/issues/3901
fuchsia_sdk_path = ctx.path(ctx.attr.sdk_root_label.relative("//:BUILD.bazel")).dirname
ctx.file("WORKSPACE.bazel", content = "")
ctx.symlink(
Label("//fuchsia/workspace/clang_templates:defs.bzl"),
"defs.bzl",
)
# Create symlinks to the @fuchsia_sdk sysroots, which allows defining
# a filegroup() in this repository that uses glob() to list all the files.
# (see fuchsia-sysroot-{arch} definitions in cc_toolchain_config.bzl.
ctx.symlink(str(fuchsia_sdk_path) + "/arch/arm64/sysroot", "fuchsia_sysroot_aarch64")
ctx.symlink(str(fuchsia_sdk_path) + "/arch/x64/sysroot", "fuchsia_sysroot_x86_64")
normalized_os = normalize_os(ctx)
if ctx.attr.local_path:
local_clang = workspace_path(ctx, ctx.attr.local_path)
_instantiate_from_local_dir(ctx, local_clang)
elif _LOCAL_FUCHSIA_PLATFORM_BUILD in ctx.os.environ:
_instantiate_from_local_fuchsia_tree(ctx)
elif ctx.attr.local_archive:
_instantiate_local_archive(ctx)
elif ctx.attr.cipd_tag:
sha256 = ""
if ctx.attr.sha256:
sha256 = ctx.attr.sha256[normalized_os]
ctx.download_and_extract(
_clang_url(normalized_os, ctx.attr.cipd_tag),
type = "zip",
sha256 = sha256,
)
else:
fetch_cipd_contents(ctx, ctx.attr._cipd_bin, ctx.attr._cipd_ensure_file)
# find the clang version as the largest number
clang_version = "0"
for v in ctx.path("lib/clang").readdir():
v = str(v.basename)
if v.split(".")[0].isdigit() and v > clang_version:
clang_version = v
# Set up the BUILD file from the Fuchsia SDK.
ctx.template(
"BUILD.bazel",
crosstool_template,
substitutions = {
"%{CLANG_VERSION}": clang_version,
},
)
# To properly use a custom Bazel C++ sysroot, the following are necessary:
#
# - The cc_toolchain() `compiler_files` argument must list
# the sysroot headers, to ensure they are exposed to the sandbox
# at compile time.
#
# - The cc_toolchain() `linker_files` argument must list
# the link-time sysroot libraries, to ensure they are exposed to
# the sandbox at link time.
#
# - The create_cc_toolchain_config_info() `builtin_sysroot` argument
# is a string that is passed directly to the compile and link command
# lines (as `--sysroot=<value>`) without further interpretation.
#
# To ensure that build outputs are remote cacheable, using a path
# relative to the execroot is necessary. An absolute path will work,
# but will pollute the cache keys and prevent sharing artifacts between
# different workspaces.
#
# - The create_cc_toolchain_config_info() `cxx_builtin_include_directories`
# must list a series of directories that Bazel uses to ensure that the
# compiler-generated .d files only contain input paths that belong to
# this list.
#
# These path strings can have several formats (for full details
# see the CcToolchainProviderHelper.resolveIncludeDir() method in
# the Bazel sources):
#
# - An absolute path is used as-is, and does not hurt cacheability.
#
# - A prefix of %sysroot% is replaced with the sysroot path.
#
# - A prefix of %crosstool_top% is replaced with the crosstool directory,
# which may *not* be the same as this repository's directory. It is
# best to avoid them when using platforms.
#
# - A relative directory is resolved relative to %crosstop_top% as well!
#
# - A %package(<label>)/folder form is resolved properly. However, this
# doesn't seem to generate working results.
#
# For now, use absolute paths because they are simple and they work,
# and there is no way to debug the expansions performed for these path
# expressions!
#
# The sysroot path value that will be used in build commands must
# be relative to the execroot to ensure remote cacheability of build.
#
# The line below uses ctx.name because enabling BzlMod will rename the
# repository's directory path when it is generated by a module extension.
sysroot_path_prefix = "external/%s/fuchsia_sysroot_" % ctx.name
# Set up the toolchain config file from the template.
ctx.template(
"cc_toolchain_config.bzl",
toolchain_config_template,
substitutions = {
"%{SYSROOT_PATH_PREFIX}": sysroot_path_prefix,
"%{CROSSTOOL_ROOT}": str(ctx.path(".")),
"%{CLANG_VERSION}": clang_version,
"%{SDK_ROOT}": ctx.attr.sdk_root_label.workspace_name,
},
)
fuchsia_clang_repository = repository_rule(
doc = """
Loads a particular version of clang.
One of cipd_tag or local_archive must be set.
If cipd_tag is set, sha256 can optionally be set to verify the downloaded file
and to allow Bazel to cache the file.
If cipd_tag is not set, local_archive must be set to the path of a core IDK
archive file.
""",
implementation = _fuchsia_clang_repository_impl,
environ = [_LOCAL_FUCHSIA_PLATFORM_BUILD],
attrs = {
"cipd_tag": attr.string(
doc = "CIPD tag for the version to load.",
),
"sha256": attr.string_dict(
doc = "Optional SHA-256 hash of the clang archive. Valid keys are mac and linux",
),
"local_archive": attr.string(
doc = "local clang archive file.",
),
"local_path": attr.string(
doc = "local clang installation directory, relative to workspace root.",
),
"sdk_root_label": attr.label(
doc = "The fuchsia sdk root label. eg: @fuchsia_sdk",
default = "@fuchsia_sdk",
),
"rules_fuchsia_root_label": attr.label(
doc = "The fuchsia rules root label. eg: @rules_fuchsia",
default = "@rules_fuchsia",
),
"_cipd_ensure_file": attr.label(
doc = "A cipd ensure file to use to download clang.",
default = "//fuchsia/manifests:clang.ensure",
),
"_cipd_bin": attr.label(
doc = "The cipd binary that will be used to download the sdk",
default = "@cipd_tool//:cipd",
),
},
)
def _fuchsia_clang_repository_ext(ctx):
cipd_tag = None
sha256 = None
local_archive = None
local_path = None
sdk_root_label = None
rules_fuchsia_root_label = None
for mod in ctx.modules:
# only the root module can set tags
if mod.is_root:
if mod.tags.labels:
labels = mod.tags.labels[0]
sdk_root_label = labels.sdk_root_label
rules_fuchsia_root_label = labels.rules_fuchsia_root_label
if mod.tags.cipd:
cipd = mod.tags.cipd[0]
cipd_tag = cipd.cipd_tag
sha256 = cipd.sha256
if mod.tags.archive:
local_archive = mod.tags.archive[0].local_archive
if mod.tags.local:
local_path = mod.tags.local[0].local_path
fuchsia_clang_repository(
name = "fuchsia_clang",
cipd_tag = cipd_tag,
sha256 = sha256,
local_archive = local_archive,
local_path = local_path,
sdk_root_label = sdk_root_label,
rules_fuchsia_root_label = rules_fuchsia_root_label,
)
_labels_tag = tag_class(
attrs = {
"sdk_root_label": attr.label(
doc = "The fuchsia sdk root label. eg: @fuchsia_sdk",
default = "@fuchsia_sdk",
),
"rules_fuchsia_root_label": attr.label(
doc = "The fuchsia rules root label. eg: @rules_fuchsia",
default = "@rules_fuchsia",
),
},
)
_cipd_tag = tag_class(
attrs = {
"cipd_tag": attr.string(
doc = "CIPD tag for the version to load.",
),
"sha256": attr.string_dict(
doc = "Optional SHA-256 hash of the clang archive. Valid keys are mac and linux",
),
},
)
_archive_tag = tag_class(
attrs = {"local_archive": attr.string(
doc = "local clang archive file.",
)},
)
_local_tag = tag_class(
attrs = {"local_path": attr.string(
doc = "local clang installation path, relative to workspace_root.",
)},
)
fuchsia_clang_ext = module_extension(
implementation = _fuchsia_clang_repository_ext,
tag_classes = {
"labels": _labels_tag,
"cipd": _cipd_tag,
"archive": _archive_tag,
"local": _local_tag,
},
)