| use std::collections::HashMap; | |
| use std::ffi::OsStr; | |
| use std::fs; | |
| use std::os::unix::fs::symlink; | |
| use std::path::{Path, PathBuf}; | |
| use crate::config::{Channel, ConfigInfo}; | |
| use crate::utils::{ | |
| create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, walk_dir, | |
| }; | |
| #[derive(Default)] | |
| struct BuildArg { | |
| flags: Vec<String>, | |
| config_info: ConfigInfo, | |
| build_sysroot: bool, | |
| } | |
| impl BuildArg { | |
| /// Creates a new `BuildArg` instance by parsing command-line arguments. | |
| fn new() -> Result<Option<Self>, String> { | |
| let mut build_arg = Self::default(); | |
| // Skip binary name and the `build` command. | |
| let mut args = std::env::args().skip(2); | |
| while let Some(arg) = args.next() { | |
| match arg.as_str() { | |
| "--sysroot" => { | |
| build_arg.build_sysroot = true; | |
| } | |
| "--help" => { | |
| Self::usage(); | |
| return Ok(None); | |
| } | |
| arg => { | |
| if !build_arg.config_info.parse_argument(arg, &mut args)? { | |
| return Err(format!("Unknown argument `{arg}`")); | |
| } | |
| } | |
| } | |
| } | |
| Ok(Some(build_arg)) | |
| } | |
| fn usage() { | |
| println!( | |
| r#" | |
| `build` command help: | |
| --sysroot : When used on its own, build backend in dev mode with optimized dependencies | |
| and sysroot in dev mode (unoptimized) | |
| When used together with --release, build backend in release mode with optimized dependencies | |
| When used together with --release-sysroot, | |
| build the sysroot in release mode with optimized dependencies instead of in dev mode | |
| --release-sysroot : When combined with --sysroot, additionally | |
| build the sysroot in release mode with optimized dependencies. | |
| It has no effect if `--sysroot` is not specified. | |
| It should not be used on its own. | |
| --sysroot-panic-abort : Build the sysroot without unwinding support"# | |
| ); | |
| ConfigInfo::show_usage(); | |
| println!(" --help : Show this help"); | |
| } | |
| } | |
| fn cleanup_sysroot_previous_build(library_dir: &Path) { | |
| // Cleanup for previous run | |
| // Clean target dir except for build scripts and incremental cache | |
| let _ = walk_dir( | |
| library_dir.join("target"), | |
| &mut |dir: &Path| { | |
| for top in &["debug", "release"] { | |
| let _ = fs::remove_dir_all(dir.join(top).join("build")); | |
| let _ = fs::remove_dir_all(dir.join(top).join("deps")); | |
| let _ = fs::remove_dir_all(dir.join(top).join("examples")); | |
| let _ = fs::remove_dir_all(dir.join(top).join("native")); | |
| let _ = walk_dir( | |
| dir.join(top), | |
| &mut |sub_dir: &Path| { | |
| if sub_dir | |
| .file_name() | |
| .map(|filename| filename.to_str().unwrap().starts_with("libsysroot")) | |
| .unwrap_or(false) | |
| { | |
| let _ = fs::remove_dir_all(sub_dir); | |
| } | |
| Ok(()) | |
| }, | |
| &mut |file: &Path| { | |
| if file | |
| .file_name() | |
| .map(|filename| filename.to_str().unwrap().starts_with("libsysroot")) | |
| .unwrap_or(false) | |
| { | |
| let _ = fs::remove_file(file); | |
| } | |
| Ok(()) | |
| }, | |
| false, | |
| ); | |
| } | |
| Ok(()) | |
| }, | |
| &mut |_| Ok(()), | |
| false, | |
| ); | |
| } | |
| pub fn build_sysroot(env: &HashMap<String, String>, config: &ConfigInfo) -> Result<(), String> { | |
| let start_dir = get_sysroot_dir(); | |
| // Symlink libgccjit.so to sysroot. | |
| let lib_path = start_dir.join("sysroot").join("lib"); | |
| let rustlib_target_path = lib_path | |
| .join("rustlib") | |
| .join(&config.host_triple) | |
| .join("codegen-backends") | |
| .join("lib") | |
| .join(&config.target_triple); | |
| let libgccjit_path = | |
| PathBuf::from(config.gcc_path.as_ref().expect("libgccjit should be set by this point")) | |
| .join("libgccjit.so"); | |
| let libgccjit_in_sysroot_path = rustlib_target_path.join("libgccjit.so"); | |
| // First remove the file to be able to create the symlink even when the file already exists. | |
| let _ = fs::remove_file(&libgccjit_in_sysroot_path); | |
| create_dir(&rustlib_target_path)?; | |
| symlink(libgccjit_path, &libgccjit_in_sysroot_path) | |
| .map_err(|error| format!("Cannot create symlink for libgccjit.so: {}", error))?; | |
| let library_dir = start_dir.join("sysroot_src").join("library"); | |
| cleanup_sysroot_previous_build(&library_dir); | |
| // Builds libs | |
| let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); | |
| // Record the sysroot sources under the path the `rust-src` component uses, which is where | |
| // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests | |
| // print the build path where they expect `$SRC_DIR`. | |
| let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); | |
| rustflags.push_str(&format!( | |
| " --remap-path-prefix={library_dir}={sysroot_source_dir}", | |
| library_dir = std::path::absolute(&library_dir) | |
| .map_err(|error| format!( | |
| "Failed to get the absolute path of the sysroot sources: {error:?}" | |
| ))? | |
| .display(), | |
| sysroot_source_dir = std::path::absolute(&sysroot_source_dir) | |
| .map_err(|error| format!( | |
| "Failed to get the absolute path of the sysroot sources: {error:?}" | |
| ))? | |
| .display(), | |
| )); | |
| if config.sysroot_panic_abort { | |
| rustflags.push_str(" -Cpanic=abort -Zpanic-abort-tests"); | |
| } | |
| rustflags.push_str(" -Z force-unstable-if-unmarked"); | |
| if config.no_default_features { | |
| rustflags.push_str(" -Csymbol-mangling-version=v0"); | |
| } | |
| let mut args: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &"build", &"--target", &config.target]; | |
| if config.target.ends_with(".json") { | |
| args.push(&"-Zjson-target-spec"); | |
| } | |
| for feature in &config.features { | |
| args.push(&"--features"); | |
| args.push(feature); | |
| } | |
| if config.no_default_features { | |
| rustflags.push_str(" -Csymbol-mangling-version=v0"); | |
| args.push(&"--no-default-features"); | |
| } | |
| let channel = if config.sysroot_release_channel { | |
| rustflags.push_str(" -Zmir-opt-level=3"); | |
| args.push(&"--release"); | |
| "release" | |
| } else { | |
| "debug" | |
| }; | |
| // We have a different environment variable than RUSTFLAGS to make sure those flags are only | |
| // sent to rustc_codegen_gcc and not the LLVM backend. | |
| if let Ok(cg_rustflags) = std::env::var("CG_RUSTFLAGS") { | |
| rustflags.push(' '); | |
| rustflags.push_str(&cg_rustflags); | |
| } | |
| args.push(&"--features"); | |
| args.push(&"backtrace"); | |
| let mut env = env.clone(); | |
| env.insert("RUSTFLAGS".to_string(), rustflags); | |
| let sysroot_dir = library_dir.join("sysroot"); | |
| run_command_with_output_and_env(&args, Some(&sysroot_dir), Some(&env))?; | |
| // Copy files to sysroot | |
| let sysroot_path = lib_path.join(format!("rustlib/{}/lib/", config.target_triple)); | |
| // To avoid errors like "multiple candidates for `rmeta` dependency `core` found", we clean the | |
| // sysroot directory before copying the sysroot build artifacts. | |
| let _ = fs::remove_dir_all(&sysroot_path); | |
| create_dir(&sysroot_path)?; | |
| let mut copier = |dir_to_copy: &Path| { | |
| // FIXME: should not use shell command! | |
| run_command(&[&"cp", &"-r", &dir_to_copy, &sysroot_path], None).map(|_| ()) | |
| }; | |
| let target_dir = library_dir.join(format!("target/{}/{}", config.target_triple, channel)); | |
| let deps_dir = target_dir.join("deps"); | |
| if deps_dir.is_dir() { | |
| // Keep copying in the old directory just in case. | |
| walk_dir(&deps_dir, &mut copier.clone(), &mut copier, false)?; | |
| } else { | |
| let build_dir = target_dir.join("build"); | |
| walk_dir( | |
| &build_dir, | |
| &mut |package_dir: &Path| { | |
| walk_dir( | |
| package_dir, | |
| &mut |unit_dir: &Path| { | |
| let out_dir = unit_dir.join("out"); | |
| if out_dir.is_dir() { | |
| walk_dir(&out_dir, &mut copier.clone(), &mut copier.clone(), false)?; | |
| } | |
| Ok(()) | |
| }, | |
| &mut |_| Ok(()), | |
| false, | |
| ) | |
| }, | |
| &mut |_| Ok(()), | |
| false, | |
| )?; | |
| } | |
| // Copy the source files to the sysroot (Rust for Linux needs this). | |
| let sysroot_src_path = start_dir.join("sysroot/lib/rustlib/src/rust"); | |
| create_dir(&sysroot_src_path)?; | |
| run_command(&[&"cp", &"-r", &library_dir, &sysroot_src_path], None)?; | |
| Ok(()) | |
| } | |
| fn build_codegen(args: &mut BuildArg) -> Result<(), String> { | |
| let mut env = HashMap::new(); | |
| if args.config_info.no_default_features { | |
| env.insert("RUSTFLAGS".to_string(), "-Csymbol-mangling-version=v0".to_string()); | |
| } | |
| let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &"rustc"]; | |
| if args.config_info.channel == Channel::Release { | |
| command.push(&"--release"); | |
| env.insert("CHANNEL".to_string(), "release".to_string()); | |
| env.insert("CARGO_INCREMENTAL".to_string(), "1".to_string()); | |
| } else { | |
| env.insert("CHANNEL".to_string(), "debug".to_string()); | |
| } | |
| if args.config_info.no_default_features { | |
| command.push(&"--no-default-features"); | |
| } | |
| let flags = args.flags.iter().map(|s| s.as_str()).collect::<Vec<_>>(); | |
| for flag in &flags { | |
| command.push(flag); | |
| } | |
| run_command_with_output_and_env(&command, None, Some(&env))?; | |
| args.config_info.setup(&mut env, false, true)?; | |
| // We voluntarily ignore the error. | |
| let _ = fs::remove_dir_all("target/out"); | |
| let gccjit_target = "target/out/gccjit"; | |
| create_dir(gccjit_target)?; | |
| if args.build_sysroot { | |
| println!("[BUILD] sysroot"); | |
| build_sysroot(&env, &args.config_info)?; | |
| } | |
| Ok(()) | |
| } | |
| /// Executes the build process. | |
| pub fn run() -> Result<(), String> { | |
| let mut args = match BuildArg::new()? { | |
| Some(args) => args, | |
| None => return Ok(()), | |
| }; | |
| args.config_info.setup_gcc_path()?; | |
| build_codegen(&mut args)?; | |
| Ok(()) | |
| } |