blueprint_build_utils/
lib.rs1use std::io::{Read, Write};
2
3use blueprint_std::{
4 env, fs,
5 path::{Path, PathBuf},
6 process::Command,
7};
8
9pub fn build_contracts(contract_dirs: Vec<&str>) {
22 let root = workspace_or_manifest_dir();
24
25 let forge_executable = find_forge_executable();
27
28 for dir in contract_dirs {
29 let full_path = root.join(dir).canonicalize().unwrap_or_else(|_| {
30 println!(
31 "Directory not found or inaccessible: {}",
32 root.join(dir).display()
33 );
34 root.join(dir)
35 });
36
37 if full_path.exists() {
38 if full_path != root.join("./contracts") {
39 let foundry_toml_path = full_path.join("foundry.toml");
41
42 if foundry_toml_path.exists() {
44 let mut content = String::new();
46 std::fs::File::open(&foundry_toml_path)
47 .expect("Failed to open foundry.toml")
48 .read_to_string(&mut content)
49 .expect("Failed to read foundry.toml");
50
51 if !content.contains("evm_version") {
53 if let Some(pos) = content.find("[profile.default]") {
55 let mut new_content = content.clone();
57 let insert_pos = content[pos..]
58 .find('\n')
59 .map_or(content.len(), |p| p + pos + 1);
60 new_content.insert_str(insert_pos, " evm_version = \"shanghai\"\n");
61
62 std::fs::write(&foundry_toml_path, new_content)
64 .expect("Failed to write to foundry.toml");
65 } else {
66 let mut file = std::fs::OpenOptions::new()
68 .append(true)
69 .open(&foundry_toml_path)
70 .expect("Failed to open foundry.toml for appending");
71
72 file.write_all(b"\n[profile.default]\nevm_version = \"shanghai\"\n")
73 .expect("Failed to append to foundry.toml");
74 }
75 }
76 } else {
77 panic!("Failed to read dependency foundry.toml");
78 }
79 }
80
81 let status = Command::new(&forge_executable)
83 .current_dir(&full_path)
84 .arg("build")
85 .arg("--evm-version")
86 .arg("shanghai")
87 .arg("--use")
88 .arg("0.8.27")
89 .status()
90 .expect("Failed to execute Forge build");
91
92 assert!(
93 status.success(),
94 "Forge build failed for directory: {}",
95 full_path.display()
96 );
97 } else {
98 panic!(
99 "Directory not found or does not exist: {}",
100 full_path.display()
101 );
102 }
103 }
104}
105
106fn is_directory_empty(path: &Path) -> bool {
107 fs::read_dir(path)
108 .map(|mut i| i.next().is_none())
109 .unwrap_or(true)
110}
111
112fn workspace_or_manifest_dir() -> PathBuf {
113 let dir = env::var("CARGO_WORKSPACE_DIR")
114 .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
115 .expect("neither CARGO_WORKSPACE_DIR nor CARGO_MANIFEST_DIR is set");
116 PathBuf::from(dir)
117}
118
119pub fn soldeer_install() {
126 let root = workspace_or_manifest_dir();
128
129 let dependencies_dir = root.join("dependencies");
131 if !dependencies_dir.exists() || is_directory_empty(&dependencies_dir) {
132 let forge_executable = find_forge_executable();
133
134 println!("Populating dependencies directory");
135 let status = Command::new(&forge_executable)
136 .current_dir(&root)
137 .args(["soldeer", "install"])
138 .status()
139 .expect("Failed to execute 'forge soldeer install'");
140
141 assert!(status.success(), "'forge soldeer install' failed");
142 } else {
143 println!("Dependencies directory exists or is not empty. Skipping soldeer install.");
144 }
145}
146
147pub fn soldeer_update() {
154 let root = workspace_or_manifest_dir();
156
157 let forge_executable = find_forge_executable();
159
160 let status = Command::new(&forge_executable)
161 .current_dir(&root)
162 .args(["soldeer", "update", "-d"])
163 .status()
164 .expect("Failed to execute 'forge soldeer update'");
165
166 assert!(status.success(), "'forge soldeer update' failed");
167}
168
169#[must_use]
174pub fn find_forge_executable() -> String {
175 match Command::new("which").arg("forge").output() {
177 Ok(output) => {
178 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
179 assert!(
180 !path.is_empty(),
181 "Forge executable not found. Make sure Foundry is installed."
182 );
183 path
184 }
185 Err(e) => panic!("Failed to find `forge` executable: {e}"),
186 }
187}