bootstrap/core/build_steps/
gcc.rs1use std::fs;
12use std::path::{Path, PathBuf};
13use std::sync::OnceLock;
14
15use crate::FileType;
16use crate::core::builder::{Builder, Cargo, Kind, RunConfig, ShouldRun, Step};
17use crate::core::config::TargetSelection;
18use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash};
19use crate::utils::exec::command;
20use crate::utils::helpers::{self, t};
21
22#[derive(Debug, Clone, Hash, PartialEq, Eq)]
23pub struct Gcc {
24 pub target: TargetSelection,
25}
26
27#[derive(Clone)]
28pub struct GccOutput {
29 pub libgccjit: PathBuf,
30}
31
32impl GccOutput {
33 pub fn install_to(&self, builder: &Builder<'_>, directory: &Path) {
35 if builder.config.dry_run() {
36 return;
37 }
38
39 let target_filename = self.libgccjit.file_name().unwrap().to_str().unwrap().to_string();
40
41 let actual_libgccjit_path = t!(
45 self.libgccjit.canonicalize(),
46 format!("Cannot find libgccjit at {}", self.libgccjit.display())
47 );
48
49 let dst = directory.join(target_filename);
50 builder.copy_link(&actual_libgccjit_path, &dst, FileType::NativeLibrary);
51 }
52}
53
54impl Step for Gcc {
55 type Output = GccOutput;
56
57 const IS_HOST: bool = true;
58
59 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
60 run.path("src/gcc").alias("gcc")
61 }
62
63 fn make_run(run: RunConfig<'_>) {
64 run.builder.ensure(Gcc { target: run.target });
65 }
66
67 fn run(self, builder: &Builder<'_>) -> Self::Output {
69 let target = self.target;
70
71 let metadata = match get_gcc_build_status(builder, target) {
73 GccBuildStatus::AlreadyBuilt(path) => return GccOutput { libgccjit: path },
74 GccBuildStatus::ShouldBuild(m) => m,
75 };
76
77 let _guard = builder.msg_unstaged(Kind::Build, "GCC", target);
78 t!(metadata.stamp.remove());
79 let _time = helpers::timeit(builder);
80
81 let libgccjit_path = libgccjit_built_path(&metadata.install_dir);
82 if builder.config.dry_run() {
83 return GccOutput { libgccjit: libgccjit_path };
84 }
85
86 build_gcc(&metadata, builder, target);
87
88 t!(metadata.stamp.write());
89
90 GccOutput { libgccjit: libgccjit_path }
91 }
92}
93
94pub struct Meta {
95 stamp: BuildStamp,
96 out_dir: PathBuf,
97 install_dir: PathBuf,
98 root: PathBuf,
99}
100
101pub enum GccBuildStatus {
102 AlreadyBuilt(PathBuf),
104 ShouldBuild(Meta),
105}
106
107#[cfg(not(test))]
111fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option<PathBuf> {
112 use build_helper::git::PathFreshness;
113
114 if !matches!(builder.config.gcc_ci_mode, crate::core::config::GccCiMode::DownloadFromCi) {
116 return None;
117 }
118 if target != "x86_64-unknown-linux-gnu" {
119 eprintln!("GCC CI download is only available for the `x86_64-unknown-linux-gnu` target");
120 return None;
121 }
122 let source = detect_gcc_freshness(
123 &builder.config,
124 builder.config.rust_info.is_managed_git_subrepository(),
125 );
126 builder.do_if_verbose(|| {
127 eprintln!("GCC freshness: {source:?}");
128 });
129 match source {
130 PathFreshness::LastModifiedUpstream { upstream } => {
131 let root = ci_gcc_root(&builder.config, target);
133 let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream);
134 if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
135 builder.config.download_ci_gcc(&upstream, &root);
136 t!(gcc_stamp.write());
137 }
138
139 let libgccjit = root.join("lib").join("libgccjit.so");
140 Some(libgccjit)
141 }
142 PathFreshness::HasLocalModifications { .. } => {
143 eprintln!("Found local GCC modifications, GCC will *not* be downloaded");
145 None
146 }
147 PathFreshness::MissingUpstream => {
148 eprintln!("error: could not find commit hash for downloading GCC");
149 eprintln!("HELP: maybe your repository history is too shallow?");
150 eprintln!("HELP: consider disabling `download-ci-gcc`");
151 eprintln!("HELP: or fetch enough history to include one upstream commit");
152 None
153 }
154 }
155}
156
157#[cfg(test)]
158fn try_download_gcc(_builder: &Builder<'_>, _target: TargetSelection) -> Option<PathBuf> {
159 None
160}
161
162pub fn get_gcc_build_status(builder: &Builder<'_>, target: TargetSelection) -> GccBuildStatus {
168 if let Some(path) = try_download_gcc(builder, target) {
169 return GccBuildStatus::AlreadyBuilt(path);
170 }
171
172 static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
173 let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
174 generate_smart_stamp_hash(
175 builder,
176 &builder.config.src.join("src/gcc"),
177 builder.in_tree_gcc_info.sha().unwrap_or_default(),
178 )
179 });
180
181 builder.config.update_submodule("src/gcc");
183
184 let root = builder.src.join("src/gcc");
185 let out_dir = builder.gcc_out(target).join("build");
186 let install_dir = builder.gcc_out(target).join("install");
187
188 let stamp = BuildStamp::new(&out_dir).with_prefix("gcc").add_stamp(smart_stamp_hash);
189
190 if stamp.is_up_to_date() {
191 if stamp.stamp().is_empty() {
192 builder.info(
193 "Could not determine the GCC submodule commit hash. \
194 Assuming that an GCC rebuild is not necessary.",
195 );
196 builder.info(&format!(
197 "To force GCC to rebuild, remove the file `{}`",
198 stamp.path().display()
199 ));
200 }
201 let path = libgccjit_built_path(&install_dir);
202 if path.is_file() {
203 return GccBuildStatus::AlreadyBuilt(path);
204 } else {
205 builder.info(&format!(
206 "GCC stamp is up-to-date, but the libgccjit.so file was not found at `{}`",
207 path.display(),
208 ));
209 }
210 }
211
212 GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root })
213}
214
215fn libgccjit_built_path(install_dir: &Path) -> PathBuf {
217 install_dir.join("lib/libgccjit.so")
218}
219
220fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) {
221 if builder.build.cc_tool(target).is_like_clang()
222 || builder.build.cxx_tool(target).is_like_clang()
223 {
224 panic!(
225 "Attempting to build GCC using Clang, which is known to misbehave. Please use GCC as the host C/C++ compiler. "
226 );
227 }
228
229 let Meta { stamp: _, out_dir, install_dir, root } = metadata;
230
231 t!(fs::create_dir_all(out_dir));
232 t!(fs::create_dir_all(install_dir));
233
234 let src_dir = builder.gcc_out(target).join("src");
242 if src_dir.exists() {
243 builder.remove_dir(&src_dir);
244 }
245 builder.create_dir(&src_dir);
246 builder.cp_link_r(root, &src_dir);
247
248 command(src_dir.join("contrib/download_prerequisites")).current_dir(&src_dir).run(builder);
249 let mut configure_cmd = command(src_dir.join("configure"));
250 configure_cmd
251 .current_dir(out_dir)
252 .arg("--enable-host-shared")
253 .arg("--enable-languages=c,jit,lto")
254 .arg("--enable-checking=release")
255 .arg("--disable-bootstrap")
256 .arg("--disable-multilib")
257 .arg(format!("--prefix={}", install_dir.display()));
258
259 let cc = builder.build.cc(target).display().to_string();
260 let cc = builder
261 .build
262 .config
263 .ccache
264 .as_ref()
265 .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}"));
266 configure_cmd.env("CC", cc);
267
268 if let Ok(ref cxx) = builder.build.cxx(target) {
269 let cxx = cxx.display().to_string();
270 let cxx = builder
271 .build
272 .config
273 .ccache
274 .as_ref()
275 .map_or_else(|| cxx.clone(), |ccache| format!("{ccache} {cxx}"));
276 configure_cmd.env("CXX", cxx);
277 }
278 configure_cmd.run(builder);
279
280 command("make")
281 .current_dir(out_dir)
282 .arg("--silent")
283 .arg(format!("-j{}", builder.jobs()))
284 .run_capture_stdout(builder);
285 command("make").current_dir(out_dir).arg("--silent").arg("install").run_capture_stdout(builder);
286}
287
288pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) {
290 cargo.rustflag(&format!("-L{}", gcc.libgccjit.parent().unwrap().to_str().unwrap()));
292}
293
294#[cfg(not(test))]
296fn ci_gcc_root(config: &crate::Config, target: TargetSelection) -> PathBuf {
297 config.out.join(target).join("ci-gcc")
298}
299
300#[cfg(not(test))]
302fn detect_gcc_freshness(config: &crate::Config, is_git: bool) -> build_helper::git::PathFreshness {
303 use build_helper::git::PathFreshness;
304
305 if is_git {
306 config.check_path_modifications(&["src/gcc", "src/bootstrap/download-ci-gcc-stamp"])
307 } else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
308 PathFreshness::LastModifiedUpstream { upstream: info.sha.trim().to_owned() }
309 } else {
310 PathFreshness::MissingUpstream
311 }
312}