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