diff --git a/Cargo.lock b/Cargo.lock index f36285e6d5..123a855199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3006,7 +3006,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.1", "system-configuration", "tokio", "tower-service", @@ -3026,7 +3026,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.59.0", + "windows-core 0.62.2", ] [[package]] @@ -4279,7 +4279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.45.0", + "windows-sys 0.61.2", ] [[package]] @@ -5123,8 +5123,8 @@ dependencies = [ "regex", "rstest", "serde", + "serde_derive", "serde_json", - "serde_with", "tempfile", "thiserror 2.0.17", "tokio", @@ -5303,6 +5303,7 @@ dependencies = [ "rattler_digest", "rattler_lock", "serde", + "serde_json", "serde_with", "thiserror 2.0.17", "typed-path", @@ -5719,7 +5720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -5876,7 +5877,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.1", "thiserror 2.0.17", "tokio", "tracing", @@ -5913,7 +5914,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.1", "tracing", "windows-sys 0.60.2", ] diff --git a/Cargo.toml b/Cargo.toml index f66cb7448c..35ec37edc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ clap = { version = "4.5.31", default-features = false } clap_complete = "4.5.46" clap_complete_nushell = "4.5.5" # Rattler crates -coalesced_map = "0.1.1" +coalesced_map = "0.1.2" concat-idents = "1.1.5" console = "0.15.10" console-subscriber = "0.4.1" @@ -146,6 +146,7 @@ self-replace = "1.5.0" serde = "1.0.218" serde-untagged = "0.1.6" serde-value = "0.7.0" +serde_derive = "*" serde_ignored = "0.1.10" serde_json = "1.0.139" serde_with = "=3.14.0" diff --git a/crates/pixi_build_discovery/src/discovery.rs b/crates/pixi_build_discovery/src/discovery.rs index 875a19feab..fbf205e1de 100644 --- a/crates/pixi_build_discovery/src/discovery.rs +++ b/crates/pixi_build_discovery/src/discovery.rs @@ -49,7 +49,7 @@ pub struct BackendInitializationParams { pub workspace_root: PathBuf, /// The location of the source code. - pub source: Option, + pub build_source: Option, /// The anchor for relative paths to the location of the source code. pub source_anchor: PathBuf, @@ -202,7 +202,7 @@ impl DiscoveredBackend { backend_spec: BackendSpec::JsonRpc(JsonRpcBackendSpec::default_rattler_build(channels)), init_params: BackendInitializationParams { workspace_root: source_dir.clone(), - source: None, + build_source: None, source_anchor: source_dir, manifest_path: recipe_absolute_path, project_model: None, @@ -273,7 +273,7 @@ impl DiscoveredBackend { init_params: BackendInitializationParams { workspace_root, manifest_path: provenance.path.clone(), - source: build_system.source, + build_source: build_system.source, source_anchor: provenance .path .parent() @@ -374,7 +374,7 @@ impl DiscoveredBackend { backend_spec: BackendSpec::JsonRpc(JsonRpcBackendSpec::default_ros_build(channels)), init_params: BackendInitializationParams { workspace_root: source_dir.clone(), - source: None, + build_source: None, source_anchor: source_dir, manifest_path: package_xml_absolute_path, project_model: Some(ProjectModelV1::default()), diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__direct_package_xml.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__direct_package_xml.snap index a1471f9f8b..85a2ed2b32 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__direct_package_xml.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__direct_package_xml.snap @@ -14,7 +14,7 @@ backend-spec: - "https://prefix.dev/conda-forge" init-params: workspace-root: "file:///ros-package" - source: ~ + build-source: ~ source-anchor: "file:///ros-package" manifest-path: "file:///ros-package/package.xml" project-model: diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__direct_recipe.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__direct_recipe.snap index 5f80b4fb51..21a4b935f8 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__direct_recipe.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__direct_recipe.snap @@ -14,7 +14,7 @@ backend-spec: - "https://prefix.dev/conda-forge" init-params: workspace-root: "file:///recipe_yaml" - source: ~ + build-source: ~ source-anchor: "file:///recipe_yaml" manifest-path: "file:///recipe_yaml/recipe.yaml" project-model: ~ diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@inherit__nested.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@inherit__nested.snap index f26b3fc037..a25bdee7fb 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@inherit__nested.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@inherit__nested.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/pixi-build-backends" init-params: workspace-root: "file:///inherit" - source: ~ + build-source: ~ source-anchor: "file:///inherit/nested" manifest-path: "file:///inherit/nested/pixi.toml" project-model: diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested__nested.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested__nested.snap index e72a7a0103..9db3f8a517 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested__nested.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested__nested.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/pixi-build-backends" init-params: workspace-root: "file:///nested" - source: ~ + build-source: ~ source-anchor: "file:///nested/nested" manifest-path: "file:///nested/nested/pixi.toml" project-model: diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested_recipe_yml.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested_recipe_yml.snap index ed10f8a182..c3ef6c8027 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested_recipe_yml.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@nested_recipe_yml.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/conda-forge" init-params: workspace-root: "file:///nested_recipe_yml" - source: ~ + build-source: ~ source-anchor: "file:///nested_recipe_yml" manifest-path: "file:///nested_recipe_yml/recipe/recipe.yml" project-model: ~ diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yaml.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yaml.snap index b999c8a58b..1f9ef76315 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yaml.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yaml.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/conda-forge" init-params: workspace-root: "file:///recipe_yaml" - source: ~ + build-source: ~ source-anchor: "file:///recipe_yaml" manifest-path: "file:///recipe_yaml/recipe.yaml" project-model: ~ diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yml.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yml.snap index eb73a4c1ef..ec4d0d1177 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yml.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@recipe_yml.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/conda-forge" init-params: workspace-root: "file:///recipe_yml" - source: ~ + build-source: ~ source-anchor: "file:///recipe_yml" manifest-path: "file:///recipe_yml/recipe.yml" project-model: ~ diff --git a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@simple.snap b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@simple.snap index 1975d68310..a8dd4a1798 100644 --- a/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@simple.snap +++ b/crates/pixi_build_discovery/tests/snapshots/discovery__discovery@simple.snap @@ -15,7 +15,7 @@ backend-spec: - "https://prefix.dev/pixi-build-backends" init-params: workspace-root: "file:///simple" - source: ~ + build-source: ~ source-anchor: "file:///simple" manifest-path: "file:///simple/pixi.toml" project-model: diff --git a/crates/pixi_build_type_conversions/src/snapshots/pixi_build_type_conversions__project_model__tests__conversions_v1_examples@cpp-git-source.snap b/crates/pixi_build_type_conversions/src/snapshots/pixi_build_type_conversions__project_model__tests__conversions_v1_examples@cpp-git-source.snap new file mode 100644 index 0000000000..c372522d6b --- /dev/null +++ b/crates/pixi_build_type_conversions/src/snapshots/pixi_build_type_conversions__project_model__tests__conversions_v1_examples@cpp-git-source.snap @@ -0,0 +1,42 @@ +--- +source: crates/pixi_build_type_conversions/src/project_model.rs +expression: project_model +--- +{ + "version": "1", + "data": { + "name": "sdl_example", + "version": "0.1.0", + "description": null, + "authors": null, + "license": null, + "licenseFile": null, + "readme": null, + "homepage": null, + "repository": null, + "documentation": null, + "targets": { + "defaultTarget": { + "hostDependencies": { + "sdl2": { + "binary": { + "version": ">=2.26.5,<3.0", + "build": null, + "buildNumber": null, + "fileName": null, + "channel": null, + "subdir": null, + "md5": null, + "sha256": null, + "url": null, + "license": null + } + } + }, + "buildDependencies": {}, + "runDependencies": {} + }, + "targets": {} + } + } +} diff --git a/crates/pixi_cli/src/build.rs b/crates/pixi_cli/src/build.rs index d0c13dfc82..fd647e3ce8 100644 --- a/crates/pixi_cli/src/build.rs +++ b/crates/pixi_cli/src/build.rs @@ -16,7 +16,7 @@ use pixi_utils::variants::VariantConfig; use rattler_conda_types::{GenericVirtualPackage, Platform}; use tempfile::tempdir; -use crate::cli_config::WorkspaceConfig; +use crate::cli_config::{LockAndInstallConfig, WorkspaceConfig}; #[derive(Parser, Debug)] #[clap(verbatim_doc_comment)] @@ -27,6 +27,9 @@ pub struct Args { #[clap(flatten)] pub config_cli: ConfigCli, + #[clap(flatten)] + pub lock_and_install_config: LockAndInstallConfig, + /// The target platform to build for (defaults to the current platform) #[clap(long, short, default_value_t = Platform::current())] pub target_platform: Platform, @@ -113,31 +116,43 @@ pub async fn execute(args: Args) -> miette::Result<()> { manifest_path.display() ) })?; - // Store the manifest location relative to the workspace root when possible to + // Determine the directory that contains the manifest. + let manifest_dir_canonical = if manifest_path_canonical.is_file() { + manifest_path_canonical.parent().ok_or_else(|| { + miette::miette!( + "explicit manifest path: {} doesn't have a parent", + manifest_path.display() + ) + })? + } else { + manifest_path_canonical.as_path() + }; + + // Store the manifest directory relative to the workspace root when possible to // keep the pinned path relocatable and avoid double-prefixing during resolution. - let manifest_spec_path = pathdiff::diff_paths(&manifest_path_canonical, workspace.root()) - .unwrap_or(manifest_path_canonical.clone()); + let manifest_dir_spec = pathdiff::diff_paths(manifest_dir_canonical, workspace.root()) + .unwrap_or_else(|| manifest_dir_canonical.to_path_buf()); let channel_config = workspace.channel_config(); let channels = workspace .default_environment() .channel_urls(&channel_config) .into_diagnostic()?; - // Determine the source of the package. - let source: PinnedSourceSpec = PinnedPathSpec { - path: manifest_spec_path.to_string_lossy().into_owned().into(), + let manifest_source: PinnedSourceSpec = PinnedPathSpec { + path: manifest_dir_spec.to_string_lossy().into_owned().into(), } .into(); // Create the build backend metadata specification. let backend_metadata_spec = BuildBackendMetadataSpec { - source: source.clone(), + manifest_source: manifest_source.clone(), channels: channels.clone(), channel_config: channel_config.clone(), build_environment: build_environment.clone(), variants: Some(variants.clone()), variant_files: Some(variant_files.clone()), enabled_protocols: Default::default(), + pin_override: None, }; let backend_metadata = command_dispatcher .build_backend_metadata(backend_metadata_spec.clone()) @@ -168,7 +183,8 @@ pub async fn execute(args: Args) -> miette::Result<()> { package, // Build into a temporary directory first output_directory: Some(temp_output_dir.path().to_path_buf()), - source: source.clone(), + manifest_source: manifest_source.clone(), + build_source: None, channels: channels.clone(), channel_config: channel_config.clone(), build_environment: build_environment.clone(), diff --git a/crates/pixi_cli/src/update.rs b/crates/pixi_cli/src/update.rs index 7475508a0a..a33664b124 100644 --- a/crates/pixi_cli/src/update.rs +++ b/crates/pixi_cli/src/update.rs @@ -162,6 +162,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let updated_lock_file = UpdateContext::builder(&workspace) .with_lock_file(relaxed_lock_file.clone()) .with_no_install(args.no_install) + .with_update_targets(specs.packages.clone()) .finish() .await? .update() diff --git a/crates/pixi_command_dispatcher/Cargo.toml b/crates/pixi_command_dispatcher/Cargo.toml index aa2697d8bb..8160eb2e95 100644 --- a/crates/pixi_command_dispatcher/Cargo.toml +++ b/crates/pixi_command_dispatcher/Cargo.toml @@ -74,3 +74,6 @@ tokio = { workspace = true, features = ["macros"] } [features] slow_integration_tests = [] + +[package.metadata.cargo-machete] +ignored = ["serde_with"] diff --git a/crates/pixi_command_dispatcher/src/build/build_cache.rs b/crates/pixi_command_dispatcher/src/build/build_cache.rs index 6b1c7da725..c76324df6f 100644 --- a/crates/pixi_command_dispatcher/src/build/build_cache.rs +++ b/crates/pixi_command_dispatcher/src/build/build_cache.rs @@ -356,7 +356,7 @@ impl<'a> From<&'a DiscoveredBackend> for PackageBuildInputHash { // These fields are not relevant for the package build input hash workspace_root: _, - source: _, + build_source: _, source_anchor: _, manifest_path: _, } = &value.init_params; diff --git a/crates/pixi_command_dispatcher/src/build/conversion.rs b/crates/pixi_command_dispatcher/src/build/conversion.rs index a9244dc401..bea7b35a49 100644 --- a/crates/pixi_command_dispatcher/src/build/conversion.rs +++ b/crates/pixi_command_dispatcher/src/build/conversion.rs @@ -98,7 +98,8 @@ pub fn from_package_spec_v1(source: PackageSpecV1) -> pixi_spec::PixiSpec { } pub(crate) fn package_metadata_to_source_records( - source: &PinnedSourceSpec, + manifest_source: &PinnedSourceSpec, + build_source: Option<&PinnedSourceSpec>, packages: &[CondaPackageMetadata], package: &PackageName, input_hash: &Option, @@ -111,7 +112,8 @@ pub(crate) fn package_metadata_to_source_records( .map(|p| { SourceRecord { input_hash: input_hash.clone(), - source: source.clone(), + manifest_source: manifest_source.clone(), + build_source: build_source.cloned(), sources: p .sources .iter() diff --git a/crates/pixi_command_dispatcher/src/build_backend_metadata/mod.rs b/crates/pixi_command_dispatcher/src/build_backend_metadata/mod.rs index e3eeb6873c..88e5224893 100644 --- a/crates/pixi_command_dispatcher/src/build_backend_metadata/mod.rs +++ b/crates/pixi_command_dispatcher/src/build_backend_metadata/mod.rs @@ -49,8 +49,8 @@ fn warn_once_per_backend(backend_name: &str) { /// particular source. #[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize)] pub struct BuildBackendMetadataSpec { - /// The source specification - pub source: PinnedSourceSpec, + /// The source specification where manifest is located at. + pub manifest_source: PinnedSourceSpec, /// The channel configuration to use for the build backend. pub channel_config: ChannelConfig, @@ -71,13 +71,21 @@ pub struct BuildBackendMetadataSpec { /// The protocols that are enabled for this source #[serde(skip_serializing_if = "crate::is_default")] pub enabled_protocols: EnabledProtocols, + + /// Optional override for the pinned build source of the current package. + /// When set, this takes precedence over any discovered build_source. + #[serde(skip)] + pub pin_override: Option, } /// The metadata of a source checkout. #[derive(Debug)] pub struct BuildBackendMetadata { /// The source checkout that the manifest was extracted from. - pub source: PinnedSourceSpec, + pub manifest_source: PinnedSourceSpec, + + /// The source checkout from which we want to build package. + pub build_source: Option, /// The cache entry that contains the metadata acquired from the build /// backend. @@ -95,7 +103,7 @@ impl BuildBackendMetadataSpec { skip_all, name="backend-metadata", fields( - source = %self.source, + source = %self.manifest_source, platform = %self.build_environment.host_platform, ) )] @@ -105,21 +113,46 @@ impl BuildBackendMetadataSpec { log_sink: UnboundedSender, ) -> Result> { // Ensure that the source is checked out before proceeding. - let source_checkout = command_dispatcher - .checkout_pinned_source(self.source.clone()) + let manifest_source_checkout = command_dispatcher + .checkout_pinned_source(self.manifest_source.clone()) .await .map_err_with(BuildBackendMetadataError::SourceCheckout)?; // Discover information about the build backend from the source code (cached by path). let discovered_backend = command_dispatcher .discover_backend( - &source_checkout.path, + &manifest_source_checkout.path, self.channel_config.clone(), self.enabled_protocols.clone(), ) .await .map_err_with(BuildBackendMetadataError::Discovery)?; + let build_source_checkout = if let Some(pin_override) = &self.pin_override { + Some( + command_dispatcher + .checkout_pinned_source(pin_override.clone()) + .await + .map_err_with(BuildBackendMetadataError::SourceCheckout)?, + ) + } else if let Some(build_source) = &discovered_backend.init_params.build_source { + Some( + command_dispatcher + .pin_and_checkout(build_source.clone()) + .await + .map_err_with(BuildBackendMetadataError::SourceCheckout)?, + ) + } else { + None + }; + + let (build_source_checkout, build_source) = if let Some(checkout) = build_source_checkout { + let pinned = checkout.pinned.clone(); + (checkout, Some(pinned)) + } else { + (manifest_source_checkout.clone(), None) + }; + // Calculate the hash of the project model let additional_glob_hash = calculate_additional_glob_hash( &discovered_backend.init_params.project_model, @@ -144,7 +177,7 @@ impl BuildBackendMetadataSpec { if !skip_cache { if let Some(metadata) = Self::verify_cache_freshness( - &source_checkout, + &build_source_checkout, &command_dispatcher, metadata, &additional_glob_hash, @@ -154,7 +187,8 @@ impl BuildBackendMetadataSpec { return Ok(BuildBackendMetadata { metadata, cache_entry, - source: source_checkout.pinned, + manifest_source: manifest_source_checkout.pinned, + build_source, }); } } else { @@ -164,22 +198,24 @@ impl BuildBackendMetadataSpec { warn_once_per_backend(backend_name); } + let build_source_dir = build_source_checkout.path.clone(); // Instantiate the backend with the discovered information. - let backend = command_dispatcher - .instantiate_backend(InstantiateBackendSpec { - backend_spec: discovered_backend - .backend_spec - .clone() - .resolve(SourceAnchor::from(SourceSpec::from(self.source.clone()))), - init_params: discovered_backend.init_params.clone(), - channel_config: self.channel_config.clone(), - enabled_protocols: self.enabled_protocols.clone(), - }) - .await - .map_err_with(BuildBackendMetadataError::Initialize)?; + let backend = + command_dispatcher + .instantiate_backend(InstantiateBackendSpec { + backend_spec: discovered_backend.backend_spec.clone().resolve( + SourceAnchor::from(SourceSpec::from(self.manifest_source.clone())), + ), + init_params: discovered_backend.init_params.clone(), + build_source_dir, + channel_config: self.channel_config.clone(), + enabled_protocols: self.enabled_protocols.clone(), + }) + .await + .map_err_with(BuildBackendMetadataError::Initialize)?; // Call the conda_outputs method to get metadata. - let source = source_checkout.pinned.clone(); + let manifest_source = manifest_source_checkout.pinned.clone(); if !backend.capabilities().provides_conda_outputs() { return Err(CommandDispatcherError::Failed( BuildBackendMetadataError::BackendMissingCapabilities( @@ -195,7 +231,7 @@ impl BuildBackendMetadataSpec { let metadata = self .call_conda_outputs( command_dispatcher, - source_checkout, + build_source_checkout, backend, additional_glob_hash, log_sink, @@ -210,9 +246,10 @@ impl BuildBackendMetadataSpec { .map_err(CommandDispatcherError::Failed)?; Ok(BuildBackendMetadata { + manifest_source, + build_source, metadata, cache_entry, - source, }) } @@ -428,7 +465,7 @@ impl BuildBackendMetadataSpec { build_environment: self.build_environment.clone(), build_variants: self.variants.clone().unwrap_or_default(), enabled_protocols: self.enabled_protocols.clone(), - pinned_source: self.source.clone(), + pinned_source: self.manifest_source.clone(), } } } diff --git a/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs b/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs index cb5a3ffabd..07444fb4ba 100644 --- a/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs +++ b/crates/pixi_command_dispatcher/src/command_dispatcher/instantiate_backend.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use miette::Diagnostic; use pixi_build_discovery::{ BackendInitializationParams, BackendSpec, CommandSpec, EnabledProtocols, @@ -8,7 +10,7 @@ use pixi_build_frontend::{ tool::{IsolatedTool, SystemTool, Tool}, }; use pixi_build_types::{PixiBuildApiVersion, procedures::initialize::InitializeParams}; -use pixi_spec::{SourceLocationSpec, SpecConversionError}; +use pixi_spec::SpecConversionError; use rattler_conda_types::ChannelConfig; use rattler_shell::{ activation::{ActivationError, ActivationVariables, Activator}, @@ -18,7 +20,7 @@ use rattler_virtual_packages::DetectVirtualPackageError; use thiserror::Error; use crate::{ - BuildEnvironment, CommandDispatcher, CommandDispatcherErrorResultExt, + BuildEnvironment, CommandDispatcher, CommandDispatcherErrorResultExt, SourceCheckoutError, command_dispatcher::error::CommandDispatcherError, instantiate_tool_env::{ InstantiateToolEnvironmentError, InstantiateToolEnvironmentResult, @@ -34,6 +36,9 @@ pub struct InstantiateBackendSpec { /// The parameters to initialize the backend with pub init_params: BackendInitializationParams, + /// The source directory to use for the backend + pub build_source_dir: PathBuf, + /// The channel configuration to use for any source packages required by the /// backend. pub channel_config: ChannelConfig, @@ -50,13 +55,7 @@ impl CommandDispatcher { ) -> Result> { let BackendSpec::JsonRpc(backend_spec) = spec.backend_spec; - let source_dir = if let Some(SourceLocationSpec::Path(path)) = spec.init_params.source { - path.resolve(&spec.init_params.source_anchor) - .map_err(InstantiateBackendError::from) - .map_err(CommandDispatcherError::Failed)? - } else { - spec.init_params.source_anchor - }; + let source_dir = spec.build_source_dir; // Canonicalize the source_dir to ensure it's a fully resolved absolute path // without any relative components like ".." or "." @@ -218,4 +217,8 @@ pub enum InstantiateBackendError { #[error(transparent)] SpecConversionError(#[from] SpecConversionError), + + #[error(transparent)] + #[diagnostic(transparent)] + SourceCheckout(#[from] SourceCheckoutError), } diff --git a/crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs b/crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs index 804d54b5e6..5523a8196b 100644 --- a/crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs +++ b/crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs @@ -20,7 +20,7 @@ use pixi_build_frontend::BackendOverride; use pixi_git::resolver::GitResolver; use pixi_glob::GlobHashCache; use pixi_record::{PinnedPathSpec, PinnedSourceSpec, PixiRecord}; -use pixi_spec::{SourceLocationSpec, SourceSpec}; +use pixi_spec::SourceLocationSpec; use rattler::package_cache::PackageCache; use rattler_conda_types::{ChannelConfig, GenericVirtualPackage, Platform}; use rattler_networking::LazyClient; @@ -223,6 +223,7 @@ pub(crate) struct SourceBuildCacheStatusId(pub usize); pub(crate) struct InstantiatedToolEnvId(pub usize); /// A message send to the dispatch task. +#[allow(clippy::large_enum_variant)] #[derive(derive_more::From)] pub(crate) enum ForegroundMessage { SolveCondaEnvironment(SolveCondaEnvironmentTask), @@ -560,7 +561,7 @@ impl CommandDispatcher { self.execute_task(spec).await } - /// Checks out a particular source based on a source spec. + /// Checks out a particular source based on a source location spec. /// /// This function resolves the source specification to a concrete checkout /// by: @@ -576,9 +577,9 @@ impl CommandDispatcher { /// same source is used multiple times. pub async fn pin_and_checkout( &self, - source_spec: SourceSpec, + source_location_spec: SourceLocationSpec, ) -> Result> { - match source_spec.location { + match source_location_spec { SourceLocationSpec::Url(url) => { unimplemented!("fetching URL sources ({}) is not yet implemented", url.url) } diff --git a/crates/pixi_command_dispatcher/src/install_pixi/mod.rs b/crates/pixi_command_dispatcher/src/install_pixi/mod.rs index 849178e1bd..98b6b1de63 100644 --- a/crates/pixi_command_dispatcher/src/install_pixi/mod.rs +++ b/crates/pixi_command_dispatcher/src/install_pixi/mod.rs @@ -215,7 +215,7 @@ impl InstallPixiEnvironmentSpec { .contains(&source_record.package_record.name); let built_source = command_dispatcher .source_build(SourceBuildSpec { - source: source_record.source.clone(), + manifest_source: source_record.manifest_source.clone(), package: source_record.into(), channel_config: self.channel_config.clone(), channels: self.channels.clone(), @@ -230,6 +230,7 @@ impl InstallPixiEnvironmentSpec { force, // When we install a pixi environment we always build in development mode. build_profile: BuildProfile::Development, + build_source: None, }) .await?; @@ -248,7 +249,7 @@ pub enum InstallPixiEnvironmentError { #[error("failed to build '{}' from '{}'", .0.package_record.name.as_source(), - .0.source)] + .0.manifest_source)] BuildSourceError( Box, #[diagnostic_source] diff --git a/crates/pixi_command_dispatcher/src/instantiate_tool_env/mod.rs b/crates/pixi_command_dispatcher/src/instantiate_tool_env/mod.rs index 665a6367bb..3e83bd31e8 100644 --- a/crates/pixi_command_dispatcher/src/instantiate_tool_env/mod.rs +++ b/crates/pixi_command_dispatcher/src/instantiate_tool_env/mod.rs @@ -216,6 +216,7 @@ impl InstantiateToolEnvironmentSpec { variants: self.variants.clone(), variant_files: self.variant_files.clone(), strategy: SolveStrategy::default(), + pin_overrides: BTreeMap::new(), }) .await .map_err_with(Box::new) diff --git a/crates/pixi_command_dispatcher/src/solve_conda/mod.rs b/crates/pixi_command_dispatcher/src/solve_conda/mod.rs index 876f4dcaab..3617937f42 100644 --- a/crates/pixi_command_dispatcher/src/solve_conda/mod.rs +++ b/crates/pixi_command_dispatcher/src/solve_conda/mod.rs @@ -155,6 +155,8 @@ impl SolveCondaEnvironmentSpec { ), channel: None, }; + let mut record = record.clone(); + record.build_source = source_metadata.build_source.clone(); url_to_source_package.insert(url, (record, repodata_record)); } } @@ -223,7 +225,7 @@ impl SolveCondaEnvironmentSpec { /// Generates a unique URL for a source record. fn unique_url(source: &SourceRecord) -> Url { - let mut url = source.source.identifiable_url(); + let mut url = source.manifest_source.identifiable_url(); // Add unique identifiers to the URL. url.query_pairs_mut() diff --git a/crates/pixi_command_dispatcher/src/solve_pixi/mod.rs b/crates/pixi_command_dispatcher/src/solve_pixi/mod.rs index 80ad930b06..af8f645bd5 100644 --- a/crates/pixi_command_dispatcher/src/solve_pixi/mod.rs +++ b/crates/pixi_command_dispatcher/src/solve_pixi/mod.rs @@ -86,6 +86,12 @@ pub struct PixiEnvironmentSpec { /// The protocols that are enabled for source packages #[serde(skip_serializing_if = "crate::is_default")] pub enabled_protocols: EnabledProtocols, + + /// Optional override for a specific packages: use this pinned + /// source for checkout and as the `package_build_source` instead + /// of pinning anew. + #[serde(skip)] + pub pin_overrides: BTreeMap, } impl Default for PixiEnvironmentSpec { @@ -104,6 +110,7 @@ impl Default for PixiEnvironmentSpec { variants: None, variant_files: None, enabled_protocols: EnabledProtocols::default(), + pin_overrides: BTreeMap::new(), } } } @@ -141,6 +148,7 @@ impl PixiEnvironmentSpec { self.variants.clone(), self.variant_files.clone(), self.enabled_protocols.clone(), + self.pin_overrides.clone(), ) .collect( source_specs diff --git a/crates/pixi_command_dispatcher/src/solve_pixi/source_metadata_collector.rs b/crates/pixi_command_dispatcher/src/solve_pixi/source_metadata_collector.rs index b09010f97a..d4d4faf33d 100644 --- a/crates/pixi_command_dispatcher/src/solve_pixi/source_metadata_collector.rs +++ b/crates/pixi_command_dispatcher/src/solve_pixi/source_metadata_collector.rs @@ -29,6 +29,7 @@ pub struct SourceMetadataCollector { enabled_protocols: EnabledProtocols, variants: Option>>, variant_files: Option>, + pin_overrides: BTreeMap, } #[derive(Default)] @@ -68,6 +69,7 @@ pub enum CollectSourceMetadataError { } impl SourceMetadataCollector { + #[allow(clippy::too_many_arguments)] pub fn new( command_queue: CommandDispatcher, channel_urls: Vec, @@ -76,6 +78,7 @@ impl SourceMetadataCollector { variants: Option>>, variant_files: Option>, enabled_protocols: EnabledProtocols, + pin_overrides: BTreeMap, ) -> Self { Self { command_queue, @@ -85,6 +88,7 @@ impl SourceMetadataCollector { channel_config, variants, variant_files, + pin_overrides, } } @@ -123,7 +127,7 @@ impl SourceMetadataCollector { // Process transitive dependencies for record in &source_metadata.records { chain.push(record.package_record.name.clone()); - let anchor = SourceAnchor::from(SourceSpec::from(record.source.clone())); + let anchor = SourceAnchor::from(SourceSpec::from(record.manifest_source.clone())); for depend in &record.package_record.depends { if let Ok(spec) = MatchSpec::from_str(depend, ParseStrictness::Lenient) { if let Some((name, source_spec)) = spec.name.as_ref().and_then(|name| { @@ -160,10 +164,14 @@ impl SourceMetadataCollector { > { tracing::trace!("Collecting source metadata for {name:#?}"); - // Get the source for the particular package. + // Determine if we should override the build_source pin for this package. + let override_pin = self.pin_overrides.get(&name).cloned(); + + // Always checkout the manifest-defined source location (root), discovery + // will pick build_source; we only override the build pin later. let source = self .command_queue - .pin_and_checkout(spec) + .pin_and_checkout(spec.location) .await .map_err(|err| CollectSourceMetadataError::SourceCheckoutError { name: name.as_source().to_string(), @@ -177,13 +185,14 @@ impl SourceMetadataCollector { .source_metadata(SourceMetadataSpec { package: name.clone(), backend_metadata: BuildBackendMetadataSpec { - source: source.pinned, + manifest_source: source.pinned, channel_config: self.channel_config.clone(), channels: self.channels.clone(), build_environment: self.build_environment.clone(), variants: self.variants.clone(), variant_files: self.variant_files.clone(), enabled_protocols: self.enabled_protocols.clone(), + pin_override: override_pin, }, }) .await @@ -221,7 +230,7 @@ impl SourceMetadataCollector { source_metadata.skipped_packages.clone(), ), name, - pinned_source: Box::new(source_metadata.source.clone()), + pinned_source: Box::new(source_metadata.manifest_source.clone()), }, )); } diff --git a/crates/pixi_command_dispatcher/src/source_build/mod.rs b/crates/pixi_command_dispatcher/src/source_build/mod.rs index a7d97c5b6e..7914bf8f3f 100644 --- a/crates/pixi_command_dispatcher/src/source_build/mod.rs +++ b/crates/pixi_command_dispatcher/src/source_build/mod.rs @@ -10,7 +10,7 @@ use pixi_build_discovery::EnabledProtocols; use pixi_build_frontend::Backend; use pixi_build_types::procedures::conda_outputs::CondaOutputsParams; use pixi_record::{PinnedSourceSpec, PixiRecord}; -use pixi_spec::{SourceAnchor, SourceSpec}; +use pixi_spec::{SourceAnchor, SourceLocationSpec, SourceSpec}; use rattler_conda_types::{ ChannelConfig, ChannelUrl, ConvertSubdirError, InvalidPackageNameError, PackageRecord, Platform, RepoDataRecord, prefix::Prefix, @@ -52,7 +52,11 @@ pub struct SourceBuildSpec { pub package: PackageIdentifier, /// The location of the source code to build. - pub source: PinnedSourceSpec, + pub manifest_source: PinnedSourceSpec, + + /// Optional source spec of sources which will be built. + #[serde(skip_serializing_if = "Option::is_none")] + pub build_source: Option, /// The channel configuration to use when resolving metadata pub channel_config: ChannelConfig, @@ -120,7 +124,7 @@ impl SourceBuildSpec { skip_all, name = "source-build", fields( - source= %self.source, + source= %self.manifest_source, package = %self.package, ) )] @@ -139,10 +143,11 @@ impl SourceBuildSpec { } else { // Query the source build cache. let build_cache = command_dispatcher + .clone() .source_build_cache_status(SourceBuildCacheStatusSpec { package: self.package.clone(), build_environment: self.build_environment.clone(), - source: self.source.clone(), + source: self.manifest_source.clone(), channels: self.channels.clone(), channel_config: self.channel_config.clone(), enabled_protocols: self.enabled_protocols.clone(), @@ -158,7 +163,7 @@ impl SourceBuildSpec { if !self.force { // If the build is up to date, we can return the cached build. tracing::debug!( - source = %self.source, + source = %self.manifest_source, package = ?cached_build.record.package_record.name, build = %cached_build.record.package_record.build, output = %cached_build.record.file_name, @@ -180,7 +185,7 @@ impl SourceBuildSpec { self.package.name.as_normalized() ); tracing::debug!( - source = %self.source, + source = %self.manifest_source, package = ?cached_build.record.package_record.name, build = %cached_build.record.package_record.build, output = %cached_build.record.file_name, @@ -196,7 +201,7 @@ impl SourceBuildSpec { match &*build_cache.cached_build.lock().await { CachedBuildStatus::Stale(existing) => { tracing::debug!( - source = %self.source, + source = %self.manifest_source, package = ?existing.record.package_record.name, build = %existing.record.package_record.build, "rebuilding stale source build", @@ -204,7 +209,7 @@ impl SourceBuildSpec { } CachedBuildStatus::Missing => { tracing::debug!( - source = %self.source, + source = %self.manifest_source, "no cached source build; starting fresh build", ); } @@ -215,8 +220,8 @@ impl SourceBuildSpec { }; // Check out the source code. - let source_checkout = command_dispatcher - .checkout_pinned_source(self.source.clone()) + let manifest_source_checkout = command_dispatcher + .checkout_pinned_source(self.manifest_source.clone()) .await .map_err_with(SourceBuildError::SourceCheckout)?; @@ -224,7 +229,7 @@ impl SourceBuildSpec { // path). let discovered_backend = command_dispatcher .discover_backend( - &source_checkout.path, + &manifest_source_checkout.path, self.channel_config.clone(), self.enabled_protocols.clone(), ) @@ -234,19 +239,56 @@ impl SourceBuildSpec { // Compute the package input hash for caching purposes. let package_build_input_hash = PackageBuildInputHash::from(discovered_backend.as_ref()); + // Determine the build source to use: either from lock file or workspace + + // Ensure legacy lock entries that missed the git subdirectory pick it up from the + // manifest so we check out the correct directory. + let mut build_source = self.build_source.clone(); + if let (Some(PinnedSourceSpec::Git(pinned_git)), Some(SourceLocationSpec::Git(git_spec))) = ( + build_source.as_mut(), + discovered_backend.init_params.build_source.clone(), + ) { + if pinned_git.source.subdirectory.is_none() { + pinned_git.source.subdirectory = git_spec.subdirectory.clone(); + } + } + + // Here we have to get path in which we will run build. We have those options in order of decreasing priority: + // 1. Lock file `package_build_source`. Since we're running lock file update before building package it should pin source in there. + // 2. Manifest package build. This can happen if package isn't added to the dependencies of manifest, so no pinning happens in that case. + // 3. Manifest source. Just assume that source is located at the same directory as the manifest. + let build_source_dir = if let Some(pinned_build_source) = build_source { + let build_source_checkout = command_dispatcher + .checkout_pinned_source(pinned_build_source) + .await + .map_err_with(SourceBuildError::SourceCheckout)?; + build_source_checkout.path + } else if let Some(manifest_build_source) = + discovered_backend.init_params.build_source.clone() + { + let build_source_checkout = command_dispatcher + .pin_and_checkout(manifest_build_source) + .await + .map_err_with(SourceBuildError::SourceCheckout)?; + build_source_checkout.path + } else { + manifest_source_checkout.path + }; + // Instantiate the backend with the discovered information. - let backend = command_dispatcher - .instantiate_backend(InstantiateBackendSpec { - backend_spec: discovered_backend - .backend_spec - .clone() - .resolve(SourceAnchor::from(SourceSpec::from(self.source.clone()))), - init_params: discovered_backend.init_params.clone(), - channel_config: self.channel_config.clone(), - enabled_protocols: self.enabled_protocols.clone(), - }) - .await - .map_err_with(SourceBuildError::Initialize)?; + let backend = + command_dispatcher + .instantiate_backend(InstantiateBackendSpec { + backend_spec: discovered_backend.backend_spec.clone().resolve( + SourceAnchor::from(SourceSpec::from(self.manifest_source.clone())), + ), + init_params: discovered_backend.init_params.clone(), + build_source_dir, + channel_config: self.channel_config.clone(), + enabled_protocols: self.enabled_protocols.clone(), + }) + .await + .map_err_with(SourceBuildError::Initialize)?; // Determine the working directory for the build. let work_directory = match std::mem::take(&mut self.work_directory) { @@ -254,7 +296,7 @@ impl SourceBuildSpec { None => command_dispatcher.cache_dirs().working_dirs().join( WorkDirKey { source: SourceRecordOrCheckout::Record { - pinned: self.source.clone(), + pinned: self.manifest_source.clone(), package_name: self.package.name.clone(), }, host_platform: self.build_environment.host_platform, @@ -264,7 +306,7 @@ impl SourceBuildSpec { ), }; tracing::debug!( - source = %self.source, + source = %self.manifest_source, work_directory = %work_directory.display(), backend = backend.identifier(), "using work directory for source build", @@ -280,7 +322,7 @@ impl SourceBuildSpec { } // Build the package using the v1 build method. - let source_for_logging = self.source.clone(); + let source_for_logging = self.manifest_source.clone(); let mut built_source = self .build_v1( command_dispatcher, @@ -374,7 +416,7 @@ impl SourceBuildSpec { // so on the next run we can distinguish between up to date ( was already saved from previous session) // and new that was just build now let cached_build = CachedBuild { - source: source_checkout + source: manifest_source_checkout .pinned .is_mutable() .then_some(built_source.metadata.clone()), @@ -396,9 +438,41 @@ impl SourceBuildSpec { }) } + /// Little helper function the build a `BuildHostPackage` from expected and + /// installed records. + fn extract_prefix_repodata( + records: Vec, + prefix: Option, + ) -> Vec { + let Some(prefix) = prefix else { + return vec![]; + }; + + records + .into_iter() + .map(|record| match record { + PixiRecord::Binary(repodata_record) => BuildHostPackage { + repodata_record, + source: None, + }, + PixiRecord::Source(source) => { + let repodata_record = prefix + .resolved_source_records + .get(&source.package_record.name) + .cloned() + .expect("the source record should be present in the result sources"); + BuildHostPackage { + repodata_record, + source: Some(source.manifest_source), + } + } + }) + .collect() + } + /// Returns whether the package should be built in an editable mode. fn editable(&self) -> bool { - self.build_profile == BuildProfile::Development && self.source.is_mutable() + self.build_profile == BuildProfile::Development && self.manifest_source.is_mutable() } async fn build_v1( @@ -410,7 +484,7 @@ impl SourceBuildSpec { reporter: Option>, mut log_sink: UnboundedSender, ) -> Result> { - let source_anchor = SourceAnchor::from(SourceSpec::from(self.source.clone())); + let source_anchor = SourceAnchor::from(SourceSpec::from(self.manifest_source.clone())); let host_platform = self.build_environment.host_platform; let build_platform = self.build_environment.build_platform; @@ -633,7 +707,7 @@ impl SourceBuildSpec { }), backend, package: self.package, - source: self.source, + source: self.manifest_source, work_directory, channels: self.channels, channel_config: self.channel_config, @@ -656,38 +730,6 @@ impl SourceBuildSpec { }) } - /// Little helper function the build a `BuildHostPackage` from expected and - /// installed records. - fn extract_prefix_repodata( - records: Vec, - prefix: Option, - ) -> Vec { - let Some(prefix) = prefix else { - return vec![]; - }; - - records - .into_iter() - .map(|record| match record { - PixiRecord::Binary(repodata_record) => BuildHostPackage { - repodata_record, - source: None, - }, - PixiRecord::Source(source) => { - let repodata_record = prefix - .resolved_source_records - .get(&source.package_record.name) - .cloned() - .expect("the source record should be present in the result sources"); - BuildHostPackage { - repodata_record, - source: Some(source.source), - } - } - }) - .collect() - } - async fn solve_dependencies( &self, name: String, @@ -721,6 +763,7 @@ impl SourceBuildSpec { variants: self.variants.clone(), variant_files: self.variant_files.clone(), enabled_protocols: self.enabled_protocols.clone(), + pin_overrides: BTreeMap::new(), }) .await } diff --git a/crates/pixi_command_dispatcher/src/source_metadata/mod.rs b/crates/pixi_command_dispatcher/src/source_metadata/mod.rs index ef0aba85a3..cc6d01cfe0 100644 --- a/crates/pixi_command_dispatcher/src/source_metadata/mod.rs +++ b/crates/pixi_command_dispatcher/src/source_metadata/mod.rs @@ -1,6 +1,9 @@ mod cycle; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; pub use cycle::{Cycle, CycleEnvironment}; use futures::TryStreamExt; @@ -43,7 +46,11 @@ pub struct SourceMetadataSpec { pub struct SourceMetadata { /// Information about the source checkout that was used to build the /// package. - pub source: PinnedSourceSpec, + pub manifest_source: PinnedSourceSpec, + + /// The optional location of where the actual source code is located, + /// this is used mainly for out-of-tree builds + pub build_source: Option, /// All the source records for this particular package. pub records: Vec, @@ -57,7 +64,7 @@ impl SourceMetadataSpec { skip_all, name = "source-metadata", fields( - source= %self.backend_metadata.source, + source= %self.backend_metadata.manifest_source, name = %self.package.as_source(), platform = %self.backend_metadata.build_environment.host_platform, ) @@ -79,17 +86,19 @@ impl SourceMetadataSpec { MetadataKind::GetMetadata { packages } => { // Convert the metadata to source records. let records = conversion::package_metadata_to_source_records( - &build_backend_metadata.source, + &build_backend_metadata.manifest_source, + build_backend_metadata.build_source.as_ref(), packages, &self.package, &build_backend_metadata.metadata.input_hash, ); Ok(SourceMetadata { - source: build_backend_metadata.source.clone(), + manifest_source: build_backend_metadata.manifest_source.clone(), records, // As the GetMetadata kind returns all records at once and we don't solve them we can skip this. skipped_packages: Default::default(), + build_source: build_backend_metadata.build_source.clone(), }) } MetadataKind::Outputs { outputs } => { @@ -104,15 +113,17 @@ impl SourceMetadataSpec { &command_dispatcher, output, build_backend_metadata.metadata.input_hash.clone(), - build_backend_metadata.source.clone(), + build_backend_metadata.manifest_source.clone(), + build_backend_metadata.build_source.clone(), reporter.clone(), )); } Ok(SourceMetadata { - source: build_backend_metadata.source.clone(), + manifest_source: build_backend_metadata.manifest_source.clone(), records: futures.try_collect().await?, skipped_packages, + build_source: build_backend_metadata.build_source.clone(), }) } } @@ -123,15 +134,17 @@ impl SourceMetadataSpec { command_dispatcher: &CommandDispatcher, output: &CondaOutput, input_hash: Option, - source: PinnedSourceSpec, + manifest_source: PinnedSourceSpec, + build_source: Option, reporter: Option>, ) -> Result> { - let source_anchor = SourceAnchor::from(SourceSpec::from(source.clone())); + let source_anchor = SourceAnchor::from(SourceSpec::from(manifest_source.clone())); // Solve the build environment for the output. let build_dependencies = output .build_dependencies .as_ref() + // TODO(tim): we need to check if this works for out-of-tree builds with source dependencies in the out-of-tree, this might be incorrectly anchored .map(|deps| Dependencies::new(deps, Some(source_anchor.clone()))) .transpose() .map_err(SourceMetadataError::from) @@ -340,8 +353,9 @@ impl SourceMetadataSpec { // These are not important at this point. experimental_extra_depends: Default::default(), }, - source, + manifest_source, input_hash, + build_source, sources: sources .into_iter() .map(|(name, source)| (name.as_source().to_string(), source)) @@ -360,6 +374,12 @@ impl SourceMetadataSpec { if dependencies.dependencies.is_empty() { return Ok(vec![]); } + let pin_overrides = self + .backend_metadata + .pin_override + .as_ref() + .map(|pinned| BTreeMap::from([(pkg_name.clone(), pinned.clone())])) + .unwrap_or_default(); match command_dispatcher .solve_pixi_environment(PixiEnvironmentSpec { name: Some(format!("{} ({})", pkg_name.as_source(), env_type)), @@ -383,6 +403,7 @@ impl SourceMetadataSpec { variants: self.backend_metadata.variants.clone(), variant_files: self.backend_metadata.variant_files.clone(), enabled_protocols: self.backend_metadata.enabled_protocols.clone(), + pin_overrides, }) .await { diff --git a/crates/pixi_command_dispatcher/tests/integration/event_reporter.rs b/crates/pixi_command_dispatcher/tests/integration/event_reporter.rs index fec1a81a8c..dd79ad6fdd 100644 --- a/crates/pixi_command_dispatcher/tests/integration/event_reporter.rs +++ b/crates/pixi_command_dispatcher/tests/integration/event_reporter.rs @@ -19,6 +19,7 @@ use pixi_command_dispatcher::{ use pixi_git::resolver::RepositoryReference; use serde::Serialize; +#[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize)] #[serde(tag = "type", rename_all = "kebab-case")] pub enum Event { diff --git a/crates/pixi_command_dispatcher/tests/integration/event_tree.rs b/crates/pixi_command_dispatcher/tests/integration/event_tree.rs index 14a33ecbf3..234df1521a 100644 --- a/crates/pixi_command_dispatcher/tests/integration/event_tree.rs +++ b/crates/pixi_command_dispatcher/tests/integration/event_tree.rs @@ -130,7 +130,7 @@ impl EventTree { format!( "{} @ {}", &spec.package.as_source(), - spec.backend_metadata.source + spec.backend_metadata.manifest_source ), ); builder.set_event_parent((*id).into(), *context); @@ -146,7 +146,7 @@ impl EventTree { } Event::SourceMetadataFinished { .. } => {} Event::BuildBackendMetadataQueued { id, context, spec } => { - build_backend_metadata_label.insert(*id, spec.source.to_string()); + build_backend_metadata_label.insert(*id, spec.manifest_source.to_string()); builder.set_event_parent((*id).into(), *context); } Event::BuildBackendMetadataStarted { id } => { @@ -162,7 +162,11 @@ impl EventTree { Event::SourceBuildQueued { id, context, spec } => { source_build_label.insert( *id, - format!("{} @ {}", spec.package.name.as_source(), spec.source), + format!( + "{} @ {}", + spec.package.name.as_source(), + spec.manifest_source + ), ); builder.set_event_parent((*id).into(), *context); } diff --git a/crates/pixi_core/src/lock_file/satisfiability/mod.rs b/crates/pixi_core/src/lock_file/satisfiability/mod.rs index cfe809d1dd..29d7898f00 100644 --- a/crates/pixi_core/src/lock_file/satisfiability/mod.rs +++ b/crates/pixi_core/src/lock_file/satisfiability/mod.rs @@ -397,6 +397,11 @@ pub enum PlatformUnsat { #[error("'{name}' is locked as a conda package but only requested by pypi dependencies")] CondaPackageShouldBePypi { name: String }, + + #[error( + "the locked package build source for '{0}' does not match the requested build source, {1}" + )] + PackageBuildSourceMismatch(String, SourceMismatchError), } #[derive(Debug, Error, Diagnostic)] @@ -1320,9 +1325,9 @@ pub(crate) async fn verify_package_platform_satisfiability( Cow::Owned(format!( "{} @ {}", record.package_record.name.as_source(), - &record.source + &record.manifest_source )), - SourceSpec::from(record.source.clone()).into(), + SourceSpec::from(record.manifest_source.clone()).into(), ), }; @@ -1492,7 +1497,7 @@ pub(crate) async fn verify_package_platform_satisfiability( .iter() .filter_map(PixiRecord::as_source) { - let Some(path_record) = source_record.source.as_path() else { + let Some(path_record) = source_record.manifest_source.as_path() else { continue; }; @@ -1588,6 +1593,9 @@ pub(crate) async fn verify_package_platform_satisfiability( ))); } + // Verify the pixi build package's package_build_source matches the manifest. + verify_build_source_matches_manifest(environment, locked_pixi_records)?; + Ok(VerifiedIndividualEnvironment { expected_conda_packages, conda_packages_used_by_pypi, @@ -1706,7 +1714,7 @@ fn find_matching_source_package( }; source_package - .source + .manifest_source .satisfies(&source_spec) .map_err(|e| PlatformUnsat::SourcePackageMismatch(name.as_source().to_string(), e))?; @@ -1841,6 +1849,95 @@ impl Display for EditablePackagesMismatch { } } +/// Verify that the current package's build.source in the manifest +/// matches the lock file's `package_build_source` (if applicable). +/// Path-based sources are not represented in the lock file's +/// `package_build_source` and are skipped. +fn verify_build_source_matches_manifest( + environment: &Environment<'_>, + locked_pixi_records: &PixiRecordsByName, +) -> Result<(), Box> { + let Some(pkg_manifest) = environment.workspace().package.as_ref() else { + return Ok(()); + }; + let Some(pkg_name) = &pkg_manifest.value.package.name else { + return Ok(()); + }; + let Some(requested_loc) = pkg_manifest.value.build.source.clone() else { + return Ok(()); + }; + + // Find the source record for the current package in locked conda packages. + let Some(record) = locked_pixi_records + .records + .iter() + .find(|r| r.package_record().name.as_source() == pkg_name) + else { + return Ok(()); + }; + + let PixiRecord::Source(src_record) = record else { + return Ok(()); + }; + + match requested_loc { + pixi_spec::SourceLocationSpec::Url(url_spec) => { + let Some(locked_url) = src_record.build_source.as_ref().and_then(|p| p.as_url()) else { + return Err(Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + SourceMismatchError::SourceTypeMismatch, + ))); + }; + locked_url.satisfies(&url_spec).map_err(|e| { + Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + e, + )) + }) + } + pixi_spec::SourceLocationSpec::Git(mut git_spec) => { + let Some(locked_git) = src_record.build_source.as_ref().and_then(|p| p.as_git()) else { + return Err(Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + SourceMismatchError::SourceTypeMismatch, + ))); + }; + // If the lock omitted subdirectory for package_build_source, ignore subdirectory + // difference in comparison. + if locked_git.source.subdirectory.is_none() { + git_spec.subdirectory = None; + } + // If manifest does not specify a rev (branch/tag/rev), treat it as DefaultBranch + // to ensure we compare references, not silently accept any locked branch. + if git_spec.rev.is_none() { + git_spec.rev = Some(pixi_spec::GitReference::DefaultBranch); + } + locked_git.satisfies(&git_spec).map_err(|e| { + Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + e, + )) + }) + } + pixi_spec::SourceLocationSpec::Path(path_spec) => { + let Some(locked_path) = src_record.build_source.as_ref().and_then(|p| p.as_path()) + else { + return Err(Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + SourceMismatchError::SourceTypeMismatch, + ))); + }; + + locked_path.satisfies(&path_spec).map_err(|e| { + Box::new(PlatformUnsat::PackageBuildSourceMismatch( + src_record.package_record.name.as_source().to_string(), + e, + )) + }) + } + } +} + #[cfg(test)] mod tests { use std::{ diff --git a/crates/pixi_core/src/lock_file/update.rs b/crates/pixi_core/src/lock_file/update.rs index 1621e7dbd5..27796fc072 100644 --- a/crates/pixi_core/src/lock_file/update.rs +++ b/crates/pixi_core/src/lock_file/update.rs @@ -1,6 +1,6 @@ use std::{ cmp::PartialEq, - collections::{HashMap, HashSet, hash_map::Entry}, + collections::{BTreeMap, HashMap, HashSet, hash_map::Entry}, future::{Future, ready}, iter, path::PathBuf, @@ -851,6 +851,9 @@ pub struct UpdateContext<'p> { /// The progress bar where all the command dispatcher progress will be /// placed. dispatcher_progress_bar: ProgressBar, + + /// Optional list of packages explicitly targeted for update. + update_targets: Option>, } impl<'p> UpdateContext<'p> { @@ -1027,6 +1030,9 @@ pub struct UpdateContextBuilder<'p> { /// Set the command dispatcher to use for the update process. command_dispatcher: Option, + + /// Optional list of package names explicitly targeted for update. + update_targets: Option>, } impl<'p> UpdateContextBuilder<'p> { @@ -1067,6 +1073,17 @@ impl<'p> UpdateContextBuilder<'p> { } } + /// Sets the packages explicitly targeted for update. + pub fn with_update_targets( + self, + update_targets: Option>, + ) -> Self { + Self { + update_targets, + ..self + } + } + /// Explicitly set the environments that are considered out-of-date. Only /// these environments will be updated during the update process. pub fn with_outdated_environments( @@ -1309,6 +1326,7 @@ impl<'p> UpdateContextBuilder<'p> { dispatcher_progress_bar: anchor_pb, no_install: self.no_install, + update_targets: self.update_targets, }) } } @@ -1326,6 +1344,7 @@ impl<'p> UpdateContext<'p> { glob_hash_cache: None, mapping_client: None, command_dispatcher: None, + update_targets: None, } } @@ -1396,6 +1415,33 @@ impl<'p> UpdateContext<'p> { .unwrap_or_default(); // Spawn a task to solve the group. + // Determine override pinned sources for source packages when performing + // a targeted update. + let pin_overrides = (|| { + let targets = self.update_targets.as_ref()?; + if targets.is_empty() { + return None; + } + Some( + locked_group_records + .records + .iter() + .filter_map(|r| match r { + PixiRecord::Source(src) => { + let name = src.package_record.name.clone(); + if targets.contains(name.as_source()) { + src.build_source.clone().map(|spec| (name, spec)) + } else { + None + } + } + _ => None, + }) + .collect(), + ) + })() + .unwrap_or_default(); + let group_solve_task = spawn_solve_conda_environment_task( source.clone(), locked_group_records, @@ -1403,6 +1449,7 @@ impl<'p> UpdateContext<'p> { platform, channel_priority, self.command_dispatcher.clone(), + pin_overrides, ) .map_err(Report::new) .boxed_local(); @@ -1908,6 +1955,7 @@ async fn spawn_solve_conda_environment_task( platform: Platform, channel_priority: ChannelPriority, command_dispatcher: CommandDispatcher, + pin_overrides: BTreeMap, ) -> Result { // Get the dependencies for this platform let dependencies = group.combined_dependencies(Some(platform)); @@ -1964,6 +2012,7 @@ async fn spawn_solve_conda_environment_task( let start = Instant::now(); // Solve the environment using the command dispatcher. + // Determine if we should override the pinned source for the current package let mut records = command_dispatcher .solve_pixi_environment(PixiEnvironmentSpec { name: Some(group_name.to_string()), @@ -1979,6 +2028,7 @@ async fn spawn_solve_conda_environment_task( variants: Some(variants), variant_files: Some(variant_files), enabled_protocols: Default::default(), + pin_overrides, }) .await .map_err(|source| SolveCondaEnvironmentError::SolveFailed { diff --git a/crates/pixi_global/Cargo.toml b/crates/pixi_global/Cargo.toml index 51a7220465..ea691d7874 100644 --- a/crates/pixi_global/Cargo.toml +++ b/crates/pixi_global/Cargo.toml @@ -46,8 +46,8 @@ rattler_virtual_packages = { workspace = true } regex = { workspace = true } rstest = { workspace = true } serde = { workspace = true } +serde_derive = { workspace = true } serde_json = { workspace = true } -serde_with = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/crates/pixi_global/src/project/mod.rs b/crates/pixi_global/src/project/mod.rs index 7dcaebf890..a39953ff6b 100644 --- a/crates/pixi_global/src/project/mod.rs +++ b/crates/pixi_global/src/project/mod.rs @@ -1372,7 +1372,7 @@ impl Project { ) -> Result { let command_dispatcher = self.command_dispatcher()?; let checkout = command_dispatcher - .pin_and_checkout(source_spec) + .pin_and_checkout(source_spec.location) .await .map_err(|e| InferPackageNameError::BuildBackendMetadata(Box::new(e)))?; @@ -1380,7 +1380,7 @@ impl Project { // Create the metadata spec let metadata_spec = BuildBackendMetadataSpec { - source: pinned_source_spec, + manifest_source: pinned_source_spec, channel_config: self.global_channel_config().clone(), channels: self .config() @@ -1392,6 +1392,7 @@ impl Project { variants: None, variant_files: None, enabled_protocols: Default::default(), + pin_override: None, }; // Get the metadata using the command dispatcher diff --git a/crates/pixi_global/src/project/parsed_manifest.rs b/crates/pixi_global/src/project/parsed_manifest.rs index bb403a4d37..10f6b2abe6 100644 --- a/crates/pixi_global/src/project/parsed_manifest.rs +++ b/crates/pixi_global/src/project/parsed_manifest.rs @@ -11,7 +11,7 @@ use pixi_spec::PixiSpec; use pixi_toml::{TomlFromStr, TomlIndexMap, TomlIndexSet, TomlWith}; use rattler_conda_types::{NamedChannelOrUrl, PackageName, Platform}; use serde::{Serialize, Serializer, ser::SerializeMap}; -use serde_with::serde_derive::Deserialize; +use serde_derive::Deserialize; use thiserror::Error; use toml_span::{DeserError, Deserialize, Value, de_helpers::TableHelper}; diff --git a/crates/pixi_manifest/src/build_system.rs b/crates/pixi_manifest/src/build_system.rs index a523af3352..ef5a11ed01 100644 --- a/crates/pixi_manifest/src/build_system.rs +++ b/crates/pixi_manifest/src/build_system.rs @@ -106,7 +106,47 @@ mod tests { "https://prefix.dev/pixi-build-backends", "https://prefix.dev/conda-forge", ] - source = { git = "https://github.com/conda-forge/numpy-feedstock" } + source = { git = "https://github.com/conda-forge/numpy-feedstock", rev ="ee87916a49d5e96d4f322f68c3650e8ff6b8866b" } + "#; + + let build = PackageBuild::from_toml_str(toml).unwrap(); + assert_eq!( + build.value.backend.name.as_source(), + "pixi-build-rattler-build" + ); + assert!(build.value.source.is_some()); + assert!(build.value.source.unwrap().is_git()); + } + + #[test] + fn deserialize_build_with_git_source_branch() { + let toml = r#" + backend = { name = "pixi-build-rattler-build", version = "0.1.*" } + channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", + ] + source = { git = "https://github.com/conda-forge/numpy-feedstock", branch = "main" } + "#; + + let build = PackageBuild::from_toml_str(toml).unwrap(); + assert_eq!( + build.value.backend.name.as_source(), + "pixi-build-rattler-build" + ); + assert!(build.value.source.is_some()); + assert!(build.value.source.unwrap().is_git()); + } + + #[test] + fn deserialize_build_with_git_source_tag() { + let toml = r#" + backend = { name = "pixi-build-rattler-build", version = "0.1.*" } + channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", + ] + source = { git = "https://github.com/conda-forge/numpy-feedstock", tag = "v1.0.0" } "#; let build = PackageBuild::from_toml_str(toml).unwrap(); diff --git a/crates/pixi_manifest/src/toml/build_backend.rs b/crates/pixi_manifest/src/toml/build_backend.rs index a5df603eee..457f547e17 100644 --- a/crates/pixi_manifest/src/toml/build_backend.rs +++ b/crates/pixi_manifest/src/toml/build_backend.rs @@ -169,7 +169,7 @@ static BOTH_ADDITIONAL_DEPS_WARNING: Once = Once::new(); fn spec_from_spanned_toml_location( spanned_toml: Spanned, ) -> Result { - spanned_toml + let source_location_spec = spanned_toml .value .into_source_location_spec() .map_err(|err| { @@ -178,7 +178,9 @@ fn spec_from_spanned_toml_location( span: spanned_toml.span, line_info: None, }) - }) + })?; + + Ok(source_location_spec) } impl<'de> toml_span::Deserialize<'de> for TomlPackageBuild { diff --git a/crates/pixi_record/Cargo.toml b/crates/pixi_record/Cargo.toml index fb59a6397b..31a4990a4a 100644 --- a/crates/pixi_record/Cargo.toml +++ b/crates/pixi_record/Cargo.toml @@ -22,3 +22,6 @@ serde_with = { workspace = true } thiserror = { workspace = true } typed-path = { workspace = true } url = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/crates/pixi_record/src/lib.rs b/crates/pixi_record/src/lib.rs index 48e4d7917b..7992cc29ea 100644 --- a/crates/pixi_record/src/lib.rs +++ b/crates/pixi_record/src/lib.rs @@ -17,6 +17,7 @@ use thiserror::Error; /// binary file or something that still requires building. /// /// This is basically a superset of a regular [`RepoDataRecord`]. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum PixiRecord { diff --git a/crates/pixi_record/src/source_record.rs b/crates/pixi_record/src/source_record.rs index 85537b4b50..68bf60c0b5 100644 --- a/crates/pixi_record/src/source_record.rs +++ b/crates/pixi_record/src/source_record.rs @@ -1,12 +1,17 @@ -use std::collections::{BTreeSet, HashMap}; +use std::{ + collections::{BTreeSet, HashMap}, + str::FromStr, +}; -use pixi_spec::SourceSpec; +use pixi_git::sha::GitSha; +use pixi_spec::{GitReference, SourceSpec}; use rattler_conda_types::{MatchSpec, Matches, NamelessMatchSpec, PackageRecord}; use rattler_digest::{Sha256, Sha256Hash}; -use rattler_lock::{CondaPackageData, CondaSourceData}; +use rattler_lock::{CondaPackageData, CondaSourceData, GitShallowSpec, PackageBuildSource}; use serde::{Deserialize, Serialize}; +use typed_path::Utf8TypedPathBuf; -use crate::{ParseLockFileError, PinnedSourceSpec}; +use crate::{ParseLockFileError, PinnedGitCheckout, PinnedSourceSpec}; /// A record of a conda package that still requires building. #[derive(Debug, Clone, serde::Serialize)] @@ -16,7 +21,11 @@ pub struct SourceRecord { pub package_record: PackageRecord, /// Exact definition of the source of the package. - pub source: PinnedSourceSpec, + pub manifest_source: PinnedSourceSpec, + + /// The optional pinned source where the build should be executed + /// This is used when the manifest is not in the same location ad + pub build_source: Option, /// The hash of the input that was used to build the metadata of the /// package. This can be used to verify that the metadata is still valid. @@ -48,9 +57,41 @@ pub struct InputHash { impl From for CondaPackageData { fn from(value: SourceRecord) -> Self { + let package_build_source = value.build_source.map(|s| match s { + PinnedSourceSpec::Url(pinned_url_spec) => PackageBuildSource::Url { + url: pinned_url_spec.url, + sha256: pinned_url_spec.sha256, + subdir: None, + }, + PinnedSourceSpec::Git(pinned_git_spec) => { + let subdirectory = pinned_git_spec + .source + .subdirectory + .as_deref() + .map(Utf8TypedPathBuf::from); + + let spec = match &pinned_git_spec.source.reference { + GitReference::Branch(branch) => Some(GitShallowSpec::Branch(branch.clone())), + GitReference::Tag(tag) => Some(GitShallowSpec::Tag(tag.clone())), + GitReference::Rev(_) => Some(GitShallowSpec::Rev), + GitReference::DefaultBranch => None, + }; + + PackageBuildSource::Git { + url: pinned_git_spec.git, + spec, + rev: pinned_git_spec.source.commit.to_string(), + subdir: subdirectory, + } + } + PinnedSourceSpec::Path(pinned_path) => PackageBuildSource::Path { + path: pinned_path.path, + }, + }); CondaPackageData::Source(CondaSourceData { package_record: value.package_record, - location: value.source.into(), + location: value.manifest_source.clone().into(), + package_build_source, input: value.input_hash.map(|i| rattler_lock::InputHash { hash: i.hash, // TODO: fix this in rattler @@ -61,7 +102,6 @@ impl From for CondaPackageData { .into_iter() .map(|(k, v)| (k, v.into())) .collect(), - package_build_source: None, }) } } @@ -70,13 +110,50 @@ impl TryFrom for SourceRecord { type Error = ParseLockFileError; fn try_from(value: CondaSourceData) -> Result { + let pinned_source_spec = value.package_build_source.map(|source| match source { + PackageBuildSource::Git { + url, + spec, + rev, + subdir, + } => { + let reference = match spec { + Some(GitShallowSpec::Branch(branch)) => GitReference::Branch(branch), + Some(GitShallowSpec::Tag(tag)) => GitReference::Tag(tag), + Some(GitShallowSpec::Rev) => GitReference::Rev(rev.clone()), + None => GitReference::DefaultBranch, + }; + + PinnedSourceSpec::Git(crate::PinnedGitSpec { + git: url, + source: PinnedGitCheckout { + commit: GitSha::from_str(&rev).unwrap(), + subdirectory: subdir.map(|s| s.to_string()), + reference, + }, + }) + } + PackageBuildSource::Url { + url, + sha256, + subdir: _, + } => PinnedSourceSpec::Url(crate::PinnedUrlSpec { + url, + sha256, + md5: None, + }), + PackageBuildSource::Path { path } => { + PinnedSourceSpec::Path(crate::PinnedPathSpec { path }) + } + }); Ok(Self { package_record: value.package_record, - source: value.location.try_into()?, + manifest_source: value.location.try_into()?, input_hash: value.input.map(|hash| InputHash { hash: hash.hash, globs: BTreeSet::from_iter(hash.globs), }), + build_source: pinned_source_spec, sources: value .sources .into_iter() @@ -123,3 +200,77 @@ impl AsRef for SourceRecord { &self.package_record } } + +#[cfg(test)] +mod tests { + use super::*; + use pixi_git::sha::GitSha; + use serde_json::json; + use std::str::FromStr; + use url::Url; + + #[test] + fn package_build_source_roundtrip_preserves_git_subdirectory() { + let package_record: PackageRecord = serde_json::from_value(json!({ + "name": "example", + "version": "1.0.0", + "build": "0", + "build_number": 0, + "subdir": "noarch", + })) + .expect("valid package record"); + + let git_url = Url::parse("https://example.com/repo.git").unwrap(); + let pinned_source = PinnedSourceSpec::Git(crate::PinnedGitSpec { + git: git_url.clone(), + source: PinnedGitCheckout { + commit: GitSha::from_str("0123456789abcdef0123456789abcdef01234567").unwrap(), + subdirectory: Some("nested/project".to_string()), + reference: GitReference::Branch("main".to_string()), + }, + }); + + let record = SourceRecord { + package_record, + manifest_source: pinned_source.clone(), + build_source: Some(pinned_source.clone()), + input_hash: None, + sources: Default::default(), + }; + + let CondaPackageData::Source(conda_source) = record.clone().into() else { + panic!("expected source package data"); + }; + + let package_build_source = conda_source + .package_build_source + .as_ref() + .expect("expected package build source"); + + let PackageBuildSource::Git { + url, + spec, + rev, + subdir, + } = package_build_source + else { + panic!("expected git package build source"); + }; + + assert_eq!(url.path(), "/repo.git"); + assert_eq!(url.host_str(), Some("example.com")); + assert_eq!(subdir.as_ref().map(|s| s.as_str()), Some("nested/project")); + assert!(matches!(spec, Some(GitShallowSpec::Branch(branch)) if branch == "main")); + assert_eq!(rev, "0123456789abcdef0123456789abcdef01234567"); + + let roundtrip = SourceRecord::try_from(conda_source).expect("roundtrip should succeed"); + let Some(PinnedSourceSpec::Git(roundtrip_git)) = roundtrip.build_source else { + panic!("expected git pinned source"); + }; + assert_eq!( + roundtrip_git.source.subdirectory.as_deref(), + Some("nested/project") + ); + assert_eq!(roundtrip_git.git, git_url); + } +} diff --git a/docs/reference/cli/pixi.md b/docs/reference/cli/pixi.md index 95810c0173..1ab64084ce 100644 --- a/docs/reference/cli/pixi.md +++ b/docs/reference/cli/pixi.md @@ -13,7 +13,7 @@ pixi [OPTIONS] [COMMAND] |---------|-------------| | [`add`](pixi/add.md) | Adds dependencies to the workspace | | [`auth`](pixi/auth.md) | Login to prefix.dev or anaconda.org servers to access private channels | -| [`build`](pixi/build.md) | Workspace configuration | +| [`build`](pixi/build.md) | Lock file and installation configuration with --as-is support Used by shell, shell-hook, and run commands | | [`clean`](pixi/clean.md) | Cleanup the environments | | [`completion`](pixi/completion.md) | Generates a completion script for a shell | | [`config`](pixi/config.md) | Configuration management | diff --git a/docs/reference/cli/pixi/build.md b/docs/reference/cli/pixi/build.md index c9fb0452c1..075b2300c9 100644 --- a/docs/reference/cli/pixi/build.md +++ b/docs/reference/cli/pixi/build.md @@ -2,7 +2,7 @@ # [pixi](../pixi.md) build ## About -Workspace configuration +Lock file and installation configuration with --as-is support Used by shell, shell-hook, and run commands --8<-- "docs/reference/cli/pixi/build_extender:description" @@ -46,6 +46,18 @@ pixi build [OPTIONS] - `--use-environment-activation-cache` : Use environment activation cache (experimental) +## Update Options +- `--no-install` +: Don't modify the environment, only modify the lock-file +- `--frozen` +: Install the environment as defined in the lockfile, doesn't update lockfile if it isn't up-to-date with the manifest file +
**env**: `PIXI_FROZEN` +- `--locked` +: Check if lockfile is up-to-date before installing the environment, aborts when lockfile isn't up-to-date with the manifest file +
**env**: `PIXI_LOCKED` +- `--as-is` +: Shorthand for the combination of --no-install and --frozen + ## Global Options - `--manifest-path ` : The path to `pixi.toml`, `pyproject.toml`, or the workspace directory diff --git a/docs/reference/pixi_manifest.md b/docs/reference/pixi_manifest.md index f3a9fb557b..c0e40692ce 100644 --- a/docs/reference/pixi_manifest.md +++ b/docs/reference/pixi_manifest.md @@ -1067,6 +1067,9 @@ The build system is a table that can contain the following fields: - `source`: specifies the location of the source code for the package. Default: manifest directory. Currently supported options: - `path`: a string representing a relative or absolute path to the source code. + - `git`: a string representing URL to the source repository. + - `rev`: a string representing SHA revision to checkout. + - `subdirectory`: a string representing path to subdirectory to use. - `channels`: specifies the channels to get the build backend from. - `backend`: specifies the build backend to use. This is a table that can contain the following fields: - `name`: the name of the build backend to use. This will also be the executable name. diff --git a/examples/pixi-build/cpp-git-source/pixi.lock b/examples/pixi-build/cpp-git-source/pixi.lock new file mode 100644 index 0000000000..eb02736880 --- /dev/null +++ b/examples/pixi-build/cpp-git-source/pixi.lock @@ -0,0 +1,1123 @@ +version: 6 +environments: + default: + channels: + - url: https://prefix.dev/conda-forge/ + packages: + linux-64: + - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://prefix.dev/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/gettext-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/gettext-tools-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/libasprintf-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libasprintf-devel-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libcap-2.76-h0b2e76d_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libflac-1.4.3-h59595ed_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcrypt-lib-1.11.1-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgettextpo-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgettextpo-devel-0.25.1-h3f43e3d_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libglib-2.86.0-h1fed272_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libopus-1.5.2-hd0c01bc_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsndfile-1.2.2-hc60ed4a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsystemd0-257.9-h996ca69_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libudev1-257.9-h085a93f_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libunwind-1.8.3-h65a8314_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/liburing-2.12-hb700be7_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libusb-1.0.29-h73b1eb8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libvorbis-1.3.7-h54a6638_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libxkbcommon-1.11.0-hca5e8e5_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libxml2-16-2.15.0-ha9997c6_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libxml2-2.15.0-h26afc86_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/mpg123-1.32.9-hc50e24c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://prefix.dev/conda-forge/linux-64/pulseaudio-client-17.0-h9a8bead_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/sdl2-2.32.56-h54a6638_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/sdl3-3.2.22-h68140b3_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/wayland-1.24.0-h3e06ad9_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xkeyboard-config-2.45-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxscrnsaver-1.2.4-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - conda: . + subdir: linux-64 + osx-arm64: + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/dbus-1.16.2-hda038a8_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.2-hf598326_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libglib-2.86.0-h1bb475b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libusb-1.0.29-hbc156a2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/sdl2-2.32.56-h248ca61_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/sdl3-3.2.22-he22eeb8_0.conda + - conda: . + subdir: osx-arm64 + win-64: + - conda: https://prefix.dev/conda-forge/win-64/libusb-1.0.29-h1839187_0.conda + - conda: https://prefix.dev/conda-forge/win-64/sdl2-2.32.56-h5112557_0.conda + - conda: https://prefix.dev/conda-forge/win-64/sdl3-3.2.22-h5112557_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + - conda: . + build: h9352c13_0 +packages: +- conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + size: 2562 + timestamp: 1578324546067 +- conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + build_number: 16 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + size: 23621 + timestamp: 1650670423406 +- conda: https://prefix.dev/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda + sha256: a9c114cbfeda42a226e2db1809a538929d2f118ef855372293bd188f71711c48 + md5: 791365c5f65975051e4e017b5da3abf5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: GPL-2.0-or-later + license_family: GPL + size: 68072 + timestamp: 1756738968573 +- conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 + md5: 51a19bba1b8ebfb60df25cde030b7ebc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + size: 260341 + timestamp: 1757437258798 +- conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1 + md5: 58fd217444c2a5701a44244faf518206 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + size: 125061 + timestamp: 1757437486465 +- conda: https://prefix.dev/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda + sha256: 3b988146a50e165f0fa4e839545c679af88e4782ec284cc7b6d07dd226d6a068 + md5: 679616eb5ad4e521c83da4650860aba7 + depends: + - libstdcxx >=13 + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libexpat >=2.7.0,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - libglib >=2.84.2,<3.0a0 + license: GPL-2.0-or-later + license_family: GPL + size: 437860 + timestamp: 1747855126005 +- conda: https://prefix.dev/conda-forge/osx-arm64/dbus-1.16.2-hda038a8_0.conda + sha256: 2ef01ab52dedb477cb7291994ad556279b37c8ad457521e75c47cad20248ea30 + md5: 80c663e4f6b0fd8d6723ff7d68f09429 + depends: + - __osx >=11.0 + - libcxx >=18 + - libzlib >=1.3.1,<2.0a0 + - libglib >=2.84.2,<3.0a0 + - libexpat >=2.7.0,<3.0a0 + license: GPL-2.0-or-later + license_family: GPL + size: 384376 + timestamp: 1747855177419 +- conda: https://prefix.dev/conda-forge/linux-64/gettext-0.25.1-h3f43e3d_1.conda + sha256: cbfa8c80771d1842c2687f6016c5e200b52d4ca8f2cc119f6377f64f899ba4ff + md5: c42356557d7f2e37676e121515417e3b + depends: + - __glibc >=2.17,<3.0.a0 + - gettext-tools 0.25.1 h3f43e3d_1 + - libasprintf 0.25.1 h3f43e3d_1 + - libasprintf-devel 0.25.1 h3f43e3d_1 + - libgcc >=14 + - libgettextpo 0.25.1 h3f43e3d_1 + - libgettextpo-devel 0.25.1 h3f43e3d_1 + - libiconv >=1.18,<2.0a0 + - libstdcxx >=14 + license: LGPL-2.1-or-later AND GPL-3.0-or-later + size: 541357 + timestamp: 1753343006214 +- conda: https://prefix.dev/conda-forge/linux-64/gettext-tools-0.25.1-h3f43e3d_1.conda + sha256: c792729288bdd94f21f25f80802d4c66957b4e00a57f7cb20513f07aadfaff06 + md5: a59c05d22bdcbb4e984bf0c021a2a02f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + license: GPL-3.0-or-later + license_family: GPL + size: 3644103 + timestamp: 1753342966311 +- conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + size: 12129203 + timestamp: 1720853576813 +- conda: https://prefix.dev/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 + sha256: aad2a703b9d7b038c0f745b853c6bb5f122988fe1a7a096e0e606d9cbec4eaab + md5: a8832b479f93521a9e7b5b743803be51 + depends: + - libgcc-ng >=12 + license: LGPL-2.0-only + license_family: LGPL + size: 508258 + timestamp: 1664996250081 +- conda: https://prefix.dev/conda-forge/linux-64/libasprintf-0.25.1-h3f43e3d_1.conda + sha256: cb728a2a95557bb6a5184be2b8be83a6f2083000d0c7eff4ad5bbe5792133541 + md5: 3b0d184bc9404516d418d4509e418bdc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: LGPL-2.1-or-later + size: 53582 + timestamp: 1753342901341 +- conda: https://prefix.dev/conda-forge/linux-64/libasprintf-devel-0.25.1-h3f43e3d_1.conda + sha256: 2fc95060efc3d76547b7872875af0b7212d4b1407165be11c5f830aeeb57fc3a + md5: fd9cf4a11d07f0ef3e44fc061611b1ed + depends: + - __glibc >=2.17,<3.0.a0 + - libasprintf 0.25.1 h3f43e3d_1 + - libgcc >=14 + license: LGPL-2.1-or-later + size: 34734 + timestamp: 1753342921605 +- conda: https://prefix.dev/conda-forge/linux-64/libcap-2.76-h0b2e76d_0.conda + sha256: a946b61be1af15ff08c7722e9bac0fab446d8b9896c9f0f35657dfcf887fda8a + md5: 0f7f0c878c8dceb3b9ec67f5c06d6057 + depends: + - __glibc >=2.17,<3.0.a0 + - attr >=2.5.1,<2.6.0a0 + - libgcc >=13 + license: BSD-3-Clause + license_family: BSD + size: 121852 + timestamp: 1744577167992 +- conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.2-hf598326_0.conda + sha256: 3de00998c8271f599d6ed9aea60dc0b3e5b1b7ff9f26f8eac95f86f135aa9beb + md5: edfa256c5391f789384e470ce5c9f340 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 568154 + timestamp: 1758698306949 +- conda: https://prefix.dev/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda + sha256: c076a213bd3676cc1ef22eeff91588826273513ccc6040d9bea68bccdc849501 + md5: 9314bc5a1fe7d1044dc9dfd3ef400535 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpciaccess >=0.18,<0.19.0a0 + license: MIT + license_family: MIT + size: 310785 + timestamp: 1757212153962 +- conda: https://prefix.dev/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda + sha256: 7fd5408d359d05a969133e47af580183fbf38e2235b562193d427bb9dad79723 + md5: c151d5eb730e9b7480e6d48c0fc44048 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_2 + license: LicenseRef-libglvnd + size: 44840 + timestamp: 1731330973553 +- conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 + md5: 4211416ecba1866fab0c6470986c22d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + size: 74811 + timestamp: 1752719572741 +- conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + sha256: 8fbb17a56f51e7113ed511c5787e0dec0d4b10ef9df921c4fd1cccca0458f648 + md5: b1ca5f21335782f71a8bd69bdc093f67 + depends: + - __osx >=11.0 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + size: 65971 + timestamp: 1752719657566 +- conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab + md5: ede4673863426c0883c0063d853bbd85 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 57433 + timestamp: 1743434498161 +- conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + sha256: c6a530924a9b14e193ea9adfe92843de2a806d1b7dbfd341546ece9653129e60 + md5: c215a60c2935b517dcda8cad4705734d + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 39839 + timestamp: 1743434670405 +- conda: https://prefix.dev/conda-forge/linux-64/libflac-1.4.3-h59595ed_0.conda + sha256: 65908b75fa7003167b8a8f0001e11e58ed5b1ef5e98b96ab2ba66d7c1b822c7d + md5: ee48bf17cc83a00f59ca1494d5646869 + depends: + - gettext >=0.21.1,<1.0a0 + - libgcc-ng >=12 + - libogg 1.3.* + - libogg >=1.3.4,<1.4.0a0 + - libstdcxx-ng >=12 + license: BSD-3-Clause + license_family: BSD + size: 394383 + timestamp: 1687765514062 +- conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda + sha256: 0caed73aac3966bfbf5710e06c728a24c6c138605121a3dacb2e03440e8baa6a + md5: 264fbfba7fb20acf3b29cde153e345ce + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgomp 15.1.0 h767d61c_5 + - libgcc-ng ==15.1.0=*_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 824191 + timestamp: 1757042543820 +- conda: https://prefix.dev/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda + sha256: f54bb9c3be12b24be327f4c1afccc2969712e0b091cdfbd1d763fb3e61cda03f + md5: 069afdf8ea72504e48d23ae1171d951c + depends: + - libgcc 15.1.0 h767d61c_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 29187 + timestamp: 1757042549554 +- conda: https://prefix.dev/conda-forge/linux-64/libgcrypt-lib-1.11.1-hb9d3cd8_0.conda + sha256: dc9c7d7a6c0e6639deee6fde2efdc7e119e7739a6b229fa5f9049a449bae6109 + md5: 8504a291085c9fb809b66cabd5834307 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libgpg-error >=1.55,<2.0a0 + license: LGPL-2.1-or-later + size: 590353 + timestamp: 1747060639058 +- conda: https://prefix.dev/conda-forge/linux-64/libgettextpo-0.25.1-h3f43e3d_1.conda + sha256: 50a9e9815cf3f5bce1b8c5161c0899cc5b6c6052d6d73a4c27f749119e607100 + md5: 2f4de899028319b27eb7a4023be5dfd2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + license: GPL-3.0-or-later + license_family: GPL + size: 188293 + timestamp: 1753342911214 +- conda: https://prefix.dev/conda-forge/linux-64/libgettextpo-devel-0.25.1-h3f43e3d_1.conda + sha256: c7ea10326fd450a2a21955987db09dde78c99956a91f6f05386756a7bfe7cc04 + md5: 3f7a43b3160ec0345c9535a9f0d7908e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgettextpo 0.25.1 h3f43e3d_1 + - libiconv >=1.18,<2.0a0 + license: GPL-3.0-or-later + license_family: GPL + size: 37407 + timestamp: 1753342931100 +- conda: https://prefix.dev/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda + sha256: dc2752241fa3d9e40ce552c1942d0a4b5eeb93740c9723873f6fcf8d39ef8d2d + md5: 928b8be80851f5d8ffb016f9c81dae7a + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_2 + - libglx 1.7.0 ha4b6fd6_2 + license: LicenseRef-libglvnd + size: 134712 + timestamp: 1731330998354 +- conda: https://prefix.dev/conda-forge/linux-64/libglib-2.86.0-h1fed272_0.conda + sha256: 33336bd55981be938f4823db74291e1323454491623de0be61ecbe6cf3a4619c + md5: b8e4c93f4ab70c3b6f6499299627dbdc + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.4.6,<3.5.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pcre2 >=10.46,<10.47.0a0 + constrains: + - glib 2.86.0 *_0 + license: LGPL-2.1-or-later + size: 3978602 + timestamp: 1757403291664 +- conda: https://prefix.dev/conda-forge/osx-arm64/libglib-2.86.0-h1bb475b_0.conda + sha256: 92d17f998e14218810493c9190c8721bf7f7f006bfc5c00dbba1cede83c02f1a + md5: 9e065148e6013b7d7cae64ed01ab7081 + depends: + - __osx >=11.0 + - libffi >=3.4.6,<3.5.0a0 + - libiconv >=1.18,<2.0a0 + - libintl >=0.25.1,<1.0a0 + - libzlib >=1.3.1,<2.0a0 + - pcre2 >=10.46,<10.47.0a0 + constrains: + - glib 2.86.0 *_0 + license: LGPL-2.1-or-later + size: 3701880 + timestamp: 1757404501093 +- conda: https://prefix.dev/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda + sha256: 1175f8a7a0c68b7f81962699751bb6574e6f07db4c9f72825f978e3016f46850 + md5: 434ca7e50e40f4918ab701e3facd59a0 + depends: + - __glibc >=2.17,<3.0.a0 + license: LicenseRef-libglvnd + size: 132463 + timestamp: 1731330968309 +- conda: https://prefix.dev/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda + sha256: 2d35a679624a93ce5b3e9dd301fff92343db609b79f0363e6d0ceb3a6478bfa7 + md5: c8013e438185f33b13814c5c488acd5c + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_2 + - xorg-libx11 >=1.8.10,<2.0a0 + license: LicenseRef-libglvnd + size: 75504 + timestamp: 1731330988898 +- conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + sha256: 125051d51a8c04694d0830f6343af78b556dd88cc249dfec5a97703ebfb1832d + md5: dcd5ff1940cd38f6df777cac86819d60 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 447215 + timestamp: 1757042483384 +- conda: https://prefix.dev/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + sha256: 697334de4786a1067ea86853e520c64dd72b11a05137f5b318d8a444007b5e60 + md5: 2bd47db5807daade8500ed7ca4c512a4 + depends: + - libstdcxx >=13 + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + size: 312184 + timestamp: 1745575272035 +- conda: https://prefix.dev/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-only + size: 790176 + timestamp: 1754908768807 +- conda: https://prefix.dev/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + sha256: de0336e800b2af9a40bdd694b03870ac4a848161b35c8a2325704f123f185f03 + md5: 4d5a7445f0b25b6a3ddbb56e790f5251 + depends: + - __osx >=11.0 + license: LGPL-2.1-only + size: 750379 + timestamp: 1754909073836 +- conda: https://prefix.dev/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + sha256: 99d2cebcd8f84961b86784451b010f5f0a795ed1c08f1e7c76fbb3c22abf021a + md5: 5103f6a6b210a3912faf8d7db516918c + depends: + - __osx >=11.0 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.1-or-later + size: 90957 + timestamp: 1751558394144 +- conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 + md5: 1a580f7796c7bf6393fddb8bbbde58dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - xz 5.8.1.* + license: 0BSD + size: 112894 + timestamp: 1749230047870 +- conda: https://prefix.dev/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + sha256: ffb066ddf2e76953f92e06677021c73c85536098f1c21fcd15360dbc859e22e4 + md5: 68e52064ed3897463c0e958ab5c8f91b + depends: + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + size: 218500 + timestamp: 1745825989535 +- conda: https://prefix.dev/conda-forge/linux-64/libopus-1.5.2-hd0c01bc_0.conda + sha256: 786d43678d6d1dc5f88a6bad2d02830cfd5a0184e84a8caa45694049f0e3ea5f + md5: b64523fb87ac6f87f0790f324ad43046 + depends: + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + size: 312472 + timestamp: 1744330953241 +- conda: https://prefix.dev/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + sha256: 0bd91de9b447a2991e666f284ae8c722ffb1d84acb594dbd0c031bd656fa32b2 + md5: 70e3400cbbfa03e96dcde7fc13e38c7b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 28424 + timestamp: 1749901812541 +- conda: https://prefix.dev/conda-forge/linux-64/libsndfile-1.2.2-hc60ed4a_1.conda + sha256: f709cbede3d4f3aee4e2f8d60bd9e256057f410bd60b8964cb8cf82ec1457573 + md5: ef1910918dd895516a769ed36b5b3a4e + depends: + - lame >=3.100,<3.101.0a0 + - libflac >=1.4.3,<1.5.0a0 + - libgcc-ng >=12 + - libogg >=1.3.4,<1.4.0a0 + - libopus >=1.3.1,<2.0a0 + - libstdcxx-ng >=12 + - libvorbis >=1.3.7,<1.4.0a0 + - mpg123 >=1.32.1,<1.33.0a0 + license: LGPL-2.1-or-later + license_family: LGPL + size: 354372 + timestamp: 1695747735668 +- conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda + sha256: 0f5f61cab229b6043541c13538d75ce11bd96fb2db76f94ecf81997b1fde6408 + md5: 4e02a49aaa9d5190cb630fa43528fbe6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.1.0 h767d61c_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 3896432 + timestamp: 1757042571458 +- conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda + sha256: 7b8cabbf0ab4fe3581ca28fe8ca319f964078578a51dd2ca3f703c1d21ba23ff + md5: 8bba50c7f4679f08c861b597ad2bda6b + depends: + - libstdcxx 15.1.0 h8f9b012_5 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 29233 + timestamp: 1757042603319 +- conda: https://prefix.dev/conda-forge/linux-64/libsystemd0-257.9-h996ca69_0.conda + sha256: 6b063df2d13dc9cedeae7b1591b1917ced7f4e1b04f7246e66cc7fb0088dea07 + md5: b6d222422c17dc11123e63fae4ad4178 + depends: + - __glibc >=2.17,<3.0.a0 + - libcap >=2.76,<2.77.0a0 + - libgcc >=14 + - libgcrypt-lib >=1.11.1,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: LGPL-2.1-or-later + size: 492733 + timestamp: 1757520335407 +- conda: https://prefix.dev/conda-forge/linux-64/libudev1-257.9-h085a93f_0.conda + sha256: 1c8f0b02c400617a9f2ea8429c604b28e25a10f51b3c8d73ce127b4e7b462297 + md5: 973f365f19c1d702bda523658a77de26 + depends: + - __glibc >=2.17,<3.0.a0 + - libcap >=2.76,<2.77.0a0 + - libgcc >=14 + license: LGPL-2.1-or-later + size: 144265 + timestamp: 1757520342166 +- conda: https://prefix.dev/conda-forge/linux-64/libunwind-1.8.3-h65a8314_0.conda + sha256: 71c8b9d5c72473752a0bb6e91b01dd209a03916cb71f36cc6a564e3a2a132d7a + md5: e179a69edd30d75c0144d7a380b88f28 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + size: 75995 + timestamp: 1757032240102 +- conda: https://prefix.dev/conda-forge/linux-64/liburing-2.12-hb700be7_0.conda + sha256: 880b1f76b24814c9f07b33402e82fa66d5ae14738a35a943c21c4434eef2403d + md5: f0531fc1ebc0902555670e9cb0127758 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + size: 127967 + timestamp: 1756125594973 +- conda: https://prefix.dev/conda-forge/linux-64/libusb-1.0.29-h73b1eb8_0.conda + sha256: 89c84f5b26028a9d0f5c4014330703e7dff73ba0c98f90103e9cef6b43a5323c + md5: d17e3fb595a9f24fa9e149239a33475d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libudev1 >=257.4 + license: LGPL-2.1-or-later + size: 89551 + timestamp: 1748856210075 +- conda: https://prefix.dev/conda-forge/osx-arm64/libusb-1.0.29-hbc156a2_0.conda + sha256: 5eee9a2bf359e474d4548874bcfc8d29ebad0d9ba015314439c256904e40aaad + md5: f6654e9e96e9d973981b3b2f898a5bfa + depends: + - __osx >=11.0 + license: LGPL-2.1-or-later + size: 83849 + timestamp: 1748856224950 +- conda: https://prefix.dev/conda-forge/win-64/libusb-1.0.29-h1839187_0.conda + sha256: 9837f8e8de20b6c9c033561cd33b4554cd551b217e3b8d2862b353ed2c23d8b8 + md5: a656b2c367405cd24988cf67ff2675aa + depends: + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + license: LGPL-2.1-or-later + size: 118204 + timestamp: 1748856290542 +- conda: https://prefix.dev/conda-forge/linux-64/libvorbis-1.3.7-h54a6638_2.conda + sha256: ca494c99c7e5ecc1b4cd2f72b5584cef3d4ce631d23511184411abcbb90a21a5 + md5: b4ecbefe517ed0157c37f8182768271c + depends: + - libogg + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - libogg >=1.3.5,<1.4.0a0 + license: BSD-3-Clause + license_family: BSD + size: 285894 + timestamp: 1753879378005 +- conda: https://prefix.dev/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa + md5: 92ed62436b625154323d40d5f2f11dd7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp + license: MIT + license_family: MIT + size: 395888 + timestamp: 1727278577118 +- conda: https://prefix.dev/conda-forge/linux-64/libxkbcommon-1.11.0-hca5e8e5_1.conda + sha256: 2febd0cdea153a97737df3cfb900c312b012c0af3cc5a62f2968bd398d25b6b6 + md5: 9abb1e8cbc0039155a8ed2aa149b1067 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxcb >=1.17.0,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - xkeyboard-config + - xorg-libxau >=1.0.12,<2.0a0 + license: MIT/X11 Derivative + license_family: MIT + size: 800138 + timestamp: 1757977095650 +- conda: https://prefix.dev/conda-forge/linux-64/libxml2-2.15.0-h26afc86_1.conda + sha256: 4310577d7eea817d35a1c05e1e54575b06ce085d73e6dd59aa38523adf50168f + md5: 8337b675e0cad517fbcb3daf7588087a + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libxml2-16 2.15.0 ha9997c6_1 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + size: 45363 + timestamp: 1758640621036 +- conda: https://prefix.dev/conda-forge/linux-64/libxml2-16-2.15.0-ha9997c6_1.conda + sha256: 5420ea77505a8d5ca7b5351ddb2da7e8a178052fccf8fca00189af7877608e89 + md5: b24dd2bd61cd8e4f8a13ee2a945a723c + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.0 + license: MIT + license_family: MIT + size: 556276 + timestamp: 1758640612398 +- conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + size: 60963 + timestamp: 1727963148474 +- conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b + md5: 369964e85dc26bfe78f41399b366c435 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + size: 46438 + timestamp: 1727963202283 +- conda: https://prefix.dev/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 + md5: 9de5350a85c4a20c685259b889aa6393 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: BSD-2-Clause + license_family: BSD + size: 167055 + timestamp: 1733741040117 +- conda: https://prefix.dev/conda-forge/linux-64/mpg123-1.32.9-hc50e24c_0.conda + sha256: 39c4700fb3fbe403a77d8cc27352fa72ba744db487559d5d44bf8411bb4ea200 + md5: c7f302fd11eeb0987a6a5e1f3aed6a21 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: LGPL-2.1-only + license_family: LGPL + size: 491140 + timestamp: 1730581373280 +- conda: https://prefix.dev/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda + sha256: 5c7380c8fd3ad5fc0f8039069a45586aa452cf165264bc5a437ad80397b32934 + md5: 7fa07cb0fb1b625a089ccc01218ee5b1 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 1209177 + timestamp: 1756742976157 +- conda: https://prefix.dev/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda + sha256: 5bf2eeaa57aab6e8e95bea6bd6bb2a739f52eb10572d8ed259d25864d3528240 + md5: 0e6e82c3cc3835f4692022e9b9cd5df8 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 835080 + timestamp: 1756743041908 +- conda: https://prefix.dev/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 + md5: b3c17d95b5a10c6e64a21fa17573e70e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 8252 + timestamp: 1726802366959 +- conda: https://prefix.dev/conda-forge/linux-64/pulseaudio-client-17.0-h9a8bead_2.conda + sha256: 8a6729861c9813a756b0438c30bd271722fb3f239ded3afc3bf1cb03327a640e + md5: b6f21b1c925ee2f3f7fc37798c5988db + depends: + - __glibc >=2.17,<3.0.a0 + - dbus >=1.16.2,<2.0a0 + - libgcc >=14 + - libglib >=2.86.0,<3.0a0 + - libiconv >=1.18,<2.0a0 + - libsndfile >=1.2.2,<1.3.0a0 + - libsystemd0 >=257.7 + - libxcb >=1.17.0,<2.0a0 + constrains: + - pulseaudio 17.0 *_2 + license: LGPL-2.1-or-later + license_family: LGPL + size: 761857 + timestamp: 1757472971364 +- conda: https://prefix.dev/conda-forge/linux-64/sdl2-2.32.56-h54a6638_0.conda + sha256: 987ad072939fdd51c92ea8d3544b286bb240aefda329f9b03a51d9b7e777f9de + md5: cdd138897d94dc07d99afe7113a07bec + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libgl >=1.7.0,<2.0a0 + - sdl3 >=3.2.22,<4.0a0 + - libegl >=1.7.0,<2.0a0 + license: Zlib + size: 589145 + timestamp: 1757842881 +- conda: https://prefix.dev/conda-forge/osx-arm64/sdl2-2.32.56-h248ca61_0.conda + sha256: 704c5cae4bc839a18c70cbf3387d7789f1902828c79c6ddabcd34daf594f4103 + md5: 092c5b693dc6adf5f409d12f33295a2a + depends: + - libcxx >=19 + - __osx >=11.0 + - sdl3 >=3.2.22,<4.0a0 + license: Zlib + size: 542508 + timestamp: 1757842919681 +- conda: https://prefix.dev/conda-forge/win-64/sdl2-2.32.56-h5112557_0.conda + sha256: d17da21386bdbf32bce5daba5142916feb95eed63ef92b285808c765705bbfd2 + md5: 4cffbfebb6614a1bff3fc666527c25c7 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - sdl3 >=3.2.22,<4.0a0 + license: Zlib + size: 572101 + timestamp: 1757842925694 +- conda: https://prefix.dev/conda-forge/linux-64/sdl3-3.2.22-h68140b3_0.conda + sha256: 789ae811b7b93b01c2300461345027fd1a19a7a404e1b8729f58fbe81a82b3bc + md5: ebfddf2601e082193bb550924bbb9744 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 + - xorg-libxfixes >=6.0.1,<7.0a0 + - dbus >=1.16.2,<2.0a0 + - libegl >=1.7.0,<2.0a0 + - libudev1 >=257.7 + - libdrm >=2.4.125,<2.5.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxcursor >=1.2.3,<2.0a0 + - libxkbcommon >=1.11.0,<2.0a0 + - libusb >=1.0.29,<2.0a0 + - libgl >=1.7.0,<2.0a0 + - pulseaudio-client >=17.0,<17.1.0a0 + - xorg-libxscrnsaver >=1.2.4,<2.0a0 + - libunwind >=1.8.2,<1.9.0a0 + - wayland >=1.24.0,<2.0a0 + - liburing >=2.12,<2.13.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: Zlib + size: 1936633 + timestamp: 1756780211365 +- conda: https://prefix.dev/conda-forge/osx-arm64/sdl3-3.2.22-he22eeb8_0.conda + sha256: f4bebfe966e4df667887b06bea6539f2fde23bf3a89649f5b57b53716f1cc2d5 + md5: cd2b01e16daf07b77c3754bfdeb8095d + depends: + - __osx >=11.0 + - libcxx >=19 + - libusb >=1.0.29,<2.0a0 + - dbus >=1.16.2,<2.0a0 + license: Zlib + size: 1416196 + timestamp: 1756780255242 +- conda: https://prefix.dev/conda-forge/win-64/sdl3-3.2.22-h5112557_0.conda + sha256: 01d040f2ebe976a0b9cafc13e8b6fd2cf297afbcdec462a5e254cc8c261f70c5 + md5: ce2d3317d46b92ea361dd9178bc7df91 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libusb >=1.0.29,<2.0a0 + license: Zlib + size: 1521753 + timestamp: 1756780243694 +- conda: . + name: sdl_example + version: 0.1.0 + build: h9352c13_0 + subdir: win-64 + depends: + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - ucrt >=10.0.20348.0 + - sdl2 >=2.32.56,<3.0a0 + input: + hash: 21845b1fd51f8827fc89a50b134dc086fba8a8b5f8c9cc5ed3b800c99ea61f57 + globs: [] + package_build_source: + git: https://github.com/prefix-dev/pixi-build-testsuite?subdirectory=tests%2Fdata%2Fpixi_build%2Fcpp-with-path-to-source%2Fproject + rev: 6f94028045df9c753596a3084ce6ca2df4bc25c5 +- conda: . + name: sdl_example + version: 0.1.0 + build: hbf21a9e_0 + subdir: linux-64 + depends: + - libstdcxx >=15 + - libgcc >=15 + - sdl2 >=2.32.56,<3.0a0 + input: + hash: 21845b1fd51f8827fc89a50b134dc086fba8a8b5f8c9cc5ed3b800c99ea61f57 + globs: [] + package_build_source: + git: https://github.com/prefix-dev/pixi-build-testsuite?subdirectory=tests%2Fdata%2Fpixi_build%2Fcpp-with-path-to-source%2Fproject + rev: 6f94028045df9c753596a3084ce6ca2df4bc25c5 +- conda: . + name: sdl_example + version: 0.1.0 + build: hbf21a9e_0 + subdir: osx-arm64 + depends: + - libcxx >=21 + - sdl2 >=2.32.56,<3.0a0 + input: + hash: 21845b1fd51f8827fc89a50b134dc086fba8a8b5f8c9cc5ed3b800c99ea61f57 + globs: [] + package_build_source: + git: https://github.com/prefix-dev/pixi-build-testsuite?subdirectory=tests%2Fdata%2Fpixi_build%2Fcpp-with-path-to-source%2Fproject + rev: 6f94028045df9c753596a3084ce6ca2df4bc25c5 +- conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + size: 694692 + timestamp: 1756385147981 +- conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + sha256: cb357591d069a1e6cb74199a8a43a7e3611f72a6caed9faa49dbb3d7a0a98e0b + md5: 28f4ca1e0337d0f27afb8602663c5723 + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + size: 18249 + timestamp: 1753739241465 +- conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + sha256: af4b4b354b87a9a8d05b8064ff1ea0b47083274f7c30b4eb96bc2312c9b5f08f + md5: 603e41da40a765fd47995faa021da946 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_31 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + size: 682424 + timestamp: 1753739239305 +- conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + sha256: 67b317b64f47635415776718d25170a9a6f9a1218c0f5a6202bfd687e07b6ea4 + md5: a6b1d5c1fc3cb89f88f7179ee6a9afe3 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + size: 113963 + timestamp: 1753739198723 +- conda: https://prefix.dev/conda-forge/linux-64/wayland-1.24.0-h3e06ad9_0.conda + sha256: ba673427dcd480cfa9bbc262fd04a9b1ad2ed59a159bd8f7e750d4c52282f34c + md5: 0f2ca7906bf166247d1d760c3422cb8a + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - libgcc >=13 + - libstdcxx >=13 + license: MIT + license_family: MIT + size: 330474 + timestamp: 1751817998141 +- conda: https://prefix.dev/conda-forge/linux-64/xkeyboard-config-2.45-hb9d3cd8_0.conda + sha256: a5d4af601f71805ec67403406e147c48d6bad7aaeae92b0622b7e2396842d3fe + md5: 397a013c2dc5145a70737871aaa87e98 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT + size: 392406 + timestamp: 1749375847832 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda + sha256: 51909270b1a6c5474ed3978628b341b4d4472cd22610e5f22b506855a5e20f67 + md5: db038ce880f100acc74dba10302b5630 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + size: 835896 + timestamp: 1741901112627 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + sha256: ed10c9283974d311855ae08a16dfd7e56241fac632aec3b92e3cfe73cff31038 + md5: f6ebe2cb3f82ba6c057dde5d9debe4f7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 14780 + timestamp: 1734229004433 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + sha256: 832f538ade441b1eee863c8c91af9e69b356cd3e9e1350fff4fe36cc573fc91a + md5: 2ccd714aa2242315acaf0a67faea780b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + - xorg-libxrender >=0.9.11,<0.10.0a0 + license: MIT + license_family: MIT + size: 32533 + timestamp: 1730908305254 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + sha256: 6b250f3e59db07c2514057944a3ea2044d6a8cdde8a47b6497c254520fade1ee + md5: 8035c64cb77ed555e3f150b7b3972480 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 19901 + timestamp: 1727794976192 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda + sha256: da5dc921c017c05f38a38bd75245017463104457b63a1ce633ed41f214159c14 + md5: febbab7d15033c913d53c7a2c102309d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + size: 50060 + timestamp: 1727752228921 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda + sha256: 2fef37e660985794617716eb915865ce157004a4d567ed35ec16514960ae9271 + md5: 4bdb303603e9821baf5fe5fdff1dc8f8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + size: 19575 + timestamp: 1727794961233 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 + md5: 96d57aba173e878a2089d5638016dc5e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + size: 33005 + timestamp: 1734229037766 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libxscrnsaver-1.2.4-hb9d3cd8_0.conda + sha256: 58e8fc1687534124832d22e102f098b5401173212ac69eb9fd96b16a3e2c8cb2 + md5: 303f7a0e9e0cd7d250bb6b952cecda90 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: MIT + license_family: MIT + size: 14412 + timestamp: 1727899730073 +- conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 567578 + timestamp: 1742433379869 diff --git a/examples/pixi-build/cpp-git-source/pixi.toml b/examples/pixi-build/cpp-git-source/pixi.toml new file mode 100644 index 0000000000..5468332679 --- /dev/null +++ b/examples/pixi-build/cpp-git-source/pixi.toml @@ -0,0 +1,28 @@ +[package.build.source] +git = "https://github.com/prefix-dev/pixi-build-testsuite.git" +# branch = "fix/logging_tests" +subdirectory = "tests/data/pixi_build/cpp-with-path-to-source/project" + +[package.build.backend] +channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", +] +name = "pixi-build-cmake" +version = "*" + +[package] +name = "sdl_example" +version = "0.1.0" + +[package.host-dependencies] +# This ensures that SDL2 is available at build time. +sdl2 = ">=2.26.5,<3.0" + +[workspace] +channels = ["https://prefix.dev/conda-forge"] +platforms = ["osx-arm64", "linux-64", "win-64"] +preview = ["pixi-build"] + +[dependencies] +sdl_example = { path = "." } diff --git a/schema/model.py b/schema/model.py index 9edab33d32..32c508add8 100644 --- a/schema/model.py +++ b/schema/model.py @@ -751,11 +751,11 @@ class SourceLocation(StrictBaseModel): # md5: Md5Sum | None = Field(None, description="The md5 hash of the source") # sha256: Sha256Sum | None = Field(None, description="The sha256 hash of the source") - # git: NonEmptyStr | None = Field(None, description="The git URL to the source repo") - # rev: NonEmptyStr | None = Field(None, description="A git SHA revision to use") - # tag: NonEmptyStr | None = Field(None, description="A git tag to use") - # branch: NonEmptyStr | None = Field(None, description="A git branch to use") - # subdirectory: NonEmptyStr | None = Field(None, description="A subdirectory to use in the repo") + git: NonEmptyStr | None = Field(None, description="The git URL to the source repo") + rev: NonEmptyStr | None = Field(None, description="A git SHA revision to use") + tag: NonEmptyStr | None = Field(None, description="A git tag to use") + branch: NonEmptyStr | None = Field(None, description="A git branch to use") + subdirectory: NonEmptyStr | None = Field(None, description="A subdirectory to use in the repo") class Build(StrictBaseModel): @@ -777,7 +777,14 @@ class Build(StrictBaseModel): source: SourceLocation | None = Field( None, description="The source from which to build the package", - examples=[{"path": "project"}], + examples=[ + {"path": "project"}, + { + "git": "https://github.com/user/repo.git", + "rev": "bd62770509b8afd792e98d20f8b458e2a7f19ec2", + "subdirectory": "subproject/src", + }, + ], ) diff --git a/schema/schema.json b/schema/schema.json index d45550859e..6b0b8a2b8e 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -342,6 +342,11 @@ "examples": [ { "path": "project" + }, + { + "git": "https://github.com/user/repo.git", + "rev": "bd62770509b8afd792e98d20f8b458e2a7f19ec2", + "subdirectory": "subproject/src" } ] }, @@ -1721,11 +1726,41 @@ "type": "object", "additionalProperties": false, "properties": { + "branch": { + "title": "Branch", + "description": "A git branch to use", + "type": "string", + "minLength": 1 + }, + "git": { + "title": "Git", + "description": "The git URL to the source repo", + "type": "string", + "minLength": 1 + }, "path": { "title": "Path", "description": "The path to the source", "type": "string", "minLength": 1 + }, + "rev": { + "title": "Rev", + "description": "A git SHA revision to use", + "type": "string", + "minLength": 1 + }, + "subdirectory": { + "title": "Subdirectory", + "description": "A subdirectory to use in the repo", + "type": "string", + "minLength": 1 + }, + "tag": { + "title": "Tag", + "description": "A git tag to use", + "type": "string", + "minLength": 1 } } }, diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index f24babb173..07ce9829fe 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -3,6 +3,7 @@ import shutil import sys import tomllib +import tomli_w from pathlib import Path import pytest @@ -1191,6 +1192,55 @@ def test_frozen_no_install_invariant(pixi: Path, tmp_pixi_workspace: Path) -> No # Add bzip2 package to keep installation time low verify_cli_command([pixi, "add", "--manifest-path", manifest_path, "bzip2"]) + recipe_path = tmp_pixi_workspace / "recipe.yaml" + recipe_path.write_text( + """recipe: + name: frozen_no_install_build + version: "0.1.0" + +outputs: + - package: + name: frozen_no_install_build + build: + script: + - if: win + then: + - mkdir -p %PREFIX%\\bin + - echo @echo off > %PREFIX%\\bin\\frozen_no_install_build.bat + - echo echo Hello from frozen_no_install_build >> %PREFIX%\\bin\\frozen_no_install_build.bat + else: + - mkdir -p $PREFIX/bin + - echo "#!/usr/bin/env bash" > $PREFIX/bin/frozen_no_install_build + - echo "echo Hello from frozen_no_install_build" >> $PREFIX/bin/frozen_no_install_build + - chmod +x $PREFIX/bin/frozen_no_install_build +""" + ) + + manifest_data = tomllib.loads(manifest_path.read_text()) + workspace_table = manifest_data.setdefault("workspace", {}) + preview_list = workspace_table.setdefault("preview", []) + if "pixi-build" not in preview_list: + preview_list.append("pixi-build") + + dependencies = manifest_data.setdefault("dependencies", {}) + dependencies.pop("frozen_no_install_build", None) + + package_table = manifest_data.setdefault("package", {}) + package_table["name"] = "frozen_no_install_build" + package_table["version"] = "0.1.0" + build_table = package_table.setdefault("build", {}) + backend_table = build_table.setdefault("backend", {}) + backend_table["name"] = "pixi-build-rattler-build" + backend_table["version"] = "*" + backend_table["channels"] = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge", + ] + + manifest_path.write_text(tomli_w.dumps(manifest_data)) + + verify_cli_command([pixi, "lock", "--manifest-path", manifest_path]) + # Create a simple environment.yml file for import testing simple_env_yml = tmp_pixi_workspace / "simple_env.yml" simple_env_yml.write_text("""name: simple-env @@ -1250,6 +1300,8 @@ def check_invariants(command_name: str) -> None: ), # Upgrade commands (["upgrade"], [], "pixi upgrade"), + # Pixi build (can lock its source) + (["build"], [], "pixi build"), ] # This command needs to stay last so we always have something that requires a re-solve # Dont move this!