Skip to content

Commit

Permalink
Switch to a carbon_binary rule with target config support. (#4076)
Browse files Browse the repository at this point in the history
This switches from a macro that simply wraps genrules to a proper
Starlark rule that runs first compile and then link actions.

Most interestingly, this uses the rule structure to allow using the
Carbon toolchain built either in the target config or the exec config.
While the exec config is more principled and even necessary in a
cross-compile situaiton, it is dramatically less efficient when
developing Carbon as all the binaries and tests outside of our examples
will be built with the target config. This triggers a complete second
build of the toolchain in the exec config for examples before this PR.

It is tempting to try to keep the exec config but make it not cause
redundant actions, but the way Bazel sets up exec and target config
makes it essentially impossible to share their artifacts. There used to
be a hack in Bazel itself to force sharing but it was removed due to it
violating the principled design. Instead, these rules are explicit about
their intent to use the target config, much like a test would be.

I have rigged up a flag that is carefully threaded through a wrapper
macro with `select`s to allow easily switching to the exec configuration
in case it is desired or needed. But the the `.bazelrc` sets the default
to the target config. The `BUILD` file default is the principled `exec`
in case these rules are used by importing into some other Bazel
workspace where we might *only* need the exec config.

The net outcome of this is shaving over 2500 actions off of a clean
rebuild such as is triggered by a version bump to LLVM, including some
of the very slow and expensive compiles of LLVM and Clang themselves.
These would only be triggered if you built the examples so this may
mostly impact our CI latency.

---------

Co-authored-by: Jon Ross-Perkins <[email protected]>
  • Loading branch information
chandlerc and jonmeow authored Jun 24, 2024
1 parent f4b20fc commit 734b54e
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 28 deletions.
6 changes: 6 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ build:clang-tidy --@bazel_clang_tidy//:clang_tidy_config=//:clang_tidy_config
# not firing in our normal builds.
build:clang-tidy --copt=-Wno-unknown-pragmas

# Provide an alias for controlling the `carbon_*` Bazel rules' configuration. We
# enable use of the target config here to make our build and tests more
# efficient, see the documentation in //bazel/carbon_rules/BUILD for details.
build --flag_alias=use_target_config_carbon_rules=//bazel/carbon_rules:use_target_config_carbon_rules
build --use_target_config_carbon_rules

# Default to using a disk cache to minimize re-building LLVM and Clang which we
# try to avoid updating too frequently to minimize rebuild cost. The location
# here can be overridden in the user configuration where needed.
Expand Down
21 changes: 21 additions & 0 deletions bazel/carbon_rules/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")

# Flag controlling whether the target config is used for the `carbon_*` Bazel
# rules. The default is to use the exec config as that is more correct in cases
# where the target config is not compatible with the exec (cross compiling), and
# for library users of Carbon likely the most efficient as it will provide an
# optimized toolchain.
#
# However, for building the Carbon project itself, this will roughly double the
# build cost by forcing a build in both target and exec config. As a consequence
# we disable the flag in the `.bazelrc` of the project for its builds.
bool_flag(
name = "use_target_config_carbon_rules",
build_setting_default = False,
)

config_setting(
name = "use_target_config_carbon_rules_config",
flag_values = {":use_target_config_carbon_rules": "True"},
)
130 changes: 102 additions & 28 deletions bazel/carbon_rules/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

"""Provides rules for building Carbon files using the toolchain."""

def carbon_binary(name, srcs):
"""Compiles a Carbon binary.
def _carbon_binary_impl(ctx):
toolchain_driver = ctx.executable.internal_exec_toolchain_driver
toolchain_data = ctx.files.internal_exec_toolchain_data

Args:
name: The name of the build target.
srcs: List of Carbon source files to compile.
"""
for src in srcs:
# If the exec driver isn't provided, that means we're trying to use a target
# config toolchain, likely to avoid build overhead of two configs.
if toolchain_driver == None:
toolchain_driver = ctx.executable.internal_target_toolchain_driver
toolchain_data = ctx.files.internal_target_toolchain_data

objs = []
for src in ctx.files.srcs:
# Build each source file. For now, we pass all sources to each compile
# because we don't have visibility into dependencies and have no way to
# specify multiple output files. Object code for each input is written
Expand All @@ -23,32 +27,102 @@ def carbon_binary(name, srcs):
#
# TODO: Switch to the `prefix_root` based rule similar to linking when
# the prelude moves there.
out = src + ".o"
srcs_reordered = [s for s in srcs if s != src] + [src]
native.genrule(
name = src + ".compile",
tools = [
"//toolchain/install:prefix_root/bin/carbon",
"//toolchain/install:install_data",
],
cmd = "$(execpath //toolchain/install:prefix_root/bin/carbon) compile --output=$@ $(SRCS)",
srcs = srcs_reordered,
outs = [out],
out = ctx.actions.declare_file("_objs/{0}/{1}o".format(
ctx.label.name,
src.short_path.removeprefix(ctx.label.package).removesuffix(src.extension),
))
objs.append(out)
srcs_reordered = [s for s in ctx.files.srcs if s != src] + [src]
ctx.actions.run(
outputs = [out],
inputs = srcs_reordered,
executable = toolchain_driver,
tools = depset(toolchain_data),
arguments = ["compile", "--output=" + out.path] + [s.path for s in srcs_reordered],
mnemonic = "CarbonCompile",
progress_message = "Compiling " + src.short_path,
)

# For now, we assume that the prelude doesn't produce any necessary object
# code, and don't include the .o files for //core/prelude... in the final
# linked binary.
#
# TODO: This will need to be revisited eventually.
objs = [s + ".o" for s in srcs]
native.genrule(
name = name + ".link",
tools = [
"//toolchain/install:prefix_root/bin/carbon",
"//toolchain/install:install_data",
],
cmd = "$(execpath //toolchain/install:prefix_root/bin/carbon) link --output=$@ $(SRCS)",
srcs = objs,
outs = [name],
bin = ctx.actions.declare_file(ctx.label.name)
ctx.actions.run(
outputs = [bin],
inputs = objs,
executable = toolchain_driver,
tools = depset(toolchain_data),
arguments = ["link", "--output=" + bin.path] + [o.path for o in objs],
mnemonic = "CarbonLink",
progress_message = "Linking " + bin.short_path,
)
return [DefaultInfo(files = depset([bin]))]

_carbon_binary_internal = rule(
implementation = _carbon_binary_impl,
attrs = {
# The exec config toolchain driver and data. These will be `None` when
# using the target config and populated when using the exec config. We
# have to use duplicate attributes here and below to have different
# `cfg` settings, as that isn't `select`-able, and we'll use `select`s
# when populating these.
"internal_exec_toolchain_data": attr.label(
cfg = "exec",
),
"internal_exec_toolchain_driver": attr.label(
allow_single_file = True,
executable = True,
cfg = "exec",
),

# The target config toolchain driver and data. These will be 'None' when
# using the exec config and populated when using the target config. We
# have to use duplicate attributes here and below to have different
# `cfg` settings, as that isn't `select`-able, and we'll use `select`s
# when populating these.
"internal_target_toolchain_data": attr.label(
cfg = "target",
),
"internal_target_toolchain_driver": attr.label(
allow_single_file = True,
executable = True,
cfg = "target",
),
"srcs": attr.label_list(allow_files = [".carbon"]),
},
)

def carbon_binary(name, srcs):
"""Compiles a Carbon binary.
Args:
name: The name of the build target.
srcs: List of Carbon source files to compile.
"""
_carbon_binary_internal(
name = name,
srcs = srcs,

# We synthesize two sets of attributes from mirrored `select`s here
# because we want to select on an internal property of these attributes
# but that isn't `select`-able. Instead, we have both attributes and
# `select` which one we use.
internal_exec_toolchain_driver = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
"//conditions:default": "//toolchain/install:prefix_root/bin/carbon",
}),
internal_exec_toolchain_data = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
"//conditions:default": "//toolchain/install:install_data",
}),
internal_target_toolchain_driver = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:prefix_root/bin/carbon",
"//conditions:default": None,
}),
internal_target_toolchain_data = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:install_data",
"//conditions:default": None,
}),
)

0 comments on commit 734b54e

Please sign in to comment.