Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: lock-file API #942

Open
wants to merge 9 commits into
base: feat/pixi-build
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/rattler-bin/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> {
/// Prints the operations of the transaction to the console.
fn print_transaction(transaction: &Transaction<PrefixRecord, RepoDataRecord>) {
let format_record = |r: &RepoDataRecord| {
let direct_url_print = if r.clone().channel.is_empty() {
r.url.as_str()
let direct_url_print = if let Some(channel) = &r.channel {
channel.clone()
} else {
""
String::new()
};

format!(
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,6 @@ pub(crate) fn get_repodata_record(package_path: impl AsRef<std::path::Path>) ->
.unwrap()
.to_string(),
url: url::Url::from_file_path(package_path).unwrap(),
channel: "test".to_string(),
channel: Some(String::from("test")),
}
}
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/match_spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ mod tests {
),
file_name: String::from("mamba-1.0-py37_0"),
url: url::Url::parse("https://mamba.io/mamba-1.0-py37_0.conda").unwrap(),
channel: String::from("mamba"),
channel: Some(String::from("mamba")),
};
let package_record = repodata_record.clone().package_record;

Expand Down
3 changes: 1 addition & 2 deletions crates/rattler_conda_types/src/repo_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ impl RepoData {
/// given the source of the data.
pub fn into_repo_data_records(self, channel: &Channel) -> Vec<RepoDataRecord> {
let mut records = Vec::with_capacity(self.packages.len() + self.conda_packages.len());
let channel_name = channel.canonical_name();
let base_url = self.base_url().map(ToOwned::to_owned);

// Determine the base_url of the channel
Expand All @@ -241,7 +240,7 @@ impl RepoData {
base_url.as_deref(),
&filename,
),
channel: channel_name.clone(),
channel: Some(channel.base_url.as_str().to_string()),
package_record,
file_name: filename,
});
Expand Down
3 changes: 2 additions & 1 deletion crates/rattler_conda_types/src/repo_data_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub struct RepoDataRecord {
/// String representation of the channel where the package comes from. This could be a URL but
/// it could also be a channel name. Personally I would always add the complete URL here to be
/// explicit about where the package came from.
pub channel: String,
/// TODO: Refactor this into `Source` which can be a "name", "channelurl", or "direct url".
pub channel: Option<String>,
}

impl AsRef<PackageRecord> for RepoDataRecord {
Expand Down
63 changes: 51 additions & 12 deletions crates/rattler_lock/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,54 @@ use rattler_conda_types::Platform;

use crate::{
file_format_version::FileFormatVersion, Channel, CondaPackageData, EnvironmentData,
EnvironmentPackageData, LockFile, LockFileInner, Package, PypiIndexes, PypiPackageData,
PypiPackageEnvironmentData,
EnvironmentPackageData, LockFile, LockFileInner, LockedPackageRef, PypiIndexes,
PypiPackageData, PypiPackageEnvironmentData,
};

/// Information about a single locked package in an environment.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum LockedPackage {
/// A conda package
Conda(CondaPackageData),

/// A pypi package in an environment
Pypi(PypiPackageData, PypiPackageEnvironmentData),
}

impl From<LockedPackageRef<'_>> for LockedPackage {
fn from(value: LockedPackageRef<'_>) -> Self {
match value {
LockedPackageRef::Conda(data) => LockedPackage::Conda(data.clone()),
LockedPackageRef::Pypi(data, env) => LockedPackage::Pypi(data.clone(), env.clone()),
}
}
}

impl From<CondaPackageData> for LockedPackage {
fn from(value: CondaPackageData) -> Self {
LockedPackage::Conda(value)
}
}

impl LockedPackage {
/// Returns the conda package data if this is a conda package.
pub fn as_conda(&self) -> Option<&CondaPackageData> {
match self {
LockedPackage::Conda(data) => Some(data),
LockedPackage::Pypi(..) => None,
}
}

/// Returns the pypi package data if this is a pypi package.
pub fn as_pypi(&self) -> Option<(&PypiPackageData, &PypiPackageEnvironmentData)> {
match self {
LockedPackage::Conda(..) => None,
LockedPackage::Pypi(data, env) => Some((data, env)),
}
}
}

/// A struct to incrementally build a lock-file.
#[derive(Default)]
pub struct LockFileBuilder {
Expand Down Expand Up @@ -162,7 +206,7 @@ impl LockFileBuilder {
mut self,
environment: impl Into<String>,
platform: Platform,
locked_package: Package,
locked_package: LockedPackage,
) -> Self {
self.add_package(environment, platform, locked_package);
self
Expand All @@ -174,18 +218,13 @@ impl LockFileBuilder {
&mut self,
environment: impl Into<String>,
platform: Platform,
locked_package: Package,
locked_package: LockedPackage,
) -> &mut Self {
match locked_package {
Package::Conda(p) => {
self.add_conda_package(environment, platform, p.package_data().clone())
LockedPackage::Conda(p) => self.add_conda_package(environment, platform, p),
LockedPackage::Pypi(data, env_data) => {
self.add_pypi_package(environment, platform, data, env_data)
}
Package::Pypi(p) => self.add_pypi_package(
environment,
platform,
p.package_data().clone(),
p.environment_data().clone(),
),
}
}

Expand Down
151 changes: 111 additions & 40 deletions crates/rattler_lock/src/conda.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,106 @@
use std::{cmp::Ordering, hash::Hash};

use rattler_conda_types::{PackageRecord, RepoDataRecord};
use rattler_conda_types::{ChannelUrl, PackageRecord, RepoDataRecord};
use rattler_digest::Sha256Hash;
use url::Url;

use crate::UrlOrPath;

/// A locked conda dependency is just a [`PackageRecord`] with some additional
/// information on where it came from. It is very similar to a
/// [`RepoDataRecord`], but it does not explicitly contain the channel name.
/// A locked conda dependency can be either a binary package or a source
/// package.
///
/// A binary package is a package that is already built and can be installed
/// directly.
///
/// A source package is a package that needs to be built before it can
/// be installed. Although the source package is not built, it does contain
/// dependency information through the [`PackageRecord`] struct.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CondaPackageData {
/// A binary package. A binary package is identified by looking at the
/// location or filename of the package file and seeing if it represents a
/// valid binary package name.
Binary(CondaBinaryData),

/// A source package.
Source(CondaSourceData),
}

impl CondaPackageData {
/// Returns the location of the package.
pub fn location(&self) -> &UrlOrPath {
match self {
Self::Binary(data) => &data.location,
Self::Source(data) => &data.location,
}
}

/// Returns the dependency information of the package.
pub fn record(&self) -> &PackageRecord {
match self {
CondaPackageData::Binary(data) => &data.package_record,
CondaPackageData::Source(data) => &data.package_record,
}
}

/// Returns a reference to the binary representation of this instance if it
/// exists.
pub fn as_binary(&self) -> Option<&CondaBinaryData> {
match self {
Self::Binary(data) => Some(data),
Self::Source(_) => None,
}
}

/// Returns a reference to the source representation of this instance if it
/// exists.
pub fn as_source(&self) -> Option<&CondaSourceData> {
match self {
Self::Binary(_) => None,
Self::Source(data) => Some(data),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CondaPackageData {
pub struct CondaBinaryData {
/// The package record.
pub package_record: PackageRecord,

/// The location of the package. This can be a URL or a local path.
pub location: UrlOrPath,

/// The filename of the package.
pub file_name: Option<String>,
pub file_name: String,

/// The channel of the package.
pub channel: Option<ChannelUrl>,
}

/// The channel of the package if this cannot be derived from the url.
pub channel: Option<Url>,
impl From<CondaBinaryData> for CondaPackageData {
fn from(value: CondaBinaryData) -> Self {
Self::Binary(value)
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CondaSourceData {
/// The package record.
pub package_record: PackageRecord,

/// The location of the package. This can be a URL or a local path.
pub location: UrlOrPath,

/// The input hash of the package (only valid for source packages)
/// The input hash of the package
pub input: Option<InputHash>,
}

impl From<CondaSourceData> for CondaPackageData {
fn from(value: CondaSourceData) -> Self {
Self::Source(value)
}
}

/// A record of input files that were used to define the metadata of the
/// package.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
Expand All @@ -40,7 +114,10 @@ pub struct InputHash {

impl AsRef<PackageRecord> for CondaPackageData {
fn as_ref(&self) -> &PackageRecord {
&self.package_record
match self {
Self::Binary(data) => &data.package_record,
Self::Source(data) => &data.package_record,
}
}
}

Expand All @@ -52,58 +129,52 @@ impl PartialOrd<Self> for CondaPackageData {

impl Ord for CondaPackageData {
fn cmp(&self, other: &Self) -> Ordering {
self.location
.cmp(&other.location)
.then_with(|| self.package_record.name.cmp(&other.package_record.name))
.then_with(|| {
self.package_record
.version
.cmp(&other.package_record.version)
})
.then_with(|| self.package_record.build.cmp(&other.package_record.build))
.then_with(|| self.package_record.subdir.cmp(&other.package_record.subdir))
let pkg_a: &PackageRecord = self.as_ref();
let pkg_b: &PackageRecord = other.as_ref();
let location_a = self.location();
let location_b = other.location();

location_a
.cmp(location_b)
.then_with(|| pkg_a.name.cmp(&pkg_b.name))
.then_with(|| pkg_a.version.cmp(&pkg_b.version))
.then_with(|| pkg_a.build.cmp(&pkg_b.build))
.then_with(|| pkg_a.subdir.cmp(&pkg_b.subdir))
}
}

impl From<RepoDataRecord> for CondaPackageData {
fn from(value: RepoDataRecord) -> Self {
let location = UrlOrPath::from(value.url).normalize().into_owned();
Self {
Self::Binary(CondaBinaryData {
package_record: value.package_record,
file_name: Some(value.file_name),
channel: Url::parse(&value.channel).ok(),
file_name: value.file_name,
channel: value
.channel
.and_then(|channel| Url::parse(&channel).ok())
.map(Into::into),
location,
input: None,
}
})
}
}

impl TryFrom<&CondaPackageData> for RepoDataRecord {
impl TryFrom<&CondaBinaryData> for RepoDataRecord {
type Error = ConversionError;

fn try_from(value: &CondaPackageData) -> Result<Self, Self::Error> {
fn try_from(value: &CondaBinaryData) -> Result<Self, Self::Error> {
Self::try_from(value.clone())
}
}

impl TryFrom<CondaPackageData> for RepoDataRecord {
impl TryFrom<CondaBinaryData> for RepoDataRecord {
type Error = ConversionError;

fn try_from(value: CondaPackageData) -> Result<Self, Self::Error> {
// Determine the channel and file name based on the url stored in the record.
let channel = value
.channel
.map_or_else(String::default, |url| url.to_string());

let file_name = value
.file_name
.ok_or_else(|| ConversionError::Missing("file name".to_string()))?;

fn try_from(value: CondaBinaryData) -> Result<Self, Self::Error> {
Ok(Self {
package_record: value.package_record,
file_name,
file_name: value.file_name,
url: value.location.try_into_url()?,
channel,
channel: value.channel.map(|channel| channel.to_string()),
})
}
}
Expand Down
Loading