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}