tock_build_scripts/
default.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.

//! Provide helpers for building Tock boards that match the default conventions.

use std::fs;
use std::path::Path;

const LINKER_SCRIPT: &str = "layout.ld";

/// Setup the Tock board to build with a board-provided linker script called
/// `layout.ld`.
///
/// The board linker script (i.e., `layout.ld`) should end with the command:
///
/// ```
/// INCLUDE tock_kernel_layout.ld
/// ```
///
/// This function will ensure that the linker's search path is configured to
/// find `tock_kernel_layout.ld`.
pub fn default_linker_script() {
    if !Path::new(LINKER_SCRIPT).exists() {
        panic!("Boards must provide a `layout.ld` link script file");
    }

    rustflags_check();

    include_tock_kernel_layout();

    add_board_dir_to_linker_search_path();

    set_and_track_linker_script(LINKER_SCRIPT);
}

/// Include the folder where the board's Cargo.toml is in the linker file
/// search path.
pub fn add_board_dir_to_linker_search_path() {
    // Note this is a different path than the one returned by
    // `std::env!("CARGO_MANIFEST_DIR")` in `include_tock_kernel_layout()`,
    // since that is evaluated at compile
    // time while this `std::env::var("CARGO_MANIFEST_DIR")` is evaluated at runtime.
    println!(
        "cargo:rustc-link-arg=-L{}",
        std::env::var("CARGO_MANIFEST_DIR").unwrap()
    );
}

/// Include the folder where this build_script crate's Cargo.toml is in the
/// linker file search path for `tock_kernel_layout.ld`, and instruct cargo
/// to rebuild if that linker script is changed.
pub fn include_tock_kernel_layout() {
    println!("cargo:rustc-link-arg=-L{}", std::env!("CARGO_MANIFEST_DIR"));
    // Directive to rebuild if the linker script in this crate is changed.
    println!(
        "cargo:rerun-if-changed={}",
        Path::new(std::env!("CARGO_MANIFEST_DIR"))
            .join("tock_kernel_layout.ld")
            .to_string_lossy()
    );
}

pub fn rustflags_check() {
    // The `RUSTFLAGS` that the Tock config files set can be easily overridden
    // by command line flags. The build will still succeed but the resulting
    // binary may be invalid as it was not built with the intended flags. This
    // check seeks to prevent that. Our approach is we set a sentinel flag in
    // our configuration file and then check that it is set here. If it isn't,
    // the flags were overwritten and the build will be invalid.
    //
    // We only do this check if we are actually building for an embedded target
    // (i.e., the TARGET is not the same as the HOST). This avoids a false
    // positive when running tools like `cargo clippy`.
    //
    // If you are intentionally not using the standard Tock config files, set
    // `cfg-tock-buildflagssentinel` in your cargo config to prevent this
    // error.
    if std::env::var("HOST") != std::env::var("TARGET") {
        let rust_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS");
        if !rust_flags
            .iter()
            .any(|f| f.contains("cfg_tock_buildflagssentinel"))
        {
            panic!(
                "Incorrect build configuration. Verify you are using unstable cargo and have not unintentionally set the RUSTFLAGS environment variable."
            );
        }
    }
}

/// Pass the given linker script to cargo, and track it and all of its `INCLUDE`s
pub fn set_and_track_linker_script<P: AsRef<Path> + ToString>(path: P) {
    // Use the passed linker script
    println!("cargo:rustc-link-arg=-T{}", path.to_string());
    track_linker_script(path);
}

/// Track the given linker script and all of its `INCLUDE`s so that the build
/// is rerun when any of them change.
pub fn track_linker_script<P: AsRef<Path>>(path: P) {
    let path = path.as_ref();

    // Skip the default Tock linker script as we have manually added the
    // containing directory to the linker search path and we do not know the
    // path to add the rerun directive here. Instead, we add the rerun directory
    // for the default Tock linker script manually before calling this function.
    if path.to_str() == Some("tock_kernel_layout.ld") {
        return;
    }

    assert!(path.is_file(), "expected path {path:?} to be a file");

    println!("cargo:rerun-if-changed={}", path.display());

    // Find all the `INCLUDE <relative path>` lines in the linker script.
    let link_script = fs::read_to_string(path).expect("failed to read {path:?}");
    let includes = link_script
        .lines()
        .filter_map(|line| line.strip_prefix("INCLUDE").map(str::trim));

    // Recursively track included linker scripts.
    for include in includes {
        track_linker_script(include);
    }
}