bootstrap/utils/
build_stamp.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Module for managing build stamp files.
//!
//! Contains the core implementation of how bootstrap utilizes stamp files on build processes.

use std::path::{Path, PathBuf};
use std::{fs, io};

use sha2::digest::Digest;

use crate::core::builder::Builder;
use crate::core::config::TargetSelection;
use crate::utils::helpers::{hex_encode, mtime};
use crate::{Compiler, Mode, helpers, t};

#[cfg(test)]
mod tests;

/// Manages a stamp file to track build state. The file is created in the given
/// directory and can have custom content and name.
#[derive(Clone)]
pub struct BuildStamp {
    path: PathBuf,
    stamp: String,
}

impl BuildStamp {
    /// Creates a new `BuildStamp` for a given directory.
    ///
    /// By default, stamp will be an empty file named `.stamp` within the specified directory.
    pub fn new(dir: &Path) -> Self {
        // Avoid using `is_dir()` as the directory may not exist yet.
        // It is more appropriate to assert that the path is not a file.
        assert!(!dir.is_file(), "can't be a file path");
        Self { path: dir.join(".stamp"), stamp: String::new() }
    }

    /// Returns path of the stamp file.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Returns the value of the stamp.
    ///
    /// Note that this is empty by default and is populated using `BuildStamp::add_stamp`.
    /// It is not read from an actual file, but rather it holds the value that will be used
    /// when `BuildStamp::write` is called.
    pub fn stamp(&self) -> &str {
        &self.stamp
    }

    /// Adds specified stamp content to the current value.
    ///
    /// This method can be used incrementally e.g., `add_stamp("x").add_stamp("y").add_stamp("z")`.
    pub fn add_stamp<S: ToString>(mut self, stamp: S) -> Self {
        self.stamp.push_str(&stamp.to_string());
        self
    }

    /// Adds a prefix to stamp's name.
    ///
    /// Prefix cannot start or end with a dot (`.`).
    pub fn with_prefix(mut self, prefix: &str) -> Self {
        assert!(
            !prefix.starts_with('.') && !prefix.ends_with('.'),
            "prefix can not start or end with '.'"
        );

        let stamp_filename = self.path.file_name().unwrap().to_str().unwrap();
        let stamp_filename = stamp_filename.strip_prefix('.').unwrap_or(stamp_filename);
        self.path.set_file_name(format!(".{prefix}-{stamp_filename}"));

        self
    }

    /// Removes the stamp file if it exists.
    pub fn remove(&self) -> io::Result<()> {
        match fs::remove_file(&self.path) {
            Ok(()) => Ok(()),
            Err(e) => {
                if e.kind() == io::ErrorKind::NotFound {
                    Ok(())
                } else {
                    Err(e)
                }
            }
        }
    }

    /// Creates the stamp file.
    pub fn write(&self) -> io::Result<()> {
        fs::write(&self.path, &self.stamp)
    }

    /// Checks if the stamp file is up-to-date.
    ///
    /// It is considered up-to-date if file content matches with the stamp string.
    pub fn is_up_to_date(&self) -> bool {
        match fs::read(&self.path) {
            Ok(h) => self.stamp.as_bytes() == h.as_slice(),
            Err(e) if e.kind() == io::ErrorKind::NotFound => false,
            Err(e) => {
                panic!("failed to read stamp file `{}`: {}", self.path.display(), e);
            }
        }
    }
}

/// Clear out `dir` if `input` is newer.
///
/// After this executes, it will also ensure that `dir` exists.
pub fn clear_if_dirty(builder: &Builder<'_>, dir: &Path, input: &Path) -> bool {
    let stamp = BuildStamp::new(dir);
    let mut cleared = false;
    if mtime(stamp.path()) < mtime(input) {
        builder.verbose(|| println!("Dirty - {}", dir.display()));
        let _ = fs::remove_dir_all(dir);
        cleared = true;
    } else if stamp.path().exists() {
        return cleared;
    }
    t!(fs::create_dir_all(dir));
    t!(fs::File::create(stamp.path()));
    cleared
}

/// Cargo's output path for librustc_codegen_llvm in a given stage, compiled by a particular
/// compiler for the specified target and backend.
pub fn codegen_backend_stamp(
    builder: &Builder<'_>,
    compiler: Compiler,
    target: TargetSelection,
    backend: &str,
) -> BuildStamp {
    BuildStamp::new(&builder.cargo_out(compiler, Mode::Codegen, target))
        .with_prefix(&format!("librustc_codegen_{backend}"))
}

/// Cargo's output path for the standard library in a given stage, compiled
/// by a particular compiler for the specified target.
pub fn libstd_stamp(
    builder: &Builder<'_>,
    compiler: Compiler,
    target: TargetSelection,
) -> BuildStamp {
    BuildStamp::new(&builder.cargo_out(compiler, Mode::Std, target)).with_prefix("libstd")
}

/// Cargo's output path for librustc in a given stage, compiled by a particular
/// compiler for the specified target.
pub fn librustc_stamp(
    builder: &Builder<'_>,
    compiler: Compiler,
    target: TargetSelection,
) -> BuildStamp {
    BuildStamp::new(&builder.cargo_out(compiler, Mode::Rustc, target)).with_prefix("librustc")
}

/// Computes a hash representing the state of a repository/submodule and additional input.
///
/// It uses `git diff` for the actual changes, and `git status` for including the untracked
/// files in the specified directory. The additional input is also incorporated into the
/// computation of the hash.
///
/// # Parameters
///
/// - `dir`: A reference to the directory path of the target repository/submodule.
/// - `additional_input`: An additional input to be included in the hash.
///
/// # Panics
///
/// In case of errors during `git` command execution (e.g., in tarball sources), default values
/// are used to prevent panics.
pub fn generate_smart_stamp_hash(
    builder: &Builder<'_>,
    dir: &Path,
    additional_input: &str,
) -> String {
    let diff = helpers::git(Some(dir))
        .allow_failure()
        .arg("diff")
        .arg(".")
        .run_capture_stdout(builder)
        .stdout_if_ok()
        .unwrap_or_default();

    let status = helpers::git(Some(dir))
        .allow_failure()
        .arg("status")
        .arg(".")
        .arg("--porcelain")
        .arg("-z")
        .arg("--untracked-files=normal")
        .run_capture_stdout(builder)
        .stdout_if_ok()
        .unwrap_or_default();

    let mut hasher = sha2::Sha256::new();

    hasher.update(diff);
    hasher.update(status);
    hasher.update(additional_input);

    hex_encode(hasher.finalize().as_slice())
}