bootstrap/core/build_steps/
gcc.rs
1use 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 if !matches!(builder.config.gcc_ci_mode, crate::core::config::GccCiMode::DownloadFromCi) {
101 return None;
102 }
103 if target != "x86_64-unknown-linux-gnu" {
104 eprintln!("GCC CI download is only available for the `x86_64-unknown-linux-gnu` target");
105 return None;
106 }
107 let sha =
108 detect_gcc_sha(&builder.config, builder.config.rust_info.is_managed_git_subrepository());
109 let root = ci_gcc_root(&builder.config);
110 let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&sha);
111 if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
112 builder.config.download_ci_gcc(&sha, &root);
113 t!(gcc_stamp.write());
114 }
115
116 let libgccjit = root.join("lib").join("libgccjit.so");
117 create_lib_alias(builder, &libgccjit);
118 Some(libgccjit)
119}
120
121#[cfg(test)]
122fn try_download_gcc(_builder: &Builder<'_>, _target: TargetSelection) -> Option<PathBuf> {
123 None
124}
125
126pub fn get_gcc_build_status(builder: &Builder<'_>, target: TargetSelection) -> GccBuildStatus {
132 if let Some(path) = try_download_gcc(builder, target) {
133 return GccBuildStatus::AlreadyBuilt(path);
134 }
135
136 static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
137 let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
138 generate_smart_stamp_hash(
139 builder,
140 &builder.config.src.join("src/gcc"),
141 builder.in_tree_gcc_info.sha().unwrap_or_default(),
142 )
143 });
144
145 builder.config.update_submodule("src/gcc");
147
148 let root = builder.src.join("src/gcc");
149 let out_dir = builder.gcc_out(target).join("build");
150 let install_dir = builder.gcc_out(target).join("install");
151
152 let stamp = BuildStamp::new(&out_dir).with_prefix("gcc").add_stamp(smart_stamp_hash);
153
154 if stamp.is_up_to_date() {
155 if stamp.stamp().is_empty() {
156 builder.info(
157 "Could not determine the GCC submodule commit hash. \
158 Assuming that an GCC rebuild is not necessary.",
159 );
160 builder.info(&format!(
161 "To force GCC to rebuild, remove the file `{}`",
162 stamp.path().display()
163 ));
164 }
165 let path = libgccjit_built_path(&install_dir);
166 if path.is_file() {
167 return GccBuildStatus::AlreadyBuilt(path);
168 } else {
169 builder.info(&format!(
170 "GCC stamp is up-to-date, but the libgccjit.so file was not found at `{}`",
171 path.display(),
172 ));
173 }
174 }
175
176 GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root })
177}
178
179fn libgccjit_built_path(install_dir: &Path) -> PathBuf {
181 install_dir.join("lib/libgccjit.so")
182}
183
184fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) {
185 if builder.build.cc_tool(target).is_like_clang()
186 || builder.build.cxx_tool(target).is_like_clang()
187 {
188 panic!(
189 "Attempting to build GCC using Clang, which is known to misbehave. Please use GCC as the host C/C++ compiler. "
190 );
191 }
192
193 let Meta { stamp: _, out_dir, install_dir, root } = metadata;
194
195 t!(fs::create_dir_all(out_dir));
196 t!(fs::create_dir_all(install_dir));
197
198 let src_dir = if builder.config.is_running_on_ci {
204 let src_dir = builder.gcc_out(target).join("src");
205 if src_dir.exists() {
206 builder.remove_dir(&src_dir);
207 }
208 builder.create_dir(&src_dir);
209 builder.cp_link_r(root, &src_dir);
210 src_dir
211 } else {
212 root.clone()
213 };
214
215 command(src_dir.join("contrib/download_prerequisites")).current_dir(&src_dir).run(builder);
216 let mut configure_cmd = command(src_dir.join("configure"));
217 configure_cmd
218 .current_dir(out_dir)
219 .arg("--enable-host-shared")
220 .arg("--enable-languages=c,jit,lto")
221 .arg("--enable-checking=release")
222 .arg("--disable-bootstrap")
223 .arg("--disable-multilib")
224 .arg(format!("--prefix={}", install_dir.display()));
225
226 let cc = builder.build.cc(target).display().to_string();
227 let cc = builder
228 .build
229 .config
230 .ccache
231 .as_ref()
232 .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}"));
233 configure_cmd.env("CC", cc);
234
235 if let Ok(ref cxx) = builder.build.cxx(target) {
236 let cxx = cxx.display().to_string();
237 let cxx = builder
238 .build
239 .config
240 .ccache
241 .as_ref()
242 .map_or_else(|| cxx.clone(), |ccache| format!("{ccache} {cxx}"));
243 configure_cmd.env("CXX", cxx);
244 }
245 configure_cmd.run(builder);
246
247 command("make")
248 .current_dir(out_dir)
249 .arg("--silent")
250 .arg(format!("-j{}", builder.jobs()))
251 .run_capture_stdout(builder);
252 command("make").current_dir(out_dir).arg("--silent").arg("install").run_capture_stdout(builder);
253}
254
255pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) {
257 cargo.rustflag(&format!("-L{}", gcc.libgccjit.parent().unwrap().to_str().unwrap()));
259}
260
261#[cfg(not(test))]
263fn ci_gcc_root(config: &crate::Config) -> PathBuf {
264 config.out.join(config.build).join("ci-gcc")
265}
266
267#[cfg(not(test))]
269fn detect_gcc_sha(config: &crate::Config, is_git: bool) -> String {
270 use build_helper::git::get_closest_merge_commit;
271
272 let gcc_sha = if is_git {
273 get_closest_merge_commit(
274 Some(&config.src),
275 &config.git_config(),
276 &["src/gcc", "src/bootstrap/download-ci-gcc-stamp"],
277 )
278 .unwrap()
279 } else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
280 info.sha.trim().to_owned()
281 } else {
282 "".to_owned()
283 };
284
285 if gcc_sha.is_empty() {
286 eprintln!("error: could not find commit hash for downloading GCC");
287 eprintln!("HELP: maybe your repository history is too shallow?");
288 eprintln!("HELP: consider disabling `download-ci-gcc`");
289 eprintln!("HELP: or fetch enough history to include one upstream commit");
290 panic!();
291 }
292
293 gcc_sha
294}