Skip to content
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
10 changes: 10 additions & 0 deletions crates/pixi_cli/src/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ async fn setup_environment(
project.manifest.set_platform(env_name, platform)?;
}

// Record any `CONDA_OVERRIDE_*` set during install as virtual packages on
// the environment's platform, so later solves (`pixi global update`/`sync`)
// reuse the same values without the variable having to be set again.
let env_overrides = pixi_core::workspace::virtual_packages_from_env_overrides();
if !env_overrides.is_empty() {
project
.manifest
.add_platform_virtual_packages(env_name, env_overrides)?;
}

let converted_with_inclusions = args
.with
.iter()
Expand Down
14 changes: 14 additions & 0 deletions crates/pixi_core/src/workspace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,20 @@ fn apply_environment_variable_overrides(packages: &mut Vec<GenericVirtualPackage
apply_glibc_override(packages);
}

/// The virtual packages defined purely by the `CONDA_OVERRIDE_*` environment
/// variables currently set, with nothing detected from the machine. Used to
/// record an explicit override as a declared virtual package, e.g. when
/// `CONDA_OVERRIDE_CUDA` is set during `pixi global install`.
///
/// This is [`apply_environment_variable_overrides`] applied to an empty set, so
/// only the variables that are set contribute a package (`__cuda`, `__osx`,
/// `__linux` and the libc family); unset variables add nothing.
pub fn virtual_packages_from_env_overrides() -> Vec<GenericVirtualPackage> {
let mut packages = Vec::new();
apply_environment_variable_overrides(&mut packages);
packages
}

/// Apply `CONDA_OVERRIDE_GLIBC` (rattler's only libc slot) to `packages`. The
/// glibc env var governs glibc alone: unset leaves libc packages untouched, an
/// empty value removes `__glibc`, and a concrete version pins
Expand Down
6 changes: 3 additions & 3 deletions crates/pixi_global/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub async fn list_global_environments_json(
name: env_name.as_str().to_string(),
dependencies,
exposed,
platform: env.platform.map(|p| p.to_string()),
platform: env.platform.as_ref().map(|p| p.subdir().to_string()),
});
}

Expand Down Expand Up @@ -200,12 +200,12 @@ fn print_meta_info(environment: &ParsedEnvironment) -> miette::Result<()> {
}

// Print platform
if let Some(platform) = environment.platform {
if let Some(platform) = &environment.platform {
writeln!(
std::io::stdout(),
"{} {}",
console::style("Platform:").bold().cyan(),
platform
platform.subdir()
)
.inspect_err(|e| {
if e.kind() == std::io::ErrorKind::BrokenPipe {
Expand Down
137 changes: 128 additions & 9 deletions crates/pixi_global/src/project/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ use indexmap::IndexSet;
use miette::IntoDiagnostic;
use pixi_config::Config;
use pixi_consts::consts;
use pixi_manifest::{PrioritizedChannel, toml::TomlDocument};
use pixi_manifest::{
PixiPlatform, PlatformEdit, PrioritizedChannel,
toml::{TomlDocument, pixi_platform_to_toml_value},
};
use pixi_toml::TomlIndexMap;
use pixi_utils::{executable_from_path, strip_executable_extension};
use rattler_conda_types::{NamedChannelOrUrl, PackageName, Platform};
use rattler_conda_types::{GenericVirtualPackage, NamedChannelOrUrl, PackageName, Platform};
use toml_edit::{DocumentMut, Item};
use toml_span::{DeserError, Value};

Expand Down Expand Up @@ -205,7 +208,12 @@ impl Manifest {
Ok(name.clone())
}

/// Sets the platform of a specific environment in the manifest
/// Sets the platform of a specific environment in the manifest.
///
/// This sets the bare subdir, discarding any virtual packages previously
/// recorded for the environment -- an explicit `--platform` selects the
/// platform afresh. Use [`Self::add_platform_virtual_packages`] to record
/// virtual packages on top.
pub fn set_platform(
&mut self,
env_name: &EnvironmentName,
Expand All @@ -216,28 +224,88 @@ impl Manifest {
miette::bail!("Environment {} doesn't exist", env_name.fancy_display());
}

let pixi_platform = PixiPlatform::from_subdir(platform);

// Update self.document
self.document
.get_or_insert_nested_table(&["envs", env_name.as_str()])?
.insert(
"platform",
Item::Value(pixi_platform_to_toml_value(&pixi_platform)),
);

// Update self.parsed
self.parsed
.envs
.get_mut(env_name)
.ok_or_else(|| {
miette::miette!("Can't find environment {} yet", env_name.fancy_display())
})?
.platform = Some(pixi_platform);

tracing::debug!(
"Set platform {} for environment {} in toml document",
platform,
env_name
);
Ok(())
}

/// Records virtual packages on an environment's platform, e.g. a
/// `CONDA_OVERRIDE_*` set during `pixi global install`. The recorded values
/// are then used whenever the environment is solved, including by
/// `pixi global update`/`sync`, without the variable having to be set again.
///
/// A virtual package of the same name replaces an earlier recording. The
/// subdir is preserved, defaulting to the current platform when the
/// environment had no platform recorded yet.
pub fn add_platform_virtual_packages(
&mut self,
env_name: &EnvironmentName,
virtual_packages: Vec<GenericVirtualPackage>,
) -> miette::Result<()> {
if virtual_packages.is_empty() {
return Ok(());
}

let mut platform = self
.parsed
.envs
.get(env_name)
.ok_or_else(|| {
miette::miette!("Environment {} doesn't exist", env_name.fancy_display())
})?
.platform
.replace(platform);
.clone()
.unwrap_or_else(|| PixiPlatform::from_subdir(Platform::current()));

platform
.apply_edit(PlatformEdit {
set_subdir: None,
clear_virtual_packages: false,
insert_or_update_virtual_packages: virtual_packages,
remove_virtual_packages: Vec::new(),
})
.into_diagnostic()?;

// Update self.document
self.document
.get_or_insert_nested_table(&["envs", env_name.as_str()])?
.insert(
"platform",
Item::Value(toml_edit::Value::from(platform.to_string())),
Item::Value(pixi_platform_to_toml_value(&platform)),
);

// Update self.parsed
self.parsed
.envs
.get_mut(env_name)
.expect("environment existence checked above")
.platform = Some(platform);

tracing::debug!(
"Set platform {} for environment {} in toml document",
platform,
env_name
"Recorded virtual packages on platform for environment {} in toml document",
env_name.fancy_display()
);
Ok(())
}
Expand Down Expand Up @@ -1088,8 +1156,59 @@ mod tests {
.get(&env_name)
.unwrap()
.platform
.as_ref()
.unwrap();
assert_eq!(actual_platform.subdir(), platform);
}

#[test]
fn test_add_platform_virtual_packages() {
let mut manifest = Manifest::default();
let env_name = EnvironmentName::from_str("cuda-tool").unwrap();
manifest.add_environment(&env_name, None).unwrap();

let cuda = GenericVirtualPackage {
name: PackageName::from_str("__cuda").unwrap(),
version: "12.0".parse().unwrap(),
build_string: String::new(),
};
manifest
.add_platform_virtual_packages(&env_name, vec![cuda])
.unwrap();

let declares_cuda = |platform: &PixiPlatform| {
platform
.declared_virtual_packages()
.iter()
.any(|vp| vp.name.as_normalized() == "__cuda" && vp.version.to_string() == "12.0")
};

// The override is recorded on the environment's platform, on the
// current subdir since none was set.
let platform = manifest
.parsed
.envs
.get(&env_name)
.unwrap()
.platform
.as_ref()
.unwrap();
assert_eq!(platform.subdir(), Platform::current());
assert!(declares_cuda(platform));

// And it survives a round-trip through the document.
let reparsed =
Manifest::from_str(Path::new("pixi-global.toml"), manifest.document.to_string())
.unwrap();
let reparsed_platform = reparsed
.parsed
.envs
.get(&env_name)
.unwrap()
.platform
.as_ref()
.unwrap();
assert_eq!(actual_platform, platform);
assert!(declares_cuda(reparsed_platform));
}

#[test]
Expand Down
Loading
Loading