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