@@ -13,14 +13,70 @@ use std::path::{Path, PathBuf};
1313use std:: sync:: OnceLock ;
1414
1515use build_helper:: ci:: CiEnv ;
16+ use build_helper:: git:: get_closest_merge_commit;
1617
17- use crate :: Kind ;
18- use crate :: core:: builder:: { Builder , Cargo , RunConfig , ShouldRun , Step } ;
19- use crate :: core:: config:: TargetSelection ;
18+ use crate :: Config ;
19+ use crate :: core:: builder:: { Builder , Cargo , Kind , RunConfig , ShouldRun , Step } ;
20+ use crate :: core:: config:: { GccCiMode , TargetSelection } ;
2021use crate :: utils:: build_stamp:: { BuildStamp , generate_smart_stamp_hash} ;
2122use crate :: utils:: exec:: command;
2223use crate :: utils:: helpers:: { self , t} ;
2324
25+ #[ derive( Debug , Clone , Hash , PartialEq , Eq ) ]
26+ pub struct Gcc {
27+ pub target : TargetSelection ,
28+ }
29+
30+ #[ derive( Clone ) ]
31+ pub struct GccOutput {
32+ pub libgccjit : PathBuf ,
33+ }
34+
35+ impl Step for Gcc {
36+ type Output = GccOutput ;
37+
38+ const ONLY_HOSTS : bool = true ;
39+
40+ fn should_run ( run : ShouldRun < ' _ > ) -> ShouldRun < ' _ > {
41+ run. path ( "src/gcc" ) . alias ( "gcc" )
42+ }
43+
44+ fn make_run ( run : RunConfig < ' _ > ) {
45+ run. builder . ensure ( Gcc { target : run. target } ) ;
46+ }
47+
48+ /// Compile GCC (specifically `libgccjit`) for `target`.
49+ fn run ( self , builder : & Builder < ' _ > ) -> Self :: Output {
50+ let target = self . target ;
51+
52+ // If GCC has already been built, we avoid building it again.
53+ let metadata = match get_gcc_build_status ( builder, target) {
54+ GccBuildStatus :: AlreadyBuilt ( path) => return GccOutput { libgccjit : path } ,
55+ GccBuildStatus :: ShouldBuild ( m) => m,
56+ } ;
57+
58+ let _guard = builder. msg_unstaged ( Kind :: Build , "GCC" , target) ;
59+ t ! ( metadata. stamp. remove( ) ) ;
60+ let _time = helpers:: timeit ( builder) ;
61+
62+ let libgccjit_path = libgccjit_built_path ( & metadata. install_dir ) ;
63+ if builder. config . dry_run ( ) {
64+ return GccOutput { libgccjit : libgccjit_path } ;
65+ }
66+
67+ build_gcc ( & metadata, builder, target) ;
68+
69+ let lib_alias = metadata. install_dir . join ( "lib/libgccjit.so.0" ) ;
70+ if !lib_alias. exists ( ) {
71+ t ! ( builder. symlink_file( & libgccjit_path, lib_alias) ) ;
72+ }
73+
74+ t ! ( metadata. stamp. write( ) ) ;
75+
76+ GccOutput { libgccjit : libgccjit_path }
77+ }
78+ }
79+
2480pub struct Meta {
2581 stamp : BuildStamp ,
2682 out_dir : PathBuf ,
@@ -34,17 +90,39 @@ pub enum GccBuildStatus {
3490 ShouldBuild ( Meta ) ,
3591}
3692
37- /// This returns whether we've already previously built GCC.
93+ /// Tries to download GCC from CI if it is enabled and GCC artifacts
94+ /// are available for the given target.
95+ /// Returns a path to the libgccjit.so file.
96+ fn try_download_gcc ( builder : & Builder < ' _ > , target : TargetSelection ) -> Option < PathBuf > {
97+ // Try to download GCC from CI if configured and available
98+ if !matches ! ( builder. config. gcc_ci_mode, GccCiMode :: DownloadFromCi ) {
99+ return None ;
100+ }
101+ if target != "x86_64-unknown-linux-gnu" {
102+ eprintln ! ( "GCC CI download is only available for the `x86_64-unknown-linux-gnu` target" ) ;
103+ return None ;
104+ }
105+ let sha =
106+ detect_gcc_sha ( & builder. config , builder. config . rust_info . is_managed_git_subrepository ( ) ) ;
107+ let root = ci_gcc_root ( & builder. config ) ;
108+ let gcc_stamp = BuildStamp :: new ( & root) . with_prefix ( "gcc" ) . add_stamp ( & sha) ;
109+ if !gcc_stamp. is_up_to_date ( ) && !builder. config . dry_run ( ) {
110+ builder. config . download_ci_gcc ( & sha, & root) ;
111+ t ! ( gcc_stamp. write( ) ) ;
112+ }
113+ // FIXME: put libgccjit.so into a lib directory in dist::Gcc
114+ Some ( root. join ( "libgccjit.so" ) )
115+ }
116+
117+ /// This returns information about whether GCC should be built or if it's already built.
118+ /// It transparently handles downloading GCC from CI if needed.
38119///
39120/// It's used to avoid busting caches during x.py check -- if we've already built
40121/// GCC, it's fine for us to not try to avoid doing so.
41- pub fn prebuilt_gcc_config ( builder : & Builder < ' _ > , target : TargetSelection ) -> GccBuildStatus {
42- // Initialize the gcc submodule if not initialized already.
43- builder. config . update_submodule ( "src/gcc" ) ;
44-
45- let root = builder. src . join ( "src/gcc" ) ;
46- let out_dir = builder. gcc_out ( target) . join ( "build" ) ;
47- let install_dir = builder. gcc_out ( target) . join ( "install" ) ;
122+ pub fn get_gcc_build_status ( builder : & Builder < ' _ > , target : TargetSelection ) -> GccBuildStatus {
123+ if let Some ( path) = try_download_gcc ( builder, target) {
124+ return GccBuildStatus :: AlreadyBuilt ( path) ;
125+ }
48126
49127 static STAMP_HASH_MEMO : OnceLock < String > = OnceLock :: new ( ) ;
50128 let smart_stamp_hash = STAMP_HASH_MEMO . get_or_init ( || {
@@ -55,6 +133,13 @@ pub fn prebuilt_gcc_config(builder: &Builder<'_>, target: TargetSelection) -> Gc
55133 )
56134 } ) ;
57135
136+ // Initialize the gcc submodule if not initialized already.
137+ builder. config . update_submodule ( "src/gcc" ) ;
138+
139+ let root = builder. src . join ( "src/gcc" ) ;
140+ let out_dir = builder. gcc_out ( target) . join ( "build" ) ;
141+ let install_dir = builder. gcc_out ( target) . join ( "install" ) ;
142+
58143 let stamp = BuildStamp :: new ( & out_dir) . with_prefix ( "gcc" ) . add_stamp ( smart_stamp_hash) ;
59144
60145 if stamp. is_up_to_date ( ) {
@@ -87,120 +172,107 @@ fn libgccjit_built_path(install_dir: &Path) -> PathBuf {
87172 install_dir. join ( "lib/libgccjit.so" )
88173}
89174
90- #[ derive( Debug , Clone , Hash , PartialEq , Eq ) ]
91- pub struct Gcc {
92- pub target : TargetSelection ,
93- }
94-
95- #[ derive( Clone ) ]
96- pub struct GccOutput {
97- pub libgccjit : PathBuf ,
98- }
175+ fn build_gcc ( metadata : & Meta , builder : & Builder < ' _ > , target : TargetSelection ) {
176+ let Meta { stamp : _, out_dir, install_dir, root } = metadata;
99177
100- impl Step for Gcc {
101- type Output = GccOutput ;
102-
103- const ONLY_HOSTS : bool = true ;
104-
105- fn should_run ( run : ShouldRun < ' _ > ) -> ShouldRun < ' _ > {
106- run. path ( "src/gcc" ) . alias ( "gcc" )
107- }
108-
109- fn make_run ( run : RunConfig < ' _ > ) {
110- run. builder . ensure ( Gcc { target : run. target } ) ;
111- }
112-
113- /// Compile GCC (specifically `libgccjit`) for `target`.
114- fn run ( self , builder : & Builder < ' _ > ) -> Self :: Output {
115- let target = self . target ;
178+ t ! ( fs:: create_dir_all( out_dir) ) ;
179+ t ! ( fs:: create_dir_all( install_dir) ) ;
116180
117- // If GCC has already been built, we avoid building it again.
118- let Meta { stamp, out_dir, install_dir, root } = match prebuilt_gcc_config ( builder, target)
119- {
120- GccBuildStatus :: AlreadyBuilt ( path) => return GccOutput { libgccjit : path } ,
121- GccBuildStatus :: ShouldBuild ( m) => m,
122- } ;
123-
124- let _guard = builder. msg_unstaged ( Kind :: Build , "GCC" , target) ;
125- t ! ( stamp. remove( ) ) ;
126- let _time = helpers:: timeit ( builder) ;
127- t ! ( fs:: create_dir_all( & out_dir) ) ;
128-
129- let libgccjit_path = libgccjit_built_path ( & install_dir) ;
130- if builder. config . dry_run ( ) {
131- return GccOutput { libgccjit : libgccjit_path } ;
181+ // GCC creates files (e.g. symlinks to the downloaded dependencies)
182+ // in the source directory, which does not work with our CI setup, where we mount
183+ // source directories as read-only on Linux.
184+ // Therefore, as a part of the build in CI, we first copy the whole source directory
185+ // to the build directory, and perform the build from there.
186+ let src_dir = if CiEnv :: is_ci ( ) {
187+ let src_dir = builder. gcc_out ( target) . join ( "src" ) ;
188+ if src_dir. exists ( ) {
189+ builder. remove_dir ( & src_dir) ;
132190 }
191+ builder. create_dir ( & src_dir) ;
192+ builder. cp_link_r ( root, & src_dir) ;
193+ src_dir
194+ } else {
195+ root. clone ( )
196+ } ;
133197
134- // GCC creates files (e.g. symlinks to the downloaded dependencies)
135- // in the source directory, which does not work with our CI setup, where we mount
136- // source directories as read-only on Linux.
137- // Therefore, as a part of the build in CI, we first copy the whole source directory
138- // to the build directory, and perform the build from there.
139- let src_dir = if CiEnv :: is_ci ( ) {
140- let src_dir = builder. gcc_out ( target) . join ( "src" ) ;
141- if src_dir. exists ( ) {
142- builder. remove_dir ( & src_dir) ;
143- }
144- builder. create_dir ( & src_dir) ;
145- builder. cp_link_r ( & root, & src_dir) ;
146- src_dir
147- } else {
148- root
149- } ;
198+ command ( src_dir. join ( "contrib/download_prerequisites" ) ) . current_dir ( & src_dir) . run ( builder) ;
199+ let mut configure_cmd = command ( src_dir. join ( "configure" ) ) ;
200+ configure_cmd
201+ . current_dir ( out_dir)
202+ // On CI, we compile GCC with Clang.
203+ // The -Wno-everything flag is needed to make GCC compile with Clang 19.
204+ // `-g -O2` are the default flags that are otherwise used by Make.
205+ // FIXME(kobzol): change the flags once we have [gcc] configuration in config.toml.
206+ . env ( "CXXFLAGS" , "-Wno-everything -g -O2" )
207+ . env ( "CFLAGS" , "-Wno-everything -g -O2" )
208+ . arg ( "--enable-host-shared" )
209+ . arg ( "--enable-languages=jit" )
210+ . arg ( "--enable-checking=release" )
211+ . arg ( "--disable-bootstrap" )
212+ . arg ( "--disable-multilib" )
213+ . arg ( format ! ( "--prefix={}" , install_dir. display( ) ) ) ;
214+ let cc = builder. build . cc ( target) . display ( ) . to_string ( ) ;
215+ let cc = builder
216+ . build
217+ . config
218+ . ccache
219+ . as_ref ( )
220+ . map_or_else ( || cc. clone ( ) , |ccache| format ! ( "{ccache} {cc}" ) ) ;
221+ configure_cmd. env ( "CC" , cc) ;
150222
151- command ( src_dir. join ( "contrib/download_prerequisites" ) ) . current_dir ( & src_dir) . run ( builder) ;
152- let mut configure_cmd = command ( src_dir. join ( "configure" ) ) ;
153- configure_cmd
154- . current_dir ( & out_dir)
155- // On CI, we compile GCC with Clang.
156- // The -Wno-everything flag is needed to make GCC compile with Clang 19.
157- // `-g -O2` are the default flags that are otherwise used by Make.
158- // FIXME(kobzol): change the flags once we have [gcc] configuration in config.toml.
159- . env ( "CXXFLAGS" , "-Wno-everything -g -O2" )
160- . env ( "CFLAGS" , "-Wno-everything -g -O2" )
161- . arg ( "--enable-host-shared" )
162- . arg ( "--enable-languages=jit" )
163- . arg ( "--enable-checking=release" )
164- . arg ( "--disable-bootstrap" )
165- . arg ( "--disable-multilib" )
166- . arg ( format ! ( "--prefix={}" , install_dir. display( ) ) ) ;
167- let cc = builder. build . cc ( target) . display ( ) . to_string ( ) ;
168- let cc = builder
223+ if let Ok ( ref cxx) = builder. build . cxx ( target) {
224+ let cxx = cxx. display ( ) . to_string ( ) ;
225+ let cxx = builder
169226 . build
170227 . config
171228 . ccache
172229 . as_ref ( )
173- . map_or_else ( || cc. clone ( ) , |ccache| format ! ( "{ccache} {cc}" ) ) ;
174- configure_cmd. env ( "CC" , cc) ;
175-
176- if let Ok ( ref cxx) = builder. build . cxx ( target) {
177- let cxx = cxx. display ( ) . to_string ( ) ;
178- let cxx = builder
179- . build
180- . config
181- . ccache
182- . as_ref ( )
183- . map_or_else ( || cxx. clone ( ) , |ccache| format ! ( "{ccache} {cxx}" ) ) ;
184- configure_cmd. env ( "CXX" , cxx) ;
185- }
186- configure_cmd. run ( builder) ;
187-
188- command ( "make" ) . current_dir ( & out_dir) . arg ( format ! ( "-j{}" , builder. jobs( ) ) ) . run ( builder) ;
189- command ( "make" ) . current_dir ( & out_dir) . arg ( "install" ) . run ( builder) ;
190-
191- let lib_alias = install_dir. join ( "lib/libgccjit.so.0" ) ;
192- if !lib_alias. exists ( ) {
193- t ! ( builder. symlink_file( & libgccjit_path, lib_alias) ) ;
194- }
195-
196- t ! ( stamp. write( ) ) ;
197-
198- GccOutput { libgccjit : libgccjit_path }
230+ . map_or_else ( || cxx. clone ( ) , |ccache| format ! ( "{ccache} {cxx}" ) ) ;
231+ configure_cmd. env ( "CXX" , cxx) ;
199232 }
233+ configure_cmd. run ( builder) ;
234+
235+ command ( "make" )
236+ . current_dir ( out_dir)
237+ . arg ( "--silent" )
238+ . arg ( format ! ( "-j{}" , builder. jobs( ) ) )
239+ . run_capture_stdout ( builder) ;
240+ command ( "make" ) . current_dir ( out_dir) . arg ( "--silent" ) . arg ( "install" ) . run_capture_stdout ( builder) ;
200241}
201242
202243/// Configures a Cargo invocation so that it can build the GCC codegen backend.
203244pub fn add_cg_gcc_cargo_flags ( cargo : & mut Cargo , gcc : & GccOutput ) {
204245 // Add the path to libgccjit.so to the linker search paths.
205246 cargo. rustflag ( & format ! ( "-L{}" , gcc. libgccjit. parent( ) . unwrap( ) . to_str( ) . unwrap( ) ) ) ;
206247}
248+
249+ /// The absolute path to the downloaded GCC artifacts.
250+ fn ci_gcc_root ( config : & Config ) -> PathBuf {
251+ config. out . join ( config. build ) . join ( "ci-gcc" )
252+ }
253+
254+ /// This retrieves the GCC sha we *want* to use, according to git history.
255+ fn detect_gcc_sha ( config : & Config , is_git : bool ) -> String {
256+ let gcc_sha = if is_git {
257+ get_closest_merge_commit (
258+ Some ( & config. src ) ,
259+ & config. git_config ( ) ,
260+ & [ config. src . join ( "src/gcc" ) , config. src . join ( "src/bootstrap/download-ci-gcc-stamp" ) ] ,
261+ )
262+ . unwrap ( )
263+ } else if let Some ( info) = crate :: utils:: channel:: read_commit_info_file ( & config. src ) {
264+ info. sha . trim ( ) . to_owned ( )
265+ } else {
266+ "" . to_owned ( )
267+ } ;
268+
269+ if gcc_sha. is_empty ( ) {
270+ eprintln ! ( "error: could not find commit hash for downloading GCC" ) ;
271+ eprintln ! ( "HELP: maybe your repository history is too shallow?" ) ;
272+ eprintln ! ( "HELP: consider disabling `download-ci-gcc`" ) ;
273+ eprintln ! ( "HELP: or fetch enough history to include one upstream commit" ) ;
274+ panic ! ( ) ;
275+ }
276+
277+ gcc_sha
278+ }
0 commit comments