Anki/build/runner/src/build.rs
Damien Elmes 09bfe104d8 Avoid setting path when invoking git
We don't need the amended path, and it seems to break things for
a user:

https://forums.ankiweb.net/t/anki-2-1-55-beta-5-6/24870/89
2022-12-08 12:44:39 +10:00

176 lines
5.7 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::{env, fs, io::Write, process::Command, time::Instant};
use camino::Utf8Path;
use clap::Args;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(Args)]
pub struct BuildArgs {
#[arg(trailing_var_arg = true)]
args: Vec<String>,
}
pub fn run_build(args: BuildArgs) {
let build_root = setup_build_root();
let path = if cfg!(windows) {
format!(
"out\\bin;out\\extracted\\node;{};\\msys64\\usr\\bin",
env::var("PATH").unwrap()
)
} else {
format!(
"out/bin:out/extracted/node/bin:{}",
env::var("PATH").unwrap()
)
};
maybe_update_env_file(build_root);
maybe_update_buildhash(build_root);
// Ensure build file is up to date
let build_file = build_root.join("build.ninja");
if !build_file.exists() {
bootstrap_build();
} else {
maybe_reconfigure_build(&build_file, &path);
}
// automatically convert foo:bar references to foo_bar, as Ninja can not represent the former
let ninja_args = args.args.into_iter().map(|a| a.replace(':', "_"));
let start_time = Instant::now();
let mut command = Command::new("ninja");
command
.arg("-f")
.arg(&build_file)
.args(ninja_args)
.env("NINJA_STATUS", "[%f/%t; %r active; %es] ")
.env("PATH", path)
.env(
"MYPY_CACHE_DIR",
build_root.join("tests").join("mypy").into_string(),
)
.env("PYTHONPYCACHEPREFIX", build_root.join("pycache"))
// commands will not show colors by default, as we do not provide a tty
.env("FORCE_COLOR", "1")
.env("MYPY_FORCE_COLOR", "1")
.env("TERM", "1")
// Prevents 'Warn: You must provide the URL of lib/mappings.wasm'.
// Updating svelte-check or its deps will likely remove the need for it.
.env("NODE_OPTIONS", "--no-experimental-fetch");
// run build
let mut status = command.status().expect("ninja not installed");
if !status.success() && Instant::now().duration_since(start_time).as_secs() < 3 {
// if the build fails quickly, there's a reasonable chance that build.ninja references
// a file that has been renamed/deleted. We currently don't capture stderr, so we can't
// confirm, but in case that's the case, we regenerate the build.ninja file then try again.
bootstrap_build();
status = command.status().expect("ninja missing");
}
let mut stdout = StandardStream::stdout(ColorChoice::Always);
if status.success() {
stdout
.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
.unwrap();
writeln!(&mut stdout, "\nBuild succeeded.").unwrap();
stdout.reset().unwrap();
} else {
stdout
.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))
.unwrap();
writeln!(&mut stdout, "\nBuild failed.").unwrap();
stdout.reset().unwrap();
std::process::exit(1);
}
}
fn setup_build_root() -> &'static Utf8Path {
let build_root = Utf8Path::new("out");
#[cfg(unix)]
if let Ok(new_target) = env::var("BUILD_ROOT").map(camino::Utf8PathBuf::from) {
let create = if let Ok(existing_target) = build_root.read_link_utf8() {
if existing_target != new_target {
fs::remove_file(build_root).unwrap();
true
} else {
false
}
} else {
true
};
if create {
println!("Switching build root to {}", new_target);
std::os::unix::fs::symlink(new_target, build_root).unwrap();
}
}
fs::create_dir_all(build_root).unwrap();
build_root
}
fn maybe_reconfigure_build(build_file: &Utf8Path, path: &str) {
let output = Command::new("ninja")
.arg("-f")
.arg(build_file)
.arg("build_run_configure")
.env("PATH", path)
.output()
.expect("ninja installed");
if !output.status.success() {
// The existing build.ninja may be invalid if files have been renamed/removed;
// resort to a slower cargo invocation instead to regenerate it.
bootstrap_build();
}
}
fn bootstrap_build() {
let status = Command::new("cargo")
.args(["run", "-p", "configure"])
.status();
assert!(status.expect("ninja").success());
}
fn maybe_update_buildhash(build_root: &Utf8Path) {
// only updated on release builds
let path = build_root.join("buildhash");
if env::var("RELEASE").is_ok() || !path.exists() {
write_if_changed(&path, &get_buildhash())
}
}
fn get_buildhash() -> String {
let output = Command::new("git")
.args(["rev-parse", "--short=8", "HEAD"])
.output()
.expect("git");
assert!(output.status.success(), "git failed");
String::from_utf8(output.stdout).unwrap().trim().into()
}
fn write_if_changed(path: &Utf8Path, contents: &str) {
if let Ok(old_contents) = fs::read_to_string(path) {
if old_contents == contents {
return;
}
}
fs::write(path, contents).unwrap();
}
/// Trigger reconfigure when our env vars change
fn maybe_update_env_file(build_root: &Utf8Path) {
let env_file = build_root.join("env");
let build_root_env = env::var("BUILD_ROOT").unwrap_or_default();
let release = env::var("RELEASE").unwrap_or_default();
let other_watched_env = env::var("RECONFIGURE_KEY").unwrap_or_default();
let current_env = format!("{build_root_env};{release};{other_watched_env}");
write_if_changed(&env_file, &current_env);
}