blob: 6c79385190e8e0928f23ebb9e3112cbe412cbccb [file] [log] [blame]
//! 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::{CodegenBackendKind, 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: &CodegenBackendKind,
) -> BuildStamp {
BuildStamp::new(&builder.cargo_out(compiler, Mode::Codegen, target))
.with_prefix(&format!("lib{}", backend.crate_name()))
}
/// 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
/// `build_compiler` for the specified target.
pub fn librustc_stamp(
builder: &Builder<'_>,
build_compiler: Compiler,
target: TargetSelection,
) -> BuildStamp {
BuildStamp::new(&builder.cargo_out(build_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())
}