Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a fully working toolchain #13

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build --incompatible_enable_cc_toolchain_resolution
build --incompatible_strict_action_env
build --cxxopt=-std=c++17
build:bzlmod --experimental_enable_bzlmod
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
runs-on: ubuntu-20.04
needs: fetch_test_deps
strategy:
fail-fast: false
matrix:
lib:
- "@com_google_googletest//..."
Expand Down
2 changes: 2 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ http_archive(
build_file = "@//third_party:clang_llvm_x86_64_linux_gnu_ubuntu.BUILD",
strip_prefix = "clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04",
)

register_toolchains("//...")
100 changes: 94 additions & 6 deletions cc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
load("@modular_cc_toolchain//cc:features.bzl", "cc_feature", "action_mux")
load("@modular_cc_toolchain//cc:features.bzl", "action_flag_mux", "cc_feature")
load("//cc:action_names.bzl", "ACTION_NAME_GROUPS")
load("//cc:actions.bzl", "cc_action_config")
load("//cc:actions.bzl", "action_tool_mux", "cc_action_config")
load("//cc:cc_toolchain_config.bzl", "cc_toolchain_config")
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")

cc_feature(
name = "garbage_collect_sections",
action_flags = action_mux({
enabled = True,
action_flags = action_flag_mux({
# Put each function and global var in their own linker section.
ACTION_NAME_GROUPS.all_cc_compile_actions: ["-ffunction-sections", "-fdata-sections"],
# Remove unused functions/symbols from linked binary.
ACTION_NAME_GROUPS.all_cc_link_actions: ["-Wl,--gc-sections"],
}),
doc = "Place each function in it's own section so that the linker can discard unused functions",
deps = [":default_clang"],
)

cc_feature(
name = "no_std_includes",
enabled = True,
action_flags = action_flag_mux({
ACTION_NAME_GROUPS.all_cc_compile_actions: [
"-nostdinc",
"-nostdinc++",
"-isystem/usr/include/c++/9",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these include paths listed both here (in this default-enabled feature) and in cxx_builtin_include_directories?

I guess it's just not clear to me what this feature achieves: you first exclude built-in include paths, but then add them right back in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mostly a convenience thing to make sure I have consistent includes. Sometimes clang/gcc will change/add include directories depending on other flags. This is usually true for sanitizers. But it gets rather annoying having the compiler "autodetect" different include paths and having them not matching in bazel, not to mention the error messages when this happens are rather cryptic. You end up with a lot of blackbox inconsistencies between environments. But you are probably right, it's probably not 100% necessary here and adds unnecessary indirection.

It's also an intermediate precursor to having fully hermetic libraries as well. But we aren't quite there yet with the modular toolchains.

"-isystem/usr/include/x86_64-linux-gnu/c++/9",
"-isystem/usr/include/c++/9/backward",
"-isystem/usr/lib/gcc/x86_64-linux-gnu/9/include",
"-isystem/usr/local/include",
"-isystem/usr/include/x86_64-linux-gnu",
"-isystem/usr/include",
],
}),
doc = "Don't use compiler builting include paths.",
)

cc_feature(
name = "link_cpp_stdlib",
enabled = True,
action_flags = {
ACTION_NAMES.cpp_link_executable: ["-lstdc++"],
},
)

cc_action_config(
name = "default_clang",
action_tools = {
ACTION_NAMES.c_compile: "@clang_llvm_x86_64_linux_gnu_ubuntu//:bin/clang",
}
ACTION_NAMES.cpp_compile: "@clang_llvm_x86_64_linux_gnu_ubuntu//:bin/clang++",
ACTION_NAMES.linkstamp_compile: "@clang_llvm_x86_64_linux_gnu_ubuntu//:bin/clang",
ACTION_NAMES.cpp_link_static_library: "@clang_llvm_x86_64_linux_gnu_ubuntu//:bin/llvm-ar",
ACTION_NAMES.cpp_link_executable: "@clang_llvm_x86_64_linux_gnu_ubuntu//:bin/clang++",
},
)

filegroup(
Expand All @@ -28,4 +60,60 @@ filegroup(
visibility = ["//visibility:public"],
)

exports_files(glob(["*.bzl"]))
exports_files(glob(["*.bzl"]))

cc_toolchain_config(
name = "clang_config",
action_configs = [
":default_clang",
],
cc_features = [
":garbage_collect_sections",
":link_cpp_stdlib",
":no_std_includes",
],
cxx_builtin_include_directories = [
"/usr/include/c++/9",
"/usr/include/x86_64-linux-gnu/c++/9",
"/usr/include/c++/9/backward",
"/usr/lib/gcc/x86_64-linux-gnu/9/include",
"/usr/local/include",
"/usr/include/x86_64-linux-gnu",
"/usr/include",
],
)

cc_toolchain(
name = "clang_toolchain",
toolchain_identifier = "empty",
toolchain_config = ":clang_config",
all_files = ":all_toolchain_files",
ar_files = ":all_toolchain_files",
compiler_files = ":all_toolchain_files",
dwp_files = ":all_toolchain_files",
linker_files = ":all_toolchain_files",
objcopy_files = ":all_toolchain_files",
strip_files = ":all_toolchain_files",
# TODO: This should be enabled for clang though it's easier to debug without it
supports_param_files = 0,
supports_header_parsing = 1,
)

filegroup(
name = "all_toolchain_files",
srcs = [":clang_config", "@clang_llvm_x86_64_linux_gnu_ubuntu//:all_files"],
)

toolchain(
name = "linux_clang",
exec_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
toolchain = ":clang_toolchain",
toolchain_type = "@rules_cc//cc:toolchain_type",
)
99 changes: 72 additions & 27 deletions cc/actions.bzl
Original file line number Diff line number Diff line change
@@ -1,40 +1,78 @@
load("@rules_cc//cc:cc_toolchain_config_lib.bzl", "action_config", "tool")
load("//cc:features.bzl", "cc_feature")

def action_tool_mux(action_tool_mapping):
result = {}
for actions, tool in action_tool_mapping.items():
if type(actions) == type(""):
result[actions] = tool
elif type(actions) == type(()):
for action in actions:
result[action] = tool
else:
fail("Unsupported key-type, got type:{}, wanted string or tuple of strings".format(type(actions)))
return result

ActionConfigSetInfo = provider(
doc = "A set of action_configs",
fields = {
"configs": "A set of action configs",
},
)

def _action_config_impl(ctx):
tool_symlink = ctx.actions.declare_file(ctx.label.name)
ctx.actions.symlink(output=tool_symlink,
target_file=ctx.executable.tool,
is_executable=True)
runfiles = ctx.runfiles(files = [tool_symlink])
runfiles = runfiles.merge_all([ctx.attr.tool[DefaultInfo].default_runfiles] +
[dep[DefaultInfo].default_runfiles for dep in ctx.attr.deps])
return [
# TODO(#4): Confirm this still works when bazelbuild/rules_cc#72 is
# merged.
action_config(
dependant_action_configs = []
for dep in ctx.attr.deps:
dependant_action_configs += dep[ActionConfigSetInfo].configs

if ctx.attr.tool:
tool_symlink = ctx.actions.declare_file(ctx.label.name)
ctx.actions.symlink(
output = tool_symlink,
target_file = ctx.executable.tool,
is_executable = True,
)
tool_runfiles = [ctx.attr.tool[DefaultInfo].default_runfiles]
tool_symlink = [tool_symlink]
else:
tool_runfiles = []
tool_symlink = []
runfiles = ctx.runfiles(files = tool_symlink)
runfiles = runfiles.merge_all(tool_runfiles +
[dep[DefaultInfo].default_runfiles for dep in ctx.attr.deps])
configs = []
if ctx.executable.tool:
configs.append(action_config(
action_name = ctx.attr.action_name,
enabled = ctx.attr.enabled,
tools = [tool(tool = ctx.executable.tool)],
))
return [
# TODO(#4): Confirm this still works when bazelbuild/rules_cc#72 is
# merged.
ActionConfigSetInfo(
configs = configs + dependant_action_configs,
),
DefaultInfo(
executable = tool_symlink,
runfiles = runfiles,
files = depset([tool_symlink])
)
runfiles = runfiles,
files = depset(
tool_symlink,
transitive = [
dep[DefaultInfo].files
for dep in ctx.attr.deps
],
),
),
]


_cc_action_config = rule(
_action_config_impl,
attrs = {
"action_name" : attr.string(
"action_name": attr.string(
doc = "The action name to configure the tool paths",
),
"tool": attr.label(
doc = "The tool to use for this action.",
executable = True,
mandatory = True,
cfg = "exec",
allow_single_file = True,
),
Expand All @@ -43,14 +81,18 @@ _cc_action_config = rule(
default = False,
),
"deps": attr.label_list(
doc = "The list of features and actions that this action enables."
)
doc = "The list of features and actions that this action enables.",
),
},
doc = "Maps a tool onto a specific action in a cc toolchain.",
executable = True,
provides = [ActionConfigSetInfo],
)

def cc_action_config(**kwargs):
# NOTE: Under the current action_config macros we can only specify one action name per
# action_config. This leads to a lot of uneccesary boiler plate, this rule + macro
# allows us to bundle up a set of action_configs so that we can map multiple
# tools->actions.
action_tools = kwargs.pop("action_tools")
name = kwargs.pop("name")
deps = kwargs.pop("deps", [])
Expand All @@ -63,12 +105,15 @@ def cc_action_config(**kwargs):
# using the top-level deps.
enabled = False,
deps = deps,
**kwargs,
**kwargs
)

# This is a top level feature that is used to enable the iterated action_configs.
cc_feature(
_cc_action_config(
name = name,
deps = ["{}.{}".format(name, action_name)
for action_name in action_tools.keys()] + deps,
**kwargs,
deps = [
"{}.{}".format(name, action_name)
for action_name in action_tools.keys()
] + deps,
**kwargs
)
73 changes: 73 additions & 0 deletions cc/cc_toolchain_config.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
load("@rules_cc//cc:cc_toolchain_config_lib.bzl", "FeatureSetInfo", "feature")
load("//cc:actions.bzl", "ActionConfigSetInfo")

# TODO(#12): This probably shouldn't exist.
# There are some quirks in how some features are enabled/disabled based on tool_paths.
# As we don't use tool_paths, we need to reimplement some of these quirks?
_QUIRKY_FEATURES = [
feature(
name = "quirk_enable_archiver_flags",
enabled = True,
implies = ["archiver_flags"],
)
]

def _cc_toolchain_config_impl(ctx):
runfiles = ctx.runfiles()
transitive_files = []

# Flatten action_configs
action_configs = []
for action_config_set in ctx.attr.action_configs:
action_configs += action_config_set[ActionConfigSetInfo].configs
runfiles = runfiles.merge_all([action_config_set[DefaultInfo].default_runfiles])
transitive_files.append(action_config_set[DefaultInfo].files)

# Deduplicate features
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Deduplicate action_configs

action_configs = depset(action_configs).to_list()

# Flatten features
features = []
for feature_set in ctx.attr.cc_features:
features += feature_set[FeatureSetInfo].features
runfiles = runfiles.merge_all([feature_set[DefaultInfo].default_runfiles])
transitive_files.append(feature_set[DefaultInfo].files)

# Deduplicate features
features = depset(features).to_list()

return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
toolchain_identifier = "local",
host_system_name = "local",
target_system_name = "local",
target_cpu = "k8",
target_libc = "unknown",
compiler = "clang",
abi_version = "unknown",
abi_libc_version = "unknown",
action_configs = action_configs,
features = features + _QUIRKY_FEATURES,
cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories,
)

cc_toolchain_config = rule(
_cc_toolchain_config_impl,
attrs = {
"cc_features": attr.label_list(
doc = "A list of features to include in the toolchain",
providers = [FeatureSetInfo],
),
"action_configs": attr.label_list(
doc = "A list of action_configs to include in the toolchain",
providers = [ActionConfigSetInfo],
mandatory = True,
),
# TODO(#11): We shouldn't allow this, hard coded system libs make it
# harder to create hermetic toolchains. This should be removed
# before we release the modular toolchains api.
"cxx_builtin_include_directories": attr.string_list(
doc = "A list of system libraries to include",
),
},
)
Loading