1 // Build script. 2 // 3 // This program is run by Cargo when building cranelift-codegen. It is used to generate Rust code from 4 // the language definitions in the cranelift-codegen/meta directory. 5 // 6 // Environment: 7 // 8 // OUT_DIR 9 // Directory where generated files should be placed. 10 // 11 // TARGET 12 // Target triple provided by Cargo. 13 // 14 // The build script expects to be run from the directory where this build.rs file lives. The 15 // current directory is used to find the sources. 16 17 use cranelift_codegen_meta as meta; 18 use cranelift_isle::error::Errors; 19 use meta::isle::IsleCompilation; 20 21 use std::env; 22 use std::io::Read; 23 use std::process; 24 use std::time::Instant; 25 26 fn main() { 27 let start_time = Instant::now(); 28 29 let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); 30 let out_dir = std::path::Path::new(&out_dir); 31 let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); 32 33 let all_arch = env::var("CARGO_FEATURE_ALL_ARCH").is_ok(); 34 let all_native_arch = env::var("CARGO_FEATURE_ALL_NATIVE_ARCH").is_ok(); 35 36 let mut isas = meta::isa::Isa::all() 37 .iter() 38 .cloned() 39 .filter(|isa| { 40 let env_key = match isa { 41 meta::isa::Isa::Pulley32 | meta::isa::Isa::Pulley64 => { 42 "CARGO_FEATURE_PULLEY".to_string() 43 } 44 _ => format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()), 45 }; 46 all_arch || env::var(env_key).is_ok() 47 }) 48 .collect::<Vec<_>>(); 49 50 // Don't require host isa if under 'all-arch' feature. 51 let host_isa = env::var("CARGO_FEATURE_HOST_ARCH").is_ok() && !all_native_arch; 52 53 if isas.is_empty() || host_isa { 54 // Try to match native target. 55 let target_name = target_triple.split('-').next().unwrap(); 56 if let Ok(isa) = meta::isa_from_arch(&target_name) { 57 println!("cargo:rustc-cfg=feature=\"{isa}\""); 58 isas.push(isa); 59 } 60 } 61 62 let cur_dir = env::current_dir().expect("Can't access current working directory"); 63 let crate_dir = cur_dir.as_path(); 64 65 println!("cargo:rerun-if-changed=build.rs"); 66 67 let isle_dir = if let Ok(path) = std::env::var("ISLE_SOURCE_DIR") { 68 // This will canonicalize any relative path in terms of the 69 // crate root, and will take any absolute path as overriding the 70 // `crate_dir`. 71 crate_dir.join(&path) 72 } else { 73 out_dir.into() 74 }; 75 76 std::fs::create_dir_all(&isle_dir).expect("Could not create ISLE source directory"); 77 78 if let Err(err) = meta::generate(&isas, &out_dir, &isle_dir) { 79 eprintln!("Error: {err}"); 80 process::exit(1); 81 } 82 83 if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" { 84 if let Err(err) = build_isle(crate_dir, &isle_dir) { 85 eprintln!("Error: {err}"); 86 process::exit(1); 87 } 88 } 89 90 if env::var("CRANELIFT_VERBOSE").is_ok() { 91 for isa in &isas { 92 println!("cargo:warning=Includes support for {} ISA", isa.to_string()); 93 } 94 println!( 95 "cargo:warning=Build step took {:?}.", 96 Instant::now() - start_time 97 ); 98 println!("cargo:warning=Generated files are in {}", out_dir.display()); 99 } 100 101 let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); 102 let mut cmd = std::process::Command::new("git"); 103 cmd.arg("rev-parse") 104 .arg("HEAD") 105 .stdout(std::process::Stdio::piped()) 106 .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()); 107 let version = if let Ok(mut child) = cmd.spawn() { 108 let mut git_rev = String::new(); 109 child 110 .stdout 111 .as_mut() 112 .unwrap() 113 .read_to_string(&mut git_rev) 114 .unwrap(); 115 let status = child.wait().unwrap(); 116 if status.success() { 117 let git_rev = git_rev.trim().chars().take(9).collect::<String>(); 118 format!("{pkg_version}-{git_rev}") 119 } else { 120 // not a git repo 121 pkg_version 122 } 123 } else { 124 // git not available 125 pkg_version 126 }; 127 std::fs::write( 128 std::path::Path::new(&out_dir).join("version.rs"), 129 format!( 130 "/// Version number of this crate. \n\ 131 pub const VERSION: &str = \"{version}\";" 132 ), 133 ) 134 .unwrap(); 135 } 136 137 /// Strip the current directory from the file paths, because `islec` 138 /// includes them in the generated source, and this helps us maintain 139 /// deterministic builds that don't include those local file paths. 140 fn make_isle_source_path_relative( 141 cur_dir: &std::path::Path, 142 filename: &std::path::Path, 143 ) -> std::path::PathBuf { 144 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 145 suffix.to_path_buf() 146 } else { 147 filename.to_path_buf() 148 } 149 } 150 151 fn build_isle( 152 crate_dir: &std::path::Path, 153 isle_dir: &std::path::Path, 154 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 155 let cur_dir = std::env::current_dir()?; 156 let isle_compilations = meta::isle::get_isle_compilations( 157 &make_isle_source_path_relative(&cur_dir, &crate_dir), 158 &make_isle_source_path_relative(&cur_dir, &isle_dir), 159 ); 160 161 let mut had_error = false; 162 for compilation in &isle_compilations.items { 163 for file in &compilation.inputs { 164 println!("cargo:rerun-if-changed={}", file.display()); 165 } 166 167 if let Err(e) = run_compilation(compilation) { 168 had_error = true; 169 eprintln!("Error building ISLE files:"); 170 eprintln!("{e:?}"); 171 #[cfg(not(feature = "isle-errors"))] 172 { 173 eprintln!("To see a more detailed error report, run: "); 174 eprintln!(); 175 eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); 176 eprintln!(); 177 } 178 } 179 } 180 181 if had_error { 182 std::process::exit(1); 183 } 184 185 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 186 187 Ok(()) 188 } 189 190 /// Build ISLE DSL source text into generated Rust code. 191 /// 192 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 193 /// it consumes files generated by them. 194 fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { 195 use cranelift_isle as isle; 196 197 eprintln!("Rebuilding {}", compilation.output.display()); 198 199 let code = { 200 let file_paths = compilation 201 .inputs 202 .iter() 203 .chain(compilation.untracked_inputs.iter()); 204 205 let mut options = isle::codegen::CodegenOptions::default(); 206 // Because we include!() the generated ISLE source, we cannot 207 // put the global pragmas (`#![allow(...)]`) in the ISLE 208 // source itself; we have to put them in the source that 209 // include!()s it. (See 210 // https://github.com/rust-lang/rust/issues/47995.) 211 options.exclude_global_allow_pragmas = true; 212 213 isle::compile::from_files(file_paths, &options)? 214 }; 215 216 eprintln!( 217 "Writing ISLE-generated Rust code to {}", 218 compilation.output.display() 219 ); 220 std::fs::write(&compilation.output, code) 221 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 222 223 Ok(()) 224 } 225