Skip to content

Commit 32eefc8

Browse files
baszalmstratdejagernichmor
authored
feat: merge pixi-build branch (#950)
* feat: lock file update for source dependencies (#866) Co-authored-by: Bas Zalmstra <zalmstra.bas@gmail.com> Co-authored-by: Bas Zalmstra <bas@prefix.dev> * feat: add input hash to conda package * feat: add input hash/glob to lock file (#868) * fix: mark records as changed if hash Option value is different (#874) This adds the case that where the hash changed, which happened for my source build, that a re-install is triggered. I think this is the correct behavior, not sure if it will trigger *too* many re-installs but did not feel that would be the case. * fix: python bindings issues (#872) * fix: normalize channel in lock file (#871) * fix: dead-lock issue * feat: use sha in cache lock file, needed for source builds. (#900) * fix: package hash revalidation (#904) * refactor!: lock-file API (#942) This PR significantly refactors the lock-file API to make the distinction between source and binary conda packages. It also changes the way we handle borrowing data from the lock-file, preferring borrowing over owned objects. The Python bindings have also been completely refactored to be more pythonic. Instead of having a single `LockedPackage` there is no a class hierarchy of `LockedPackage` -> `CondaLockedPackage`, `PypiLockedPackage` -> `CondaLockedSourcePackage` and `CondaLockedBinaryPackage`. --------- Co-authored-by: Tim de Jager <tdejager89@gmail.com> Co-authored-by: Tim de Jager <tim@prefix.dev> Co-authored-by: nichmor <nmorkotilo@gmail.com>
1 parent b5d2358 commit 32eefc8

File tree

73 files changed

+24687
-72611
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+24687
-72611
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ simd-json = { version = "0.14.2", features = ["serde_impl"] }
121121
serde = { version = "1.0.214" }
122122
serde_json = { version = "1.0.132" }
123123
serde_repr = "0.1"
124+
serde-value = "0.7.0"
124125
serde_with = "3.11.0"
125126
serde_yaml = "0.9.34"
126127
serde-untagged = "0.1.6"

crates/file_url/src/lib.rs

+26-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use std::path::PathBuf;
99
use std::str::FromStr;
1010
use thiserror::Error;
1111
use typed_path::{
12-
Utf8TypedComponent, Utf8TypedPath, Utf8UnixComponent, Utf8WindowsComponent, Utf8WindowsPrefix,
12+
Utf8TypedComponent, Utf8TypedPath, Utf8TypedPathBuf, Utf8UnixComponent, Utf8WindowsComponent,
13+
Utf8WindowsPrefix,
1314
};
1415
use url::{Host, Url};
1516

@@ -33,14 +34,7 @@ fn is_windows_drive_letter_segment(segment: &str) -> Option<String> {
3334
None
3435
}
3536

36-
/// Tries to convert a `file://` based URL to a path.
37-
///
38-
/// We assume that any passed URL that represents a path is an absolute path.
39-
///
40-
/// [`Url::to_file_path`] has a different code path for Windows and other operating systems, this
41-
/// can cause URLs to parse perfectly fine on Windows, but fail to parse on Linux. This function
42-
/// tries to parse the URL as a path on all operating systems.
43-
pub fn url_to_path(url: &Url) -> Option<PathBuf> {
37+
fn url_to_path_inner<T: From<String>>(url: &Url) -> Option<T> {
4438
if url.scheme() != "file" {
4539
return None;
4640
}
@@ -77,7 +71,29 @@ pub fn url_to_path(url: &Url) -> Option<PathBuf> {
7771
}
7872
}
7973

80-
Some(PathBuf::from(path))
74+
Some(T::from(path))
75+
}
76+
77+
/// Tries to convert a `file://` based URL to a path.
78+
///
79+
/// We assume that any passed URL that represents a path is an absolute path.
80+
///
81+
/// [`Url::to_file_path`] has a different code path for Windows and other operating systems, this
82+
/// can cause URLs to parse perfectly fine on Windows, but fail to parse on Linux. This function
83+
/// tries to parse the URL as a path on all operating systems.
84+
pub fn url_to_path(url: &Url) -> Option<PathBuf> {
85+
url_to_path_inner(url)
86+
}
87+
88+
/// Tries to convert a `file://` based URL to a path.
89+
///
90+
/// We assume that any passed URL that represents a path is an absolute path.
91+
///
92+
/// [`Url::to_file_path`] has a different code path for Windows and other operating systems, this
93+
/// can cause URLs to parse perfectly fine on Windows, but fail to parse on Linux. This function
94+
/// tries to parse the URL as a path on all operating systems.
95+
pub fn url_to_typed_path(url: &Url) -> Option<Utf8TypedPathBuf> {
96+
url_to_path_inner(url)
8197
}
8298

8399
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');

crates/rattler-bin/src/commands/create.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,10 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> {
313313
/// Prints the operations of the transaction to the console.
314314
fn print_transaction(transaction: &Transaction<PrefixRecord, RepoDataRecord>) {
315315
let format_record = |r: &RepoDataRecord| {
316-
let direct_url_print = if r.clone().channel.is_empty() {
317-
r.url.as_str()
316+
let direct_url_print = if let Some(channel) = &r.channel {
317+
channel.clone()
318318
} else {
319-
""
319+
String::new()
320320
};
321321

322322
format!(

crates/rattler/src/install/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ mod test {
762762
};
763763

764764
test_install_python(
765-
packages.filter_map(|p| Some(p.as_conda()?.url().clone())),
765+
packages.filter_map(|p| p.as_conda()?.location().as_url().cloned()),
766766
"conda-lock",
767767
current_platform,
768768
)

crates/rattler/src/install/transaction.rs

+5
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ fn is_python_record(record: &PackageRecord) -> bool {
219219

220220
/// Returns true if the `from` and `to` describe the same package content
221221
fn describe_same_content(from: &PackageRecord, to: &PackageRecord) -> bool {
222+
// If one hash is set and the other is not, the packages are different
223+
if from.sha256.is_some() != to.sha256.is_some() || from.md5.is_some() != to.md5.is_some() {
224+
return false;
225+
}
226+
222227
// If the hashes of the packages match we consider them to be equal
223228
if let (Some(a), Some(b)) = (from.sha256.as_ref(), to.sha256.as_ref()) {
224229
return a == b;

crates/rattler/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@ pub(crate) fn get_repodata_record(package_path: impl AsRef<std::path::Path>) ->
7878
.unwrap()
7979
.to_string(),
8080
url: url::Url::from_file_path(package_path).unwrap(),
81-
channel: "test".to_string(),
81+
channel: Some(String::from("test")),
8282
}
8383
}

crates/rattler_conda_types/src/match_spec/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ mod tests {
676676
),
677677
file_name: String::from("mamba-1.0-py37_0"),
678678
url: url::Url::parse("https://mamba.io/mamba-1.0-py37_0.conda").unwrap(),
679-
channel: String::from("mamba"),
679+
channel: Some(String::from("mamba")),
680680
};
681681
let package_record = repodata_record.clone().package_record;
682682

crates/rattler_conda_types/src/repo_data/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ impl RepoData {
226226
/// given the source of the data.
227227
pub fn into_repo_data_records(self, channel: &Channel) -> Vec<RepoDataRecord> {
228228
let mut records = Vec::with_capacity(self.packages.len() + self.conda_packages.len());
229-
let channel_name = channel.canonical_name();
230229
let base_url = self.base_url().map(ToOwned::to_owned);
231230

232231
// Determine the base_url of the channel
@@ -241,7 +240,7 @@ impl RepoData {
241240
base_url.as_deref(),
242241
&filename,
243242
),
244-
channel: channel_name.clone(),
243+
channel: Some(channel.base_url.as_str().to_string()),
245244
package_record,
246245
file_name: filename,
247246
});

crates/rattler_conda_types/src/repo_data_record.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ pub struct RepoDataRecord {
2222
/// String representation of the channel where the package comes from. This could be a URL but
2323
/// it could also be a channel name. Personally I would always add the complete URL here to be
2424
/// explicit about where the package came from.
25-
pub channel: String,
25+
/// TODO: Refactor this into `Source` which can be a "name", "channelurl", or "direct url".
26+
pub channel: Option<String>,
2627
}
2728

2829
impl AsRef<PackageRecord> for RepoDataRecord {

crates/rattler_lock/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ serde = { workspace = true, features = ["derive"] }
2424
serde_yaml = { workspace = true }
2525
serde_with = { workspace = true, features = ["indexmap_2"] }
2626
serde_repr = { workspace = true }
27+
serde-value = { workspace = true }
2728
thiserror = { workspace = true }
2829
url = { workspace = true, features = ["serde"] }
30+
typed-path = { workspace = true }
2931

3032
[dev-dependencies]
3133
insta = { workspace = true, features = ["yaml"] }

crates/rattler_lock/src/builder.rs

+103-13
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,106 @@ use pep508_rs::ExtraName;
1111
use rattler_conda_types::Platform;
1212

1313
use crate::{
14-
file_format_version::FileFormatVersion, Channel, CondaPackageData, EnvironmentData,
15-
EnvironmentPackageData, LockFile, LockFileInner, Package, PypiIndexes, PypiPackageData,
16-
PypiPackageEnvironmentData,
14+
file_format_version::FileFormatVersion, Channel, CondaBinaryData, CondaPackageData,
15+
CondaSourceData, EnvironmentData, EnvironmentPackageData, LockFile, LockFileInner,
16+
LockedPackageRef, PypiIndexes, PypiPackageData, PypiPackageEnvironmentData, UrlOrPath,
1717
};
1818

19+
/// Information about a single locked package in an environment.
20+
#[derive(Debug, Clone)]
21+
#[allow(clippy::large_enum_variant)]
22+
pub enum LockedPackage {
23+
/// A conda package
24+
Conda(CondaPackageData),
25+
26+
/// A pypi package in an environment
27+
Pypi(PypiPackageData, PypiPackageEnvironmentData),
28+
}
29+
30+
impl From<LockedPackageRef<'_>> for LockedPackage {
31+
fn from(value: LockedPackageRef<'_>) -> Self {
32+
match value {
33+
LockedPackageRef::Conda(data) => LockedPackage::Conda(data.clone()),
34+
LockedPackageRef::Pypi(data, env) => LockedPackage::Pypi(data.clone(), env.clone()),
35+
}
36+
}
37+
}
38+
39+
impl From<CondaPackageData> for LockedPackage {
40+
fn from(value: CondaPackageData) -> Self {
41+
LockedPackage::Conda(value)
42+
}
43+
}
44+
45+
impl From<(PypiPackageData, PypiPackageEnvironmentData)> for LockedPackage {
46+
fn from((data, env): (PypiPackageData, PypiPackageEnvironmentData)) -> Self {
47+
LockedPackage::Pypi(data, env)
48+
}
49+
}
50+
51+
impl LockedPackage {
52+
/// Returns the name of the package as it occurs in the lock file. This
53+
/// might not be the normalized name.
54+
pub fn name(&self) -> &str {
55+
match self {
56+
LockedPackage::Conda(data) => data.record().name.as_source(),
57+
LockedPackage::Pypi(data, _) => data.name.as_ref(),
58+
}
59+
}
60+
61+
/// Returns the location of the package.
62+
pub fn location(&self) -> &UrlOrPath {
63+
match self {
64+
LockedPackage::Conda(data) => data.location(),
65+
LockedPackage::Pypi(data, _) => &data.location,
66+
}
67+
}
68+
69+
/// Returns the conda package data if this is a conda package.
70+
pub fn as_conda(&self) -> Option<&CondaPackageData> {
71+
match self {
72+
LockedPackage::Conda(data) => Some(data),
73+
LockedPackage::Pypi(..) => None,
74+
}
75+
}
76+
77+
/// Returns the pypi package data if this is a pypi package.
78+
pub fn as_pypi(&self) -> Option<(&PypiPackageData, &PypiPackageEnvironmentData)> {
79+
match self {
80+
LockedPackage::Conda(..) => None,
81+
LockedPackage::Pypi(data, env) => Some((data, env)),
82+
}
83+
}
84+
85+
/// Returns the package as a binary conda package if this is a binary conda
86+
/// package.
87+
pub fn as_binary_conda(&self) -> Option<&CondaBinaryData> {
88+
self.as_conda().and_then(CondaPackageData::as_binary)
89+
}
90+
91+
/// Returns the package as a source conda package if this is a source conda
92+
/// package.
93+
pub fn as_source_conda(&self) -> Option<&CondaSourceData> {
94+
self.as_conda().and_then(CondaPackageData::as_source)
95+
}
96+
97+
/// Returns the conda package data if this is a conda package.
98+
pub fn into_conda(self) -> Option<CondaPackageData> {
99+
match self {
100+
LockedPackage::Conda(data) => Some(data),
101+
LockedPackage::Pypi(..) => None,
102+
}
103+
}
104+
105+
/// Returns the pypi package data if this is a pypi package.
106+
pub fn into_pypi(self) -> Option<(PypiPackageData, PypiPackageEnvironmentData)> {
107+
match self {
108+
LockedPackage::Conda(..) => None,
109+
LockedPackage::Pypi(data, env) => Some((data, env)),
110+
}
111+
}
112+
}
113+
19114
/// A struct to incrementally build a lock-file.
20115
#[derive(Default)]
21116
pub struct LockFileBuilder {
@@ -162,7 +257,7 @@ impl LockFileBuilder {
162257
mut self,
163258
environment: impl Into<String>,
164259
platform: Platform,
165-
locked_package: Package,
260+
locked_package: LockedPackage,
166261
) -> Self {
167262
self.add_package(environment, platform, locked_package);
168263
self
@@ -174,18 +269,13 @@ impl LockFileBuilder {
174269
&mut self,
175270
environment: impl Into<String>,
176271
platform: Platform,
177-
locked_package: Package,
272+
locked_package: LockedPackage,
178273
) -> &mut Self {
179274
match locked_package {
180-
Package::Conda(p) => {
181-
self.add_conda_package(environment, platform, p.package_data().clone())
275+
LockedPackage::Conda(p) => self.add_conda_package(environment, platform, p),
276+
LockedPackage::Pypi(data, env_data) => {
277+
self.add_pypi_package(environment, platform, data, env_data)
182278
}
183-
Package::Pypi(p) => self.add_pypi_package(
184-
environment,
185-
platform,
186-
p.package_data().clone(),
187-
p.environment_data().clone(),
188-
),
189279
}
190280
}
191281

0 commit comments

Comments
 (0)