bootstrap/core/build_steps/
gcc.rs

1//! Compilation of native dependencies like GCC.
2//!
3//! Native projects like GCC unfortunately aren't suited just yet for
4//! compilation in build scripts that Cargo has. This is because the
5//! compilation takes a *very* long time but also because we don't want to
6//! compile GCC 3 times as part of a normal bootstrap (we want it cached).
7//!
8//! GCC and compiler-rt are essentially just wired up to everything else to
9//! ensure that they're always in place if needed.
10
11use std::fs;
12use std::path::PathBuf;
13use std::sync::OnceLock;
14
15use build_helper::ci::CiEnv;
16
17use crate::Kind;
18use crate::core::builder::{Builder, 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
24pub struct Meta {
25    stamp: BuildStamp,
26    out_dir: PathBuf,
27    install_dir: PathBuf,
28    root: PathBuf,
29}
30
31pub enum GccBuildStatus {
32    AlreadyBuilt,
33    ShouldBuild(Meta),
34}
35
36/// This returns whether we've already previously built GCC.
37///
38/// It's used to avoid busting caches during x.py check -- if we've already built
39/// GCC, it's fine for us to not try to avoid doing so.
40pub fn prebuilt_gcc_config(builder: &Builder<'_>, target: TargetSelection) -> GccBuildStatus {
41    // Initialize the gcc submodule if not initialized already.
42    builder.config.update_submodule("src/gcc");
43
44    // FIXME (GuillaumeGomez): To be done once gccjit has been built in the CI.
45    // builder.config.maybe_download_ci_gcc();
46
47    let root = builder.src.join("src/gcc");
48    let out_dir = builder.gcc_out(target).join("build");
49    let install_dir = builder.gcc_out(target).join("install");
50
51    static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
52    let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
53        generate_smart_stamp_hash(
54            builder,
55            &builder.config.src.join("src/gcc"),
56            builder.in_tree_gcc_info.sha().unwrap_or_default(),
57        )
58    });
59
60    let stamp = BuildStamp::new(&out_dir).with_prefix("gcc").add_stamp(smart_stamp_hash);
61
62    if stamp.is_up_to_date() {
63        if stamp.stamp().is_empty() {
64            builder.info(
65                "Could not determine the GCC submodule commit hash. \
66                     Assuming that an GCC rebuild is not necessary.",
67            );
68            builder.info(&format!(
69                "To force GCC to rebuild, remove the file `{}`",
70                stamp.path().display()
71            ));
72        }
73        return GccBuildStatus::AlreadyBuilt;
74    }
75
76    GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root })
77}
78
79#[derive(Debug, Clone, Hash, PartialEq, Eq)]
80pub struct Gcc {
81    pub target: TargetSelection,
82}
83
84impl Step for Gcc {
85    type Output = bool;
86
87    const ONLY_HOSTS: bool = true;
88
89    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
90        run.path("src/gcc").alias("gcc")
91    }
92
93    fn make_run(run: RunConfig<'_>) {
94        run.builder.ensure(Gcc { target: run.target });
95    }
96
97    /// Compile GCC for `target`.
98    fn run(self, builder: &Builder<'_>) -> bool {
99        let target = self.target;
100
101        // If GCC has already been built, we avoid building it again.
102        let Meta { stamp, out_dir, install_dir, root } = match prebuilt_gcc_config(builder, target)
103        {
104            GccBuildStatus::AlreadyBuilt => return true,
105            GccBuildStatus::ShouldBuild(m) => m,
106        };
107
108        let _guard = builder.msg_unstaged(Kind::Build, "GCC", target);
109        t!(stamp.remove());
110        let _time = helpers::timeit(builder);
111        t!(fs::create_dir_all(&out_dir));
112
113        if builder.config.dry_run() {
114            return true;
115        }
116
117        // GCC creates files (e.g. symlinks to the downloaded dependencies)
118        // in the source directory, which does not work with our CI setup, where we mount
119        // source directories as read-only on Linux.
120        // Therefore, as a part of the build in CI, we first copy the whole source directory
121        // to the build directory, and perform the build from there.
122        let src_dir = if CiEnv::is_ci() {
123            let src_dir = builder.gcc_out(target).join("src");
124            if src_dir.exists() {
125                builder.remove_dir(&src_dir);
126            }
127            builder.create_dir(&src_dir);
128            builder.cp_link_r(&root, &src_dir);
129            src_dir
130        } else {
131            root
132        };
133
134        command(src_dir.join("contrib/download_prerequisites")).current_dir(&src_dir).run(builder);
135        let mut configure_cmd = command(src_dir.join("configure"));
136        configure_cmd
137            .current_dir(&out_dir)
138            // On CI, we compile GCC with Clang.
139            // The -Wno-everything flag is needed to make GCC compile with Clang 19.
140            // `-g -O2` are the default flags that are otherwise used by Make.
141            // FIXME(kobzol): change the flags once we have [gcc] configuration in config.toml.
142            .env("CXXFLAGS", "-Wno-everything -g -O2")
143            .env("CFLAGS", "-Wno-everything -g -O2")
144            .arg("--enable-host-shared")
145            .arg("--enable-languages=jit")
146            .arg("--enable-checking=release")
147            .arg("--disable-bootstrap")
148            .arg("--disable-multilib")
149            .arg(format!("--prefix={}", install_dir.display()));
150        let cc = builder.build.cc(target).display().to_string();
151        let cc = builder
152            .build
153            .config
154            .ccache
155            .as_ref()
156            .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}"));
157        configure_cmd.env("CC", cc);
158
159        if let Ok(ref cxx) = builder.build.cxx(target) {
160            let cxx = cxx.display().to_string();
161            let cxx = builder
162                .build
163                .config
164                .ccache
165                .as_ref()
166                .map_or_else(|| cxx.clone(), |ccache| format!("{ccache} {cxx}"));
167            configure_cmd.env("CXX", cxx);
168        }
169        configure_cmd.run(builder);
170
171        command("make").current_dir(&out_dir).arg(format!("-j{}", builder.jobs())).run(builder);
172        command("make").current_dir(&out_dir).arg("install").run(builder);
173
174        let lib_alias = install_dir.join("lib/libgccjit.so.0");
175        if !lib_alias.exists() {
176            t!(builder.symlink_file(install_dir.join("lib/libgccjit.so"), lib_alias,));
177        }
178
179        t!(stamp.write());
180
181        true
182    }
183}