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 35 let mut isas = meta::isa::Isa::all() 36 .iter() 37 .cloned() 38 .filter(|isa| { 39 let env_key = format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()); 40 all_arch || env::var(env_key).is_ok() 41 }) 42 .collect::<Vec<_>>(); 43 44 // Don't require host isa if under 'all-arch' feature. 45 let host_isa = env::var("CARGO_FEATURE_HOST_ARCH").is_ok() && !all_arch; 46 47 if isas.is_empty() || host_isa { 48 // Try to match native target. 49 let target_name = target_triple.split('-').next().unwrap(); 50 let isa = meta::isa_from_arch(&target_name).expect("error when identifying target"); 51 println!("cargo:rustc-cfg=feature=\"{isa}\""); 52 isas.push(isa); 53 } 54 55 let cur_dir = env::current_dir().expect("Can't access current working directory"); 56 let crate_dir = cur_dir.as_path(); 57 58 println!("cargo:rerun-if-changed=build.rs"); 59 60 let explicit_isle_dir = &crate_dir.join("isle_generated_code"); 61 #[cfg(feature = "isle-in-source-tree")] 62 let isle_dir = explicit_isle_dir; 63 #[cfg(not(feature = "isle-in-source-tree"))] 64 let isle_dir = &out_dir; 65 66 #[cfg(feature = "isle-in-source-tree")] 67 { 68 std::fs::create_dir_all(isle_dir).expect("Could not create ISLE source directory"); 69 } 70 #[cfg(not(feature = "isle-in-source-tree"))] 71 { 72 if explicit_isle_dir.is_dir() { 73 eprintln!(concat!( 74 "Error: directory isle_generated_code/ exists but is only used when\n", 75 "`--feature isle-in-source-tree` is specified. To prevent confusion,\n", 76 "this build script requires the directory to be removed when reverting\n", 77 "to the usual generated code in target/. Please delete the directory and\n", 78 "re-run this build.\n", 79 )); 80 std::process::exit(1); 81 } 82 } 83 84 if let Err(err) = meta::generate(&isas, &out_dir, isle_dir) { 85 eprintln!("Error: {err}"); 86 process::exit(1); 87 } 88 89 if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" { 90 if let Err(err) = build_isle(crate_dir, isle_dir) { 91 eprintln!("Error: {err}"); 92 process::exit(1); 93 } 94 } 95 96 if env::var("CRANELIFT_VERBOSE").is_ok() { 97 for isa in &isas { 98 println!("cargo:warning=Includes support for {} ISA", isa.to_string()); 99 } 100 println!( 101 "cargo:warning=Build step took {:?}.", 102 Instant::now() - start_time 103 ); 104 println!("cargo:warning=Generated files are in {}", out_dir.display()); 105 } 106 107 let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); 108 let mut cmd = std::process::Command::new("git"); 109 cmd.arg("rev-parse") 110 .arg("HEAD") 111 .stdout(std::process::Stdio::piped()) 112 .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()); 113 let version = if let Ok(mut child) = cmd.spawn() { 114 let mut git_rev = String::new(); 115 child 116 .stdout 117 .as_mut() 118 .unwrap() 119 .read_to_string(&mut git_rev) 120 .unwrap(); 121 let status = child.wait().unwrap(); 122 if status.success() { 123 let git_rev = git_rev.trim().chars().take(9).collect::<String>(); 124 format!("{pkg_version}-{git_rev}") 125 } else { 126 // not a git repo 127 pkg_version 128 } 129 } else { 130 // git not available 131 pkg_version 132 }; 133 std::fs::write( 134 std::path::Path::new(&out_dir).join("version.rs"), 135 format!( 136 "/// Version number of this crate. \n\ 137 pub const VERSION: &str = \"{version}\";" 138 ), 139 ) 140 .unwrap(); 141 } 142 143 /// Strip the current directory from the file paths, because `islec` 144 /// includes them in the generated source, and this helps us maintain 145 /// deterministic builds that don't include those local file paths. 146 fn make_isle_source_path_relative( 147 cur_dir: &std::path::Path, 148 filename: &std::path::Path, 149 ) -> std::path::PathBuf { 150 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 151 suffix.to_path_buf() 152 } else { 153 filename.to_path_buf() 154 } 155 } 156 157 fn build_isle( 158 crate_dir: &std::path::Path, 159 isle_dir: &std::path::Path, 160 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 161 let cur_dir = std::env::current_dir()?; 162 let isle_compilations = meta::isle::get_isle_compilations( 163 &make_isle_source_path_relative(&cur_dir, &crate_dir), 164 &make_isle_source_path_relative(&cur_dir, &isle_dir), 165 ); 166 167 let mut had_error = false; 168 for compilation in &isle_compilations.items { 169 for file in &compilation.inputs { 170 println!("cargo:rerun-if-changed={}", file.display()); 171 } 172 173 if let Err(e) = run_compilation(compilation) { 174 had_error = true; 175 eprintln!("Error building ISLE files:"); 176 eprintln!("{e:?}"); 177 #[cfg(not(feature = "isle-errors"))] 178 { 179 eprintln!("To see a more detailed error report, run: "); 180 eprintln!(); 181 eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); 182 eprintln!(); 183 } 184 } 185 } 186 187 if had_error { 188 std::process::exit(1); 189 } 190 191 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 192 193 Ok(()) 194 } 195 196 /// Build ISLE DSL source text into generated Rust code. 197 /// 198 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 199 /// it consumes files generated by them. 200 fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { 201 use cranelift_isle as isle; 202 203 eprintln!("Rebuilding {}", compilation.output.display()); 204 205 let code = { 206 let file_paths = compilation 207 .inputs 208 .iter() 209 .chain(compilation.untracked_inputs.iter()); 210 211 let mut options = isle::codegen::CodegenOptions::default(); 212 // Because we include!() the generated ISLE source, we cannot 213 // put the global pragmas (`#![allow(...)]`) in the ISLE 214 // source itself; we have to put them in the source that 215 // include!()s it. (See 216 // https://github.com/rust-lang/rust/issues/47995.) 217 options.exclude_global_allow_pragmas = true; 218 219 isle::compile::from_files(file_paths, &options)? 220 }; 221 222 let code = rustfmt(&code).unwrap_or_else(|e| { 223 println!("cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {e:?}"); 224 code 225 }); 226 227 eprintln!( 228 "Writing ISLE-generated Rust code to {}", 229 compilation.output.display() 230 ); 231 std::fs::write(&compilation.output, code) 232 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 233 234 Ok(()) 235 } 236 237 fn rustfmt(code: &str) -> std::io::Result<String> { 238 use std::io::Write; 239 240 let mut rustfmt = std::process::Command::new("rustfmt") 241 .stdin(std::process::Stdio::piped()) 242 .stdout(std::process::Stdio::piped()) 243 .spawn()?; 244 245 let mut stdin = rustfmt.stdin.take().unwrap(); 246 stdin.write_all(code.as_bytes())?; 247 drop(stdin); 248 249 let mut stdout = rustfmt.stdout.take().unwrap(); 250 let mut data = vec![]; 251 stdout.read_to_end(&mut data)?; 252 253 let status = rustfmt.wait()?; 254 if !status.success() { 255 return Err(std::io::Error::new( 256 std::io::ErrorKind::Other, 257 format!("`rustfmt` exited with status {status}"), 258 )); 259 } 260 261 Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) 262 } 263