Skip to content

Commit d197867

Browse files
baszalmstraclaude
andauthored
fix: preserve build constraints when converting wildcard version dependencies (#449)
* fix: preserve build constraints when converting wildcard version dependencies When converting dependencies from ProjectModel with wildcard versions (e.g., `tk = { build = "xft*" }`), the code was creating a minimal MatchSpec from just the package name, discarding all other fields including the build constraint. This caused the build string matcher to be lost in resolved run dependencies. The fix removes the special case handling for wildcard versions and always uses `to_nameless()` method, which properly preserves all fields including build constraints, regardless of the version spec. Fixes: Build constraints (build = "xft*") are now properly included in resolved run dependencies * test: add unit tests for build constraint preservation Add three comprehensive unit tests to verify that build constraints are properly preserved when converting PackageSpec to MatchSpec: 1. test_to_match_spec_preserves_build_constraint_with_wildcard_version - Tests tk = { build = "xft*" } with wildcard version - Verifies the original bug case is fixed 2. test_to_match_spec_preserves_build_constraint_with_specific_version - Tests tk = { version = "8.6.13", build = "xft*" } - Ensures build constraints work with specific versions 3. test_to_match_spec_without_build_constraint - Tests python = "*" without build constraint - Provides a control case for normal behavior All tests pass, confirming the fix works correctly. --------- Co-authored-by: Claude <[email protected]>
1 parent 752bd28 commit d197867

File tree

1 file changed

+98
-10
lines changed

1 file changed

+98
-10
lines changed

crates/pixi-build-backend/src/traits/package_spec.rs

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
1111
use std::{fmt::Debug, sync::Arc};
1212

13-
use miette::IntoDiagnostic;
1413
use pixi_build_types::{self as pbt};
1514
use rattler_conda_types::{Channel, MatchSpec, NamelessMatchSpec, PackageName};
1615

@@ -87,15 +86,8 @@ impl PackageSpec for pbt::PackageSpecV1 {
8786
) -> miette::Result<(MatchSpec, Option<Self::SourceSpec>)> {
8887
match self {
8988
pbt::PackageSpecV1::Binary(binary_spec) => {
90-
let match_spec = if binary_spec.version == Some("*".parse().unwrap()) {
91-
// Skip dependencies with wildcard versions.
92-
name.as_normalized()
93-
.to_string()
94-
.parse::<MatchSpec>()
95-
.into_diagnostic()?
96-
} else {
97-
MatchSpec::from_nameless(binary_spec.to_nameless(), Some(name))
98-
};
89+
// Always use to_nameless() to preserve all fields including build constraints
90+
let match_spec = MatchSpec::from_nameless(binary_spec.to_nameless(), Some(name));
9991
Ok((match_spec, None))
10092
}
10193
pbt::PackageSpecV1::Source(source_spec) => Ok((
@@ -142,3 +134,99 @@ impl PackageSourceSpec for pbt::SourcePackageSpecV1 {
142134
self
143135
}
144136
}
137+
138+
#[cfg(test)]
139+
mod tests {
140+
use super::*;
141+
use rattler_conda_types::{ParseStrictness, StringMatcher, VersionSpec};
142+
143+
#[test]
144+
fn test_to_match_spec_preserves_build_constraint_with_wildcard_version() {
145+
// Test case: dependency with wildcard version and build constraint
146+
// e.g., tk = { build = "xft*" }
147+
let build_matcher: StringMatcher = "xft*".parse().unwrap();
148+
let binary_spec = pbt::BinaryPackageSpecV1 {
149+
version: Some(VersionSpec::Any),
150+
build: Some(build_matcher.clone()),
151+
build_number: None,
152+
file_name: None,
153+
channel: None,
154+
subdir: None,
155+
md5: None,
156+
sha256: None,
157+
url: None,
158+
license: None,
159+
};
160+
161+
let package_spec = pbt::PackageSpecV1::Binary(Box::new(binary_spec));
162+
let package_name = PackageName::try_from("tk").unwrap();
163+
164+
let (match_spec, _) = package_spec.to_match_spec(package_name).unwrap();
165+
166+
// Verify the build constraint is preserved
167+
assert_eq!(match_spec.name, Some(PackageName::try_from("tk").unwrap()));
168+
assert_eq!(match_spec.version, Some(VersionSpec::Any));
169+
assert_eq!(match_spec.build, Some(build_matcher));
170+
}
171+
172+
#[test]
173+
fn test_to_match_spec_preserves_build_constraint_with_specific_version() {
174+
// Test case: dependency with specific version and build constraint
175+
// e.g., tk = { version = "8.6.13", build = "xft*" }
176+
let version = VersionSpec::from_str("8.6.13", ParseStrictness::Lenient).unwrap();
177+
let build_matcher: StringMatcher = "xft*".parse().unwrap();
178+
let binary_spec = pbt::BinaryPackageSpecV1 {
179+
version: Some(version.clone()),
180+
build: Some(build_matcher.clone()),
181+
build_number: None,
182+
file_name: None,
183+
channel: None,
184+
subdir: None,
185+
md5: None,
186+
sha256: None,
187+
url: None,
188+
license: None,
189+
};
190+
191+
let package_spec = pbt::PackageSpecV1::Binary(Box::new(binary_spec));
192+
let package_name = PackageName::try_from("tk").unwrap();
193+
194+
let (match_spec, _) = package_spec.to_match_spec(package_name).unwrap();
195+
196+
// Verify both version and build constraint are preserved
197+
assert_eq!(match_spec.name, Some(PackageName::try_from("tk").unwrap()));
198+
assert_eq!(match_spec.version, Some(version));
199+
assert_eq!(match_spec.build, Some(build_matcher));
200+
}
201+
202+
#[test]
203+
fn test_to_match_spec_without_build_constraint() {
204+
// Test case: dependency with wildcard version but no build constraint
205+
// e.g., python = "*"
206+
let binary_spec = pbt::BinaryPackageSpecV1 {
207+
version: Some(VersionSpec::Any),
208+
build: None,
209+
build_number: None,
210+
file_name: None,
211+
channel: None,
212+
subdir: None,
213+
md5: None,
214+
sha256: None,
215+
url: None,
216+
license: None,
217+
};
218+
219+
let package_spec = pbt::PackageSpecV1::Binary(Box::new(binary_spec));
220+
let package_name = PackageName::try_from("python").unwrap();
221+
222+
let (match_spec, _) = package_spec.to_match_spec(package_name).unwrap();
223+
224+
// Verify the match spec is correct
225+
assert_eq!(
226+
match_spec.name,
227+
Some(PackageName::try_from("python").unwrap())
228+
);
229+
assert_eq!(match_spec.version, Some(VersionSpec::Any));
230+
assert_eq!(match_spec.build, None);
231+
}
232+
}

0 commit comments

Comments
 (0)