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