bootstrap/utils/
build_stamp.rs1use std::path::{Path, PathBuf};
6use std::{fs, io};
7
8use sha2::digest::Digest;
9
10use crate::core::backend::CodegenBackendKind;
11use crate::core::builder::Builder;
12use crate::core::compiler::Compiler;
13use crate::core::config::TargetSelection;
14use crate::core::session::Mode;
15use crate::utils::helpers::{self, hex_encode, mtime, t};
16
17#[cfg(test)]
18mod tests;
19
20#[derive(Clone)]
23pub struct BuildStamp {
24 path: PathBuf,
25 stamp: String,
26}
27
28impl BuildStamp {
29 pub fn new(dir: &Path) -> Self {
33 assert!(!dir.is_file(), "can't be a file path");
36 Self { path: dir.join(".stamp"), stamp: String::new() }
37 }
38
39 pub fn path(&self) -> &Path {
41 &self.path
42 }
43
44 pub fn stamp(&self) -> &str {
50 &self.stamp
51 }
52
53 pub fn add_stamp<S: ToString>(mut self, stamp: S) -> Self {
57 self.stamp.push_str(&stamp.to_string());
58 self
59 }
60
61 pub fn with_prefix(mut self, prefix: &str) -> Self {
65 assert!(
66 !prefix.starts_with('.') && !prefix.ends_with('.'),
67 "prefix can not start or end with '.'"
68 );
69
70 let stamp_filename = self.path.file_name().unwrap().to_str().unwrap();
71 let stamp_filename = stamp_filename.strip_prefix('.').unwrap_or(stamp_filename);
72 self.path.set_file_name(format!(".{prefix}-{stamp_filename}"));
73
74 self
75 }
76
77 pub fn remove(&self) -> io::Result<()> {
79 match fs::remove_file(&self.path) {
80 Ok(()) => Ok(()),
81 Err(e) => {
82 if e.kind() == io::ErrorKind::NotFound {
83 Ok(())
84 } else {
85 Err(e)
86 }
87 }
88 }
89 }
90
91 pub fn write(&self) -> io::Result<()> {
93 fs::write(&self.path, &self.stamp)
94 }
95
96 pub fn is_up_to_date(&self) -> bool {
100 match fs::read(&self.path) {
101 Ok(h) => self.stamp.as_bytes() == h.as_slice(),
102 Err(e) if e.kind() == io::ErrorKind::NotFound => false,
103 Err(e) => {
104 panic!("failed to read stamp file `{}`: {}", self.path.display(), e);
105 }
106 }
107 }
108}
109
110pub fn clear_if_dirty(builder: &Builder<'_>, dir: &Path, input: &Path) -> bool {
114 let stamp = BuildStamp::new(dir);
115 let mut cleared = false;
116 if mtime(stamp.path()) < mtime(input) {
117 builder.do_if_verbose(|| {
118 println!(
119 "Removing dirty directory `{}` because `{}` changed",
120 dir.display(),
121 input.display(),
122 )
123 });
124 let _ = fs::remove_dir_all(dir);
125 cleared = true;
126 } else if stamp.path().exists() {
127 return cleared;
128 }
129 t!(fs::create_dir_all(dir));
130 t!(fs::File::create(stamp.path()));
131 cleared
132}
133
134pub fn codegen_backend_stamp(
137 builder: &Builder<'_>,
138 compiler: Compiler,
139 target: TargetSelection,
140 backend: &CodegenBackendKind,
141) -> BuildStamp {
142 BuildStamp::new(&builder.cargo_out(compiler, Mode::Codegen, target))
143 .with_prefix(&format!("lib{}", backend.crate_name()))
144}
145
146pub fn libstd_stamp(
149 builder: &Builder<'_>,
150 build_compiler: Compiler,
151 target: TargetSelection,
152) -> BuildStamp {
153 BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Std, target)).with_prefix("libstd")
154}
155
156pub fn librustc_stamp(
159 builder: &Builder<'_>,
160 build_compiler: Compiler,
161 target: TargetSelection,
162) -> BuildStamp {
163 BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Rustc, target)).with_prefix("librustc")
164}
165
166pub fn generate_smart_stamp_hash(
182 builder: &Builder<'_>,
183 dir: &Path,
184 additional_input: &str,
185) -> String {
186 let diff = helpers::git(Some(dir))
187 .allow_failure()
188 .arg("diff")
189 .arg(".")
190 .run_capture_stdout(builder)
191 .stdout_if_ok()
192 .unwrap_or_default();
193
194 let status = helpers::git(Some(dir))
195 .allow_failure()
196 .arg("status")
197 .arg(".")
198 .arg("--porcelain")
199 .arg("-z")
200 .arg("--untracked-files=normal")
201 .run_capture_stdout(builder)
202 .stdout_if_ok()
203 .unwrap_or_default();
204
205 let mut hasher = sha2::Sha256::new();
206
207 hasher.update(diff);
208 hasher.update(status);
209 hasher.update(additional_input);
210
211 hex_encode(hasher.finalize().as_slice())
212}