Clean error msg when lockfile contains duplicate packages.
authorboxdot <d@zerovolt.org>
Mon, 28 May 2018 17:02:42 +0000 (19:02 +0200)
committerboxdot <d@zerovolt.org>
Mon, 28 May 2018 17:02:42 +0000 (19:02 +0200)
src/cargo/ops/lockfile.rs
tests/testsuite/generate_lockfile.rs

index d90e5f4084868506d11882f310c05921e7ff8c84..c3b648c2e25c62bfdb36dbc8ad14f1a3ee87690a 100644 (file)
@@ -1,12 +1,13 @@
+use std::collections::HashSet;
 use std::io::prelude::*;
 
 use toml;
 
-use core::{resolver, Resolve, Workspace};
 use core::resolver::WorkspaceResolve;
-use util::Filesystem;
-use util::errors::{CargoResult, CargoResultExt};
+use core::{resolver, Resolve, Workspace};
+use util::errors::{CargoResult, CargoResultExt, Internal};
 use util::toml as cargo_toml;
+use util::Filesystem;
 
 pub fn load_pkg_lockfile(ws: &Workspace) -> CargoResult<Option<Resolve>> {
     if !ws.root().join("Cargo.lock").exists() {
@@ -25,11 +26,33 @@ pub fn load_pkg_lockfile(ws: &Workspace) -> CargoResult<Option<Resolve>> {
             let resolve: toml::Value = cargo_toml::parse(&s, f.path(), ws.config())?;
             let v: resolver::EncodableResolve = resolve.try_into()?;
             Ok(Some(v.into_resolve(ws)?))
-        })()
-            .chain_err(|| format!("failed to parse lock file at: {}", f.path().display()))?;
+        })().chain_err(|| format!("failed to parse lock file at: {}", f.path().display()))?;
     Ok(resolve)
 }
 
+fn duplicate_pkg_names(resolve: &Resolve) -> Vec<&'static str> {
+    let mut unique_names = HashSet::new();
+    let mut result = HashSet::new();
+    for pkg_id in resolve.iter() {
+        if !unique_names.insert(pkg_id.name()) {
+            result.insert(pkg_id.name().as_str());
+        }
+    }
+    result.into_iter().collect()
+}
+
+fn check_duplicate_pkg_names(resolve: &Resolve) -> Result<(), Internal> {
+    let names = duplicate_pkg_names(resolve);
+    if names.is_empty() {
+        Ok(())
+    } else {
+        Err(Internal::new(format_err!(
+            "dependencies contain duplicate package(s): {}",
+            names.join(", ")
+        )))
+    }
+}
+
 pub fn write_pkg_lockfile(ws: &Workspace, resolve: &Resolve) -> CargoResult<()> {
     // Load the original lockfile if it exists.
     let ws_root = Filesystem::new(ws.root().to_path_buf());
@@ -40,6 +63,8 @@ pub fn write_pkg_lockfile(ws: &Workspace, resolve: &Resolve) -> CargoResult<()>
         Ok(s)
     });
 
+    check_duplicate_pkg_names(resolve).chain_err(|| format!("failed to generate lock file"))?;
+
     let toml = toml::Value::try_from(WorkspaceResolve { ws, resolve }).unwrap();
 
     let mut out = String::new();
index 6018904f03fab97c09e20282b7d9996103416e29..398722a1333d72d82b0d8eb7cbf6314d071c0ebf 100644 (file)
@@ -1,8 +1,8 @@
 use std::fs::{self, File};
 use std::io::prelude::*;
 
-use cargotest::support::{execs, project};
 use cargotest::support::registry::Package;
+use cargotest::support::{execs, project, ProjectBuilder, paths};
 use cargotest::ChannelChanger;
 use hamcrest::{assert_that, existing_file, is_not};
 
@@ -250,3 +250,59 @@ fn cargo_update_generate_lockfile() {
     assert_that(p.cargo("update"), execs().with_status(0).with_stdout(""));
     assert_that(&lockfile, existing_file());
 }
+
+#[test]
+fn duplicate_entries_in_lockfile() {
+    let _a = ProjectBuilder::new("a", paths::root().join("a"))
+        .file(
+            "Cargo.toml",
+            r#"
+            [package]
+            name = "a"
+            authors = []
+            version = "0.0.1"
+
+            [dependencies]
+            common = {path="common"}
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    let common_toml = r#"
+        [package]
+        name = "common"
+        authors = []
+        version = "0.0.1"
+        "#;
+
+    let _common_in_a = ProjectBuilder::new("common", paths::root().join("a/common"))
+        .file("Cargo.toml", common_toml)
+        .file("src/lib.rs", "")
+        .build();
+
+    let b = ProjectBuilder::new("common", paths::root().join("b"))
+        .file(
+            "Cargo.toml",
+            r#"
+            [package]
+            name = "b"
+            authors = []
+            version = "0.0.1"
+
+            [dependencies]
+            common = {path="common"}
+            a = {path="../a"}
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    let _common_in_b = ProjectBuilder::new("common", paths::root().join("b/common"))
+        .file("Cargo.toml", common_toml)
+        .file("src/lib.rs", "")
+        .build();
+
+    // should fail due to a duplicate package `common` in the lockfile
+    assert_that(b.cargo("build"), execs().with_status(101));
+}