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