From 3361b760375594d0ab3b4a5048fd69e3e279ffd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orhun=20Parmaks=C4=B1z?= Date: Mon, 29 Jan 2024 16:46:19 +0300 Subject: [PATCH] feat: check duplicate dependencies --- src/project/manifest/feature.rs | 48 ++++++++++++++++++++++++++++----- src/project/manifest/mod.rs | 46 ++++++++++++++++++++++++++----- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/project/manifest/feature.rs b/src/project/manifest/feature.rs index 0e8906d65..71e95c3d0 100644 --- a/src/project/manifest/feature.rs +++ b/src/project/manifest/feature.rs @@ -12,8 +12,9 @@ use serde::de::Error; use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; use std::borrow::{Borrow, Cow}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; +use std::str::FromStr; /// The name of a feature. This is either a string or default for the default feature. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] @@ -224,15 +225,15 @@ impl<'de> Deserialize<'de> for Feature { #[serde(default)] #[serde_as(as = "IndexMap<_, PickFirst<(DisplayFromStr, _)>>")] - dependencies: IndexMap, + dependencies: IndexMap, #[serde(default)] #[serde_as(as = "Option>>")] - host_dependencies: Option>, + host_dependencies: Option>, #[serde(default)] #[serde_as(as = "Option>>")] - build_dependencies: Option>, + build_dependencies: Option>, #[serde(default)] pypi_dependencies: Option>, @@ -248,12 +249,45 @@ impl<'de> Deserialize<'de> for Feature { let inner = FeatureInner::deserialize(deserializer)?; - let mut dependencies = HashMap::from_iter([(SpecType::Run, inner.dependencies)]); + // check duplicate dependencies (run, host, build) + let mut dependencies = inner.dependencies.clone(); + dependencies.extend(inner.host_dependencies.clone().unwrap_or_default()); + dependencies.extend(inner.build_dependencies.clone().unwrap_or_default()); + let mut dependency_map = HashSet::new(); + for package in dependencies.keys() { + let package_name = PackageName::from_str(package).map_err(serde::de::Error::custom)?; + if !dependency_map.insert(package_name) { + return Err(serde::de::Error::custom(&format!( + "duplicate dependency: {package}" + ))); + } + } + + let mut dependencies = HashMap::from_iter([( + SpecType::Run, + inner + .dependencies + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + )]); if let Some(host_deps) = inner.host_dependencies { - dependencies.insert(SpecType::Host, host_deps); + dependencies.insert( + SpecType::Host, + host_deps + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + ); } if let Some(build_deps) = inner.build_dependencies { - dependencies.insert(SpecType::Build, build_deps); + dependencies.insert( + SpecType::Build, + build_deps + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + ); } let default_target = Target { diff --git a/src/project/manifest/mod.rs b/src/project/manifest/mod.rs index eaa64a29b..d5170594b 100644 --- a/src/project/manifest/mod.rs +++ b/src/project/manifest/mod.rs @@ -24,6 +24,7 @@ use miette::{miette, Diagnostic, IntoDiagnostic, LabeledSpan, NamedSource}; pub use python::PyPiRequirement; use rattler_conda_types::{MatchSpec, NamelessMatchSpec, PackageName, Platform, Version}; use serde_with::{serde_as, DisplayFromStr, Map, PickFirst}; +use std::collections::HashSet; use std::hash::Hash; use std::{ collections::HashMap, @@ -767,15 +768,15 @@ impl<'de> Deserialize<'de> for ProjectManifest { // default_target: Target, #[serde(default)] #[serde_as(as = "IndexMap<_, PickFirst<(DisplayFromStr, _)>>")] - dependencies: IndexMap, + dependencies: IndexMap, #[serde(default)] #[serde_as(as = "Option>>")] - host_dependencies: Option>, + host_dependencies: Option>, #[serde(default)] #[serde_as(as = "Option>>")] - build_dependencies: Option>, + build_dependencies: Option>, #[serde(default)] pypi_dependencies: Option>, @@ -800,12 +801,45 @@ impl<'de> Deserialize<'de> for ProjectManifest { let toml_manifest = TomlProjectManifest::deserialize(deserializer)?; - let mut dependencies = HashMap::from_iter([(SpecType::Run, toml_manifest.dependencies)]); + // check duplicate dependencies (run, host, build) + let mut dependencies = toml_manifest.dependencies.clone(); + dependencies.extend(toml_manifest.host_dependencies.clone().unwrap_or_default()); + dependencies.extend(toml_manifest.build_dependencies.clone().unwrap_or_default()); + let mut dependency_map = HashSet::new(); + for package in dependencies.keys() { + let package_name = PackageName::from_str(package).map_err(serde::de::Error::custom)?; + if !dependency_map.insert(package_name) { + return Err(serde::de::Error::custom(&format!( + "duplicate dependency: {package}" + ))); + } + } + + let mut dependencies = HashMap::from_iter([( + SpecType::Run, + toml_manifest + .dependencies + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + )]); if let Some(host_deps) = toml_manifest.host_dependencies { - dependencies.insert(SpecType::Host, host_deps); + dependencies.insert( + SpecType::Host, + host_deps + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + ); } if let Some(build_deps) = toml_manifest.build_dependencies { - dependencies.insert(SpecType::Build, build_deps); + dependencies.insert( + SpecType::Build, + build_deps + .into_iter() + .flat_map(|(p, s)| PackageName::from_str(&p).ok().map(|p| (p, s))) + .collect(), + ); } let default_target = Target {