Skip to content
Merged
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
16 changes: 14 additions & 2 deletions crates/pixi_build_frontend/src/backend/in_memory/passthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use pixi_build_types::{
initialize::InitializeParams,
},
};
use rattler_conda_types::{Platform, Version, package::IndexJson};
use rattler_conda_types::{PackageName, Platform, Version, package::IndexJson};
use serde::Deserialize;

use crate::{
Expand Down Expand Up @@ -64,7 +64,19 @@ impl InMemoryBackend for PassthroughBackend {
Ok(CondaOutputsResult {
outputs: vec![CondaOutput {
metadata: CondaOutputMetadata {
name: self.project_model.name.parse().unwrap(),
name: self
.project_model
.name
.as_ref()
.map(|name| PackageName::try_from(name.as_str()).unwrap())
.unwrap_or_else(|| {
self.index_json
.as_ref()
.map(|j| j.name.clone())
.unwrap_or_else(|| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to do here, we could also make this an unreachable as that would be really weird. But rattler-build currently already errors.

PackageName::try_from("pixi-package_name").unwrap()
})
}),
version: self
.project_model
.version
Expand Down
2 changes: 1 addition & 1 deletion crates/pixi_build_type_conversions/src/project_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ pub fn to_project_model_v1(
) -> Result<pbt::ProjectModelV1, SpecConversionError> {
let project = pbt::ProjectModelV1 {
name: manifest.package.name.clone(),
version: Some(manifest.package.version.clone()),
version: manifest.package.version.clone(),
description: manifest.package.description.clone(),
authors: manifest.package.authors.clone(),
license: manifest.package.license.clone(),
Expand Down
14 changes: 13 additions & 1 deletion crates/pixi_build_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ use rattler_conda_types::{
};
use serde::{Deserialize, Serialize};

// Version 0: Initial version
// Version 1: Added conda/outputs and conda/build_v1
// Version 2: Name in project models can be `None`.

/// The constraint for the pixi build api version package
/// Adding this constraint when solving a pixi build backend environment ensures
/// that a backend is selected that uses the same interface version as Pixi does
pub static PIXI_BUILD_API_VERSION_NAME: LazyLock<PackageName> =
LazyLock::new(|| PackageName::new_unchecked("pixi-build-api-version"));
pub const PIXI_BUILD_API_VERSION_LOWER: u64 = 0;
pub const PIXI_BUILD_API_VERSION_CURRENT: u64 = 1;
pub const PIXI_BUILD_API_VERSION_CURRENT: u64 = 2;
pub const PIXI_BUILD_API_VERSION_UPPER: u64 = PIXI_BUILD_API_VERSION_CURRENT + 1;
pub static PIXI_BUILD_API_VERSION_SPEC: LazyLock<VersionSpec> = LazyLock::new(|| {
VersionSpec::Group(
Expand Down Expand Up @@ -85,9 +89,17 @@ impl PixiBuildApiVersion {
provides_conda_build_v1: Some(true),
..Self(0).expected_backend_capabilities()
},
2 => BackendCapabilities {
..Self(1).expected_backend_capabilities()
},
_ => BackendCapabilities::default(),
}
}

/// Returns true if this version of the protocol supports the name field in the project model to be `None`.
pub fn supports_name_none(&self) -> bool {
self.0 >= 2
}
}

impl Display for PixiBuildApiVersion {
Expand Down
37 changes: 21 additions & 16 deletions crates/pixi_build_types/src/project_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub type SourcePackageName = String;
#[serde(rename_all = "camelCase")]
pub struct ProjectModelV1 {
/// The name of the project
pub name: String,
pub name: Option<String>,

/// The version of the project
pub version: Option<Version>,
Expand Down Expand Up @@ -176,17 +176,18 @@ impl TargetsV1 {
}

impl IsDefault for TargetsV1 {
fn is_default(&self) -> bool {
self.is_empty()
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
if !self.is_empty() { Some(self) } else { None }
}
}

impl<T: IsDefault> IsDefault for Option<T> {
fn is_default(&self) -> bool {
match self {
None => true,
Some(value) => value.is_default(),
}
type Item = T::Item;

fn is_non_default(&self) -> Option<&Self::Item> {
self.as_ref()?.is_non_default()
}
}

Expand Down Expand Up @@ -219,8 +220,10 @@ impl TargetV1 {
}

impl IsDefault for TargetV1 {
fn is_default(&self) -> bool {
self.is_empty()
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
if !self.is_empty() { Some(self) } else { None }
}
}

Expand Down Expand Up @@ -601,8 +604,10 @@ impl Hash for GitReferenceV1 {
}

impl IsDefault for GitReferenceV1 {
fn is_default(&self) -> bool {
false // Never skip GitReferenceV1 fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip GitReferenceV1 fields
}
}

Expand Down Expand Up @@ -642,7 +647,7 @@ mod tests {
fn test_hash_stability_with_default_values() {
// Create a minimal ProjectModelV1 instance
let mut project_model = ProjectModelV1 {
name: "test-project".to_string(),
name: Some("test-project".to_string()),
version: None,
description: None,
authors: None,
Expand Down Expand Up @@ -698,7 +703,7 @@ mod tests {
fn test_hash_changes_with_meaningful_values() {
// Create a minimal ProjectModelV1 instance
let mut project_model = ProjectModelV1 {
name: "test-project".to_string(),
name: Some("test-project".to_string()),
version: None,
description: None,
authors: None,
Expand Down Expand Up @@ -993,14 +998,14 @@ mod tests {
fn test_hash_collision_bug_project_model() {
// Test the same issue in ProjectModelV1
let project1 = ProjectModelV1 {
name: "test".to_string(),
name: Some("test".to_string()),
description: Some("test description".to_string()),
license: None,
..Default::default()
};

let project2 = ProjectModelV1 {
name: "test".to_string(),
name: Some("test".to_string()),
description: None,
license: Some("test description".to_string()),
..Default::default()
Expand Down
73 changes: 49 additions & 24 deletions crates/pixi_build_types/src/stable_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ impl Hash for FieldDiscriminant {
/// Trait to determine if a value should be considered "default" and thus skipped in hash calculations.
/// This helps maintain forward/backward compatibility by only including discriminants for meaningful values.
pub(crate) trait IsDefault {
fn is_default(&self) -> bool;
type Item;

fn is_non_default(&self) -> Option<&Self::Item>;
}

/// A dyn-compatible hashing trait that works with any hasher type
Expand Down Expand Up @@ -66,9 +68,12 @@ impl<'a, H: std::hash::Hasher> StableHashBuilder<'a, H> {

/// Add a field to the hash if it's not in its default state.
/// Fields will be automatically sorted alphabetically before hashing.
pub fn field<T: Hash + IsDefault>(mut self, name: &'static str, value: &'a T) -> Self {
if !value.is_default() {
self.fields.insert(name, value);
pub fn field<T: IsDefault>(mut self, name: &'static str, value: &'a T) -> Self
where
T::Item: Hash,
{
if let Some(item) = value.is_non_default() {
self.fields.insert(name, item);
}
self
}
Expand All @@ -83,61 +88,81 @@ impl<'a, H: std::hash::Hasher> StableHashBuilder<'a, H> {
}

impl<K, V> IsDefault for OrderMap<K, V> {
fn is_default(&self) -> bool {
self.is_empty()
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
if !self.is_empty() { Some(self) } else { None }
}
}

impl IsDefault for String {
fn is_default(&self) -> bool {
false // Never skip required string fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip required string fields
}
}

impl IsDefault for url::Url {
fn is_default(&self) -> bool {
false // Never skip required URL fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip required URL fields
}
}

impl IsDefault for std::path::PathBuf {
fn is_default(&self) -> bool {
false // Never skip PathBuf fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip PathBuf fields
}
}

impl<T> IsDefault for Vec<T> {
fn is_default(&self) -> bool {
self.is_empty()
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
if !self.is_empty() { Some(self) } else { None }
}
}

impl IsDefault for rattler_conda_types::Version {
fn is_default(&self) -> bool {
false // Never skip version fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip version fields
}
}

impl IsDefault for rattler_conda_types::StringMatcher {
fn is_default(&self) -> bool {
false // Never skip StringMatcher fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip StringMatcher fields
}
}

impl IsDefault for rattler_conda_types::BuildNumberSpec {
fn is_default(&self) -> bool {
false // Never skip BuildNumberSpec fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip BuildNumberSpec fields
}
}

impl IsDefault for rattler_conda_types::VersionSpec {
fn is_default(&self) -> bool {
false // Never skip VersionSpec fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip VersionSpec fields
}
}

impl<U, T: rattler_digest::digest::generic_array::ArrayLength<U>> IsDefault for GenericArray<U, T> {
fn is_default(&self) -> bool {
false // Never skip digest output fields
type Item = Self;

fn is_non_default(&self) -> Option<&Self::Item> {
Some(self) // Never skip digest output fields
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ impl CommandDispatcher {
api_version,
);

// Make sure that the project model is compatible with the API version.
if !api_version.supports_name_none()
&& spec
.init_params
.project_model
.as_ref()
.is_some_and(|p| p.name.is_none())
{
return Err(CommandDispatcherError::Failed(
InstantiateBackendError::SpecConversionError(SpecConversionError::MissingName),
));
}

JsonRpcBackend::setup(
source_dir,
spec.init_params.manifest_path,
Expand Down
4 changes: 3 additions & 1 deletion crates/pixi_core/src/lock_file/satisfiability/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,9 @@ pub(crate) async fn verify_package_platform_satisfiability(
ParseChannelError::InvalidPath(p).into()
}
SpecConversionError::InvalidChannel(_name, p) => p.into(),
SpecConversionError::MissingName => {
ParseMatchSpecError::MissingPackageName
}
};
return Err(Box::new(PlatformUnsat::FailedToParseMatchSpec(
name.as_source().to_string(),
Expand Down Expand Up @@ -1951,7 +1954,6 @@ mod tests {

#[rstest]
#[tokio::test]
#[cfg_attr(not(feature = "slow_integration_tests"), ignore)]
async fn test_example_satisfiability(
#[files("../../examples/**/p*.toml")] manifest_path: PathBuf,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
source: src/lock_file/satisfiability/mod.rs
source: crates/pixi_core/src/lock_file/satisfiability/mod.rs
expression: s
snapshot_kind: text
---
environment 'default' does not satisfy the requirements of the project for platform 'linux-64'
Diagnostic severity: error
Caused by: the input hash for 'child-package' (f0d841a0cfd56becd0041a6bbcd215a19ce9c34c08530a98b642381af8ebd32a) does not match the hash in the lock-file (64a8e9eb88c4f6e944a37279a38a47fe7872454fb62bcafad63d200aef4b4a05)
Caused by: the input hash for 'child-package' (6a7fcb5c9f728504123fff40635207fb963b77f26b1a2dffb2993efa7f81559d) does not match the hash in the lock-file (64a8e9eb88c4f6e944a37279a38a47fe7872454fb62bcafad63d200aef4b4a05)
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
source: crates/pixi_glob/src/glob_hash.rs
expression: snapshot
snapshot_kind: text
---
Globs:
- tests/data/satisfiability/source-dependency/**/*
Hash: 4ae275fa46ad1fa335806cd37767a8fe530046fb38aea40b24527fddf3a769b8
Hash: 15638d2bfdb15971bb19d0593ee872437fb6a5c88b143e01f3044e473888e39c
Matched files:
- tests/data/satisfiability/source-dependency/child-package/pixi.toml
- tests/data/satisfiability/source-dependency/pixi.lock
Expand Down
Loading
Loading