bootstrap/core/build_steps/
gcc.rs1use std::fmt::{Display, Formatter};
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::sync::OnceLock;
15
16use build_helper::git::PathFreshness;
17
18use crate::core::builder::{Builder, Cargo, Kind, RunConfig, ShouldRun, Step};
19use crate::core::config::TargetSelection;
20use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash};
21use crate::utils::exec::command;
22use crate::utils::helpers::{self, t};
23
24#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
28pub struct GccTargetPair {
29 host: TargetSelection,
31 target: TargetSelection,
33}
34
35impl GccTargetPair {
36 pub fn for_native_build(target: TargetSelection) -> Self {
38 Self { host: target, target }
39 }
40
41 pub fn for_target_pair(host: TargetSelection, target: TargetSelection) -> Self {
44 Self { host, target }
45 }
46
47 pub fn host(&self) -> TargetSelection {
48 self.host
49 }
50
51 pub fn target(&self) -> TargetSelection {
52 self.target
53 }
54}
55
56impl Display for GccTargetPair {
57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 write!(f, "{} -> {}", self.host, self.target)
59 }
60}
61
62#[derive(Debug, Clone, Hash, PartialEq, Eq)]
63pub struct Gcc {
64 pub target_pair: GccTargetPair,
65}
66
67#[derive(Clone)]
68pub struct GccOutput {
69 libgccjit: PathBuf,
71}
72
73impl GccOutput {
74 pub fn libgccjit(&self) -> &Path {
75 &self.libgccjit
76 }
77}
78
79impl Step for Gcc {
80 type Output = GccOutput;
81
82 const IS_HOST: bool = true;
83
84 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
85 run.path("src/gcc").alias("gcc")
86 }
87
88 fn make_run(run: RunConfig<'_>) {
89 run.builder
92 .ensure(Gcc { target_pair: GccTargetPair { host: run.target, target: run.target } });
93 }
94
95 fn run(self, builder: &Builder<'_>) -> Self::Output {
97 let target_pair = self.target_pair;
98
99 let metadata = match get_gcc_build_status(builder, target_pair) {
101 GccBuildStatus::AlreadyBuilt(path) => return GccOutput { libgccjit: path },
102 GccBuildStatus::ShouldBuild(m) => m,
103 };
104
105 let action = Kind::Build.description();
106 let msg = format!("{action} GCC for {target_pair}");
107 let _guard = builder.group(&msg);
108 t!(metadata.stamp.remove());
109 let _time = helpers::timeit(builder);
110
111 let libgccjit_path = libgccjit_built_path(&metadata.install_dir);
112 if builder.config.dry_run() {
113 return GccOutput { libgccjit: libgccjit_path };
114 }
115
116 build_gcc(&metadata, builder, target_pair);
117
118 t!(metadata.stamp.write());
119
120 GccOutput { libgccjit: libgccjit_path }
121 }
122}
123
124pub struct Meta {
125 stamp: BuildStamp,
126 out_dir: PathBuf,
127 install_dir: PathBuf,
128 root: PathBuf,
129}
130
131pub enum GccBuildStatus {
132 AlreadyBuilt(PathBuf),
134 ShouldBuild(Meta),
135}
136
137fn try_download_gcc(builder: &Builder<'_>, target_pair: GccTargetPair) -> Option<PathBuf> {
141 if cfg!(test) {
143 return None;
144 }
145
146 if !matches!(builder.config.gcc_ci_mode, crate::core::config::GccCiMode::DownloadFromCi) {
148 return None;
149 }
150
151 if target_pair.host != target_pair.target {
153 eprintln!(
154 "GCC CI download is not available when the host ({}) does not equal the compilation target ({}).",
155 target_pair.host, target_pair.target
156 );
157 return None;
158 }
159
160 if target_pair.host != "x86_64-unknown-linux-gnu" {
161 eprintln!(
162 "GCC CI download is only available for the `x86_64-unknown-linux-gnu` host/target"
163 );
164 return None;
165 }
166 let source = detect_gcc_freshness(
167 &builder.config,
168 builder.config.rust_info.is_managed_git_subrepository(),
169 );
170 builder.do_if_verbose(|| {
171 eprintln!("GCC freshness: {source:?}");
172 });
173 match source {
174 PathFreshness::LastModifiedUpstream { upstream } => {
175 let root = ci_gcc_root(&builder.config, target_pair.target);
177 let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream);
178 if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
179 builder.config.download_ci_gcc(&upstream, &root);
180 t!(gcc_stamp.write());
181 }
182
183 let libgccjit = root.join("lib").join("libgccjit.so");
184 Some(libgccjit)
185 }
186 PathFreshness::HasLocalModifications { .. } => {
187 eprintln!("Found local GCC modifications, GCC will *not* be downloaded");
189 None
190 }
191 PathFreshness::MissingUpstream => {
192 eprintln!("error: could not find commit hash for downloading GCC");
193 eprintln!("HELP: maybe your repository history is too shallow?");
194 eprintln!("HELP: consider disabling `download-ci-gcc`");
195 eprintln!("HELP: or fetch enough history to include one upstream commit");
196 None
197 }
198 }
199}
200
201pub fn get_gcc_build_status(builder: &Builder<'_>, target_pair: GccTargetPair) -> GccBuildStatus {
207 if let Some(dir) = &builder.config.libgccjit_libs_dir {
209 let host_dir = dir.join(target_pair.host);
211 let path = host_dir.join(target_pair.target).join("libgccjit.so");
212 if path.exists() {
213 return GccBuildStatus::AlreadyBuilt(path);
214 } else {
215 builder.info(&format!(
216 "libgccjit.so for `{target_pair}` was not found at `{}`",
217 path.display()
218 ));
219
220 if target_pair.host != target_pair.target || target_pair.host != builder.host_target {
221 eprintln!(
222 "info: libgccjit.so for `{target_pair}` was not found at `{}`",
223 path.display()
224 );
225 eprintln!("error: we do not support downloading or building a GCC cross-compiler");
226 std::process::exit(1);
227 }
228 }
229 }
230
231 if let Some(path) = try_download_gcc(builder, target_pair) {
233 return GccBuildStatus::AlreadyBuilt(path);
234 }
235
236 static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
238 let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
239 generate_smart_stamp_hash(
240 builder,
241 &builder.config.src.join("src/gcc"),
242 builder.in_tree_gcc_info.sha().unwrap_or_default(),
243 )
244 });
245
246 builder.config.update_submodule("src/gcc");
248
249 let root = builder.src.join("src/gcc");
250 let out_dir = gcc_out(builder, target_pair).join("build");
251 let install_dir = gcc_out(builder, target_pair).join("install");
252
253 let stamp = BuildStamp::new(&out_dir).with_prefix("gcc").add_stamp(smart_stamp_hash);
254
255 if stamp.is_up_to_date() {
256 if stamp.stamp().is_empty() {
257 builder.info(
258 "Could not determine the GCC submodule commit hash. \
259 Assuming that an GCC rebuild is not necessary.",
260 );
261 builder.info(&format!(
262 "To force GCC to rebuild, remove the file `{}`",
263 stamp.path().display()
264 ));
265 }
266 let path = libgccjit_built_path(&install_dir);
267 if path.is_file() {
268 return GccBuildStatus::AlreadyBuilt(path);
269 } else {
270 builder.info(&format!(
271 "GCC stamp is up-to-date, but the libgccjit.so file was not found at `{}`",
272 path.display(),
273 ));
274 }
275 }
276
277 GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root })
278}
279
280fn gcc_out(builder: &Builder<'_>, pair: GccTargetPair) -> PathBuf {
281 builder.out.join(pair.host).join("gcc").join(pair.target)
282}
283
284fn libgccjit_built_path(install_dir: &Path) -> PathBuf {
286 install_dir.join("lib/libgccjit.so")
287}
288
289fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target_pair: GccTargetPair) {
290 let host = target_pair.host;
293 if builder.build.cc_tool(host).is_like_clang() || builder.build.cxx_tool(host).is_like_clang() {
294 panic!(
295 "Attempting to build GCC using Clang, which is known to misbehave. Please use GCC as the host C/C++ compiler. "
296 );
297 }
298
299 let Meta { stamp: _, out_dir, install_dir, root } = metadata;
300
301 t!(fs::create_dir_all(out_dir));
302 t!(fs::create_dir_all(install_dir));
303
304 let src_dir = gcc_out(builder, target_pair).join("src");
312 if src_dir.exists() {
313 builder.remove_dir(&src_dir);
314 }
315 builder.create_dir(&src_dir);
316 builder.cp_link_r(root, &src_dir);
317
318 command(src_dir.join("contrib/download_prerequisites")).current_dir(&src_dir).run(builder);
319 let mut configure_cmd = command(src_dir.join("configure"));
320 configure_cmd
321 .current_dir(out_dir)
322 .arg("--enable-host-shared")
323 .arg("--enable-languages=c,jit,lto")
324 .arg("--enable-checking=release")
325 .arg("--disable-bootstrap")
326 .arg("--disable-multilib")
327 .arg("--with-bugurl=https://github.com/rust-lang/gcc/")
328 .arg(format!("--prefix={}", install_dir.display()));
329
330 let cc = builder.build.cc(host).display().to_string();
331 let cc = builder
332 .build
333 .config
334 .ccache
335 .as_ref()
336 .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}"));
337 configure_cmd.env("CC", cc);
338
339 if let Ok(ref cxx) = builder.build.cxx(host) {
340 let cxx = cxx.display().to_string();
341 let cxx = builder
342 .build
343 .config
344 .ccache
345 .as_ref()
346 .map_or_else(|| cxx.clone(), |ccache| format!("{ccache} {cxx}"));
347 configure_cmd.env("CXX", cxx);
348 }
349 configure_cmd.env("CXXFLAGS", "-O2 -g0");
351 configure_cmd.env("CFLAGS", "-O2 -g0");
352 configure_cmd.run(builder);
353
354 command("make")
355 .current_dir(out_dir)
356 .arg("--silent")
357 .arg(format!("-j{}", builder.jobs()))
358 .run_capture_stdout(builder);
359 command("make").current_dir(out_dir).arg("--silent").arg("install").run_capture_stdout(builder);
360}
361
362pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) {
364 cargo.rustflag(&format!("-L{}", gcc.libgccjit.parent().unwrap().to_str().unwrap()));
366}
367
368fn ci_gcc_root(config: &crate::Config, target: TargetSelection) -> PathBuf {
370 config.out.join(target).join("ci-gcc")
371}
372
373fn detect_gcc_freshness(config: &crate::Config, is_git: bool) -> build_helper::git::PathFreshness {
375 assert!(cfg!(not(test)), "unit tests shouldn't care about GCC freshness");
376
377 if is_git {
378 config.check_path_modifications(&["src/gcc", "src/bootstrap/download-ci-gcc-stamp"])
379 } else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
380 PathFreshness::LastModifiedUpstream { upstream: info.sha.trim().to_owned() }
381 } else {
382 PathFreshness::MissingUpstream
383 }
384}