use std::{
borrow::Cow, collections::BTreeMap, marker::PhantomData, path::Path, str::FromStr as _,
sync::Arc,
};
use ahash::HashMapExt;
use indexmap::IndexSet;
use pep440_rs::VersionSpecifiers;
use rattler_conda_types::{PackageName, VersionWithSource};
use serde::{Deserialize, Deserializer, de::Error};
use serde_with::{DeserializeAs, serde_as};
use serde_yaml::Value;
use crate::{
Channel, CondaBinaryData, CondaPackageData, CondaSourceData, EnvironmentData, EnvironmentIndex,
EnvironmentPackages, LockFile, LockFileInner, LockedPackage, PackageHandle, PackageHashes,
PackageIndex, ParseCondaLockError, PlatformIndex, PypiDistributionData, PypiIndexes,
PypiPackageData, PypiSourceData, SelectorId, SelectorKind, SolveOptions, SourceData,
SourceIdentifier, UrlOrPath, Verbatim,
file_format_version::FileFormatVersion,
parse::{
V5, V6, V7,
models::{self, legacy::LegacyCondaPackageData, v6, v7, v7::PackageSelector},
},
platform::{ParsePlatformError, PlatformData, PlatformName},
};
#[serde_as]
#[derive(Deserialize)]
#[serde(bound(deserialize = "P: DeserializeAs<'de, PackageData>"))]
struct DeserializableLockFile<P> {
#[serde(default)]
platforms: Vec<DeserializablePlatformData>,
environments: BTreeMap<String, DeserializableEnvironment>,
#[serde_as(as = "Vec<P>")]
packages: Vec<PackageData>,
#[serde(skip)]
_data: PhantomData<P>,
}
#[serde_as]
#[derive(Deserialize)]
#[serde(bound(deserialize = "P: DeserializeAs<'de, LegacyPackageData>"))]
struct DeserializableLockFileLegacy<P> {
environments: BTreeMap<String, LegacyEnvironment>,
#[serde_as(as = "Vec<P>")]
packages: Vec<LegacyPackageData>,
#[serde(skip)]
_data: PhantomData<P>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct DeserializablePlatformData {
name: String,
#[serde(default)]
subdir: Option<String>,
#[serde(default)]
virtual_packages: Vec<String>,
}
impl TryFrom<&DeserializablePlatformData> for PlatformData {
type Error = ParsePlatformError;
fn try_from(value: &DeserializablePlatformData) -> Result<Self, Self::Error> {
let subdir = value.subdir.as_ref().map_or_else(
|| rattler_conda_types::Platform::from_str(&value.name),
|s| rattler_conda_types::Platform::from_str(s),
)?;
Ok(Self {
name: PlatformName::try_from(value.name.clone())?,
subdir,
virtual_packages: value.virtual_packages.clone(),
})
}
}
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct PypiPackageDataRaw {
pub name: pep508_rs::PackageName,
pub version: Option<pep440_rs::Version>,
pub location: Verbatim<UrlOrPath>,
pub hash: Option<PackageHashes>,
pub index_url: Option<url::Url>,
pub requires_dist: Vec<String>,
pub requires_python: Option<VersionSpecifiers>,
}
impl From<PypiPackageData> for PypiPackageDataRaw {
fn from(value: PypiPackageData) -> Self {
match value {
PypiPackageData::Distribution(w) => Self {
name: w.name,
version: Some(w.version),
location: w.location,
hash: w.hash,
index_url: w.index_url,
requires_dist: w.requires_dist.iter().map(ToString::to_string).collect(),
requires_python: w.requires_python,
},
PypiPackageData::Source(s) => Self {
name: s.name,
version: None,
location: s.location,
hash: None,
index_url: None,
requires_dist: s.requires_dist.iter().map(ToString::to_string).collect(),
requires_python: s.requires_python,
},
}
}
}
#[allow(clippy::large_enum_variant)]
enum PackageData {
Conda(CondaBinaryData),
CondaSource {
data: CondaSourceData,
build_packages: Vec<PackageSelector>,
host_packages: Vec<PackageSelector>,
},
Pypi {
data: PypiPackageDataRaw,
build_packages: Vec<PackageSelector>,
host_packages: Vec<PackageSelector>,
},
}
#[allow(clippy::large_enum_variant)]
enum LegacyPackageData {
Conda(LegacyCondaPackageData),
Pypi(PypiPackageDataRaw),
}
#[derive(Deserialize)]
struct DeserializableEnvironment {
channels: Vec<Channel>,
#[serde(flatten)]
indexes: Option<PypiIndexes>,
#[serde(default)]
options: SolveOptions,
packages: BTreeMap<String, Vec<DeserializablePackageSelector>>,
}
#[derive(Deserialize)]
struct LegacyEnvironment {
channels: Vec<Channel>,
#[serde(flatten)]
indexes: Option<PypiIndexes>,
#[serde(default)]
options: SolveOptions,
packages: BTreeMap<rattler_conda_types::Platform, Vec<LegacyPackageSelector>>,
}
impl<'de> DeserializeAs<'de, LegacyPackageData> for V5 {
fn deserialize_as<D>(deserializer: D) -> Result<LegacyPackageData, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
enum Inner<'d> {
Conda(models::v5::CondaPackageDataModel<'d>),
Pypi(models::v5::PypiPackageDataModel<'d>),
}
Ok(match Inner::deserialize(deserializer)? {
Inner::Conda(c) => LegacyPackageData::Conda(c.into()),
Inner::Pypi(p) => LegacyPackageData::Pypi(p.into()),
})
}
}
impl<'de> DeserializeAs<'de, LegacyPackageData> for V6 {
fn deserialize_as<D>(deserializer: D) -> Result<LegacyPackageData, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Discriminant {
Conda {
#[serde(rename = "conda")]
_conda: String,
},
Pypi {
#[serde(rename = "pypi")]
_pypi: String,
},
}
let value = serde_value::Value::deserialize(deserializer)?;
let Ok(discriminant) = Discriminant::deserialize(
serde_value::ValueDeserializer::<D::Error>::new(value.clone()),
) else {
return Err(D::Error::custom(
"expected at least `conda` or `pypi` field",
));
};
let deserializer = serde_value::ValueDeserializer::<D::Error>::new(value);
Ok(match discriminant {
Discriminant::Conda { .. } => LegacyPackageData::Conda(
v6::CondaPackageDataModel::deserialize(deserializer)?
.try_into()
.map_err(D::Error::custom)?,
),
Discriminant::Pypi { .. } => {
LegacyPackageData::Pypi(v6::PypiPackageDataModel::deserialize(deserializer)?.into())
}
})
}
}
impl<'de> DeserializeAs<'de, PackageData> for V7 {
fn deserialize_as<D>(deserializer: D) -> Result<PackageData, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Discriminant {
Conda {
#[serde(rename = "conda")]
_conda: String,
},
Source {
#[serde(rename = "conda_source")]
_source: String,
},
Pypi {
#[serde(rename = "pypi")]
_pypi: String,
},
}
let value = serde_value::Value::deserialize(deserializer)?;
let Ok(discriminant) = Discriminant::deserialize(
serde_value::ValueDeserializer::<D::Error>::new(value.clone()),
) else {
return Err(D::Error::custom(
"expected at least `conda`, `conda_source`, or `pypi` field",
));
};
let deserializer = serde_value::ValueDeserializer::<D::Error>::new(value);
Ok(match discriminant {
Discriminant::Conda { .. } => PackageData::Conda(
v7::CondaPackageDataModel::deserialize(deserializer)?
.try_into()
.map_err(D::Error::custom)?,
),
Discriminant::Source { .. } => {
let mut model = v7::SourcePackageDataModel::deserialize(deserializer)?;
let build_packages = std::mem::take(&mut model.build_packages);
let host_packages = std::mem::take(&mut model.host_packages);
let (_identifier, data) = model.into_parts().map_err(D::Error::custom)?;
PackageData::CondaSource {
data,
build_packages,
host_packages,
}
}
Discriminant::Pypi { .. } => {
let mut model = v7::PypiPackageDataModel::deserialize(deserializer)?;
let build_packages = std::mem::take(&mut model.build_packages);
let host_packages = std::mem::take(&mut model.host_packages);
PackageData::Pypi {
data: model.into(),
build_packages,
host_packages,
}
}
})
}
}
#[derive(Deserialize)]
#[serde(untagged, rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
enum DeserializablePackageSelector {
Conda {
conda: UrlOrPath,
},
CondaSource {
conda_source: SourceIdentifier,
},
Pypi {
pypi: Verbatim<UrlOrPath>,
},
}
#[derive(Deserialize)]
#[serde(untagged, rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
enum LegacyPackageSelector {
Conda {
conda: UrlOrPath,
name: Option<PackageName>,
version: Option<VersionWithSource>,
build: Option<String>,
subdir: Option<String>,
},
Pypi {
pypi: Verbatim<UrlOrPath>,
},
}
pub fn parse_from_document_v5(
document: Value,
version: FileFormatVersion,
) -> Result<LockFile, ParseCondaLockError> {
let raw: DeserializableLockFileLegacy<V5> =
serde_yaml::from_value(document).map_err(ParseCondaLockError::ParseError)?;
parse_from_lock_legacy(version, raw, None)
}
pub fn parse_from_document_v6(
document: Value,
base_dir: Option<&Path>,
) -> Result<LockFile, ParseCondaLockError> {
let raw: DeserializableLockFileLegacy<V6> =
serde_yaml::from_value(document).map_err(ParseCondaLockError::ParseError)?;
parse_from_lock_legacy(FileFormatVersion::V6, raw, base_dir)
}
pub fn parse_from_document_v7(
document: Value,
base_dir: Option<&Path>,
) -> Result<LockFile, ParseCondaLockError> {
let raw: DeserializableLockFile<V7> =
serde_yaml::from_value::<DeserializableLockFile<V7>>(document)
.map_err(ParseCondaLockError::ParseError)?;
parse_from_lock(FileFormatVersion::V7, raw, base_dir)
}
fn is_wheel_or_archive_location(location: &UrlOrPath) -> bool {
let archives = [
".whl", ".tar.gz", ".tar.bz2", ".tar.xz", ".tar.Z", ".tgz", ".tbz", ".tar", ".zip",
];
match location {
UrlOrPath::Url(url) => {
if url.scheme() == "file" {
let path = url.path();
archives.iter().any(|a| path.ends_with(a))
} else {
true
}
}
UrlOrPath::Path(path) => archives.iter().any(|a| path.to_string().ends_with(a)),
}
}
fn convert_raw_pypi_package(
file_version: FileFormatVersion,
raw_package: PypiPackageDataRaw,
base_dir: Option<&Path>,
) -> Result<PypiPackageData, ParseCondaLockError> {
let requires_dist = raw_package
.requires_dist
.iter()
.map(|input| {
if let Some(base_dir) = base_dir {
pep508_rs::Requirement::parse(input, base_dir)
} else {
use std::str::FromStr as _;
pep508_rs::Requirement::from_str(input)
}
})
.collect::<Result<Vec<_>, _>>()?;
let location = if file_version < FileFormatVersion::V7 {
Verbatim::new(raw_package.location.take())
} else {
raw_package.location
};
let is_distribution = if file_version >= FileFormatVersion::V7 {
raw_package.hash.is_some()
|| raw_package.version.is_some()
|| raw_package.index_url.is_some()
} else {
is_wheel_or_archive_location(location.inner())
};
if is_distribution {
Ok(PypiPackageData::Distribution(Box::new(
PypiDistributionData {
name: raw_package.name,
version: raw_package
.version
.unwrap_or_else(|| pep440_rs::MIN_VERSION.clone()),
location,
index_url: raw_package.index_url,
hash: raw_package.hash,
requires_dist,
requires_python: raw_package.requires_python,
},
)))
} else {
Ok(PypiPackageData::Source(Box::new(PypiSourceData {
name: raw_package.name,
location,
requires_dist,
requires_python: raw_package.requires_python,
source_data: SourceData::default(),
})))
}
}
fn legacy_package_matches_selector(
package: &LegacyPackageData,
name: Option<&PackageName>,
version: Option<&VersionWithSource>,
build: Option<&str>,
subdir: Option<&str>,
) -> bool {
let LegacyPackageData::Conda(package) = package else {
return false;
};
let record = package.record();
name.is_none_or(|n| n == &record.name)
&& version.is_none_or(|v| v == &record.version)
&& build.is_none_or(|b| b == record.build)
&& subdir.is_none_or(|s| s == record.subdir)
}
#[allow(clippy::too_many_arguments)]
fn resolve_legacy_conda_package(
file_version: FileFormatVersion,
platform_name: &str,
name: Option<&PackageName>,
version: Option<&VersionWithSource>,
build: Option<&str>,
subdir: Option<&str>,
candidate_indices: &[PackageIndex],
packages: &mut Vec<LegacyPackageData>,
) -> Option<PackageIndex> {
if file_version >= FileFormatVersion::V6 {
return candidate_indices
.iter()
.find(|&&index| {
legacy_package_matches_selector(&packages[index.0], name, version, build, subdir)
})
.copied();
}
let packages_for_platform: Vec<_> = candidate_indices
.iter()
.filter(|&&index| {
if let LegacyPackageData::Conda(p) = &packages[index.0] {
p.record().subdir.as_str() == platform_name
} else {
false
}
})
.copied()
.collect();
let matching_indices = if packages_for_platform.is_empty() {
candidate_indices
} else {
&packages_for_platform
};
let mut iter = matching_indices.iter().copied();
let first_index = iter.next()?;
let acc = {
let p = &packages[first_index.0];
let LegacyPackageData::Conda(p) = &p else {
panic!("Internal error: Conda package expected, but not found");
};
Cow::Borrowed(p)
};
let merged = iter.fold(acc, |acc, next_idx| {
if let LegacyPackageData::Conda(p) = &packages[next_idx.0] {
match acc.merge(p) {
Cow::Owned(merged) => Cow::Owned(merged),
Cow::Borrowed(_) => acc,
}
} else {
acc
}
});
Some(match merged {
Cow::Borrowed(_) => first_index,
Cow::Owned(merged_package) => {
let index = PackageIndex(packages.len());
packages.push(LegacyPackageData::Conda(merged_package));
index
}
})
}
fn parse_from_lock_legacy<P>(
file_version: FileFormatVersion,
mut raw: DeserializableLockFileLegacy<P>,
base_dir: Option<&Path>,
) -> Result<LockFile, ParseCondaLockError> {
let platforms = create_legacy_platforms(&raw)?;
let (conda_url_lookup, pypi_url_lookup) = {
let mut tmp_conda: ahash::HashMap<UrlOrPath, Vec<_>> = ahash::HashMap::default();
let mut tmp_pypi: ahash::HashMap<UrlOrPath, PackageIndex> = ahash::HashMap::default();
for (index, package) in raw.packages.iter().enumerate() {
match package {
LegacyPackageData::Conda(p) => {
tmp_conda
.entry(p.location().clone())
.or_default()
.push(PackageIndex(index));
}
LegacyPackageData::Pypi(p) => {
tmp_pypi.insert((*p.location).clone(), PackageIndex(index));
}
}
}
(tmp_conda, tmp_pypi)
};
let environments_raw = raw
.environments
.into_iter()
.map(|(env_name, env)| {
Ok((
env_name.clone(),
env.channels,
env.indexes,
env.options,
env.packages
.into_iter()
.map(|(platform, packages)| {
let platform_name = platform.to_string();
let Some((platform_index, _)) = platforms
.iter()
.enumerate()
.find(|(_, p)| p.name.as_str() == platform_name.as_str())
else {
return Err(ParseCondaLockError::UnknownPlatform {
environment: env_name.clone(),
platform: platform_name,
});
};
let platform_index = PlatformIndex(platform_index);
let indices = packages
.into_iter()
.map(|p| {
Ok::<PackageIndex, ParseCondaLockError>(match p {
LegacyPackageSelector::Conda {
conda,
name,
version,
build,
subdir,
} => {
let candidate_indices = conda_url_lookup
.get(&conda)
.map_or(&[] as &[PackageIndex], Vec::as_slice);
let package_index = resolve_legacy_conda_package(
file_version,
platform.as_str(),
name.as_ref(),
version.as_ref(),
build.as_deref(),
subdir.as_deref(),
candidate_indices,
&mut raw.packages,
);
package_index.ok_or_else(|| {
ParseCondaLockError::MissingPackage {
environment: env_name.clone(),
platform: platform_name.clone(),
location: conda.to_string(),
}
})?
}
LegacyPackageSelector::Pypi { pypi } => *pypi_url_lookup
.get(&pypi)
.ok_or_else(|| ParseCondaLockError::MissingPackage {
environment: env_name.clone(),
platform: platform_name.clone(),
location: pypi.inner().to_string(),
})?,
})
})
.collect::<Result<IndexSet<PackageIndex>, ParseCondaLockError>>()?;
Ok((platform_index, indices))
})
.collect::<Result<Vec<_>, ParseCondaLockError>>()?,
))
})
.collect::<Result<Vec<_>, ParseCondaLockError>>()?;
let built_packages = raw
.packages
.into_iter()
.map(|p| match p {
LegacyPackageData::Conda(p) => Ok(LockedPackage::Conda(p.into())),
LegacyPackageData::Pypi(p) => {
convert_raw_pypi_package(file_version, p, base_dir).map(LockedPackage::Pypi)
}
})
.collect::<Result<Vec<_>, _>>()?;
let environments = environments_raw
.into_iter()
.map(
|(env_name, channels, indexes, options, packages_per_platform)| {
let packages = packages_per_platform
.into_iter()
.map(|(platform_index, indices)| {
Ok((
platform_index,
EnvironmentPackages::from_indices(indices, &built_packages)?,
))
})
.collect::<Result<_, ParseCondaLockError>>()?;
Ok::<_, ParseCondaLockError>((
env_name,
EnvironmentData {
channels,
indexes,
options,
packages,
},
))
},
)
.collect::<Result<BTreeMap<_, _>, ParseCondaLockError>>()?;
let (environment_lookup, environments) = environments
.into_iter()
.enumerate()
.map(|(index, (name, env))| ((name, EnvironmentIndex(index)), env))
.unzip();
let packages = built_packages;
Ok(LockFile {
inner: Arc::new(LockFileInner {
version: file_version,
platforms,
environments,
environment_lookup,
packages,
}),
})
}
fn create_legacy_platforms<P>(
raw: &DeserializableLockFileLegacy<P>,
) -> Result<Vec<PlatformData>, ParseCondaLockError> {
let mut unique_platforms = ahash::HashSet::default();
raw.environments
.iter()
.flat_map(|(env_name, env)| {
env.packages
.keys()
.map(move |platform| (env_name, platform))
})
.filter(move |(_, platform)| unique_platforms.insert(*platform))
.map(|(_, subdir)| -> Result<_, ParseCondaLockError> {
let name = PlatformName::try_from(subdir.as_str())?;
Ok(PlatformData {
name,
subdir: *subdir,
virtual_packages: Vec::new(),
})
})
.collect::<Result<Vec<_>, _>>()
}
fn read_platforms<P>(
raw: &DeserializableLockFile<P>,
) -> Result<Vec<PlatformData>, ParseCondaLockError> {
let mut unique_platforms = ahash::HashSet::default();
raw.platforms
.iter()
.map(PlatformData::try_from)
.map(move |platform| match platform {
Ok(platform) => {
if unique_platforms.insert(platform.name.clone()) {
Ok(platform)
} else {
Err(ParseCondaLockError::DuplicatePlatformName(
platform.name.to_string(),
))
}
}
Err(e) => Err(e.into()),
})
.collect::<Result<Vec<_>, _>>()
}
fn parse_from_lock<P>(
file_version: FileFormatVersion,
raw: DeserializableLockFile<P>,
base_dir: Option<&Path>,
) -> Result<LockFile, ParseCondaLockError> {
let platforms = read_platforms(&raw)?;
let (mut packages, pending_source_data) = {
let num_packages = raw.packages.len();
let mut packages = Vec::with_capacity(num_packages);
let mut pending_source_data: Vec<(
PackageIndex,
Vec<PackageSelector>,
Vec<PackageSelector>,
)> = Vec::new();
for (index, package) in raw.packages.into_iter().enumerate() {
let index = PackageIndex(index);
let package = match package {
PackageData::Conda(binary) => Ok(LockedPackage::Conda(CondaPackageData::Binary(
Box::new(binary),
))),
PackageData::CondaSource {
data,
build_packages,
host_packages,
} => {
if !build_packages.is_empty() || !host_packages.is_empty() {
pending_source_data.push((index, build_packages, host_packages));
}
Ok(LockedPackage::Conda(CondaPackageData::Source(Box::new(
data,
))))
}
PackageData::Pypi {
data,
build_packages,
host_packages,
} => {
if !build_packages.is_empty() || !host_packages.is_empty() {
pending_source_data.push((index, build_packages, host_packages));
}
convert_raw_pypi_package(file_version, data, base_dir).map(LockedPackage::Pypi)
}
}?;
packages.push(package);
}
Ok::<_, ParseCondaLockError>((packages, pending_source_data))
}?;
let mut selector_index: Vec<(SelectorId, PackageIndex)> = packages
.iter()
.enumerate()
.map(|(i, pkg)| (SelectorId::new(pkg), PackageIndex(i)))
.collect();
selector_index.sort_by(|a, b| a.0.cmp(&b.0));
let resolve_index = |selector: &PackageSelector| -> Result<PackageIndex, ParseCondaLockError> {
let id = selector.to_selector_id();
selector_index
.binary_search_by(|(sel, _)| sel.cmp(&id))
.map(|pos| selector_index[pos].1)
.map_err(|_not_found| ParseCondaLockError::MissingPackage {
environment: String::new(),
platform: String::new(),
location: selector.id().to_owned(),
})
};
for (pkg_idx, build_selectors, host_selectors) in pending_source_data {
let (build_packages, host_packages) = {
let resolve =
|selector: &PackageSelector| -> Result<PackageHandle, ParseCondaLockError> {
let index = resolve_index(selector)?;
Ok(PackageHandle::new(index, &packages[index.0]))
};
let build = EnvironmentPackages::from_selector_ids(build_selectors, &resolve)?;
let host = EnvironmentPackages::from_selector_ids(host_selectors, &resolve)?;
(build, host)
};
let source_data = SourceData {
build_packages,
host_packages,
};
match &mut packages[pkg_idx.0] {
LockedPackage::Conda(CondaPackageData::Source(data)) => {
data.source_data = source_data;
}
LockedPackage::Pypi(PypiPackageData::Source(data)) => {
data.source_data = source_data;
}
_ => {
}
}
}
let num_environments = raw.environments.len();
let mut environments = Vec::with_capacity(num_environments);
let mut environment_lookup: ahash::HashMap<String, EnvironmentIndex> =
ahash::HashMap::with_capacity(num_environments);
for (env_name, env) in raw.environments {
let mut env_packages: ahash::HashMap<PlatformIndex, EnvironmentPackages> =
ahash::HashMap::with_capacity(env.packages.len());
for (platform_name, selectors) in env.packages {
let Some((platform_index, platform_data)) = platforms
.iter()
.enumerate()
.find(|(_, p)| p.name.as_str() == platform_name)
else {
return Err(ParseCondaLockError::UnknownPlatform {
environment: env_name.clone(),
platform: platform_name,
});
};
let platform_index = PlatformIndex(platform_index);
let mut resolved = IndexSet::with_capacity(selectors.len());
for selector in selectors {
let package_data = resolve_package_selector(
selector,
&env_name,
platform_data.subdir,
&selector_index,
)?;
resolved.insert(package_data);
}
if file_version >= FileFormatVersion::V7 {
let default_index = env
.indexes
.as_ref()
.and_then(|i| i.indexes.first())
.cloned()
.unwrap_or_else(|| {
url::Url::parse("https://pypi.org/simple").expect("valid hard-coded URL")
});
for pkg in &resolved {
if let LockedPackage::Pypi(p) = &mut packages[pkg.0]
&& let Some(w) = p.as_wheel_mut()
&& w.index_url.is_none()
{
w.index_url = Some(default_index.clone());
}
}
}
env_packages.insert(
platform_index,
EnvironmentPackages::from_indices(resolved, &packages)?,
);
}
environment_lookup.insert(env_name, EnvironmentIndex(environments.len()));
environments.push(EnvironmentData {
channels: env.channels,
indexes: env.indexes,
options: env.options,
packages: env_packages,
});
}
Ok(LockFile {
inner: Arc::new(LockFileInner {
version: file_version,
platforms,
environments,
environment_lookup,
packages,
}),
})
}
fn resolve_package_selector(
selector: DeserializablePackageSelector,
env_name: &str,
platform: rattler_conda_types::Platform,
selector_index: &[(SelectorId, PackageIndex)],
) -> Result<PackageIndex, ParseCondaLockError> {
let (kind, id) = match selector {
DeserializablePackageSelector::Conda { conda } => {
(SelectorKind::CondaBinary, conda.as_str().to_owned())
}
DeserializablePackageSelector::CondaSource {
conda_source: source,
} => (SelectorKind::CondaSource, source.to_string()),
DeserializablePackageSelector::Pypi { pypi } => {
let id = pypi
.given()
.map_or_else(|| pypi.inner().as_str().to_owned(), str::to_owned);
(SelectorKind::Pypi, id)
}
};
let full_id = SelectorId::from_parts(kind, &id);
selector_index
.binary_search_by(|(sel, _)| sel.cmp(&full_id))
.map(|pos| selector_index[pos].1)
.map_err(|_not_found| ParseCondaLockError::MissingPackage {
environment: env_name.to_owned(),
platform: platform.to_string(),
location: id,
})
}