|
| 1 | +//! Build script for Chromobius decoder integration |
| 2 | +
|
| 3 | +use log::info; |
| 4 | +use pecos_build::{Manifest, Result, ensure_dep_ready, report_cache_config}; |
| 5 | +use std::env; |
| 6 | +use std::fs; |
| 7 | +use std::path::{Path, PathBuf}; |
| 8 | + |
| 9 | +// Use the shared modules from the parent |
| 10 | +use crate::build_stim; |
| 11 | +use crate::chromobius_patch; |
| 12 | + |
| 13 | +/// Get the build profile from Cargo's environment |
| 14 | +fn get_build_profile() -> String { |
| 15 | + if let Ok(out_dir) = env::var("OUT_DIR") { |
| 16 | + let parts: Vec<&str> = out_dir.split(std::path::MAIN_SEPARATOR).collect(); |
| 17 | + if let Some(target_idx) = parts.iter().position(|&p| p == "target") |
| 18 | + && let Some(profile_name) = parts.get(target_idx + 1) |
| 19 | + { |
| 20 | + return match *profile_name { |
| 21 | + "native" => "native", |
| 22 | + "release" => "release", |
| 23 | + "debug" => "debug", |
| 24 | + _ => { |
| 25 | + if env::var("PROFILE").as_deref() == Ok("release") { |
| 26 | + "release" |
| 27 | + } else { |
| 28 | + "debug" |
| 29 | + } |
| 30 | + } |
| 31 | + } |
| 32 | + .to_string(); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + match env::var("PROFILE").as_deref() { |
| 37 | + Ok("release") => "release".to_string(), |
| 38 | + _ => "debug".to_string(), |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +/// Main build function for Chromobius |
| 43 | +pub fn build() -> Result<()> { |
| 44 | + println!("cargo:rerun-if-changed=build_chromobius.rs"); |
| 45 | + println!("cargo:rerun-if-changed=src/bridge.rs"); |
| 46 | + println!("cargo:rerun-if-changed=src/bridge.cpp"); |
| 47 | + println!("cargo:rerun-if-changed=include/chromobius_bridge.h"); |
| 48 | + println!("cargo:rerun-if-env-changed=FORCE_REBUILD"); |
| 49 | + |
| 50 | + let out_dir = PathBuf::from(env::var("OUT_DIR")?); |
| 51 | + |
| 52 | + // Always emit link directives - Cargo will cache these |
| 53 | + println!("cargo:rustc-link-search=native={}", out_dir.display()); |
| 54 | + println!("cargo:rustc-link-lib=static=chromobius-bridge"); |
| 55 | + |
| 56 | + // Get dependencies (downloads to ~/.pecos/cache/, extracts to ~/.pecos/deps/) |
| 57 | + let manifest = Manifest::find_and_load_validated()?; |
| 58 | + let chromobius_dir = ensure_dep_ready("chromobius", &manifest)?; |
| 59 | + let stim_dir = ensure_dep_ready("stim", &manifest)?; |
| 60 | + let pymatching_dir = ensure_dep_ready("pymatching", &manifest)?; |
| 61 | + |
| 62 | + // Apply compatibility patches for newer Stim version |
| 63 | + chromobius_patch::patch_chromobius_for_newer_stim(&chromobius_dir)?; |
| 64 | + |
| 65 | + // Generate amalgamated stim.h if needed |
| 66 | + build_stim::generate_amalgamated_header(&stim_dir)?; |
| 67 | + |
| 68 | + // Build using cxx |
| 69 | + build_cxx_bridge(&chromobius_dir, &stim_dir, &pymatching_dir)?; |
| 70 | + |
| 71 | + Ok(()) |
| 72 | +} |
| 73 | + |
| 74 | +fn build_cxx_bridge(chromobius_dir: &Path, stim_dir: &Path, pymatching_dir: &Path) -> Result<()> { |
| 75 | + let chromobius_src_dir = chromobius_dir.join("src"); |
| 76 | + let stim_src_dir = stim_dir.join("src"); |
| 77 | + let pymatching_src_dir = pymatching_dir.join("src"); |
| 78 | + |
| 79 | + // Find essential source files |
| 80 | + let chromobius_files = collect_chromobius_sources(&chromobius_src_dir)?; |
| 81 | + let stim_files = build_stim::collect_stim_sources(&stim_src_dir); |
| 82 | + let pymatching_files = collect_pymatching_sources(&pymatching_src_dir)?; |
| 83 | + |
| 84 | + // Build the cxx bridge first to generate headers |
| 85 | + let mut build = cxx_build::bridge("src/bridge.rs"); |
| 86 | + |
| 87 | + let target = env::var("TARGET").unwrap_or_default(); |
| 88 | + |
| 89 | + // On macOS, explicitly use system clang to ensure SDK paths are correct. |
| 90 | + if target.contains("darwin") && env::var("CXX").is_err() && env::var("CC").is_err() { |
| 91 | + build.compiler("/usr/bin/clang++"); |
| 92 | + } |
| 93 | + |
| 94 | + // Add our bridge implementation |
| 95 | + build.file("src/bridge.cpp"); |
| 96 | + |
| 97 | + // Add Chromobius core files |
| 98 | + for file in chromobius_files { |
| 99 | + build.file(file); |
| 100 | + } |
| 101 | + |
| 102 | + // Add PyMatching files |
| 103 | + for file in pymatching_files { |
| 104 | + build.file(file); |
| 105 | + } |
| 106 | + |
| 107 | + // Configure build |
| 108 | + build |
| 109 | + .std("c++20") |
| 110 | + .include(&chromobius_src_dir) |
| 111 | + .include(&stim_src_dir) |
| 112 | + .include(stim_dir) // For amalgamated stim.h |
| 113 | + .include(&pymatching_src_dir) |
| 114 | + .include("include") |
| 115 | + .include("src") |
| 116 | + .define("CHROMOBIUS_BRIDGE_EXPORTS", None); |
| 117 | + |
| 118 | + // Report ccache/sccache configuration |
| 119 | + report_cache_config(); |
| 120 | + |
| 121 | + // Use build profile for optimization settings |
| 122 | + let profile = get_build_profile(); |
| 123 | + match profile.as_str() { |
| 124 | + "native" => { |
| 125 | + build.flag_if_supported("-O3"); |
| 126 | + if env::var("CARGO_CFG_TARGET_ARCH").ok() == env::var("HOST_ARCH").ok() { |
| 127 | + build.flag_if_supported("-march=native"); |
| 128 | + } |
| 129 | + } |
| 130 | + "release" => { |
| 131 | + build.flag_if_supported("-O3"); |
| 132 | + } |
| 133 | + _ => { |
| 134 | + build.flag_if_supported("-O0"); |
| 135 | + build.flag_if_supported("-g"); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + // Add Stim files to the main build |
| 140 | + for file in &stim_files { |
| 141 | + build.file(file); |
| 142 | + } |
| 143 | + |
| 144 | + // Platform-specific configurations |
| 145 | + if cfg!(not(target_env = "msvc")) { |
| 146 | + build |
| 147 | + .flag("-fvisibility=hidden") |
| 148 | + .flag("-fvisibility-inlines-hidden") |
| 149 | + .flag("-w") |
| 150 | + .flag_if_supported("-fopenmp") |
| 151 | + .flag("-fPIC"); |
| 152 | + |
| 153 | + if target.contains("darwin") { |
| 154 | + build.flag("-stdlib=libc++"); |
| 155 | + build.flag("-L/usr/lib"); |
| 156 | + build.flag("-Wl,-search_paths_first"); |
| 157 | + } |
| 158 | + } else { |
| 159 | + build |
| 160 | + .flag("/W0") |
| 161 | + .flag("/MD") |
| 162 | + .flag("/EHsc") // Enable C++ exception handling |
| 163 | + .flag_if_supported("/permissive-") |
| 164 | + .flag_if_supported("/Zc:__cplusplus"); |
| 165 | + |
| 166 | + // Force include standard headers that external libraries assume are available |
| 167 | + // MSVC is stricter than GCC/Clang about transitive includes |
| 168 | + build.flag("/FI").flag("array"); // For std::array |
| 169 | + build.flag("/FI").flag("numeric"); // For std::iota (used by PyMatching) |
| 170 | + } |
| 171 | + |
| 172 | + build.compile("chromobius-bridge"); |
| 173 | + |
| 174 | + // On macOS, link against the system C++ library |
| 175 | + if target.contains("darwin") { |
| 176 | + println!("cargo:rustc-link-search=native=/usr/lib"); |
| 177 | + println!("cargo:rustc-link-lib=c++"); |
| 178 | + println!("cargo:rustc-link-arg=-Wl,-search_paths_first"); |
| 179 | + } |
| 180 | + |
| 181 | + Ok(()) |
| 182 | +} |
| 183 | + |
| 184 | +fn collect_chromobius_sources(chromobius_src_dir: &Path) -> Result<Vec<PathBuf>> { |
| 185 | + let mut files = Vec::new(); |
| 186 | + |
| 187 | + // Collect all non-test, non-perf, non-pybind .cc files |
| 188 | + collect_cc_files_filtered(chromobius_src_dir, &mut files)?; |
| 189 | + |
| 190 | + info!("Found {} Chromobius source files", files.len()); |
| 191 | + Ok(files) |
| 192 | +} |
| 193 | + |
| 194 | +fn collect_pymatching_sources(pymatching_src_dir: &Path) -> Result<Vec<PathBuf>> { |
| 195 | + let mut files = Vec::new(); |
| 196 | + |
| 197 | + // PyMatching sparse_blossom implementation files |
| 198 | + let sparse_blossom_dir = pymatching_src_dir.join("pymatching/sparse_blossom"); |
| 199 | + if sparse_blossom_dir.exists() { |
| 200 | + collect_cc_files_filtered(&sparse_blossom_dir, &mut files)?; |
| 201 | + } |
| 202 | + |
| 203 | + info!("Found {} PyMatching source files", files.len()); |
| 204 | + Ok(files) |
| 205 | +} |
| 206 | + |
| 207 | +fn collect_cc_files_filtered(dir: &Path, files: &mut Vec<PathBuf>) -> Result<()> { |
| 208 | + for entry in fs::read_dir(dir)? { |
| 209 | + let entry = entry?; |
| 210 | + let path = entry.path(); |
| 211 | + |
| 212 | + if path.is_dir() { |
| 213 | + // Skip test directories |
| 214 | + if let Some(name) = path.file_name().and_then(|n| n.to_str()) |
| 215 | + && (name == "test" || name == "tests") |
| 216 | + { |
| 217 | + continue; |
| 218 | + } |
| 219 | + collect_cc_files_filtered(&path, files)?; |
| 220 | + } else if path.extension().and_then(|s| s.to_str()) == Some("cc") { |
| 221 | + let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); |
| 222 | + // Skip test, perf, pybind, and main files |
| 223 | + if filename.contains(".test.") |
| 224 | + || filename.contains(".perf.") |
| 225 | + || filename.contains(".pybind.") |
| 226 | + || filename == "main.cc" |
| 227 | + { |
| 228 | + continue; |
| 229 | + } |
| 230 | + if !files.contains(&path) { |
| 231 | + files.push(path); |
| 232 | + } |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + Ok(()) |
| 237 | +} |
0 commit comments