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