use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use string_auto_indent::auto_indent;
use toml::Value;
pub fn inject_build_metadata(project_dest_path: PathBuf) {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let dest_path = manifest_dir.join(&project_dest_path);
let destination_dir = dest_path.parent().unwrap();
fs::create_dir_all(&destination_dir).expect("Failed to create generated directory");
let build_target = env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string());
set_cargo_env_var("BUILD_TARGET", &build_target);
let build_time_utc = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs()
.to_string();
set_cargo_env_var("BUILD_TIME_UTC", &build_time_utc);
if let Some(license_path) = get_license_file_path(&manifest_dir) {
if let Ok(license_content) = fs::read_to_string(&license_path) {
set_cargo_env_var("LICENSE_CONTENT", &license_content);
}
}
const CONTENTS: &[u8] = include_bytes!("inject_build_metadata.struct.rs");
if let Ok(existing_contents) = fs::read(&dest_path) {
if existing_contents == CONTENTS {
println!(
"No changes to {}; skipping file write.",
dest_path.display()
);
} else {
fs::write(&dest_path, CONTENTS).expect("Failed to write metadata file");
}
} else {
fs::write(&dest_path, CONTENTS).expect("Failed to write metadata file");
}
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=inject.rs");
if let Some(license_path) = get_license_file_path(&manifest_dir) {
println!("cargo:rerun-if-changed={}", license_path.display());
}
}
fn escape_newlines(s: &str) -> String {
s.replace("\r\n", "\n").replace('\n', "\\n")
}
pub fn get_cargo_field(manifest_dir: &Path, field: &str) -> Option<String> {
let cargo_toml_path = manifest_dir.join("Cargo.toml");
let cargo_toml_content = fs::read_to_string(&cargo_toml_path).ok()?;
let cargo_toml: Value = toml::from_str(&cargo_toml_content).ok()?;
cargo_toml
.get("package")?
.get(field)?
.as_str()
.map(|s| s.to_string())
}
pub fn get_license_file_path(manifest_dir: &Path) -> Option<PathBuf> {
get_cargo_field(manifest_dir, "license-file").map(|rel_path| manifest_dir.join(rel_path))
}
pub fn set_cargo_env_var(var_name: &str, value: &str) {
assert!(
is_valid_env_var_name(var_name),
"Invalid Cargo environment variable name: '{}'",
var_name
);
assert!(
!var_name.starts_with("CARGO_"),
"Environment variable name '{}' is reserved (cannot start with 'CARGO_')",
var_name
);
let formatted_value = escape_newlines(value); println!("cargo:rustc-env={}={}", var_name, formatted_value);
}
pub fn set_multi_line_cargo_env_var(var_name: &str, value: &str) {
let auto_indented_value = auto_indent(value);
set_cargo_env_var(var_name, auto_indented_value.as_str());
}
fn is_valid_env_var_name(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() => (),
_ => return false,
}
chars.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
}