diff --git a/src/main.rs b/src/main.rs index 51eeef8..8a49698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,8 +18,8 @@ use tar::{Builder, Header}; #[derive(Deserialize)] struct Options { arg_path: String, - flag_no_delete: Option, - flag_sync: Option, + flag_no_delete: bool, + flag_sync: Vec, flag_host: Option, flag_verbose: u32, flag_quiet: Option, @@ -68,7 +68,7 @@ fn main() { Vendor all dependencies for a project locally Usage: - cargo local-registry [options] [] + cargo local-registry [options] [--sync=]... [] Options: -h, --help Print this message @@ -111,12 +111,30 @@ fn real_main(options: Options, config: &mut Config) -> CargoResult<()> { None => SourceId::crates_io(config)?, }; - let lockfile = match options.flag_sync { - Some(ref file) => file, - None => return Ok(()), - }; + if options.flag_sync.is_empty() { + return Ok(()); + } + + let mut added_crates = HashSet::new(); + let mut added_index = HashSet::new(); + for lockfile in &options.flag_sync { + sync( + Path::new(lockfile), + &path, + &id, + &options, + config, + &mut added_crates, + &mut added_index, + ) + .chain_err(|| format!("failed to sync {}", lockfile))?; + } - sync(Path::new(lockfile), &path, &id, &options, config).chain_err(|| "failed to sync")?; + if !options.flag_no_delete { + let canonical_local_dst = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + delete_unused(&canonical_local_dst, &added_crates)?; + scan_delete(&canonical_local_dst.join("index"), 3, &added_index)?; + } println!( "add this to your .cargo/config somewhere: @@ -142,9 +160,13 @@ fn sync( registry_id: &SourceId, options: &Options, config: &Config, + added_crates: &mut HashSet, + added_index: &mut HashSet, ) -> CargoResult<()> { - let no_delete = options.flag_no_delete.unwrap_or(false); - let canonical_local_dst = local_dst.canonicalize().unwrap_or(local_dst.to_path_buf()); + let no_delete = options.flag_no_delete; + let canonical_local_dst = local_dst + .canonicalize() + .unwrap_or_else(|_| local_dst.to_path_buf()); let manifest = lockfile.parent().unwrap().join("Cargo.toml"); let manifest = env::current_dir().unwrap().join(&manifest); let ws = Workspace::new(&manifest, config)?; @@ -157,8 +179,6 @@ fn sync( let cache = config.registry_cache_path().join(&part); - let mut added_crates = HashSet::new(); - let mut added_index = HashSet::new(); for id in resolve.iter() { if id.source_id().is_git() { if !options.flag_git { @@ -173,6 +193,9 @@ fn sync( .chain_err(|| "failed to fetch package")?; let filename = format!("{}-{}.crate", id.name(), id.version()); let dst = canonical_local_dst.join(&filename); + if added_crates.contains(&dst) { + continue; + } if id.source_id().is_registry() { let src = cache.join(&filename).into_path_unlocked(); fs::copy(&src, &dst).chain_err(|| { @@ -220,28 +243,28 @@ fn sync( added_index.insert(dst); } - if !no_delete { - let existing_crates: Vec = canonical_local_dst - .read_dir() - .map(|iter| { - iter.filter_map(|e| e.ok()) - .filter(|e| { - e.file_name() - .to_str() - .map_or(false, |name| name.ends_with(".crate")) - }) - .map(|e| e.path()) - .collect::>() - }) - .unwrap_or_else(|_| Vec::new()); + Ok(()) +} - for path in existing_crates { - if !added_crates.contains(&path) { - fs::remove_file(&path)?; - } - } +fn delete_unused(canonical_local_dst: &Path, keep: &HashSet) -> CargoResult<()> { + let existing_crates: Vec = canonical_local_dst + .read_dir() + .map(|iter| { + iter.filter_map(|e| e.ok()) + .filter(|e| { + e.file_name() + .to_str() + .map_or(false, |name| name.ends_with(".crate")) + }) + .map(|e| e.path()) + .collect::>() + }) + .unwrap_or_else(|_| Vec::new()); - scan_delete(&canonical_local_dst.join("index"), 3, &added_index)?; + for path in existing_crates { + if !keep.contains(&path) { + fs::remove_file(&path)?; + } } Ok(()) } diff --git a/tests/all.rs b/tests/all.rs index 244f501..b0c043d 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -459,6 +459,166 @@ source = "registry+https://github.com/rust-lang/crates.io-index" assert_eq!(contents, r#"{"name":"lazycell","vers":"1.2.1","deps":[{"name":"clippy","req":"^0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null}],"cksum":"b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f","features":{"nightly":[],"nightly-testing":["clippy","nightly"]},"yanked":false}"#); } +// Creates two crates with different dependencies, runs sync to both of +// them and checks that files for both crates are vendored. +// +// Then deletes a dependency from one of the crates and resyncs, and checks that +// that only the removed dependency is deleted to verify that delete_unused +// behaves correctly with multiple crates. +#[test] +fn multiple_cargo_lock() { + let td = TempDir::new().unwrap(); + let registry = td.path().join("registry"); + let foo_dir = td.path().join("foo"); + let foo_lock = foo_dir.join("Cargo.lock"); + let bar_dir = td.path().join("bar"); + let bar_lock = bar_dir.join("Cargo.lock"); + + fs::create_dir_all(foo_dir.join("src")).unwrap(); + fs::create_dir_all(bar_dir.join("src")).unwrap(); + File::create(foo_dir.join("src/lib.rs")) + .unwrap() + .write_all(b"") + .unwrap(); + File::create(bar_dir.join("src/lib.rs")) + .unwrap() + .write_all(b"") + .unwrap(); + + File::create(foo_dir.join("Cargo.toml")) + .unwrap() + .write_all( + br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + libc = "0.2.6" + "#, + ) + .unwrap(); + File::create(&foo_lock) + .unwrap() + .write_all( + br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +"#, + ) + .unwrap(); + + File::create(bar_dir.join("Cargo.toml")) + .unwrap() + .write_all( + br#" +[package] +name = "bar" +version = "0.1.0" +authors = [] + +[dependencies] +lazy_static = "1.2.0" +lazycell = "1.2.1" +"#) + .unwrap(); + + File::create(&bar_lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"#) + .unwrap(); + + println!( + "first run: {}", + run(cmd().arg(®istry).arg("--sync").arg(&foo_lock).arg(&"--sync").arg(&bar_lock)) + ); + + assert!(registry.join("index").is_dir()); + assert!(registry.join("index/li/bc/libc").is_file()); + assert!(registry.join("libc-0.2.7.crate").is_file()); + assert!(registry.join("index/la/zy/lazy_static").is_file()); + assert!(registry.join("index/la/zy/lazycell").is_file()); + assert!(registry.join("lazy_static-1.2.0.crate").is_file()); + assert!(registry.join("lazycell-1.2.1.crate").is_file()); + + // Remove lazycell from bar + File::create(bar_dir.join("Cargo.toml")) + .unwrap() + .write_all( + br#" +[package] +name = "bar" +version = "0.1.0" +authors = [] + +[dependencies] +lazy_static = "1.2.0" +"#) + .unwrap(); + + File::create(&bar_lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"#) + .unwrap(); + + println!( + "second run: {}", + run(cmd().arg(®istry).arg("--sync").arg(&foo_lock).arg(&"--sync").arg(&bar_lock)) + ); + + assert!(registry.join("index").is_dir()); + assert!(registry.join("index/li/bc/libc").is_file()); + assert!(registry.join("libc-0.2.7.crate").is_file()); + assert!(registry.join("index/la/zy/lazy_static").is_file()); + assert!(!registry.join("index/la/zy/lazycell").is_file()); + assert!(registry.join("lazy_static-1.2.0.crate").is_file()); + assert!(!registry.join("lazycell-1.2.1.crate").is_file()); +} + fn run(cmd: &mut Command) -> String { let output = cmd.env("RUST_BACKTRACE", "1").output().unwrap(); if !output.status.success() {