Skip to content

Commit

Permalink
Merge pull request #1808 from tjkirch/build-once-thanks
Browse files Browse the repository at this point in the history
model: set mtime of links to epoch to prevent spurious rebuilds
  • Loading branch information
tjkirch authored Nov 10, 2021
2 parents 2b61ad2 + 65bca26 commit ba0571c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
1 change: 1 addition & 0 deletions sources/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sources/models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ url = "2.1"

[build-dependencies]
cargo-readme = "3.1"
filetime = "0.2"
rand = "0.8"

[lib]
Expand Down
43 changes: 39 additions & 4 deletions sources/models/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// See README.md to understand the symlink setup.

use filetime::{set_symlink_file_times, FileTime};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::env;
use std::fs::{self, File};
Expand All @@ -12,9 +13,21 @@ use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process;

/// The name of the environment variable that tells us the current variant; we need to rebuild if
/// this changes.
const VARIANT_ENV: &str = "VARIANT";

/// We create a link from 'current' to the variant selected by the environment variable above.
const VARIANT_LINK: &str = "src/variant/current";

/// We create a link for the 'variant' module's mod.rs; this can't be checked into the repo because
/// the `src/variant` directory is a cache mount created by Docker before building a package.
/// This isn't variant-specific, so we can have a fixed link target. The file has top-level
/// definitions that apply to all models, and defines a 'current' submodule (that Rust will be able
/// to find through the 'current' link mentioned above) and re-exports everything in 'current' so
/// that consumers of the model don't have to care what the current variant is.
const MOD_LINK: &str = "src/variant/mod.rs";
const VARIANT_ENV: &str = "VARIANT";
const MOD_LINK_TARGET: &str = "../variant_mod.rs";

fn main() {
// Tell cargo when we have to rerun; we always want variant links to be correct, especially
Expand Down Expand Up @@ -54,11 +67,33 @@ fn link_current_variant() {

// Also create the link for mod.rs so Rust can import source from the "current" link
// created above.
let mod_target = "../variant_mod.rs";
symlink_safe(&mod_target, MOD_LINK).unwrap_or_else(|e| {
eprintln!("Failed to create symlink at '{}' pointing to '{}' - we need this to build a Rust module structure through the `current` link. Error: {}", MOD_LINK, mod_target, e);
symlink_safe(MOD_LINK_TARGET, MOD_LINK).unwrap_or_else(|e| {
eprintln!("Failed to create symlink at '{}' pointing to '{}' - we need this to build a Rust module structure through the `current` link. Error: {}", MOD_LINK, MOD_LINK_TARGET, e);
process::exit(1);
});

// Set the mtime of the links to a fixed time, the epoch. This is because cargo decides
// whether to rerun build.rs based on the "rerun-if-changed" statements printed above and the
// mtime of the files they reference. If the mtime of the file doesn't match the mtime of the
// "output" file in the build directory (which contains the output of the rerun-if prints) then
// it rebuilds. Those times won't match because we don't control when they happen, meaning
// we'd rebuild every time. Setting to a consistent time means we only rebuild when the other
// rerun-if statements apply, the important one being the variant changing.
//
// Note that we still use rerun-if-changed for these links in case someone changes them outside
// of this build.rs. If they really want to get around our system, they'd also need to set the
// mtime to epoch, and then hopefully they know what they're doing.
for link in &[VARIANT_LINK, MOD_LINK] {
// Do our best, but if we fail, rebuilding isn't the end of the world.
// Note: set_symlink_file_times is the only method that operates on the symlink rather than
// its target, and it also updates atime, which we don't care about but isn't harmful.
if let Err(e) = set_symlink_file_times(link, FileTime::zero(), FileTime::zero()) {
eprintln!(
"Warning: unable to set mtime on {}; crate may rebuild unnecessarily: {}",
link, e
);
}
}
}

fn generate_readme() {
Expand Down

0 comments on commit ba0571c

Please sign in to comment.