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 target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); 31 32 let all_arch = env::var("CARGO_FEATURE_ALL_ARCH").is_ok(); 33 34 let mut isas = meta::isa::Isa::all() 35 .iter() 36 .cloned() 37 .filter(|isa| { 38 let env_key = format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()); 39 all_arch || env::var(env_key).is_ok() 40 }) 41 .collect::<Vec<_>>(); 42 43 // Don't require host isa if under 'all-arch' feature. 44 let host_isa = env::var("CARGO_FEATURE_HOST_ARCH").is_ok() && !all_arch; 45 46 if isas.is_empty() || host_isa { 47 // Try to match native target. 48 let target_name = target_triple.split('-').next().unwrap(); 49 let isa = meta::isa_from_arch(&target_name).expect("error when identifying target"); 50 println!("cargo:rustc-cfg=feature=\"{}\"", isa); 51 isas.push(isa); 52 } 53 54 let cur_dir = env::current_dir().expect("Can't access current working directory"); 55 let crate_dir = cur_dir.as_path(); 56 57 println!("cargo:rerun-if-changed=build.rs"); 58 59 let explicit_isle_dir = &crate_dir.join("isle_generated_code"); 60 #[cfg(feature = "isle-in-source-tree")] 61 let isle_dir = explicit_isle_dir; 62 #[cfg(not(feature = "isle-in-source-tree"))] 63 let isle_dir = std::path::Path::new(&out_dir); 64 65 #[cfg(feature = "isle-in-source-tree")] 66 { 67 std::fs::create_dir_all(isle_dir).expect("Could not create ISLE source directory"); 68 } 69 #[cfg(not(feature = "isle-in-source-tree"))] 70 { 71 if explicit_isle_dir.is_dir() { 72 eprintln!(concat!( 73 "Error: directory isle_generated_code/ exists but is only used when\n", 74 "`--feature isle-in-source-tree` is specified. To prevent confusion,\n", 75 "this build script requires the directory to be removed when reverting\n", 76 "to the usual generated code in target/. Please delete the directory and\n", 77 "re-run this build.\n", 78 )); 79 std::process::exit(1); 80 } 81 } 82 83 if let Err(err) = meta::generate(&isas, &out_dir, isle_dir.to_str().unwrap()) { 84 eprintln!("Error: {}", err); 85 process::exit(1); 86 } 87 88 if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" { 89 if let Err(err) = build_isle(crate_dir, isle_dir) { 90 eprintln!("Error: {}", err); 91 process::exit(1); 92 } 93 } 94 95 if env::var("CRANELIFT_VERBOSE").is_ok() { 96 for isa in &isas { 97 println!("cargo:warning=Includes support for {} ISA", isa.to_string()); 98 } 99 println!( 100 "cargo:warning=Build step took {:?}.", 101 Instant::now() - start_time 102 ); 103 println!("cargo:warning=Generated files are in {}", out_dir); 104 } 105 106 let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); 107 let mut cmd = std::process::Command::new("git"); 108 cmd.arg("rev-parse") 109 .arg("HEAD") 110 .stdout(std::process::Stdio::piped()) 111 .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()); 112 let version = if let Ok(mut child) = cmd.spawn() { 113 let mut git_rev = String::new(); 114 child 115 .stdout 116 .as_mut() 117 .unwrap() 118 .read_to_string(&mut git_rev) 119 .unwrap(); 120 let status = child.wait().unwrap(); 121 if status.success() { 122 let git_rev = git_rev.trim().chars().take(9).collect::<String>(); 123 format!("{}-{}", pkg_version, git_rev) 124 } else { 125 // not a git repo 126 pkg_version 127 } 128 } else { 129 // git not available 130 pkg_version 131 }; 132 std::fs::write( 133 std::path::Path::new(&out_dir).join("version.rs"), 134 format!( 135 "/// Version number of this crate. \n\ 136 pub const VERSION: &str = \"{}\";", 137 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!( 224 "cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {:?}", 225 e 226 ); 227 code 228 }); 229 230 eprintln!( 231 "Writing ISLE-generated Rust code to {}", 232 compilation.output.display() 233 ); 234 std::fs::write(&compilation.output, code) 235 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 236 237 Ok(()) 238 } 239 240 fn rustfmt(code: &str) -> std::io::Result<String> { 241 use std::io::Write; 242 243 let mut rustfmt = std::process::Command::new("rustfmt") 244 .stdin(std::process::Stdio::piped()) 245 .stdout(std::process::Stdio::piped()) 246 .spawn()?; 247 248 let mut stdin = rustfmt.stdin.take().unwrap(); 249 stdin.write_all(code.as_bytes())?; 250 drop(stdin); 251 252 let mut stdout = rustfmt.stdout.take().unwrap(); 253 let mut data = vec![]; 254 stdout.read_to_end(&mut data)?; 255 256 let status = rustfmt.wait()?; 257 if !status.success() { 258 return Err(std::io::Error::new( 259 std::io::ErrorKind::Other, 260 format!("`rustfmt` exited with status {}", status), 261 )); 262 } 263 264 Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) 265 } 266