1*eed53cd4SHONG Yifan# Writing a custom rule_based C++ toolchain with rule-based definition. 2*eed53cd4SHONG Yifan 3*eed53cd4SHONG YifanWork in progress! 4*eed53cd4SHONG Yifan 5*eed53cd4SHONG YifanThis document serves two purposes: 6*eed53cd4SHONG Yifan* Until complete, this serves as an agreement for the final user-facing API. 7*eed53cd4SHONG Yifan* Once complete, this will serve as onboarding documentation. 8*eed53cd4SHONG Yifan 9*eed53cd4SHONG YifanThis section will be removed once complete. 10*eed53cd4SHONG Yifan 11*eed53cd4SHONG Yifan## Step 1: Define tools 12*eed53cd4SHONG YifanA tool is simply a binary. Just like any other bazel binary, a tool can specify 13*eed53cd4SHONG Yifanadditional files required to run. 14*eed53cd4SHONG Yifan 15*eed53cd4SHONG YifanWe can use any bazel binary as an input to anything that requires tools. In the 16*eed53cd4SHONG Yifanexample below, you could use both clang and ld as tools. 17*eed53cd4SHONG Yifan 18*eed53cd4SHONG Yifan``` 19*eed53cd4SHONG Yifan# @sysroot//:BUILD 20*eed53cd4SHONG Yifancc_tool( 21*eed53cd4SHONG Yifan name = "clang", 22*eed53cd4SHONG Yifan exe = ":bin/clang", 23*eed53cd4SHONG Yifan execution_requirements = ["requires-mem:24g"], 24*eed53cd4SHONG Yifan data = [...], 25*eed53cd4SHONG Yifan) 26*eed53cd4SHONG Yifan 27*eed53cd4SHONG Yifansh_binary( 28*eed53cd4SHONG Yifan name = "ld", 29*eed53cd4SHONG Yifan srcs = ["ld_wrapper.sh"], 30*eed53cd4SHONG Yifan data = [":bin/ld"], 31*eed53cd4SHONG Yifan) 32*eed53cd4SHONG Yifan 33*eed53cd4SHONG Yifan``` 34*eed53cd4SHONG Yifan 35*eed53cd4SHONG Yifan## Step 2: Generate action configs from those tools 36*eed53cd4SHONG YifanAn action config is a mapping from action to: 37*eed53cd4SHONG Yifan 38*eed53cd4SHONG Yifan* A list of tools, (the first one matching the execution requirements is used). 39*eed53cd4SHONG Yifan* A list of args and features that are always enabled for the action 40*eed53cd4SHONG Yifan* A set of additional files required for the action 41*eed53cd4SHONG Yifan 42*eed53cd4SHONG YifanEach action can only be specified once in the toolchain. Specifying multiple 43*eed53cd4SHONG Yifanactions in a single `cc_action_type_config` is just a shorthand for specifying the 44*eed53cd4SHONG Yifansame config for every one of those actions. 45*eed53cd4SHONG Yifan 46*eed53cd4SHONG YifanIf you're already familiar with how to define toolchains, the additional files 47*eed53cd4SHONG Yifanis a replacement for `compile_files`, `link_files`, etc. 48*eed53cd4SHONG Yifan 49*eed53cd4SHONG YifanAdditionally, to replace `all_files`, we add `cc_additional_files_for_actions`. 50*eed53cd4SHONG YifanThis allows you to specify that particular files are required for particular 51*eed53cd4SHONG Yifanactions. 52*eed53cd4SHONG Yifan 53*eed53cd4SHONG YifanWe provide `additional_files` on the `cc_action_type_config` as a shorthand for 54*eed53cd4SHONG Yifanspecifying `cc_additional_files_for_actions` 55*eed53cd4SHONG Yifan 56*eed53cd4SHONG YifanWarning: Implying a feature that is not listed directly in the toolchain will throw 57*eed53cd4SHONG Yifanan error. This is to ensure you don't accidentally add a feature to the 58*eed53cd4SHONG Yifantoolchain. 59*eed53cd4SHONG Yifan 60*eed53cd4SHONG Yifan``` 61*eed53cd4SHONG Yifancc_action_type_config( 62*eed53cd4SHONG Yifan name = "c_compile", 63*eed53cd4SHONG Yifan actions = ["@rules_cc//actions:all_c_compile"], 64*eed53cd4SHONG Yifan tools = ["@sysroot//:clang"], 65*eed53cd4SHONG Yifan args = [":my_args"], 66*eed53cd4SHONG Yifan implies = [":my_feature"], 67*eed53cd4SHONG Yifan additional_files = ["@sysroot//:all_header_files"], 68*eed53cd4SHONG Yifan) 69*eed53cd4SHONG Yifan 70*eed53cd4SHONG Yifancc_additional_files_for_actions( 71*eed53cd4SHONG Yifan name = "all_action_files", 72*eed53cd4SHONG Yifan actions = ["@rules_cc//actions:all_actions"], 73*eed53cd4SHONG Yifan additional_files = ["@sysroot//:always_needed_files"] 74*eed53cd4SHONG Yifan) 75*eed53cd4SHONG Yifan``` 76*eed53cd4SHONG Yifan 77*eed53cd4SHONG Yifan## Step 3: Define some arguments 78*eed53cd4SHONG YifanArguments are our replacement for `flag_set` and `env_set`. To add arguments to 79*eed53cd4SHONG Yifanour tools, we take heavy inspiration from bazel's 80*eed53cd4SHONG Yifan[`Args`](https://bazel.build/rules/lib/builtins/Args) type. We provide the same 81*eed53cd4SHONG YifanAPI, with the following caveats: 82*eed53cd4SHONG Yifan* `actions` specifies which actions the arguments apply to (same as `flag_set`). 83*eed53cd4SHONG Yifan* `requires_any_of` is equivalent to `with_features` on the `flag_set`. 84*eed53cd4SHONG Yifan* `args` may be used instead of `add` if your command-line is only strings. 85*eed53cd4SHONG Yifan* `env` may be used to add environment variables to the arguments. Environment 86*eed53cd4SHONG Yifan variables set by later args take priority. 87*eed53cd4SHONG Yifan* By default, all inputs are automatically added to the corresponding actions. 88*eed53cd4SHONG Yifan `additional_files` specifies files that are required for an action when using 89*eed53cd4SHONG Yifan that argument. 90*eed53cd4SHONG Yifan 91*eed53cd4SHONG Yifan``` 92*eed53cd4SHONG Yifancc_args( 93*eed53cd4SHONG Yifan name = "inline", 94*eed53cd4SHONG Yifan actions = ["@rules_cc//actions:all_cpp_compile_actions"], 95*eed53cd4SHONG Yifan args = ["--foo"], 96*eed53cd4SHONG Yifan requires_any_of = [":feature"] 97*eed53cd4SHONG Yifan env = {"FOO": "bar"}, 98*eed53cd4SHONG Yifan additional_files = [":file"], 99*eed53cd4SHONG Yifan) 100*eed53cd4SHONG Yifan``` 101*eed53cd4SHONG Yifan 102*eed53cd4SHONG YifanFor more complex use cases, we use the same API as `Args`. Values are either: 103*eed53cd4SHONG Yifan* A list of files (or a single file for `cc_add_args`). 104*eed53cd4SHONG Yifan* Something returning `CcVariableInfo`, which is equivalent to a list of strings. 105*eed53cd4SHONG Yifan 106*eed53cd4SHONG Yifan``` 107*eed53cd4SHONG Yifancc_variable( 108*eed53cd4SHONG Yifan name = "bar_baz", 109*eed53cd4SHONG Yifan values = ["bar", "baz"], 110*eed53cd4SHONG Yifan) 111*eed53cd4SHONG Yifan 112*eed53cd4SHONG Yifan# Expands to CcVariableInfo(values = ["x86_64-unknown-linux-gnu"]) 113*eed53cd4SHONG Yifancustom_variable_rule( 114*eed53cd4SHONG Yifan name = "triple", 115*eed53cd4SHONG Yifan ... 116*eed53cd4SHONG Yifan) 117*eed53cd4SHONG Yifan 118*eed53cd4SHONG Yifan# Taken from https://bazel.build/rules/lib/builtins/Args#add 119*eed53cd4SHONG Yifancc_add_args( 120*eed53cd4SHONG Yifan name = "single", 121*eed53cd4SHONG Yifan arg_name = "--platform", 122*eed53cd4SHONG Yifan value = ":triple", # Either a single file or a cc_variable 123*eed53cd4SHONG Yifan format = "%s", 124*eed53cd4SHONG Yifan) 125*eed53cd4SHONG Yifan 126*eed53cd4SHONG Yifan# Taken from https://bazel.build/rules/lib/builtins/Args#add_all 127*eed53cd4SHONG Yifancc_add_args_all( 128*eed53cd4SHONG Yifan name = "multiple", 129*eed53cd4SHONG Yifan arg_name = "--foo", 130*eed53cd4SHONG Yifan values = [":file", ":file_set"], # Either files or cc_variable. 131*eed53cd4SHONG Yifan # map_each not supported. Write a custom rule if you want that. 132*eed53cd4SHONG Yifan format_each = "%s", 133*eed53cd4SHONG Yifan before_each = "--foo", 134*eed53cd4SHONG Yifan omit_if_empty = True, 135*eed53cd4SHONG Yifan uniquify = False, 136*eed53cd4SHONG Yifan # Expand_directories not yet supported. 137*eed53cd4SHONG Yifan terminate_with = "foo", 138*eed53cd4SHONG Yifan) 139*eed53cd4SHONG Yifan 140*eed53cd4SHONG Yifan# Taken from https://bazel.build/rules/lib/builtins/Args#add_joined 141*eed53cd4SHONG Yifancc_add_args_joined( 142*eed53cd4SHONG Yifan name = "joined", 143*eed53cd4SHONG Yifan arg_name = "--foo", 144*eed53cd4SHONG Yifan values = [":file", ":file_set"], # Either files or cc_variable. 145*eed53cd4SHONG Yifan join_with = ",", 146*eed53cd4SHONG Yifan # map_each not supported. Write a custom rule if you want that. 147*eed53cd4SHONG Yifan format_each = "%s", 148*eed53cd4SHONG Yifan format_joined = "--foo=%s", 149*eed53cd4SHONG Yifan omit_if_empty = True, 150*eed53cd4SHONG Yifan uniquify = False, 151*eed53cd4SHONG Yifan # Expand_directories not yet supported. 152*eed53cd4SHONG Yifan) 153*eed53cd4SHONG Yifan 154*eed53cd4SHONG Yifancc_args( 155*eed53cd4SHONG Yifan name = "complex", 156*eed53cd4SHONG Yifan actions = ["@rules_cc//actions:c_compile"], 157*eed53cd4SHONG Yifan add = [":single", ":multiple", ":joined"], 158*eed53cd4SHONG Yifan) 159*eed53cd4SHONG Yifan 160*eed53cd4SHONG Yifancc_args_list( 161*eed53cd4SHONG Yifan name = "all_flags", 162*eed53cd4SHONG Yifan args = [":inline", ":complex"], 163*eed53cd4SHONG Yifan) 164*eed53cd4SHONG Yifan``` 165*eed53cd4SHONG Yifan 166*eed53cd4SHONG Yifan## Step 4: Define some features 167*eed53cd4SHONG YifanA feature is a set of args and configurations that can be enabled or disabled. 168*eed53cd4SHONG Yifan 169*eed53cd4SHONG YifanAlthough the existing toolchain recommends using features to avoid duplication 170*eed53cd4SHONG Yifanof definitions, we recommend avoiding using features unless you want the user to 171*eed53cd4SHONG Yifanbe able to enable / disable the feature themselves. This is because we provide 172*eed53cd4SHONG Yifanalternatives such as `cc_args_list` to allow combining arguments and 173*eed53cd4SHONG Yifanspecifying them on each action in the action config. 174*eed53cd4SHONG Yifan 175*eed53cd4SHONG Yifan``` 176*eed53cd4SHONG Yifancc_feature( 177*eed53cd4SHONG Yifan name = "my_feature", 178*eed53cd4SHONG Yifan feature_name = "my_feature", 179*eed53cd4SHONG Yifan args = [":all_args"], 180*eed53cd4SHONG Yifan implies = [":other_feature"], 181*eed53cd4SHONG Yifan) 182*eed53cd4SHONG Yifan``` 183*eed53cd4SHONG Yifan 184*eed53cd4SHONG Yifan## Step 5: Generate the toolchain 185*eed53cd4SHONG YifanThe `cc_toolchain` macro: 186*eed53cd4SHONG Yifan 187*eed53cd4SHONG Yifan* Performs validation on the inputs (eg. no two action configs for a single 188*eed53cd4SHONG Yifan action) 189*eed53cd4SHONG Yifan* Converts the type-safe providers to the unsafe ones in 190*eed53cd4SHONG Yifan `cc_toolchain_config_lib.bzl` 191*eed53cd4SHONG Yifan* Generates a set of providers for each of the filegroups respectively 192*eed53cd4SHONG Yifan* Generates the appropriate `native.cc_toolchain` invocation. 193*eed53cd4SHONG Yifan 194*eed53cd4SHONG Yifan``` 195*eed53cd4SHONG Yifancc_toolchain( 196*eed53cd4SHONG Yifan name = "toolchain", 197*eed53cd4SHONG Yifan features = [":my_feature"] 198*eed53cd4SHONG Yifan unconditional_args = [":all_warnings"], 199*eed53cd4SHONG Yifan action_type_configs = [":c_compile"], 200*eed53cd4SHONG Yifan additional_files = [":all_action_files"], 201*eed53cd4SHONG Yifan) 202*eed53cd4SHONG Yifan``` 203*eed53cd4SHONG Yifan 204*eed53cd4SHONG Yifan# Ancillary components for type-safe toolchains. 205*eed53cd4SHONG Yifan## Well-known features 206*eed53cd4SHONG YifanWell-known features will be defined in `@rules_cc//features/well_known:*`. 207*eed53cd4SHONG YifanAny feature with `feature_name` in the well known features will have to specify 208*eed53cd4SHONG Yifanoverrides. 209*eed53cd4SHONG Yifan 210*eed53cd4SHONG Yifan`cc_toolchain` is aware of the builtin / well-known features. In order to 211*eed53cd4SHONG Yifanensure that a user understands that this overrides the builtin opt feature (I 212*eed53cd4SHONG Yifanoriginally thought that it added extra flags to opt, but you still got the 213*eed53cd4SHONG Yifandefault ones, so that can definitely happen), and to ensure that they don't 214*eed53cd4SHONG Yifanaccidentally do so, we will force them to explicitly specify that it overrides 215*eed53cd4SHONG Yifanthe builtin one. This is essentially just an acknowledgement of "I know what 216*eed53cd4SHONG YifanI'm doing". 217*eed53cd4SHONG Yifan 218*eed53cd4SHONG YifanWarning: Specifying two features with the same name is an error, unless one 219*eed53cd4SHONG Yifanoverrides the other. 220*eed53cd4SHONG Yifan 221*eed53cd4SHONG Yifan``` 222*eed53cd4SHONG Yifancc_feature( 223*eed53cd4SHONG Yifan name = "opt", 224*eed53cd4SHONG Yifan ..., 225*eed53cd4SHONG Yifan overrides = "@rules_cc//features/well_known:opt", 226*eed53cd4SHONG Yifan) 227*eed53cd4SHONG Yifan``` 228*eed53cd4SHONG Yifan 229*eed53cd4SHONG YifanIn addition to well-known features, we could also consider in future iterations 230*eed53cd4SHONG Yifanto also use known features for partial migrations, where you still imply a 231*eed53cd4SHONG Yifanfeature that's still defined by the legacy API: 232*eed53cd4SHONG Yifan 233*eed53cd4SHONG Yifan``` 234*eed53cd4SHONG Yifan# Implementation 235*eed53cd4SHONG Yifandef cc_legacy_features(name, features): 236*eed53cd4SHONG Yifan for feature in features: 237*eed53cd4SHONG Yifan cc_known_feature(name = name + "_" + feature.name) 238*eed53cd4SHONG Yifan cc_legacy_features(name = name, features = FEATURES) 239*eed53cd4SHONG Yifan 240*eed53cd4SHONG Yifan 241*eed53cd4SHONG Yifan# Build file 242*eed53cd4SHONG YifanFOO = feature(name = "foo", args=[arg_group(...)]) 243*eed53cd4SHONG YifanFEATURES = [FOO] 244*eed53cd4SHONG Yifancc_legacy_features(name = "legacy_features", features = FEATURES) 245*eed53cd4SHONG Yifan 246*eed53cd4SHONG Yifancc_feature(name = "bar", implies = [":legacy_features_foo"]) 247*eed53cd4SHONG Yifan 248*eed53cd4SHONG Yifancc_toolchain( 249*eed53cd4SHONG Yifan name = "toolchain", 250*eed53cd4SHONG Yifan legacy_features = ":legacy_features", 251*eed53cd4SHONG Yifan features = [":bar"], 252*eed53cd4SHONG Yifan) 253*eed53cd4SHONG Yifan``` 254*eed53cd4SHONG Yifan 255*eed53cd4SHONG Yifan## Mutual exclusion 256*eed53cd4SHONG YifanFeatures can be mutually exclusive. 257*eed53cd4SHONG Yifan 258*eed53cd4SHONG YifanWe allow two approaches to mutual exclusion - via features or via categories. 259*eed53cd4SHONG Yifan 260*eed53cd4SHONG YifanThe existing toolchain uses `provides` for both of these. We rename it so that 261*eed53cd4SHONG Yifanit makes more sense semantically. 262*eed53cd4SHONG Yifan 263*eed53cd4SHONG Yifan``` 264*eed53cd4SHONG Yifancc_feature( 265*eed53cd4SHONG Yifan name = "incompatible_with_my_feature", 266*eed53cd4SHONG Yifan feature_name = "bar", 267*eed53cd4SHONG Yifan mutually_exclusive = [":my_feature"], 268*eed53cd4SHONG Yifan) 269*eed53cd4SHONG Yifan 270*eed53cd4SHONG Yifan 271*eed53cd4SHONG Yifan# This is an example of how we would define compilation mode. 272*eed53cd4SHONG Yifan# Since it already exists, this wouldn't work. 273*eed53cd4SHONG Yifancc_mutual_exclusion_category( 274*eed53cd4SHONG Yifan name = "compilation_mode", 275*eed53cd4SHONG Yifan) 276*eed53cd4SHONG Yifan 277*eed53cd4SHONG Yifancc_feature( 278*eed53cd4SHONG Yifan name = "opt", 279*eed53cd4SHONG Yifan ... 280*eed53cd4SHONG Yifan mutually_exclusive = [":compilation_mode"], 281*eed53cd4SHONG Yifan) 282*eed53cd4SHONG Yifancc_feature( 283*eed53cd4SHONG Yifan name = "dbg", 284*eed53cd4SHONG Yifan ... 285*eed53cd4SHONG Yifan mutually_exclusive = [":compilation_mode"], 286*eed53cd4SHONG Yifan) 287*eed53cd4SHONG Yifan``` 288*eed53cd4SHONG Yifan 289*eed53cd4SHONG Yifan## Feature requirements 290*eed53cd4SHONG YifanFeature requirements can come in two formats. 291*eed53cd4SHONG Yifan 292*eed53cd4SHONG YifanFor example: 293*eed53cd4SHONG Yifan 294*eed53cd4SHONG Yifan* Features can require some subset of features to be enabled. 295*eed53cd4SHONG Yifan* Arguments can require some subset of features to be enabled, but others to be 296*eed53cd4SHONG Yifan disabled. 297*eed53cd4SHONG Yifan 298*eed53cd4SHONG YifanThis is very confusing for toolchain authors, so we will simplify things with 299*eed53cd4SHONG Yifanthe use of providers: 300*eed53cd4SHONG Yifan 301*eed53cd4SHONG Yifan* `cc_feature` will provide `feature`, `feature_set`, and `with_feature` 302*eed53cd4SHONG Yifan* `cc_feature_set` will provide `feature_set` and `with_feature`. 303*eed53cd4SHONG Yifan* `cc_feature_constraint` will provide `with_features` only. 304*eed53cd4SHONG Yifan 305*eed53cd4SHONG YifanWe will rename all `with_features` and `requires` to `requires_any_of`, to make 306*eed53cd4SHONG Yifanit very clear that only one of the requirements needs to be met. 307*eed53cd4SHONG Yifan 308*eed53cd4SHONG Yifan``` 309*eed53cd4SHONG Yifancc_feature_set( 310*eed53cd4SHONG Yifan name = "my_feature_set", 311*eed53cd4SHONG Yifan all_of = [":my_feature"], 312*eed53cd4SHONG Yifan) 313*eed53cd4SHONG Yifan 314*eed53cd4SHONG Yifancc_feature_constraint( 315*eed53cd4SHONG Yifan name = "my_feature_constraint", 316*eed53cd4SHONG Yifan all_of = [":my_feature"], 317*eed53cd4SHONG Yifan none_of = [":my_other_feature"], 318*eed53cd4SHONG Yifan) 319*eed53cd4SHONG Yifan 320*eed53cd4SHONG Yifancc_args( 321*eed53cd4SHONG Yifan name = "foo", 322*eed53cd4SHONG Yifan # All of these provide with_feature. 323*eed53cd4SHONG Yifan requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"] 324*eed53cd4SHONG Yifan) 325*eed53cd4SHONG Yifan 326*eed53cd4SHONG Yifan# my_feature_constraint would be an error here. 327*eed53cd4SHONG Yifancc_feature( 328*eed53cd4SHONG Yifan name = "foo", 329*eed53cd4SHONG Yifan # Both of these provide feature_set. 330*eed53cd4SHONG Yifan requires_any_of = [":my_feature", ":my_feature_set"] 331*eed53cd4SHONG Yifan implies = [":my_other_feature", :my_other_feature_set"], 332*eed53cd4SHONG Yifan) 333*eed53cd4SHONG Yifan``` 334