use std::{env, fs, path::PathBuf};
fn main() {
let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION not set");
let min_compatible = derive_min_compatible(&version);
println!("cargo:rustc-env=PROTOCOL_VERSION={}", version);
println!("cargo:rustc-env=MIN_COMPATIBLE_VERSION={}", min_compatible);
generate_solidity_version(&version, &min_compatible);
generate_embedded_deployments();
let manifest_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let workspace_root = manifest_path
.parent()
.and_then(|p| p.parent())
.expect("Failed to find workspace root");
println!("cargo:rustc-env=WORKSPACE_ROOT={}", workspace_root.display());
println!("cargo:rerun-if-changed=../../Cargo.toml");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=../../contracts/script/deployments");
}
fn derive_min_compatible(version: &str) -> String {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() == 3 {
format!("{}.{}.0", parts[0], parts[1])
} else {
version.to_string()
}
}
fn generate_embedded_deployments() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let workspace_root = PathBuf::from(&manifest_dir)
.parent()
.and_then(|p| p.parent())
.expect("Failed to find workspace root")
.to_path_buf();
let deployments_dir = workspace_root.join("contracts").join("script").join("deployments");
let categories = ["core", "newton-prover", "policy", "newton-cross-chain"];
let mut arms = Vec::new();
let manifest_path = PathBuf::from(&manifest_dir);
for category in &categories {
let cat_dir = deployments_dir.join(category);
if !cat_dir.exists() {
continue;
}
let entries: Vec<_> = fs::read_dir(&cat_dir)
.unwrap_or_else(|e| panic!("Failed to read {:?}: {}", cat_dir, e))
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
.collect();
for entry in entries {
let filename = entry.file_name();
let filename_str = filename.to_string_lossy();
let stem = filename_str.strip_suffix(".json").unwrap();
let parts: Vec<&str> = stem.splitn(2, '-').collect();
if parts.len() != 2 {
continue;
}
let chain_id = parts[0];
let env_name = parts[1];
let abs_path = entry.path();
let rel_path = abs_path
.strip_prefix(&workspace_root)
.map(|p| format!("../../{}", p.to_string_lossy().replace('\\', "/")))
.unwrap_or_else(|_| abs_path.to_string_lossy().to_string());
println!("cargo:rerun-if-changed={}", abs_path.to_string_lossy());
arms.push(format!(
r#" ("{cat}", {chain_id}, "{env_name}") => Some(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/{rel}"))),"#,
cat = category,
chain_id = chain_id,
env_name = env_name,
rel = rel_path,
));
}
}
let generated = format!(
r#"/// Auto-generated by build.rs — do not edit.
/// Returns embedded deployment JSON for the given category, chain_id, and env.
pub fn get_embedded_deployment(category: &str, chain_id: u64, env: &str) -> Option<&'static str> {{
match (category, chain_id, env) {{
{arms}
_ => None,
}}
}}
"#,
arms = arms.join("\n"),
);
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
let out_path = PathBuf::from(&out_dir).join("embedded_deployments.rs");
let should_write = if out_path.exists() {
fs::read_to_string(&out_path).map_or(true, |existing| existing != generated)
} else {
true
};
if should_write {
fs::write(&out_path, generated).expect("Failed to write embedded_deployments.rs");
}
}
fn generate_solidity_version(version: &str, min_compatible: &str) {
let solidity_content = format!(
r#"// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
// @title Protocol Version Constants
// @notice This file is auto-generated by the Rust build system from Cargo.toml
// @dev DO NOT EDIT MANUALLY - Changes will be overwritten on next build
// @dev To update versions, modify the workspace version in the root Cargo.toml
// @dev MIN_COMPATIBLE version uses MAJOR.MINOR.0 (patch is ignored for compatibility)
// Protocol version following SemVer 2.0.0 (MAJOR.MINOR.PATCH)
string constant PROTOCOL_VERSION = "{}";
// Minimum compatible factory version (MAJOR.MINOR.0)
string constant MIN_COMPATIBLE_VERSION = "{}";
"#,
version, min_compatible
);
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let manifest_path = PathBuf::from(&manifest_dir);
let workspace_root = manifest_path
.parent()
.and_then(|p| p.parent())
.expect("Failed to find workspace root");
let output_path = workspace_root
.join("contracts")
.join("src")
.join("libraries")
.join("ProtocolVersion.sol");
let should_write = if output_path.exists() {
match fs::read_to_string(&output_path) {
Ok(existing) => existing != solidity_content,
Err(_) => true,
}
} else {
true
};
if should_write {
fs::write(&output_path, solidity_content).unwrap_or_else(|e| {
eprintln!(
"Warning: Failed to write Solidity version file to {:?}: {}",
output_path, e
);
});
println!("cargo:warning=Generated Solidity version file: {:?}", output_path);
}
}