diff --git a/crate2nix/src/resolve.rs b/crate2nix/src/resolve.rs index 4153c83e..97c4c322 100644 --- a/crate2nix/src/resolve.rs +++ b/crate2nix/src/resolve.rs @@ -773,27 +773,58 @@ impl<'a> ResolvedDependencies<'a> { let resolved = resolved_packages_by_crate_name .get(&name) .and_then(|packages| { - let exact_match = packages + let matches = packages .iter() - .find(|p| package_dep.req.matches(&p.version)); + .filter(|p| package_dep.req.matches(&p.version)) + .collect::>(); // Strip prerelease/build info from versions if we // did not find an exact match. // // E.g. "*" does not match a prerelease version in this // library but cargo thinks differently. - - exact_match.or_else(|| { - packages.iter().find(|p| { - let without_metadata = { - let mut version = p.version.clone(); - version.pre = semver::Prerelease::EMPTY; - version.build = semver::BuildMetadata::EMPTY; - version - }; - package_dep.req.matches(&without_metadata) - }) - }) + let matches = if matches.is_empty() { + packages + .iter() + .filter(|p| { + let without_metadata = { + let mut version = p.version.clone(); + version.pre = semver::Prerelease::EMPTY; + version.build = semver::BuildMetadata::EMPTY; + version + }; + package_dep.req.matches(&without_metadata) + }) + .collect() + } else { + matches + }; + + // It is possible to have multiple packages that match the name and version + // requirement of the dependency. In particular if there are multiple + // dependencies on the same package via git at different revisions - in + // that case `package_dep.req` is set to `*` so we can't use the version + // requirement to match the appropriate locked package with the dependency. + // Instead it's necessary to compare by source instead. + let matches = if matches.len() > 1 { + matches + .into_iter() + .filter(|p| { + sources_match(package_dep.source.as_deref(), p.source.as_ref()) + .unwrap_or(false) + }) + .collect() + } else { + matches + }; + + if matches.len() == 1 { + Some(matches[0]) + } else if matches.is_empty() { + None + } else { + panic!("Could not find an unambiguous package match for dependency, {}. Candidates are: {}", &package_dep.name, matches.iter().map(|p| &p.id).join(", ")); + } }); let dep_package = resolved?; @@ -815,6 +846,35 @@ impl<'a> ResolvedDependencies<'a> { } } +fn sources_match( + dependency_source: Option<&str>, + package_source: Option<&Source>, +) -> Result { + let Some(dependency_source) = dependency_source else { + return Ok(package_source.is_none()); + }; + let Some(package_source) = package_source else { + return Ok(false); // fail if dependency has a source, but package does not + }; + + let dependency = Url::parse(dependency_source)?; + let package = Url::parse(&package_source.repr)?; + + let scheme_matches = dependency.scheme() == package.scheme(); + let domain_matches = dependency.domain() == package.domain(); + let path_matches = dependency.path() == package.path(); + let query_matches = { + let package_query = package.query_pairs().collect::>(); + dependency.query_pairs().all(|(key, dep_value)| { + package_query + .get(&key) + .is_some_and(|pkg_value| &dep_value == pkg_value) + }) + }; + + Ok(scheme_matches && domain_matches && path_matches && query_matches) +} + /// Converts one type into another by serializing/deserializing it. /// /// Therefore, the output json of `I` must be deserializable to `O`. diff --git a/sample_projects/aliased-dependencies/Cargo.lock b/sample_projects/aliased-dependencies/Cargo.lock new file mode 100644 index 00000000..0971679d --- /dev/null +++ b/sample_projects/aliased-dependencies/Cargo.lock @@ -0,0 +1,26 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aliased-dependencies" +version = "0.1.0" +dependencies = [ + "ludndev-hello-world 0.0.1", + "ludndev-hello-world 0.1.0", + "ludndev-hello-world 0.1.1", +] + +[[package]] +name = "ludndev-hello-world" +version = "0.0.1" + +[[package]] +name = "ludndev-hello-world" +version = "0.1.0" +source = "git+https://github.com/ludndev/rustlang-hello-world-lib?tag=v0.1.0#ae5c3b410bae4e112fb38f4c13a0a906ceebf556" + +[[package]] +name = "ludndev-hello-world" +version = "0.1.1" +source = "git+https://github.com/ludndev/rustlang-hello-world-lib.git?tag=v0.1.1#f39a9a751058c83ac1059db18a1307a6bdfa9c33" diff --git a/sample_projects/aliased-dependencies/Cargo.toml b/sample_projects/aliased-dependencies/Cargo.toml new file mode 100644 index 00000000..111ab16e --- /dev/null +++ b/sample_projects/aliased-dependencies/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "aliased-dependencies" +version = "0.1.0" +edition = "2021" + +[dependencies] +hello = { package = "ludndev-hello-world", git = "https://github.com/ludndev/rustlang-hello-world-lib.git", tag = "v0.1.1" } +hello_v010 = { package = "ludndev-hello-world", git = "https://github.com/ludndev/rustlang-hello-world-lib", tag = "v0.1.0" } +hello_path = { package = "ludndev-hello-world", path = "./hello" } diff --git a/sample_projects/aliased-dependencies/hello/Cargo.lock b/sample_projects/aliased-dependencies/hello/Cargo.lock new file mode 100644 index 00000000..ee1957d1 --- /dev/null +++ b/sample_projects/aliased-dependencies/hello/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ludndev-hello-world" +version = "0.0.1" diff --git a/sample_projects/aliased-dependencies/hello/Cargo.toml b/sample_projects/aliased-dependencies/hello/Cargo.toml new file mode 100644 index 00000000..4d50c32c --- /dev/null +++ b/sample_projects/aliased-dependencies/hello/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "ludndev-hello-world" +version = "0.0.1" +edition = "2021" diff --git a/sample_projects/aliased-dependencies/hello/src/lib.rs b/sample_projects/aliased-dependencies/hello/src/lib.rs new file mode 100644 index 00000000..6eea7f32 --- /dev/null +++ b/sample_projects/aliased-dependencies/hello/src/lib.rs @@ -0,0 +1,3 @@ +pub fn greet() -> String { + "hello".to_string() +} diff --git a/sample_projects/aliased-dependencies/src/main.rs b/sample_projects/aliased-dependencies/src/main.rs new file mode 100644 index 00000000..c7c6fdcb --- /dev/null +++ b/sample_projects/aliased-dependencies/src/main.rs @@ -0,0 +1,5 @@ +pub fn main() { + println!("{}", hello::greet("")); + println!("{}", hello_v010::greet("")); + println!("{}", hello_path::greet()); +} diff --git a/tests.nix b/tests.nix index b244714b..3f813a9f 100644 --- a/tests.nix +++ b/tests.nix @@ -527,6 +527,12 @@ let # # FIXME: https://github.com/nix-community/crate2nix/issues/319 skip = true; } + + { + name = "aliased_dependencies"; + src = ./sample_projects/aliased-dependencies; + expectedOutput = "Hello World !\nHello World !"; + } ]; buildTestDerivationAttrSet = let