1
+ use anyhow:: Context ;
2
+ use flate2:: { Compression , write:: GzEncoder } ;
3
+ use std:: env:: consts:: EXE_EXTENSION ;
4
+ use std:: ffi:: OsStr ;
1
5
use std:: {
2
6
env,
3
7
fs:: File ,
4
8
io:: { self , BufWriter } ,
5
9
path:: { Path , PathBuf } ,
6
10
} ;
7
-
8
- use flate2:: { Compression , write:: GzEncoder } ;
9
11
use time:: OffsetDateTime ;
10
- use xshell:: { Shell , cmd} ;
12
+ use xshell:: { Cmd , Shell , cmd} ;
11
13
use zip:: { DateTime , ZipWriter , write:: SimpleFileOptions } ;
12
14
13
15
use crate :: {
@@ -38,11 +40,18 @@ impl flags::Dist {
38
40
// A hack to make VS Code prefer nightly over stable.
39
41
format ! ( "{VERSION_NIGHTLY}.{patch_version}" )
40
42
} ;
41
- dist_server ( sh, & format ! ( "{version}-standalone" ) , & target, allocator, self . zig ) ?;
43
+ dist_server (
44
+ sh,
45
+ & format ! ( "{version}-standalone" ) ,
46
+ & target,
47
+ allocator,
48
+ self . zig ,
49
+ self . pgo ,
50
+ ) ?;
42
51
let release_tag = if stable { date_iso ( sh) ? } else { "nightly" . to_owned ( ) } ;
43
52
dist_client ( sh, & version, & release_tag, & target) ?;
44
53
} else {
45
- dist_server ( sh, "0.0.0-standalone" , & target, allocator, self . zig ) ?;
54
+ dist_server ( sh, "0.0.0-standalone" , & target, allocator, self . zig , self . pgo ) ?;
46
55
}
47
56
Ok ( ( ) )
48
57
}
@@ -84,6 +93,7 @@ fn dist_server(
84
93
target : & Target ,
85
94
allocator : Malloc ,
86
95
zig : bool ,
96
+ pgo : bool ,
87
97
) -> anyhow:: Result < ( ) > {
88
98
let _e = sh. push_env ( "CFG_RELEASE" , release) ;
89
99
let _e = sh. push_env ( "CARGO_PROFILE_RELEASE_LTO" , "thin" ) ;
@@ -100,7 +110,22 @@ fn dist_server(
100
110
} ;
101
111
let features = allocator. to_features ( ) ;
102
112
let command = if linux_target && zig { "zigbuild" } else { "build" } ;
103
- cmd ! ( sh, "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" ) . run ( ) ?;
113
+
114
+ let pgo_profile = if pgo {
115
+ Some ( gather_pgo_profile (
116
+ sh,
117
+ build_command ( sh, command, & target_name, features) ,
118
+ & target_name,
119
+ ) ?)
120
+ } else {
121
+ None
122
+ } ;
123
+
124
+ let mut cmd = build_command ( sh, command, & target_name, features) ;
125
+ if let Some ( profile) = pgo_profile {
126
+ cmd = cmd. env ( "RUSTFLAGS" , format ! ( "-Cprofile-use={}" , profile. to_str( ) . unwrap( ) ) ) ;
127
+ }
128
+ cmd. run ( ) . context ( "cannot build Rust Analyzer" ) ?;
104
129
105
130
let dst = Path :: new ( "dist" ) . join ( & target. artifact_name ) ;
106
131
if target_name. contains ( "-windows-" ) {
@@ -112,6 +137,70 @@ fn dist_server(
112
137
Ok ( ( ) )
113
138
}
114
139
140
+ fn build_command < ' a > (
141
+ sh : & ' a Shell ,
142
+ command : & str ,
143
+ target_name : & str ,
144
+ features : & [ & str ] ,
145
+ ) -> Cmd < ' a > {
146
+ cmd ! (
147
+ sh,
148
+ "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release"
149
+ )
150
+ }
151
+
152
+ /// Decorates `ra_build_cmd` to add PGO instrumentation, and then runs the PGO instrumented
153
+ /// Rust Analyzer on itself to gather a PGO profile.
154
+ fn gather_pgo_profile < ' a > (
155
+ sh : & ' a Shell ,
156
+ ra_build_cmd : Cmd < ' a > ,
157
+ target : & str ,
158
+ ) -> anyhow:: Result < PathBuf > {
159
+ let pgo_dir = std:: path:: absolute ( "ra-pgo-profiles" ) ?;
160
+ // Clear out any stale profiles
161
+ if pgo_dir. is_dir ( ) {
162
+ std:: fs:: remove_dir_all ( & pgo_dir) ?;
163
+ }
164
+ std:: fs:: create_dir_all ( & pgo_dir) ?;
165
+
166
+ // Figure out a path to `llvm-profdata`
167
+ let target_libdir = cmd ! ( sh, "rustc --print=target-libdir" )
168
+ . read ( )
169
+ . context ( "cannot resolve target-libdir from rustc" ) ?;
170
+ let target_bindir = PathBuf :: from ( target_libdir) . parent ( ) . unwrap ( ) . join ( "bin" ) ;
171
+ let llvm_profdata = target_bindir. join ( format ! ( "llvm-profdata{}" , EXE_EXTENSION ) ) ;
172
+
173
+ // Build RA with PGO instrumentation
174
+ let cmd_gather =
175
+ ra_build_cmd. env ( "RUSTFLAGS" , format ! ( "-Cprofile-generate={}" , pgo_dir. to_str( ) . unwrap( ) ) ) ;
176
+ cmd_gather. run ( ) . context ( "cannot build rust-analyzer with PGO instrumentation" ) ?;
177
+
178
+ // Run RA on itself to gather profiles
179
+ let train_crate = "." ;
180
+ cmd ! (
181
+ sh,
182
+ "target/{target}/release/rust-analyzer analysis-stats {train_crate} --run-all-ide-things"
183
+ )
184
+ . run ( )
185
+ . context ( "cannot generate PGO profiles" ) ?;
186
+
187
+ // Merge profiles into a single file
188
+ let merged_profile = pgo_dir. join ( "merged.profdata" ) ;
189
+ let profile_files = std:: fs:: read_dir ( pgo_dir) ?. filter_map ( |entry| {
190
+ let entry = entry. ok ( ) ?;
191
+ if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "profraw" ) ) {
192
+ Some ( entry. path ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) )
193
+ } else {
194
+ None
195
+ }
196
+ } ) ;
197
+ cmd ! ( sh, "{llvm_profdata} merge {profile_files...} -o {merged_profile}" ) . run ( ) . context (
198
+ "cannot merge PGO profiles. Do you have the rustup `llvm-tools` component installed?" ,
199
+ ) ?;
200
+
201
+ Ok ( merged_profile)
202
+ }
203
+
115
204
fn gzip ( src_path : & Path , dest_path : & Path ) -> anyhow:: Result < ( ) > {
116
205
let mut encoder = GzEncoder :: new ( File :: create ( dest_path) ?, Compression :: best ( ) ) ;
117
206
let mut input = io:: BufReader :: new ( File :: open ( src_path) ?) ;
0 commit comments