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 = \"{}\";", 138 version 139 ), 140 ) 141 .unwrap(); 142 } 143 144 /// Strip the current directory from the file paths, because `islec` 145 /// includes them in the generated source, and this helps us maintain 146 /// deterministic builds that don't include those local file paths. 147 fn make_isle_source_path_relative( 148 cur_dir: &std::path::Path, 149 filename: &std::path::Path, 150 ) -> std::path::PathBuf { 151 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 152 suffix.to_path_buf() 153 } else { 154 filename.to_path_buf() 155 } 156 } 157 158 fn build_isle( 159 crate_dir: &std::path::Path, 160 isle_dir: &std::path::Path, 161 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 162 let cur_dir = std::env::current_dir()?; 163 let isle_compilations = meta::isle::get_isle_compilations( 164 &make_isle_source_path_relative(&cur_dir, &crate_dir), 165 &make_isle_source_path_relative(&cur_dir, &isle_dir), 166 ); 167 168 let mut had_error = false; 169 for compilation in &isle_compilations.items { 170 for file in &compilation.inputs { 171 println!("cargo:rerun-if-changed={}", file.display()); 172 } 173 174 if let Err(e) = run_compilation(compilation) { 175 had_error = true; 176 eprintln!("Error building ISLE files:"); 177 eprintln!("{:?}", e); 178 #[cfg(not(feature = "isle-errors"))] 179 { 180 eprintln!("To see a more detailed error report, run: "); 181 eprintln!(); 182 eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); 183 eprintln!(); 184 } 185 } 186 } 187 188 if had_error { 189 std::process::exit(1); 190 } 191 192 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 193 194 Ok(()) 195 } 196 197 /// Build ISLE DSL source text into generated Rust code. 198 /// 199 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 200 /// it consumes files generated by them. 201 fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { 202 use cranelift_isle as isle; 203 204 eprintln!("Rebuilding {}", compilation.output.display()); 205 206 let code = { 207 let file_paths = compilation 208 .inputs 209 .iter() 210 .chain(compilation.untracked_inputs.iter()); 211 212 let mut options = isle::codegen::CodegenOptions::default(); 213 // Because we include!() the generated ISLE source, we cannot 214 // put the global pragmas (`#![allow(...)]`) in the ISLE 215 // source itself; we have to put them in the source that 216 // include!()s it. (See 217 // https://github.com/rust-lang/rust/issues/47995.) 218 options.exclude_global_allow_pragmas = true; 219 220 isle::compile::from_files(file_paths, &options)? 221 }; 222 223 let code = rustfmt(&code).unwrap_or_else(|e| { 224 println!( 225 "cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {:?}", 226 e 227 ); 228 code 229 }); 230 231 eprintln!( 232 "Writing ISLE-generated Rust code to {}", 233 compilation.output.display() 234 ); 235 std::fs::write(&compilation.output, code) 236 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 237 238 Ok(()) 239 } 240 241 fn rustfmt(code: &str) -> std::io::Result<String> { 242 use std::io::Write; 243 244 let mut rustfmt = std::process::Command::new("rustfmt") 245 .stdin(std::process::Stdio::piped()) 246 .stdout(std::process::Stdio::piped()) 247 .spawn()?; 248 249 let mut stdin = rustfmt.stdin.take().unwrap(); 250 stdin.write_all(code.as_bytes())?; 251 drop(stdin); 252 253 let mut stdout = rustfmt.stdout.take().unwrap(); 254 let mut data = vec![]; 255 stdout.read_to_end(&mut data)?; 256 257 let status = rustfmt.wait()?; 258 if !status.success() { 259 return Err(std::io::Error::new( 260 std::io::ErrorKind::Other, 261 format!("`rustfmt` exited with status {}", status), 262 )); 263 } 264 265 Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) 266 } 267