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
4 changes: 4 additions & 0 deletions crates/rattler/src/install/installer/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub enum InstallerError {
/// Failed to create the prefix
#[error("failed to create the prefix")]
FailedToCreatePrefix(PathBuf, #[source] std::io::Error),

/// Attempted to install platform-specific packages when target platform is noarch
#[error("cannot install platform-specific packages with noarch as the target platform. The following packages have non-noarch subdirs: {}", .0.join(", "))]
PlatformSpecificPackagesWithNoarchPlatform(Vec<String>),
}

impl From<Cancelled> for InstallerError {
Expand Down
88 changes: 88 additions & 0 deletions crates/rattler/src/install/installer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,30 @@ impl Installer {

let transaction = transaction.to_owned();

// Validate that if the target platform is NoArch, all packages to be installed
// must also be noarch (subdir == "noarch")
if target_platform == Platform::NoArch {
let non_noarch_packages: Vec<String> = transaction
.installed_packages()
.filter(|record| record.package_record.subdir != "noarch")
.map(|record| {
format!(
"{}/{}-{}-{}",
record.package_record.subdir,
record.package_record.name.as_normalized(),
record.package_record.version,
record.package_record.build
)
})
.collect();

if !non_noarch_packages.is_empty() {
return Err(InstallerError::PlatformSpecificPackagesWithNoarchPlatform(
non_noarch_packages,
));
}
}

// Create a mapping from package names to requested specs
let spec_mapping = self
.requested_specs
Expand Down Expand Up @@ -1355,4 +1379,68 @@ mod tests {
"Migrated specs should match the original spec"
);
}

#[tokio::test]
async fn test_noarch_platform_rejects_platform_specific_packages() {
use rattler_conda_types::Platform;

let (_temp_dir, target_prefix) = create_test_environment();

// Create a platform-specific package (with subdir != "noarch")
let mut platform_specific_package = create_dummy_repo_record();
platform_specific_package.package_record.subdir = "osx-arm64".to_string();

// Try to install this platform-specific package with Platform::NoArch
let installer = Installer::new().with_target_platform(Platform::NoArch);
let result = installer
.install(&target_prefix, vec![platform_specific_package.clone()])
.await;

// Should fail with PlatformSpecificPackagesWithNoarchPlatform error
assert!(
result.is_err(),
"Installation should fail when installing platform-specific packages with noarch platform"
);

match result {
Err(InstallerError::PlatformSpecificPackagesWithNoarchPlatform(packages)) => {
assert!(
!packages.is_empty(),
"Error should list the problematic packages"
);
assert!(
packages[0].contains("osx-arm64"),
"Error message should include the subdir of the platform-specific package"
);
}
_ => {
panic!("Expected PlatformSpecificPackagesWithNoarchPlatform error, got: {result:?}")
}
}
}

#[tokio::test]
async fn test_noarch_platform_accepts_noarch_packages() {
use rattler_conda_types::{NoArchType, Platform};

let (_temp_dir, target_prefix) = create_test_environment();

// Create a noarch package (with subdir == "noarch")
let mut noarch_package = create_dummy_repo_record();
noarch_package.package_record.subdir = "noarch".to_string();
noarch_package.package_record.noarch = NoArchType::generic();

// Try to install this noarch package with Platform::NoArch
let installer = Installer::new().with_target_platform(Platform::NoArch);
let result = installer
.install(&target_prefix, vec![noarch_package.clone()])
.await;

// Should succeed
assert!(
result.is_ok(),
"Installation should succeed when installing noarch packages with noarch platform: {:?}",
result.err()
);
}
}