tock_build_scripts/
default.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Provide helpers for building Tock boards that match the default conventions.
6
7use std::fs;
8use std::path::Path;
9
10const LINKER_SCRIPT: &str = "layout.ld";
11
12/// Setup the Tock board to build with a board-provided linker script called
13/// `layout.ld`.
14///
15/// The board linker script (i.e., `layout.ld`) should end with the command:
16///
17/// ```
18/// INCLUDE tock_kernel_layout.ld
19/// ```
20///
21/// This function will ensure that the linker's search path is configured to
22/// find `tock_kernel_layout.ld`.
23pub fn default_linker_script() {
24    if !Path::new(LINKER_SCRIPT).exists() {
25        panic!("Boards must provide a `layout.ld` link script file");
26    }
27
28    rustflags_check();
29
30    include_tock_kernel_layout();
31
32    add_board_dir_to_linker_search_path();
33
34    set_and_track_linker_script(LINKER_SCRIPT);
35}
36
37/// Include the folder where the board's Cargo.toml is in the linker file
38/// search path.
39pub fn add_board_dir_to_linker_search_path() {
40    // Note this is a different path than the one returned by
41    // `std::env!("CARGO_MANIFEST_DIR")` in `include_tock_kernel_layout()`,
42    // since that is evaluated at compile
43    // time while this `std::env::var("CARGO_MANIFEST_DIR")` is evaluated at runtime.
44    println!(
45        "cargo:rustc-link-arg=-L{}",
46        std::env::var("CARGO_MANIFEST_DIR").unwrap()
47    );
48}
49
50/// Include the folder where this build_script crate's Cargo.toml is in the
51/// linker file search path for `tock_kernel_layout.ld`, and instruct cargo
52/// to rebuild if that linker script is changed.
53pub fn include_tock_kernel_layout() {
54    println!("cargo:rustc-link-arg=-L{}", std::env!("CARGO_MANIFEST_DIR"));
55    // Directive to rebuild if the linker script in this crate is changed.
56    println!(
57        "cargo:rerun-if-changed={}",
58        Path::new(std::env!("CARGO_MANIFEST_DIR"))
59            .join("tock_kernel_layout.ld")
60            .to_string_lossy()
61    );
62}
63
64pub fn rustflags_check() {
65    // The `RUSTFLAGS` that the Tock config files set can be easily overridden
66    // by command line flags. The build will still succeed but the resulting
67    // binary may be invalid as it was not built with the intended flags. This
68    // check seeks to prevent that. Our approach is we set a sentinel flag in
69    // our configuration file and then check that it is set here. If it isn't,
70    // the flags were overwritten and the build will be invalid.
71    //
72    // We only do this check if we are actually building for an embedded target
73    // (i.e., the TARGET is not the same as the HOST). This avoids a false
74    // positive when running tools like `cargo clippy`.
75    //
76    // If you are intentionally not using the standard Tock config files, set
77    // `cfg-tock-buildflagssentinel` in your cargo config to prevent this
78    // error.
79    if std::env::var("HOST") != std::env::var("TARGET") {
80        let rust_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS");
81        if !rust_flags
82            .iter()
83            .any(|f| f.contains("cfg_tock_buildflagssentinel"))
84        {
85            panic!(
86                "Incorrect build configuration. Verify you are using unstable cargo and have not unintentionally set the RUSTFLAGS environment variable."
87            );
88        }
89    }
90}
91
92/// Pass the given linker script to cargo, and track it and all of its `INCLUDE`s
93pub fn set_and_track_linker_script<P: AsRef<Path> + ToString>(path: P) {
94    // Use the passed linker script
95    println!("cargo:rustc-link-arg=-T{}", path.to_string());
96    track_linker_script(path);
97}
98
99/// Track the given linker script and all of its `INCLUDE`s so that the build
100/// is rerun when any of them change.
101pub fn track_linker_script<P: AsRef<Path>>(path: P) {
102    let path = path.as_ref();
103
104    // Skip the default Tock linker script as we have manually added the
105    // containing directory to the linker search path and we do not know the
106    // path to add the rerun directive here. Instead, we add the rerun directory
107    // for the default Tock linker script manually before calling this function.
108    if path.to_str() == Some("tock_kernel_layout.ld") {
109        return;
110    }
111
112    assert!(path.is_file(), "expected path {path:?} to be a file");
113
114    println!("cargo:rerun-if-changed={}", path.display());
115
116    // Find all the `INCLUDE <relative path>` lines in the linker script.
117    let link_script = fs::read_to_string(path).expect("failed to read {path:?}");
118    let includes = link_script
119        .lines()
120        .filter_map(|line| line.strip_prefix("INCLUDE").map(str::trim));
121
122    // Recursively track included linker scripts.
123    for include in includes {
124        track_linker_script(include);
125    }
126}