|
| 1 | +//! Compilation of native dependencies like LLVM. |
| 2 | +//! |
| 3 | +//! Native projects like LLVM unfortunately aren't suited just yet for |
| 4 | +//! compilation in build scripts that Cargo has. This is because the |
| 5 | +//! compilation takes a *very* long time but also because we don't want to |
| 6 | +//! compile LLVM 3 times as part of a normal bootstrap (we want it cached). |
| 7 | +//! |
| 8 | +//! LLVM and compiler-rt are essentially just wired up to everything else to |
| 9 | +//! ensure that they're always in place if needed. |
| 10 | +
|
| 11 | +use std::fs; |
| 12 | +use std::path::{Path, PathBuf}; |
| 13 | +use std::process::Command; |
| 14 | +use std::sync::OnceLock; |
| 15 | + |
| 16 | +use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; |
| 17 | +use crate::core::config::TargetSelection; |
| 18 | +use crate::utils::helpers::{self, t}; |
| 19 | +use crate::{generate_smart_stamp_hash, Kind}; |
| 20 | + |
| 21 | +use super::llvm::HashStamp; |
| 22 | + |
| 23 | +pub struct Meta { |
| 24 | + stamp: HashStamp, |
| 25 | + out_dir: PathBuf, |
| 26 | + install_dir: PathBuf, |
| 27 | + root: PathBuf, |
| 28 | +} |
| 29 | + |
| 30 | +pub enum GccBuildStatus { |
| 31 | + AlreadyBuilt, |
| 32 | + ShouldBuild(Meta), |
| 33 | +} |
| 34 | + |
| 35 | +/// This returns whether we've already previously built GCC. |
| 36 | +/// |
| 37 | +/// It's used to avoid busting caches during x.py check -- if we've already built |
| 38 | +/// GCC, it's fine for us to not try to avoid doing so. |
| 39 | +pub fn prebuilt_gcc_config(builder: &Builder<'_>, target: TargetSelection) -> GccBuildStatus { |
| 40 | + // If we have gcc submodule initialized already, sync it. |
| 41 | + builder.update_existing_submodule(&Path::new("src").join("gcc")); |
| 42 | + |
| 43 | + // FIXME (GuillaumeGomez): To be done once gccjit has been built in the CI. |
| 44 | + // builder.config.maybe_download_ci_gcc(); |
| 45 | + |
| 46 | + // Initialize the llvm submodule if not initialized already. |
| 47 | + builder.update_submodule(&Path::new("src").join("gcc")); |
| 48 | + |
| 49 | + let root = "src/gcc"; |
| 50 | + let out_dir = builder.gcc_out(target).join("build"); |
| 51 | + let install_dir = builder.gcc_out(target).join("install"); |
| 52 | + |
| 53 | + static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new(); |
| 54 | + let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| { |
| 55 | + generate_smart_stamp_hash( |
| 56 | + &builder.config.src.join("src/llvm-project"), |
| 57 | + builder.in_tree_llvm_info.sha().unwrap_or_default(), |
| 58 | + ) |
| 59 | + }); |
| 60 | + |
| 61 | + let stamp = out_dir.join("gcc-finished-building"); |
| 62 | + let stamp = HashStamp::new(stamp, Some(smart_stamp_hash)); |
| 63 | + |
| 64 | + if stamp.is_done() { |
| 65 | + if stamp.hash.is_none() { |
| 66 | + builder.info( |
| 67 | + "Could not determine the GCC submodule commit hash. \ |
| 68 | + Assuming that an GCC rebuild is not necessary.", |
| 69 | + ); |
| 70 | + builder.info(&format!( |
| 71 | + "To force GCC to rebuild, remove the file `{}`", |
| 72 | + stamp.path.display() |
| 73 | + )); |
| 74 | + } |
| 75 | + return GccBuildStatus::AlreadyBuilt; |
| 76 | + } |
| 77 | + |
| 78 | + GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root: root.into() }) |
| 79 | +} |
| 80 | + |
| 81 | +// FIXME (GuillaumeGomez): When gcc-ci-download option is added, uncomment this code. |
| 82 | +// /// This retrieves the GCC sha we *want* to use, according to git history. |
| 83 | +// pub(crate) fn detect_gcc_sha(config: &Config, is_git: bool) -> String { |
| 84 | +// let gcc_sha = if is_git { |
| 85 | +// // We proceed in 2 steps. First we get the closest commit that is actually upstream. Then we |
| 86 | +// // walk back further to the last bors merge commit that actually changed GCC. The first |
| 87 | +// // step will fail on CI because only the `auto` branch exists; we just fall back to `HEAD` |
| 88 | +// // in that case. |
| 89 | +// let closest_upstream = get_git_merge_base(&config.git_config(), Some(&config.src)) |
| 90 | +// .unwrap_or_else(|_| "HEAD".into()); |
| 91 | +// let mut rev_list = config.git(); |
| 92 | +// rev_list.args(&[ |
| 93 | +// PathBuf::from("rev-list"), |
| 94 | +// format!("--author={}", config.stage0_metadata.config.git_merge_commit_email).into(), |
| 95 | +// "-n1".into(), |
| 96 | +// "--first-parent".into(), |
| 97 | +// closest_upstream.into(), |
| 98 | +// "--".into(), |
| 99 | +// config.src.join("src/gcc"), |
| 100 | +// config.src.join("src/bootstrap/download-ci-gcc-stamp"), |
| 101 | +// // the GCC shared object file is named `gcc-12-rust-{version}-nightly` |
| 102 | +// config.src.join("src/version"), |
| 103 | +// ]); |
| 104 | +// output(&mut rev_list).trim().to_owned() |
| 105 | +// } else if let Some(info) = channel::read_commit_info_file(&config.src) { |
| 106 | +// info.sha.trim().to_owned() |
| 107 | +// } else { |
| 108 | +// "".to_owned() |
| 109 | +// }; |
| 110 | + |
| 111 | +// if gcc_sha.is_empty() { |
| 112 | +// eprintln!("error: could not find commit hash for downloading LLVM"); |
| 113 | +// eprintln!("HELP: maybe your repository history is too shallow?"); |
| 114 | +// eprintln!("HELP: consider disabling `download-ci-gcc`"); |
| 115 | +// eprintln!("HELP: or fetch enough history to include one upstream commit"); |
| 116 | +// panic!(); |
| 117 | +// } |
| 118 | + |
| 119 | +// gcc_sha |
| 120 | +// } |
| 121 | + |
| 122 | +// /// Returns whether the CI-found GCC is currently usable. |
| 123 | +// /// |
| 124 | +// /// This checks both the build triple platform to confirm we're usable at all, |
| 125 | +// /// and then verifies if the current HEAD matches the detected GCC SHA head, |
| 126 | +// /// in which case GCC is indicated as not available. |
| 127 | +// pub(crate) fn is_ci_gcc_available(config: &Config, asserts: bool) -> bool { |
| 128 | +// // This is currently all tier 1 targets and tier 2 targets with host tools |
| 129 | +// // (since others may not have CI artifacts) |
| 130 | +// // https://doc.rust-lang.org/rustc/platform-support.html#tier-1 |
| 131 | +// let supported_platforms = [ |
| 132 | +// // tier 1 |
| 133 | +// ("x86_64-unknown-linux-gnu", true), |
| 134 | +// ]; |
| 135 | + |
| 136 | +// if !supported_platforms.contains(&(&*config.build.triple, asserts)) |
| 137 | +// && (asserts || !supported_platforms.contains(&(&*config.build.triple, true))) |
| 138 | +// { |
| 139 | +// return false; |
| 140 | +// } |
| 141 | + |
| 142 | +// if is_ci_gcc_modified(config) { |
| 143 | +// eprintln!("Detected GCC as non-available: running in CI and modified GCC in this change"); |
| 144 | +// return false; |
| 145 | +// } |
| 146 | + |
| 147 | +// true |
| 148 | +// } |
| 149 | + |
| 150 | +// /// Returns true if we're running in CI with modified GCC (and thus can't download it) |
| 151 | +// pub(crate) fn is_ci_gcc_modified(config: &Config) -> bool { |
| 152 | +// CiEnv::is_ci() && config.rust_info.is_managed_git_subrepository() && { |
| 153 | +// // We assume we have access to git, so it's okay to unconditionally pass |
| 154 | +// // `true` here. |
| 155 | +// let gcc_sha = detect_gcc_sha(config, true); |
| 156 | +// let head_sha = output(config.git().arg("rev-parse").arg("HEAD")); |
| 157 | +// let head_sha = head_sha.trim(); |
| 158 | +// gcc_sha == head_sha |
| 159 | +// } |
| 160 | +// } |
| 161 | + |
| 162 | +#[derive(Debug, Clone, Hash, PartialEq, Eq)] |
| 163 | +pub struct Gcc { |
| 164 | + pub target: TargetSelection, |
| 165 | +} |
| 166 | + |
| 167 | +impl Step for Gcc { |
| 168 | + type Output = bool; |
| 169 | + |
| 170 | + const ONLY_HOSTS: bool = true; |
| 171 | + |
| 172 | + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { |
| 173 | + run.path("src/gcc") |
| 174 | + } |
| 175 | + |
| 176 | + fn make_run(run: RunConfig<'_>) { |
| 177 | + run.builder.ensure(Gcc { target: run.target }); |
| 178 | + } |
| 179 | + |
| 180 | + /// Compile GCC for `target`. |
| 181 | + fn run(self, builder: &Builder<'_>) -> bool { |
| 182 | + let target = self.target; |
| 183 | + if !target.contains("linux") || !target.contains("x86_64") { |
| 184 | + return false; |
| 185 | + } |
| 186 | + |
| 187 | + // If GCC has already been built or been downloaded through download-ci-gcc, we avoid |
| 188 | + // building it again. |
| 189 | + let Meta { stamp, out_dir, install_dir, root } = match prebuilt_gcc_config(builder, target) |
| 190 | + { |
| 191 | + GccBuildStatus::AlreadyBuilt => return true, |
| 192 | + GccBuildStatus::ShouldBuild(m) => m, |
| 193 | + }; |
| 194 | + |
| 195 | + let _guard = builder.msg_unstaged(Kind::Build, "GCC", target); |
| 196 | + t!(stamp.remove()); |
| 197 | + let _time = helpers::timeit(builder); |
| 198 | + t!(fs::create_dir_all(&out_dir)); |
| 199 | + |
| 200 | + if builder.config.dry_run() { |
| 201 | + return true; |
| 202 | + } |
| 203 | + |
| 204 | + builder.run( |
| 205 | + Command::new(root.join("configure")) |
| 206 | + .current_dir(&out_dir) |
| 207 | + .arg("--enable-host-shared") |
| 208 | + .arg("--enable-languages=jit") |
| 209 | + .arg("--enable-checking=release") |
| 210 | + .arg("--disable-bootstrap") |
| 211 | + .arg("--disable-multilib") |
| 212 | + .arg(format!("--prefix={}", install_dir.display())), |
| 213 | + ); |
| 214 | + builder |
| 215 | + .run(Command::new("make").current_dir(&out_dir).arg(format!("-j{}", builder.jobs()))); |
| 216 | + builder.run(Command::new("make").current_dir(&out_dir).arg("install")); |
| 217 | + |
| 218 | + t!(builder.symlink_file( |
| 219 | + install_dir.join("lib/libgccjit.so"), |
| 220 | + install_dir.join("lib/libgccjit.so.0") |
| 221 | + )); |
| 222 | + |
| 223 | + t!(stamp.write()); |
| 224 | + |
| 225 | + true |
| 226 | + } |
| 227 | +} |
0 commit comments