#![allow(non_snake_case)]
use serde::Deserialize;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
struct ArtifactConfig {
godot_version: String,
artifacts: Vec<Artifact>,
}
#[derive(Clone, Deserialize)]
struct Artifact {
target: String,
path: String,
url: String,
sha256: String,
}
fn main() {
println!("cargo:rerun-if-changed=godot-artifacts.toml");
println!("cargo:rerun-if-env-changed=GODORU_GODOT_DIR");
println!("cargo:rerun-if-env-changed=GODORU_NO_DOWNLOAD");
let manifestDir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let metadataPath = manifestDir.join("godot-artifacts.toml");
let metadata = fs::read_to_string(&metadataPath).unwrap_or_else(|err| {
panic!(
"failed to read Godoru artifact metadata at {}: {err}",
metadataPath.display()
)
});
let config: ArtifactConfig = toml::from_str(&metadata).unwrap_or_else(|err| {
panic!(
"failed to parse Godoru artifact metadata at {}: {err}",
metadataPath.display()
)
});
let target = env::var("TARGET").unwrap();
let artifacts: Vec<Artifact> = config
.artifacts
.iter()
.filter(|artifact| artifact.target == target)
.cloned()
.collect();
if artifacts.is_empty() {
println!("cargo:rustc-env=GODORU_GODOT_DIR_RESOLVED=");
return;
}
if let Ok(path) = env::var("GODORU_GODOT_DIR") {
let root = PathBuf::from(path);
verifyRoot(&root, &artifacts);
println!(
"cargo:rustc-env=GODORU_GODOT_DIR_RESOLVED={}",
root.display()
);
return;
}
let localVendorRoot = manifestDir
.join("vendor")
.join("godot")
.join(&config.godot_version);
if hasArtifacts(&localVendorRoot, &artifacts) {
verifyRoot(&localVendorRoot, &artifacts);
println!(
"cargo:rustc-env=GODORU_GODOT_DIR_RESOLVED={}",
localVendorRoot.display()
);
return;
}
let cacheRoot = targetRoot()
.join("godoru-godot")
.join(&config.godot_version);
if hasArtifacts(&cacheRoot, &artifacts) {
verifyRoot(&cacheRoot, &artifacts);
println!(
"cargo:rustc-env=GODORU_GODOT_DIR_RESOLVED={}",
cacheRoot.display()
);
return;
}
if env::var("GODORU_NO_DOWNLOAD").ok().as_deref() == Some("1") {
panic!(
"Godoru Godot artifacts are missing and GODORU_NO_DOWNLOAD=1 is set. Set GODORU_GODOT_DIR or restore {}",
localVendorRoot.display()
);
}
fs::create_dir_all(&cacheRoot).unwrap_or_else(|err| {
panic!(
"failed to create Godoru artifact cache at {}: {err}",
cacheRoot.display()
)
});
for artifact in &artifacts {
downloadArtifact(&cacheRoot, artifact);
}
verifyRoot(&cacheRoot, &artifacts);
println!(
"cargo:rustc-env=GODORU_GODOT_DIR_RESOLVED={}",
cacheRoot.display()
);
}
fn targetRoot() -> PathBuf {
let outDir = PathBuf::from(env::var("OUT_DIR").unwrap());
for ancestor in outDir.ancestors() {
if ancestor.file_name().and_then(|name| name.to_str()) == Some("target") {
return ancestor.to_path_buf();
}
}
outDir.join("godoru-target")
}
fn hasArtifacts(root: &Path, artifacts: &[Artifact]) -> bool {
artifacts
.iter()
.all(|artifact| root.join(&artifact.path).is_file())
}
fn verifyRoot(root: &Path, artifacts: &[Artifact]) {
for artifact in artifacts {
let path = root.join(&artifact.path);
if !path.is_file() {
panic!("missing Godoru Godot artifact: {}", path.display());
}
let digest = fileSha256(&path);
if digest != artifact.sha256 {
panic!(
"checksum mismatch for {}. expected {}, got {}",
path.display(),
artifact.sha256,
digest
);
}
}
}
fn fileSha256(path: &Path) -> String {
let mut file = fs::File::open(path)
.unwrap_or_else(|err| panic!("failed to open {}: {err}", path.display()));
let mut hasher = Sha256::new();
let mut buffer = [0u8; 64 * 1024];
loop {
let read = file
.read(&mut buffer)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()));
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
format!("{:x}", hasher.finalize())
}
fn downloadArtifact(root: &Path, artifact: &Artifact) {
let path = root.join(&artifact.path);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap_or_else(|err| {
panic!(
"failed to create Godoru artifact directory {}: {err}",
parent.display()
)
});
}
let response = ureq::get(&artifact.url)
.call()
.unwrap_or_else(|err| panic!("failed to download {}: {err}", artifact.url));
let mut reader = response.into_reader();
let mut file = fs::File::create(&path)
.unwrap_or_else(|err| panic!("failed to create {}: {err}", path.display()));
std::io::copy(&mut reader, &mut file)
.unwrap_or_else(|err| panic!("failed to write {}: {err}", path.display()));
}