@@ -848,6 +848,31 @@ enum Commands {
848
848
849
849
/// Installs the next commit for perf.rust-lang.org
850
850
InstallNext ,
851
+
852
+ /// Download a crate into collector/benchmarks.
853
+ Download ( DownloadCommand ) ,
854
+ }
855
+
856
+ #[ derive( Debug , clap:: Parser ) ]
857
+ struct DownloadCommand {
858
+ /// Name of the benchmark created directory
859
+ #[ clap( long, global = true ) ]
860
+ name : Option < String > ,
861
+
862
+ /// Overwrite the benchmark directory if it already exists.
863
+ #[ clap( long, short( 'f' ) , global = true ) ]
864
+ force : bool ,
865
+
866
+ #[ clap( subcommand) ]
867
+ command : DownloadSubcommand ,
868
+ }
869
+
870
+ #[ derive( Debug , clap:: Parser ) ]
871
+ enum DownloadSubcommand {
872
+ /// Download a crate from a git repository.
873
+ Git { url : String } ,
874
+ /// Download a crate from crates.io.
875
+ Crate { krate : String , version : String } ,
851
876
}
852
877
853
878
fn main_result ( ) -> anyhow:: Result < i32 > {
@@ -1141,9 +1166,105 @@ fn main_result() -> anyhow::Result<i32> {
1141
1166
1142
1167
Ok ( 0 )
1143
1168
}
1169
+ Commands :: Download ( cmd) => {
1170
+ let target_dir = get_downloaded_crate_target ( & benchmark_dir, & cmd) ;
1171
+ check_target_dir ( & target_dir, cmd. force ) ?;
1172
+
1173
+ match cmd. command {
1174
+ DownloadSubcommand :: Git { url } => download_from_git ( & target_dir, & url) ?,
1175
+ DownloadSubcommand :: Crate { krate, version } => {
1176
+ download_from_crates_io ( & target_dir, & krate, & version) ?
1177
+ }
1178
+ } ;
1179
+ println ! ( "Benchmark stored at {}" , target_dir. display( ) ) ;
1180
+ Ok ( 0 )
1181
+ }
1144
1182
}
1145
1183
}
1146
1184
1185
+ fn check_target_dir ( target_dir : & Path , force : bool ) -> anyhow:: Result < ( ) > {
1186
+ if target_dir. exists ( ) {
1187
+ if force {
1188
+ std:: fs:: remove_dir_all ( & target_dir) . expect ( & format ! (
1189
+ "Cannot remove previous directory at {}" ,
1190
+ target_dir. display( )
1191
+ ) ) ;
1192
+ } else {
1193
+ return Err ( anyhow:: anyhow!(
1194
+ "Directory {} already exists" ,
1195
+ target_dir. display( )
1196
+ ) ) ;
1197
+ }
1198
+ }
1199
+ Ok ( ( ) )
1200
+ }
1201
+
1202
+ fn get_downloaded_crate_target ( benchmark_dir : & Path , cmd : & DownloadCommand ) -> PathBuf {
1203
+ let name = cmd. name . clone ( ) . unwrap_or_else ( || match & cmd. command {
1204
+ // Git repository URLs sometimes end with .git, so we get rid of it.
1205
+ // URLs in general can end with /, which we also want to remove to make sure that the
1206
+ // last part of the URL is the repository name.
1207
+ DownloadSubcommand :: Git { url } => url
1208
+ . trim_end_matches ( "/" )
1209
+ . trim_end_matches ( ".git" )
1210
+ . split ( "/" )
1211
+ . last ( )
1212
+ . expect ( "Crate name could not be determined from git URL" )
1213
+ . to_string ( ) ,
1214
+ DownloadSubcommand :: Crate { krate, version } => format ! ( "{krate}-{version}" ) ,
1215
+ } ) ;
1216
+ PathBuf :: from ( benchmark_dir) . join ( name)
1217
+ }
1218
+
1219
+ fn download_from_git ( target : & Path , url : & str ) -> anyhow:: Result < ( ) > {
1220
+ let tmpdir = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
1221
+ Command :: new ( "git" )
1222
+ . arg ( "clone" )
1223
+ . arg ( url)
1224
+ . arg ( tmpdir. path ( ) )
1225
+ . status ( )
1226
+ . expect ( "Git clone failed" ) ;
1227
+ generate_lockfile ( tmpdir. path ( ) ) ;
1228
+ execute:: rename ( & tmpdir, & target) ?;
1229
+ Ok ( ( ) )
1230
+ }
1231
+
1232
+ fn download_from_crates_io ( target_dir : & Path , krate : & str , version : & str ) -> anyhow:: Result < ( ) > {
1233
+ let url = format ! ( "https://crates.io/api/v1/crates/{krate}/{version}/download" ) ;
1234
+ let response = reqwest:: blocking:: get ( url)
1235
+ . expect ( "Cannot download crate" )
1236
+ . error_for_status ( ) ?;
1237
+
1238
+ let data = flate2:: read:: GzDecoder :: new ( response) ;
1239
+ let mut archive = tar:: Archive :: new ( data) ;
1240
+
1241
+ let tmpdir = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
1242
+ archive. unpack ( & tmpdir) ?;
1243
+
1244
+ // The content of the crate is not at the package root, it should be nested
1245
+ // under <crate-name>-<version> directory.
1246
+ let unpacked_dir = tmpdir. path ( ) . join ( format ! ( "{krate}-{version}" ) ) ;
1247
+ generate_lockfile ( & unpacked_dir) ;
1248
+ execute:: rename ( & unpacked_dir, & target_dir) ?;
1249
+
1250
+ Ok ( ( ) )
1251
+ }
1252
+
1253
+ fn generate_lockfile ( directory : & Path ) {
1254
+ let manifest_path = directory. join ( "Cargo.toml" ) ;
1255
+
1256
+ // Cargo metadata should do nothing if there is already a lockfile present.
1257
+ // Otherwise it will generate a lockfile.
1258
+ Command :: new ( "cargo" )
1259
+ . arg ( "metadata" )
1260
+ . arg ( "--format-version" )
1261
+ . arg ( "1" )
1262
+ . current_dir ( manifest_path. parent ( ) . unwrap ( ) )
1263
+ . stdout ( std:: process:: Stdio :: null ( ) )
1264
+ . status ( )
1265
+ . expect ( "Cannot generate lock file" ) ;
1266
+ }
1267
+
1147
1268
pub fn get_commit_or_fake_it ( sha : & str ) -> anyhow:: Result < Commit > {
1148
1269
let rt = tokio:: runtime:: Runtime :: new ( ) . unwrap ( ) ;
1149
1270
Ok ( rt
0 commit comments