2
0
Fork 0
mirror of https://github.com/bazelbuild/rules_cc synced 2024-12-02 01:15:34 +00:00
rules_cc/cc/toolchains
Googler 0069837ab5 Create a cc_directory_tool rule.
BEGIN_PUBLIC
Create a cc_directory_tool rule.

This should allow for easy definition of tools from the sysroot. For example:
cc_directory_tool(
  name = "clang",
  directory = "@sysroot//:sysroot",
  executable = "usr/bin/clang",
)
END_PUBLIC

PiperOrigin-RevId: 639947945
Change-Id: I4c211eb9c0b5fdc6457d9d32ef9250b5384a4ef3
2024-06-03 16:24:27 -07:00
..
actions Add additional actions that are not specified in action_names.bzl. 2024-03-26 22:50:35 -07:00
features Remove macros wrapping rules that take in features. 2024-03-05 14:55:21 -08:00
impl Make toolchains use directory markers instead of strings. 2024-06-03 16:20:34 -07:00
variables Refactor action sets for consistency and simplicity 2024-03-21 15:45:05 -07:00
action_type_config.bzl Rename additional_files to data. 2024-03-13 04:22:40 -07:00
actions.bzl Add additional actions that are not specified in action_names.bzl. 2024-03-26 22:50:35 -07:00
args.bzl Implement flag_group in the new rule-based toolchain. 2024-04-05 01:47:45 -07:00
args_list.bzl BEGIN_PUBLIC 2024-02-23 14:14:27 -08:00
BUILD Implement cc_toolchain_config rule and cc_legacy_file_group rule. 2024-03-05 15:58:06 -08:00
cc_toolchain_info.bzl Gather variable metadata for the new rule-based toolchain. 2024-04-04 16:13:02 -07:00
directory_tool.bzl Create a cc_directory_tool rule. 2024-06-03 16:24:27 -07:00
feature.bzl BEGIN_PUBLIC 2024-02-27 04:45:26 -08:00
feature_constraint.bzl Remove macros wrapping rules that take in features. 2024-03-05 14:55:21 -08:00
feature_set.bzl Remove macros wrapping rules that take in features. 2024-03-05 14:55:21 -08:00
format.bzl Gather variable metadata for the new rule-based toolchain. 2024-04-04 16:13:02 -07:00
mutually_exclusive_category.bzl Implement provides in rule based toolchain. 2024-03-25 23:44:03 -07:00
nested_args.bzl Implement flag_group in the new rule-based toolchain. 2024-04-05 01:47:45 -07:00
README.md BEGIN_PUBLIC 2024-02-26 13:58:42 -08:00
tool.bzl No public description 2024-03-07 07:40:02 -08:00
toolchain.bzl Disable exec_transition_for_inputs in rule-based toolchains 2024-05-16 08:43:58 -07:00

Writing a custom rule_based C++ toolchain with rule-based definition.

Work in progress!

This document serves two purposes:

  • Until complete, this serves as an agreement for the final user-facing API.
  • Once complete, this will serve as onboarding documentation.

This section will be removed once complete.

Step 1: Define tools

A tool is simply a binary. Just like any other bazel binary, a tool can specify additional files required to run.

We can use any bazel binary as an input to anything that requires tools. In the example below, you could use both clang and ld as tools.

# @sysroot//:BUILD
cc_tool(
    name = "clang",
    exe = ":bin/clang",
    execution_requirements = ["requires-mem:24g"],
    data = [...],
)

sh_binary(
    name = "ld",
    srcs = ["ld_wrapper.sh"],
    data = [":bin/ld"],
)
    

Step 2: Generate action configs from those tools

An action config is a mapping from action to:

  • A list of tools, (the first one matching the execution requirements is used).
  • A list of args and features that are always enabled for the action
  • A set of additional files required for the action

Each action can only be specified once in the toolchain. Specifying multiple actions in a single cc_action_type_config is just a shorthand for specifying the same config for every one of those actions.

If you're already familiar with how to define toolchains, the additional files is a replacement for compile_files, link_files, etc.

Additionally, to replace all_files, we add cc_additional_files_for_actions. This allows you to specify that particular files are required for particular actions.

We provide additional_files on the cc_action_type_config as a shorthand for specifying cc_additional_files_for_actions

Warning: Implying a feature that is not listed directly in the toolchain will throw an error. This is to ensure you don't accidentally add a feature to the toolchain.

cc_action_type_config(
    name  = "c_compile",
    actions = ["@rules_cc//actions:all_c_compile"],
    tools = ["@sysroot//:clang"],
    args = [":my_args"],
    implies = [":my_feature"],
    additional_files = ["@sysroot//:all_header_files"],
)

cc_additional_files_for_actions(
    name = "all_action_files",
    actions = ["@rules_cc//actions:all_actions"],
    additional_files = ["@sysroot//:always_needed_files"]
)

Step 3: Define some arguments

Arguments are our replacement for flag_set and env_set. To add arguments to our tools, we take heavy inspiration from bazel's Args type. We provide the same API, with the following caveats:

  • actions specifies which actions the arguments apply to (same as flag_set).
  • requires_any_of is equivalent to with_features on the flag_set.
  • args may be used instead of add if your command-line is only strings.
  • env may be used to add environment variables to the arguments. Environment variables set by later args take priority.
  • By default, all inputs are automatically added to the corresponding actions. additional_files specifies files that are required for an action when using that argument.
cc_args(
    name = "inline",
    actions = ["@rules_cc//actions:all_cpp_compile_actions"],
    args = ["--foo"],
    requires_any_of = [":feature"]
    env = {"FOO": "bar"},
    additional_files = [":file"],
)

For more complex use cases, we use the same API as Args. Values are either:

  • A list of files (or a single file for cc_add_args).
  • Something returning CcVariableInfo, which is equivalent to a list of strings.
cc_variable(
  name = "bar_baz",
  values = ["bar", "baz"],
)

# Expands to CcVariableInfo(values = ["x86_64-unknown-linux-gnu"])
custom_variable_rule(
  name = "triple",
  ...
)

# Taken from https://bazel.build/rules/lib/builtins/Args#add
cc_add_args(
    name = "single",
    arg_name = "--platform",
    value = ":triple", # Either a single file or a cc_variable
    format = "%s",
)

# Taken from https://bazel.build/rules/lib/builtins/Args#add_all
cc_add_args_all(
    name = "multiple",
    arg_name = "--foo",
    values = [":file", ":file_set"], # Either files or cc_variable.
    # map_each not supported. Write a custom rule if you want that.
    format_each = "%s",
    before_each = "--foo",
    omit_if_empty = True,
    uniquify = False,
    # Expand_directories not yet supported.
    terminate_with = "foo",
)

# Taken from https://bazel.build/rules/lib/builtins/Args#add_joined
cc_add_args_joined(
    name = "joined",
    arg_name = "--foo",
    values = [":file", ":file_set"], # Either files or cc_variable.
    join_with = ",",
    # map_each not supported. Write a custom rule if you want that.
    format_each = "%s",
    format_joined = "--foo=%s",
    omit_if_empty = True,
    uniquify = False,
    # Expand_directories not yet supported.
)

cc_args(
    name = "complex",
    actions = ["@rules_cc//actions:c_compile"],
    add = [":single", ":multiple", ":joined"],
)

cc_args_list(
    name = "all_flags",
    args = [":inline", ":complex"],
)

Step 4: Define some features

A feature is a set of args and configurations that can be enabled or disabled.

Although the existing toolchain recommends using features to avoid duplication of definitions, we recommend avoiding using features unless you want the user to be able to enable / disable the feature themselves. This is because we provide alternatives such as cc_args_list to allow combining arguments and specifying them on each action in the action config.

cc_feature(
    name = "my_feature",
    feature_name = "my_feature",
    args = [":all_args"],
    implies = [":other_feature"],
)

Step 5: Generate the toolchain

The cc_toolchain macro:

  • Performs validation on the inputs (eg. no two action configs for a single action)
  • Converts the type-safe providers to the unsafe ones in cc_toolchain_config_lib.bzl
  • Generates a set of providers for each of the filegroups respectively
  • Generates the appropriate native.cc_toolchain invocation.
cc_toolchain(
    name = "toolchain",
    features = [":my_feature"]
    unconditional_args = [":all_warnings"],
    action_type_configs = [":c_compile"],
    additional_files = [":all_action_files"],
)

Ancillary components for type-safe toolchains.

Well-known features

Well-known features will be defined in @rules_cc//features/well_known:*. Any feature with feature_name in the well known features will have to specify overrides.

cc_toolchain is aware of the builtin / well-known features. In order to ensure that a user understands that this overrides the builtin opt feature (I originally thought that it added extra flags to opt, but you still got the default ones, so that can definitely happen), and to ensure that they don't accidentally do so, we will force them to explicitly specify that it overrides the builtin one. This is essentially just an acknowledgement of "I know what I'm doing".

Warning: Specifying two features with the same name is an error, unless one overrides the other.

cc_feature(
    name = "opt",
    ...,
    overrides = "@rules_cc//features/well_known:opt",
)

In addition to well-known features, we could also consider in future iterations to also use known features for partial migrations, where you still imply a feature that's still defined by the legacy API:

# Implementation
def cc_legacy_features(name, features):
  for feature in features:
    cc_known_feature(name = name + "_" + feature.name)
  cc_legacy_features(name = name, features = FEATURES)


# Build file
FOO = feature(name = "foo", args=[arg_group(...)])
FEATURES = [FOO]
cc_legacy_features(name = "legacy_features", features = FEATURES)

cc_feature(name = "bar", implies = [":legacy_features_foo"])

cc_toolchain(
  name = "toolchain",
  legacy_features = ":legacy_features",
  features = [":bar"],
)

Mutual exclusion

Features can be mutually exclusive.

We allow two approaches to mutual exclusion - via features or via categories.

The existing toolchain uses provides for both of these. We rename it so that it makes more sense semantically.

cc_feature(
   name = "incompatible_with_my_feature",
   feature_name = "bar",
   mutually_exclusive = [":my_feature"],
)


# This is an example of how we would define compilation mode.
# Since it already exists, this wouldn't work.
cc_mutual_exclusion_category(
    name = "compilation_mode",
)

cc_feature(
    name = "opt",
    ...
    mutually_exclusive = [":compilation_mode"],
)
cc_feature(
    name = "dbg",
    ...
    mutually_exclusive = [":compilation_mode"],
)

Feature requirements

Feature requirements can come in two formats.

For example:

  • Features can require some subset of features to be enabled.
  • Arguments can require some subset of features to be enabled, but others to be disabled.

This is very confusing for toolchain authors, so we will simplify things with the use of providers:

  • cc_feature will provide feature, feature_set, and with_feature
  • cc_feature_set will provide feature_set and with_feature.
  • cc_feature_constraint will provide with_features only.

We will rename all with_features and requires to requires_any_of, to make it very clear that only one of the requirements needs to be met.

cc_feature_set(
    name = "my_feature_set",
    all_of = [":my_feature"],
)

cc_feature_constraint(
    name = "my_feature_constraint",
    all_of = [":my_feature"],
    none_of = [":my_other_feature"],
)

cc_args(
   name = "foo",
   # All of these provide with_feature.
   requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"]
)

# my_feature_constraint would be an error here.
cc_feature(
   name = "foo",
   # Both of these provide feature_set.
   requires_any_of = [":my_feature", ":my_feature_set"]
   implies = [":my_other_feature", :my_other_feature_set"],
)