use std::env;
use std::fs;
use std::path::Path;
use parity_scale_codec::Decode;
use sha2::{Digest, Sha256};
use subxt_codegen::CodegenBuilder;
use subxt_metadata::Metadata;
use subxt_utils_fetchmetadata::{from_url_blocking, MetadataVersion, Url};
const FINNEY_ENDPOINT: &str = "wss://entrypoint-finney.opentensor.ai:443";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=metadata/finney.scale");
let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set");
let metadata_path = Path::new(&out_dir).join("metadata.scale");
let code_path = Path::new(&out_dir).join("metadata.rs");
let hash_path = Path::new(&out_dir).join("metadata.hash");
let bundled_metadata_path = Path::new("metadata/finney.scale");
let use_bundled = env::var("DOCS_RS").is_ok() || env::var("BITTENSOR_OFFLINE").is_ok();
if use_bundled {
println!("cargo:warning=Using bundled metadata (docs.rs or offline mode)");
generate_from_bundled(&bundled_metadata_path, &code_path, &hash_path);
return;
}
let endpoint = env::var("BITTENSOR_ENDPOINT").unwrap_or_else(|_| FINNEY_ENDPOINT.to_string());
println!("cargo:warning=Fetching metadata from {}", endpoint);
let url = Url::parse(&endpoint).expect("Invalid endpoint URL");
let metadata_bytes = match from_url_blocking(url, MetadataVersion::Latest) {
Ok(bytes) => bytes,
Err(e) => {
println!("cargo:warning=Failed to fetch metadata: {}", e);
if code_path.exists() {
println!("cargo:warning=Using cached generated code");
return;
}
if bundled_metadata_path.exists() {
println!("cargo:warning=Falling back to bundled metadata");
generate_from_bundled(&bundled_metadata_path, &code_path, &hash_path);
return;
}
panic!("No cached code and failed to fetch metadata: {}", e);
}
};
generate_from_bytes(&metadata_bytes, &metadata_path, &code_path, &hash_path);
}
fn generate_from_bundled(bundled_path: &Path, code_path: &Path, hash_path: &Path) {
let metadata_bytes = fs::read(bundled_path)
.unwrap_or_else(|e| panic!("Failed to read bundled metadata from {:?}: {}", bundled_path, e));
generate_code(&metadata_bytes, code_path, hash_path);
}
fn generate_from_bytes(metadata_bytes: &[u8], metadata_path: &Path, code_path: &Path, hash_path: &Path) {
let mut hasher = Sha256::new();
hasher.update(metadata_bytes);
let new_hash = format!("{:x}", hasher.finalize());
let should_regenerate = if let Ok(old_hash) = fs::read_to_string(hash_path) {
if old_hash.trim() == new_hash {
println!("cargo:warning=Metadata unchanged, using cached code");
false
} else {
println!("cargo:warning=Metadata changed, regenerating code");
true
}
} else {
println!("cargo:warning=No cached hash, generating code");
true
};
if should_regenerate {
fs::write(metadata_path, metadata_bytes).expect("Failed to write metadata.scale");
generate_code(metadata_bytes, code_path, hash_path);
}
}
fn generate_code(metadata_bytes: &[u8], code_path: &Path, hash_path: &Path) {
let mut hasher = Sha256::new();
hasher.update(metadata_bytes);
let new_hash = format!("{:x}", hasher.finalize());
let metadata = Metadata::decode(&mut &metadata_bytes[..]).expect("Failed to decode metadata");
let code = CodegenBuilder::new()
.generate(metadata)
.expect("Failed to generate code from metadata");
let wrapped_code = format!(
"#[allow(dead_code, unused_imports, non_camel_case_types)]\n\
#[allow(clippy::all)]\n\
{}",
code
);
fs::write(code_path, wrapped_code).expect("Failed to write metadata.rs");
fs::write(hash_path, &new_hash).expect("Failed to write metadata.hash");
println!(
"cargo:warning=Generated code from {} bytes of metadata",
metadata_bytes.len()
);
}