| # Copyright 2026 The Fuchsia Authors |
| # |
| # Use of this source code is governed by a MIT-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/MIT |
| |
| import("//build/dist/distribution_manifest.gni") |
| import("//build/dist/distribution_manifest_copy.gni") |
| import("//build/rust/config.gni") |
| import("//build/toolchain/toolchain_environment.gni") |
| |
| # This file implements the Rust custom target support for the kernel. There |
| # are two components of this: populating the JSON spec; populating the sysroot. |
| # |
| # The JSON target spec is populated by starting with the normal userland |
| # Fuchsia target for the machine, and modifying it. These edits are directed |
| # by compiler_config() instances in the tree from //zircon/kernel/switch:abi. |
| # |
| # The sysroot needs to be populated with the `target.json` file and then with |
| # the few essential crates (`.rlib` files) to which the compiler always refers. |
| # For out-of-the-box targets, these come in a prebuilt sysroot provided with |
| # the compiler. For the custom target, we provide a custom sysroot. |
| # |
| # Some complications conspire to produce some exacting requirements for how the |
| # `.rlib` files can get built and delivered to the custom sysroot. |
| # |
| # * The layout is fixed by rustc based on --sysroot and --target values. |
| # There is no way to insist these crates be found elsewhere. |
| # |
| # * When rustc finds the `lib*.rlib` file, it will not tolerate an adjacent |
| # `lib*.rmeta` file existing at the same time. |
| # |
| # * The build system's RBE Rust integration has found it necessary and best |
| # to instruct rustc to emit `.rmeta` files in all cases. This is baked |
| # into the tool() definition in the toolchain and hard to avoid. |
| # |
| # These lead to compiling to an intermediate build directory ($target_out_dir) |
| # and then making copies (actually hard links) to the sysroot directory. |
| # |
| # * Anomalous filesystem behavior is sometimes observed in incremental builds |
| # on bots with individual files / links like this seeing stale versions |
| # after the link was updated. |
| # |
| # * These anomalies are thought to be mitigated by starting every time with a |
| # freshly-`mkdir`d directory and populating _that_ with new links. |
| # |
| # * Predicting when rustc might look for one of the special crates while |
| # building another of them is subtle and fragile to rely on. |
| # |
| # * The --target string is embedded in each crate's metadata; rustc enforces |
| # that the string found in each already-built crate must match the string |
| # used in the current invocation. This is precludes using different custom |
| # targets within the same sysroot as a means just to separate the `lib` |
| # directories. It also serves a useful purpose of catching crosstalk |
| # anomalies; so its worth exploiting that to avoid crosstalk between the |
| # different kernel toolchains (switchsets and variants). |
| # |
| # All this leads to an approach whereby the custom target sysroot crates are |
| # built in separate stages that use disjoint Rust `--sysroot` directories. |
| # Each stage uses the same --target string (based on the $current_toolchain |
| # name), but finds its files in a different place. The same `target.json` |
| # contents must be placed in each directory. |
| # |
| # Each stage produces a sysroot directory that can be used by later stages. |
| # The first stage is empty except for `target.json` and never gets any `lib` |
| # contents. Each stage builds crates using the previous stage as the |
| # `--sysroot` setting, so only previous stages' crates are available to it. |
| # Its `lib` directory gets those crates along with all previous stages' crates. |
| |
| assert(toolchain_environment == "kernel", |
| "This file should not be reached by deps in $current_toolchain") |
| |
| # Where the Rust library sources are found in the toolchain package. |
| rustc_library_dir = "$rustc_prefix/lib/rustlib/src/rust/library" |
| |
| # The --target string used in this toolchain. This dictates the subdirectory |
| # path used inside the Rust --sysroot directory. |
| custom_target = get_label_info(current_toolchain, "name") |
| custom_target_relpath = "lib/rustlib/$custom_target" |
| |
| # These are configs that might be in the toolchain / variant's default list but |
| # must be disabled for building the empty pseudo-crates. |
| rust_instrumentation_configs = [ |
| "//build/config/profile:coverage", |
| |
| # TODO(https://fxbug.dev/414673148): Remove when coverage-rust is removed. |
| "//build/config/profile:coverage-rust", |
| "//build/config/profile:profile-rust", |
| ] |
| |
| stages = [ |
| { |
| name = "rust-bootstrap-empty" |
| |
| # This is the initial stage that never has any crates, just `target.json`. |
| crates = [] |
| remove_configs = [] |
| }, |
| { |
| name = "rust-bootstrap-builtins" |
| |
| # The two empty crates are built in this stage. Neither needs any rlib. |
| crates = [ |
| { |
| # This is implicitly used by all compilations. |
| name = "compiler_builtins" |
| |
| # This is actually an empty crate with just a local stub source file. |
| crate_root = "src/lib.rs" |
| rust_sources = [ crate_root ] |
| }, |
| { |
| # This is used only when coverage / profile instrumentation is enabled. |
| name = "profiler_builtins" |
| |
| # This just an empty crate, but upstream provides the stub source file. |
| # The upstream build.rs is adding in a copy of compiler-rt/lib/profile. |
| # But in the kernel //src/lib/llvm-profdata handles that instead. |
| crate_root = "$rustc_library_dir/$name/src/lib.rs" |
| rust_sources = [ crate_root ] |
| }, |
| ] |
| |
| # Disable instrumentation switches that require extra crates to exist |
| # already, like profiler_builtins itself. |
| remove_configs = rust_instrumentation_configs |
| }, |
| { |
| name = "rust-bootstrap-core" |
| |
| # The core crate is built in this stage. It may need the earlier crates. |
| crates = [ |
| { |
| # This is implicitly used by all compilations. |
| name = "core" |
| |
| crate_root = "$rustc_library_dir/$name/src/lib.rs" |
| |
| # This needs all the sources from the toolchain package directory, but |
| # there is no ready list of all the names other than the crate_root |
| # file. The magic placeholder file tells the machinery that it just has |
| # to use rustc parsing of `mod` et al to discover all the source files |
| # used. |
| rust_sources = [ "//build/rust/__SKIP_ENFORCEMENT__.rs" ] |
| }, |
| ] |
| remove_configs = [] |
| }, |
| { |
| name = "target" |
| |
| # This is the final stage where all the normal kernel code is built. |
| # Nothing that goes into the sysroot is _built_ in this final stage. |
| crates = [] |
| remove_configs = [] |
| }, |
| ] |
| |
| # These are implicitly in deps of normal targets because :target is listed in |
| # the toolchain definition. The special crate targets defined below avoid the |
| # normal target-defining templates and so don't get these injected. |
| group("target_deps") { |
| } |
| group("target_executable_deps") { |
| } |
| group("target_link_deps") { |
| } |
| group("target_source_deps") { |
| # This needs to be in public_deps so that it counts among "direct" deps for |
| # all the Rust compilation targets that have its outputs (the `target.json` |
| # file) in their inputs via the config(). |
| public_deps = [ ":rust-custom-target" ] |
| |
| # This provides an ordering dep to get the custom target sysroot populated |
| # with the crates (`.rlib` files) it needs. These rlib files aren't |
| # directly known to GN / Ninja as inputs, but will be picked up from |
| # rustc's depfile to take care of incremental rebuilds. |
| deps = [ ":target.sysroot" ] |
| } |
| |
| # Collect the edit steps defined by each compiler_config(). |
| generated_file("rust-target-edits") { |
| visibility = [ ":rust-custom-target" ] |
| outputs = [ "$root_gen_dir/rust-target-edits.json" ] |
| output_conversion = "json" |
| data_keys = [ "kernel_rust_target_edits" ] |
| deps = [ ":rust-target-edits.prologue" ] |
| |
| # That must be first in the list! Append the real deps separately. |
| deps += [ "//zircon/kernel/switch:abi.rust-target-edits" ] |
| } |
| |
| group("rust-target-edits.prologue") { |
| visibility = [ ":rust-target-edits" ] |
| metadata = { |
| # This winds up first in the collected list. It's just the plain |
| # --target string. Later elements are scopes to drive edits to that |
| # target's default JSON spec. |
| kernel_rust_target_edits = [ rust_target ] |
| } |
| } |
| |
| # Generate the full target spec from the base target and the edits. |
| action("rust-custom-target") { |
| visibility = [ "//zircon/kernel/*" ] |
| |
| script = "rust-custom-target.py" |
| |
| inputs = [ "$rustc_prefix/bin/rustc" ] |
| args = [ "--rustc=" + rebase_path(inputs[0], root_build_dir) ] |
| |
| deps = [ ":rust-target-edits" ] |
| sources = get_target_outputs(deps[0]) |
| args += [ "--edits=" + rebase_path(sources[0], root_build_dir) ] |
| |
| # There's just one JSON file, but each stage has to find it in a different |
| # subdirectory, so it has extra links. |
| outputs = [] |
| foreach(stage, stages) { |
| outputs += |
| [ "$root_gen_dir/${stage.name}/$custom_target_relpath/target.json" ] |
| } |
| foreach(output, outputs) { |
| args += [ "--output=" + rebase_path(output, root_build_dir) ] |
| } |
| } |
| |
| # Each stage defines: the config() to use its custom target; the copying action |
| # to populate its `lib` directory; and the crate targets to build (using the |
| # previous stage's custom target). |
| previous_crates = [] # Accumulates all the crate names from every stage. |
| foreach(stage, stages) { |
| stage.sysroot = "$root_gen_dir/${stage.name}" |
| stage.rustlib = "${stage.sysroot}/$custom_target_relpath" |
| stage.json = "${stage.rustlib}/target.json" |
| |
| # Apply the custom target. This confers the Ninja file dependency on the |
| # JSON file. Compile targets using this config() must have direct-equivalent |
| # deps on :rust-custom-target to match its outputs being in `inputs` here. |
| config(stage.name) { |
| inputs = [ stage.json ] |
| rustflags = [ |
| "--sysroot=" + rebase_path(stage.sysroot, root_build_dir), |
| "-Zunstable-options", # Required for using custom (JSON file) targets. |
| "--target=$custom_target", |
| ] |
| if (rust_rbe_enable) { |
| rustflags += [ |
| "--remote-flag=--clang-target=$current_target_tuple", |
| "--remote-inputs=" + |
| string_join(",", rebase_path(inputs, root_build_dir)), |
| ] |
| } |
| } |
| |
| # This avoids the //build/rust template layers for these very special cases. |
| # As this is only upstream code, none of the analysis targets or other extra |
| # support those templates provide is useful here. The implicit deps injected |
| # by just the rust_library() wrapper template layer from BUILDCONFIG.gn, into |
| # every other Rust target lead back here; using basic_rust_library() avoids |
| # the circularity. Note basic_rust_library() has no set_defaults() configs! |
| foreach(crate, stage.crates) { |
| basic_rust_library(crate.name) { |
| deps = [] |
| forward_variables_from(crate, |
| "*", |
| [ |
| "name", |
| "rust_sources", |
| ]) |
| |
| visibility = [ ":*" ] |
| |
| # This is the default in GN's actual logic, but it only gets into the |
| # GN-produced 'project.json' file if it's explicit in the target scope, |
| # and generate_cargo.py needs to find it there (see |
| # //build/rust/BUILD.gn). |
| output_name = target_name |
| |
| rustenv = [ "RUSTC_BOOTSTRAP=$target_name" ] |
| rustflags = [ "--edition=2024" ] |
| |
| # The custom target defined by the previous stage goes early in the list. |
| # The default (final) custom target and a few other things come out. |
| configs = [ ":$previous_stage" ] + default_common_binary_configs - |
| [ "//zircon/kernel/lib/rust:target" ] + stage.remove_configs - |
| stage.remove_configs |
| |
| # The config() confers an inputs list for the file dependency on the |
| # `target.json` file. Normal Rust targets all get implicit deps that |
| # propagate to the generating action via public_deps. Without that, it |
| # must appear in the direct deps list here to match implied inputs. |
| deps += [ ":rust-custom-target" ] |
| |
| # The RBE wrapper will look for these generated files, so generate them. |
| deps += [ |
| ":$target_name.rust_compilation_deps", |
| ":$target_name.rust_sources", |
| ] |
| |
| metadata = { |
| distribution_entries = [ |
| { |
| label = get_label_info(":$target_name", "label_with_toolchain") |
| destination = "lib${crate.name}.rlib" |
| source = rebase_path("$target_out_dir/$destination", root_build_dir) |
| }, |
| ] |
| } |
| } |
| |
| generated_file("${crate.name}.rust_compilation_deps") { |
| visibility = [ ":*" ] |
| outputs = [ "$target_gen_dir/$target_name" ] |
| data_keys = [ "rust_compilation_deps" ] |
| walk_keys = [ "rust_compilation_deps_barrier" ] |
| deps = [ ":${previous_stage}.sysroot" ] |
| } |
| |
| generated_file("${crate.name}.rust_sources") { |
| visibility = [ ":*" ] |
| outputs = [ "$target_gen_dir/$target_name" ] |
| contents = rebase_path(crate.rust_sources, root_build_dir) |
| } |
| |
| # Add to the collection the next iteration of the outer loop will use. |
| previous_crates += [ crate ] |
| } |
| |
| # Collect the details for populating ${stage.rustlib}/lib. Everything in |
| # ${stage.rustlib} (so, lib/... + target.json) is in rust_compilation_deps |
| # metadata so RBE knows to present it. |
| stage.compilation_deps = [ stage.json ] |
| stage.manifest_deps = [] |
| foreach(crate, previous_crates) { |
| stage.compilation_deps += [ "${stage.rustlib}/lib/lib${crate.name}.rlib" ] |
| stage.manifest_deps += [ ":${crate.name}" ] |
| } |
| |
| # Collect the manifest for all the crates built up through this stage. |
| distribution_manifest("${stage.name}.manifest") { |
| visibility = [ ":*" ] |
| deps = stage.manifest_deps |
| } |
| |
| # Use that to populate ${stage.rustlib}/lib. |
| distribution_manifest_copy("${stage.name}.sysroot") { |
| visibility = [ ":*" ] |
| outputs = [ "${stage.rustlib}/lib" ] |
| deps = [ ":${stage.name}.manifest" ] |
| metadata = { |
| # This will roll up into the generated_file()s fed to the RBE wrapper so |
| # it knows what GN and Ninja know via the inputs list in the config() |
| # (target.json) plus what's only known later via depfile (lib/*.rlib). |
| rust_compilation_deps_barrier = [] |
| rust_compilation_deps = |
| rebase_path(stage.compilation_deps, root_build_dir) |
| } |
| } |
| |
| # Reset for the next iteration to pick up. |
| previous_stage = stage.name |
| } |