From 907972d0877a619a09671629a12e0a5159a18c10 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 19 Mar 2024 18:40:19 +0100 Subject: [PATCH 01/15] use best_platform in a few places --- src/cli/run.rs | 5 ++++- src/environment.rs | 2 +- src/lock_file/update.rs | 10 ++++++---- src/project/environment.rs | 16 ++++++++++++++++ src/project/virtual_packages.rs | 2 +- tests/common/mod.rs | 7 +++++-- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/cli/run.rs b/src/cli/run.rs index 3d96f0bf6..81af1a8f0 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -102,7 +102,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { let search_environment = SearchEnvironments::from_opt_env( &project, explicit_environment.clone(), - Some(Platform::current()), + explicit_environment + .as_ref() + .map(|e| e.best_platform()) + .or(Some(Platform::current())), ) .with_disambiguate_fn(disambiguate_task_interactive); diff --git a/src/environment.rs b/src/environment.rs index f5f3bf7bd..0eda0816b 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -209,7 +209,7 @@ pub async fn get_up_to_date_prefix( mut no_install: bool, existing_repo_data: IndexMap<(Channel, Platform), SparseRepoData>, ) -> miette::Result { - let current_platform = Platform::current(); + let current_platform = environment.best_platform(); let project = environment.project(); // Do not install if the platform is not supported diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 966b13f6a..b2f252a32 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -103,7 +103,7 @@ impl<'p> LockFileDerivedData<'p> { } // Get the prefix with the conda packages installed. - let platform = Platform::current(); + let platform = environment.best_platform(); let (prefix, python_status) = self.conda_prefix(environment).await?; let repodata_records = self .repodata_records(environment, platform) @@ -176,7 +176,7 @@ impl<'p> LockFileDerivedData<'p> { } let prefix = Prefix::new(environment.dir()); - let platform = Platform::current(); + let platform = environment.best_platform(); // Determine the currently installed packages. let installed_packages = prefix @@ -646,7 +646,9 @@ pub async fn ensure_up_to_date_lock_file( // we solve the platforms. We want to solve the current platform first, so we can start // instantiating prefixes if we have to. let mut ordered_platforms = platforms.into_iter().collect::>(); - if let Some(current_platform_index) = ordered_platforms.get_index_of(¤t_platform) { + if let Some(current_platform_index) = + ordered_platforms.get_index_of(&environment.best_platform()) + { ordered_platforms.move_index(current_platform_index, 0); } @@ -732,7 +734,7 @@ pub async fn ensure_up_to_date_lock_file( // Construct a future that will resolve when we have the repodata available for the current // platform for this group. let records_future = context - .get_latest_group_repodata_records(&group, current_platform) + .get_latest_group_repodata_records(&group, environment.best_platform) .ok_or_else(|| make_unsupported_pypi_platform_error(environment, current_platform))?; // Spawn a task to instantiate the environment diff --git a/src/project/environment.rs b/src/project/environment.rs index aabc1dbe6..6c70f635b 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -187,6 +187,22 @@ impl<'p> Environment<'p> { .unwrap_or_default() } + /// Returns the best platform for the current platform & environment. + pub fn best_platform(&self) -> Platform { + let current = Platform::current(); + + // If the current platform is supported, return it. + if self.platforms().contains(¤t) { + return current; + } + + if current.is_osx() && self.platforms().contains(&Platform::Osx64) { + return Platform::Osx64; + } + + current + } + /// Returns the tasks defined for this environment. /// /// Tasks are defined on a per-target per-feature per-environment basis. diff --git a/src/project/virtual_packages.rs b/src/project/virtual_packages.rs index f0eea7dc8..765b4db5c 100644 --- a/src/project/virtual_packages.rs +++ b/src/project/virtual_packages.rs @@ -135,7 +135,7 @@ pub enum VerifyCurrentPlatformError { pub fn verify_current_platform_has_required_virtual_packages( environment: &Environment<'_>, ) -> Result<(), VerifyCurrentPlatformError> { - let current_platform = Platform::current(); + let current_platform = environment.best_platform(); // Is the current platform in the list of supported platforms? if !environment.platforms().contains(¤t_platform) { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 170ec67ac..5a45c3066 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -292,8 +292,11 @@ impl PixiControl { // Create a task graph from the command line arguments. let search_env = SearchEnvironments::from_opt_env( &project, - explicit_environment, - Some(Platform::current()), + explicit_environment.clone(), + explicit_environment + .as_ref() + .map(|e| e.best_platform()) + .or(Some(Platform::current())), ); let task_graph = TaskGraph::from_cmd_args(&project, &search_env, args.task) .map_err(RunError::TaskGraphError)?; From 8a2b916e8acccc4577ba4891a740ae3b0b6e8067 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Thu, 18 Apr 2024 17:51:13 +0200 Subject: [PATCH 02/15] improve cross-platform experience a bit more --- src/cli/global/install.rs | 15 ++++++++++++--- src/cli/global/upgrade.rs | 10 ++++++++-- src/cli/global/upgrade_all.rs | 7 ++++++- src/cli/info.rs | 4 ++-- src/cli/list.rs | 4 ++-- src/cli/run.rs | 4 ++-- src/cli/task.rs | 7 ++++--- src/cli/tree.rs | 2 +- src/environment.rs | 2 ++ src/install_pypi.rs | 7 ++----- src/lock_file/update.rs | 3 ++- src/project/environment.rs | 18 +++++++++++++++++- src/task/task_hash.rs | 6 +++--- 13 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index 1303d24e7..dc255758f 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -44,6 +44,9 @@ pub struct Args { #[clap(short, long)] channel: Vec, + #[clap(short, long, default_value_t = Platform::current())] + platform: Platform, + #[clap(flatten)] config: ConfigCli, } @@ -262,8 +265,13 @@ pub async fn execute(args: Args) -> miette::Result<()> { let package_name = package_name(&package_matchspec)?; let records = load_package_records(package_matchspec, &sparse_repodata)?; - let (prefix_package, scripts, _) = - globally_install_package(&package_name, records, authenticated_client.clone()).await?; + let (prefix_package, scripts, _) = globally_install_package( + &package_name, + records, + authenticated_client.clone(), + &args.platform, + ) + .await?; let channel_name = channel_name_from_prefix(&prefix_package, config.channel_config()); let record = &prefix_package.repodata_record.package_record; @@ -328,6 +336,7 @@ pub(super) async fn globally_install_package( package_name: &PackageName, records: Vec, authenticated_client: ClientWithMiddleware, + platform: &Platform, ) -> miette::Result<(PrefixRecord, Vec, bool)> { // Create the binary environment prefix where we install or update the package let BinEnvDir(bin_prefix) = BinEnvDir::create(package_name).await?; @@ -336,7 +345,7 @@ pub(super) async fn globally_install_package( // Create the transaction that we need let transaction = - Transaction::from_current_and_desired(prefix_records.clone(), records, Platform::current()) + Transaction::from_current_and_desired(prefix_records.clone(), records, platform.clone()) .into_diagnostic()?; let has_transactions = !transaction.operations.is_empty(); diff --git a/src/cli/global/upgrade.rs b/src/cli/global/upgrade.rs index 9578d350e..b2fdd9f25 100644 --- a/src/cli/global/upgrade.rs +++ b/src/cli/global/upgrade.rs @@ -4,7 +4,7 @@ use clap::Parser; use indicatif::ProgressBar; use itertools::Itertools; use miette::IntoDiagnostic; -use rattler_conda_types::{Channel, MatchSpec, PackageName, Version}; +use rattler_conda_types::{Channel, MatchSpec, PackageName, Platform, Version}; use rattler_conda_types::{ParseStrictness, RepoDataRecord}; use reqwest_middleware::ClientWithMiddleware; @@ -35,6 +35,10 @@ pub struct Args { /// the package was installed from will always be used. #[clap(short, long)] channel: Vec, + + /// The platform to install the package for. + #[clap(long, default_value_t = Platform::current())] + platform: Platform, } pub async fn execute(args: Args) -> miette::Result<()> { @@ -115,6 +119,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { toinstall_version, records, authenticated_client, + &args.platform, ) .await } @@ -125,6 +130,7 @@ pub(super) async fn upgrade_package( toinstall_version: Version, records: Vec, authenticated_client: ClientWithMiddleware, + platform: &Platform, ) -> miette::Result<()> { let message = format!( "{} v{} -> v{}", @@ -141,7 +147,7 @@ pub(super) async fn upgrade_package( console::style("Updating").green(), message )); - globally_install_package(package_name, records, authenticated_client).await?; + globally_install_package(package_name, records, authenticated_client, &platform).await?; pb.finish_with_message(format!("{} {}", console::style("Updated").green(), message)); Ok(()) } diff --git a/src/cli/global/upgrade_all.rs b/src/cli/global/upgrade_all.rs index 358a480fd..cab259b60 100644 --- a/src/cli/global/upgrade_all.rs +++ b/src/cli/global/upgrade_all.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use clap::Parser; use itertools::Itertools; use miette::IntoDiagnostic; -use rattler_conda_types::{Channel, MatchSpec, ParseStrictness}; +use rattler_conda_types::{Channel, MatchSpec, ParseStrictness, Platform}; use crate::config::{Config, ConfigCli}; @@ -30,6 +30,10 @@ pub struct Args { #[clap(flatten)] config: ConfigCli, + + /// The platform to install the package for. + #[clap(long, default_value_t = Platform::current())] + platform: Platform, } pub async fn execute(args: Args) -> miette::Result<()> { @@ -93,6 +97,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { toinstall_version, records, authenticated_client.clone(), + &args.platform, ) .await?; upgraded = true; diff --git a/src/cli/info.rs b/src/cli/info.rs index 33deae6d9..ec8c23376 100644 --- a/src/cli/info.rs +++ b/src/cli/info.rs @@ -323,12 +323,12 @@ pub async fn execute(args: Args) -> miette::Result<()> { .map(|solve_group| solve_group.name().to_string()), environment_size: None, dependencies: env - .dependencies(None, Some(Platform::current())) + .dependencies(None, Some(env.best_platform())) .names() .map(|p| p.as_source().to_string()) .collect(), pypi_dependencies: env - .pypi_dependencies(Some(Platform::current())) + .pypi_dependencies(Some(env.best_platform())) .into_iter() .map(|(name, _p)| name.as_source().to_string()) .collect(), diff --git a/src/cli/list.rs b/src/cli/list.rs index 77b693bca..26ca23dc3 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -121,7 +121,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { .await?; // Load the platform - let platform = args.platform.unwrap_or_else(Platform::current); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); // Get all the packages in the environment. let locked_deps = lock_file @@ -140,7 +140,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let mut registry_index = if let Some(python_record) = python_record { uv_context = UvResolutionContext::from_project(&project)?; tags = get_pypi_tags( - Platform::current(), + platform, &project.system_requirements(), python_record.package_record(), )?; diff --git a/src/cli/run.rs b/src/cli/run.rs index 81af1a8f0..cd440df26 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -207,7 +207,7 @@ fn command_not_found<'p>(project: &'p Project, explicit_environment: Option = if let Some(explicit_environment) = explicit_environment { explicit_environment - .tasks(Some(Platform::current()), true) + .tasks(Some(explicit_environment.best_platform()), true) .into_iter() .flat_map(|tasks| tasks.into_keys()) .map(ToOwned::to_owned) @@ -218,7 +218,7 @@ fn command_not_found<'p>(project: &'p Project, explicit_environment: Option miette::Result<()> { } Operation::List(args) => { let env = EnvironmentName::from_arg_or_env_var(args.environment); - let tasks = project + let env = project .environment(&env) - .ok_or(miette!("Environment `{}` not found in project", env))? - .tasks(Some(Platform::current()), true)? + .ok_or(miette!("Environment `{}` not found in project", env))?; + let tasks = env + .tasks(Some(env.best_platform()), true)? .into_keys() .collect_vec(); if tasks.is_empty() { diff --git a/src/cli/tree.rs b/src/cli/tree.rs index 3c1e9433c..a5570f9bd 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -81,7 +81,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { ..UpdateLockFileOptions::default() }) .await?; - let platform = args.platform.unwrap_or_else(Platform::current); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); let locked_deps = lock_file .lock_file .environment(environment.name().as_str()) diff --git a/src/environment.rs b/src/environment.rs index 0eda0816b..475b98955 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -252,6 +252,7 @@ pub async fn update_prefix_pypi( uv_context: UvResolutionContext, environment_variables: &HashMap, lock_file_dir: &Path, + platform: Platform, ) -> miette::Result<()> { // Remove python packages from a previous python distribution if the python version changed. @@ -271,6 +272,7 @@ pub async fn update_prefix_pypi( system_requirements, uv_context, environment_variables, + platform, ) }, ) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 8d231b66c..f3fdf2a76 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -692,6 +692,7 @@ pub async fn update_python_distributions( system_requirements: &SystemRequirements, uv_context: UvResolutionContext, environment_variables: &HashMap, + platform: Platform, ) -> miette::Result<()> { let start = std::time::Instant::now(); let Some(python_info) = status.current_info() else { @@ -712,11 +713,7 @@ pub async fn update_python_distributions( .iter() .find(|r| is_python_record(r)) .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {PROJECT_MANIFEST}, or run:\n\n\tpixi add python"))?; - let tags = get_pypi_tags( - Platform::current(), - system_requirements, - &python_record.package_record, - )?; + let tags = get_pypi_tags(platform, system_requirements, &python_record.package_record)?; // Resolve the flat indexes from `--find-links`. let flat_index = { diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index b2f252a32..58ae2a75f 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -132,6 +132,7 @@ impl<'p> LockFileDerivedData<'p> { uv_context, env_variables, self.project.root(), + environment.best_platform(), ) .await?; @@ -734,7 +735,7 @@ pub async fn ensure_up_to_date_lock_file( // Construct a future that will resolve when we have the repodata available for the current // platform for this group. let records_future = context - .get_latest_group_repodata_records(&group, environment.best_platform) + .get_latest_group_repodata_records(&group, environment.best_platform()) .ok_or_else(|| make_unsupported_pypi_platform_error(environment, current_platform))?; // Spawn a task to instantiate the environment diff --git a/src/project/environment.rs b/src/project/environment.rs index 6c70f635b..ac116a14d 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -9,7 +9,7 @@ use crate::task::TaskName; use crate::{task::Task, Project}; use indexmap::{IndexMap, IndexSet}; use itertools::{Either, Itertools}; -use rattler_conda_types::{Channel, Platform}; +use rattler_conda_types::{Arch, Channel, Platform}; use std::hash::{Hash, Hasher}; use std::{ borrow::Cow, @@ -197,9 +197,25 @@ impl<'p> Environment<'p> { } if current.is_osx() && self.platforms().contains(&Platform::Osx64) { + if !self.project.pixi_dir().join("osx-warn").exists() { + tracing::warn!( + "macOS ARM64 is not supported by the pixi.toml, falling back to osx-64 (emulated with Rosetta)" + ); + // Create a file to prevent the warning from showing up multiple times. Also ignore the result. + std::fs::File::create(self.project.pixi_dir().join("osx-emulation-warn")).ok(); + } return Platform::Osx64; } + if self.platforms().len() == 1 { + // Take the first platform and see if it is a WASM one. + if let Some(platform) = self.platforms().iter().next() { + if platform.arch() == Some(Arch::Wasm32) { + return *platform; + } + } + } + current } diff --git a/src/task/task_hash.rs b/src/task/task_hash.rs index 7f4691355..e88815aea 100644 --- a/src/task/task_hash.rs +++ b/src/task/task_hash.rs @@ -1,7 +1,6 @@ use crate::project; use crate::task::{ExecutableTask, FileHashes, FileHashesError, InvalidWorkingDirectory}; use miette::Diagnostic; -use rattler_conda_types::Platform; use rattler_lock::LockFile; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -46,7 +45,8 @@ pub struct EnvironmentHash(String); impl EnvironmentHash { fn from_environment(run_environment: &project::Environment<'_>, lock_file: &LockFile) -> Self { let mut hasher = Xxh3::new(); - let activation_scripts = run_environment.activation_scripts(Some(Platform::current())); + let activation_scripts = + run_environment.activation_scripts(Some(run_environment.best_platform())); for script in activation_scripts { script.hash(&mut hasher); @@ -55,7 +55,7 @@ impl EnvironmentHash { let mut urls = Vec::new(); if let Some(env) = lock_file.environment(run_environment.name().as_str()) { - if let Some(packages) = env.packages(Platform::current()) { + if let Some(packages) = env.packages(run_environment.best_platform()) { for package in packages { urls.push(package.url_or_path().into_owned().to_string()) } From 4a11f903996ae00d43e4144fad68b12dc561dace Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 19 Apr 2024 11:16:49 +0200 Subject: [PATCH 03/15] fix clippy --- src/cli/global/install.rs | 2 +- src/cli/global/upgrade.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index dc255758f..1f283d786 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -345,7 +345,7 @@ pub(super) async fn globally_install_package( // Create the transaction that we need let transaction = - Transaction::from_current_and_desired(prefix_records.clone(), records, platform.clone()) + Transaction::from_current_and_desired(prefix_records.clone(), records, *platform) .into_diagnostic()?; let has_transactions = !transaction.operations.is_empty(); diff --git a/src/cli/global/upgrade.rs b/src/cli/global/upgrade.rs index b2fdd9f25..854cf778e 100644 --- a/src/cli/global/upgrade.rs +++ b/src/cli/global/upgrade.rs @@ -147,7 +147,7 @@ pub(super) async fn upgrade_package( console::style("Updating").green(), message )); - globally_install_package(package_name, records, authenticated_client, &platform).await?; + globally_install_package(package_name, records, authenticated_client, platform).await?; pb.finish_with_message(format!("{} {}", console::style("Updated").green(), message)); Ok(()) } From 2c3788592b2b13cc02365617ca462cc9e3e3f40f Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 19 Apr 2024 12:46:07 +0200 Subject: [PATCH 04/15] improve emscripten wasm story --- src/lock_file/update.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 58ae2a75f..057a279cd 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -110,6 +110,10 @@ impl<'p> LockFileDerivedData<'p> { .unwrap_or_default(); let pypi_records = self.pypi_records(environment, platform).unwrap_or_default(); + if pypi_records.is_empty() && !environment.has_pypi_dependencies() { + return Ok(prefix); + } + let uv_context = match &self.uv_context { None => { let context = UvResolutionContext::from_project(self.project)?; From 7b864cb72e84c02264c9e7b274d0df8740493c49 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 19 Apr 2024 13:01:55 +0200 Subject: [PATCH 05/15] make emscripten not fail --- src/lock_file/satisfiability.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lock_file/satisfiability.rs b/src/lock_file/satisfiability.rs index 1c4cddc6b..4c21c9ea9 100644 --- a/src/lock_file/satisfiability.rs +++ b/src/lock_file/satisfiability.rs @@ -379,7 +379,18 @@ pub fn verify_package_platform_satisfiability( let marker_environment = python_interpreter_record .map(|interpreter| determine_marker_environment(platform, &interpreter.package_record)) .transpose() - .map_err(|err| PlatformUnsat::FailedToDetermineMarkerEnvironment(err.into()))?; + .map_err(|err| PlatformUnsat::FailedToDetermineMarkerEnvironment(err.into())); + + let marker_environment = match marker_environment { + Err(err) => { + if !pypi_requirements.is_empty() { + return Err(err); + } else { + None + } + } + Ok(marker_environment) => marker_environment, + }; // Determine the pypi packages provided by the locked conda packages. let locked_conda_pypi_packages = locked_conda_packages.by_pypi_name(); From d456d99360c327b9387362d4895a6f2f0a726c56 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 29 Apr 2024 23:16:45 +0200 Subject: [PATCH 06/15] fix up rust and add platforms to schema --- schema/model.py | 2 ++ schema/schema.json | 6 ++++++ src/cli/run.rs | 4 ++-- src/cli/task.rs | 2 +- src/project/environment.rs | 7 +++---- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/schema/model.py b/schema/model.py index 5ef06d08a..7c98a9624 100644 --- a/schema/model.py +++ b/schema/model.py @@ -47,6 +47,8 @@ | Literal["win-32"] | Literal["win-64"] | Literal["win-arm64"] + | Literal["emscripten-wasm32"] + | Literal["wasi-wasm32"] ) diff --git a/schema/schema.json b/schema/schema.json index bf044ab62..d1014ab02 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -629,6 +629,12 @@ }, { "const": "win-arm64" + }, + { + "const": "emscripten-wasm32" + }, + { + "const": "wasi-wasm32" } ] } diff --git a/src/cli/run.rs b/src/cli/run.rs index ca55bd0a6..cc314fcb1 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -208,7 +208,7 @@ fn command_not_found<'p>(project: &'p Project, explicit_environment: Option = if let Some(explicit_environment) = explicit_environment { explicit_environment - .tasks(Some(explicit_environment.best_platform()), true) + .tasks(Some(explicit_environment.best_platform())) .into_iter() .flat_map(|tasks| tasks.into_keys()) .map(ToOwned::to_owned) @@ -219,7 +219,7 @@ fn command_not_found<'p>(project: &'p Project, explicit_environment: Option miette::Result<()> { Operation::List(args) => { let environment = project.environment_from_name_or_env_var(args.environment)?; let tasks = environment - .tasks(Some(environment.best_platform()), true)? + .tasks(Some(environment.best_platform()))? .into_keys() .collect_vec(); if tasks.is_empty() { diff --git a/src/project/environment.rs b/src/project/environment.rs index f2eaf33b2..c5db5f244 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -7,9 +7,8 @@ use crate::project::has_features::HasFeatures; use crate::task::TaskName; use crate::{task::Task, Project}; -use indexmap::{IndexMap, IndexSet}; -use itertools::{Either, Itertools}; -use rattler_conda_types::{Arch, Channel, Platform}; +use itertools::Either; +use rattler_conda_types::{Arch, Platform}; use std::hash::{Hash, Hasher}; use std::{collections::HashMap, fmt::Debug}; @@ -128,7 +127,7 @@ impl<'p> Environment<'p> { current } - + /// Returns the tasks defined for this environment. /// /// Tasks are defined on a per-target per-feature per-environment basis. From 67078c6e89493bd7c437fcb8885fe06fd58d226a Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 30 Apr 2024 15:09:18 +0200 Subject: [PATCH 07/15] fix uv package removal --- src/lock_file/update.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 36d26e2ec..afb219555 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -24,7 +24,7 @@ use indicatif::ProgressBar; use itertools::Itertools; use miette::{IntoDiagnostic, LabeledSpan, MietteDiagnostic, WrapErr}; use rattler::package_cache::PackageCache; -use rattler_conda_types::{Channel, MatchSpec, PackageName, Platform, RepoDataRecord}; +use rattler_conda_types::{Arch, Channel, MatchSpec, PackageName, Platform, RepoDataRecord}; use rattler_lock::{LockFile, PypiPackageData, PypiPackageEnvironmentData}; use rattler_repodata_gateway::sparse::SparseRepoData; use std::path::PathBuf; @@ -111,7 +111,9 @@ impl<'p> LockFileDerivedData<'p> { .unwrap_or_default(); let pypi_records = self.pypi_records(environment, platform).unwrap_or_default(); - if pypi_records.is_empty() && !environment.has_pypi_dependencies() { + // No `uv` support for WASM right now + // TODO - figure out if we can create the `uv` context more lazily + if platform.arch() == Some(Arch::Wasm32) { return Ok(prefix); } From ba40e43aeabc17d6c5efefe442ab3a126ea954aa Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 30 Apr 2024 15:37:00 +0200 Subject: [PATCH 08/15] fix tests and add some comments --- src/consts.rs | 1 + src/lock_file/satisfiability.rs | 2 ++ src/project/environment.rs | 28 +++++++++++++++++++--------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 230c56451..da3ee91f5 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,6 +11,7 @@ pub const ENVIRONMENTS_DIR: &str = "envs"; pub const SOLVE_GROUP_ENVIRONMENTS_DIR: &str = "solve-group-envs"; pub const PYPI_DEPENDENCIES: &str = "pypi-dependencies"; pub const TASK_CACHE_DIR: &str = "task-cache-v0"; +pub const MACOS_EMULATION_WARN: &str = "macos-emulation-warn"; pub const DEFAULT_ENVIRONMENT_NAME: &str = "default"; diff --git a/src/lock_file/satisfiability.rs b/src/lock_file/satisfiability.rs index 053a2bf3c..1240912cd 100644 --- a/src/lock_file/satisfiability.rs +++ b/src/lock_file/satisfiability.rs @@ -387,6 +387,8 @@ pub fn verify_package_platform_satisfiability( .transpose() .map_err(|err| PlatformUnsat::FailedToDetermineMarkerEnvironment(err.into())); + // We cannot determine the marker environment, for example if installing `wasm32` dependencies. + // However, it also doesn't really matter if we don't have any pypi requirements. let marker_environment = match marker_environment { Err(err) => { if !pypi_requirements.is_empty() { diff --git a/src/project/environment.rs b/src/project/environment.rs index c5db5f244..341eef92c 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -5,12 +5,17 @@ use super::{ }; use crate::project::has_features::HasFeatures; +use crate::consts; use crate::task::TaskName; use crate::{task::Task, Project}; use itertools::Either; use rattler_conda_types::{Arch, Platform}; -use std::hash::{Hash, Hasher}; -use std::{collections::HashMap, fmt::Debug}; +use std::{ + collections::HashMap, + fmt::Debug, + hash::{Hash, Hasher}, + sync::Once, +}; /// Describes a single environment from a project manifest. This is used to describe environments /// that can be installed and activated. @@ -105,14 +110,19 @@ impl<'p> Environment<'p> { return current; } + static INIT: Once = Once::new(); + if current.is_osx() && self.platforms().contains(&Platform::Osx64) { - if !self.project.pixi_dir().join("osx-warn").exists() { - tracing::warn!( - "macOS ARM64 is not supported by the pixi.toml, falling back to osx-64 (emulated with Rosetta)" - ); - // Create a file to prevent the warning from showing up multiple times. Also ignore the result. - std::fs::File::create(self.project.pixi_dir().join("osx-emulation-warn")).ok(); - } + INIT.call_once(|| { + let emulation_warn = self.project.pixi_dir().join(consts::MACOS_EMULATION_WARN); + if !emulation_warn.exists() { + tracing::warn!( + "osx-arm64 (Apple Silicon) is not supported by the pixi.toml, falling back to osx-64 (emulated with Rosetta)" + ); + // Create a file to prevent the warning from showing up multiple times. Also ignore the result. + std::fs::File::create(emulation_warn).ok(); + } + }); return Platform::Osx64; } From bf05df8500dc50c39f1b60c84f17e97392db88f8 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Tue, 30 Apr 2024 11:45:37 +0200 Subject: [PATCH 09/15] feat: alternative pypi registries (#1225) Enables the use of alternative PyPI registries. The definition of these can be specified per feature. There are certain rules for making unions out of these that are explained throughout the code. This adds the options for: * extra-index-urls * index-urls * find-links Which follows the familiar pypi conventions. These are deserialized and passed per environment or solve-group solve to uv. --------- Co-authored-by: Bas Zalmstra Co-authored-by: Bas Zalmstra Co-authored-by: Ruben Arts --- Cargo.lock | 444 +++++- Cargo.toml | 24 +- docs/reference/configuration.md | 25 + examples/pypi-custom-registry/.gitattributes | 2 + examples/pypi-custom-registry/.gitignore | 3 + examples/pypi-custom-registry/pixi.lock | 1258 +++++++++++++++++ examples/pypi-custom-registry/pixi.toml | 21 + examples/pypi-find-links/.gitattributes | 2 + examples/pypi-find-links/.gitignore | 3 + .../links/requests-2.31.0-py3-none-any.whl | Bin 0 -> 62574 bytes examples/pypi-find-links/pixi.lock | 1043 ++++++++++++++ examples/pypi-find-links/pixi.toml | 21 + examples/pypi-source-deps/pixi.lock | 160 +-- schema/model.py | 19 + schema/schema.json | 93 ++ src/cli/add.rs | 3 +- src/cli/global/common.rs | 3 +- src/cli/list.rs | 5 +- src/consts.rs | 3 +- src/environment.rs | 3 + src/install_pypi.rs | 30 +- src/lock_file/outdated.rs | 2 +- src/lock_file/resolve.rs | 160 ++- src/lock_file/satisfiability.rs | 29 +- src/lock_file/update.rs | 26 +- src/project/environment.rs | 68 + src/project/has_features.rs | 15 +- src/project/manifest/feature.rs | 34 + src/project/manifest/mod.rs | 33 +- src/project/manifest/pypi_options.rs | 252 ++++ ...ts__error_on_multiple_primary_indexes.snap | 5 + ...pi_options__tests__merge_pypi_options.snap | 13 + ...project__manifest__tests__invalid_key.snap | 2 +- ...__tests__pypi_options_default_feature.snap | 10 + src/project/manifest/validation.rs | 19 +- src/project/mod.rs | 8 +- src/project/solve_group.rs | 9 +- 37 files changed, 3625 insertions(+), 225 deletions(-) create mode 100644 examples/pypi-custom-registry/.gitattributes create mode 100644 examples/pypi-custom-registry/.gitignore create mode 100644 examples/pypi-custom-registry/pixi.lock create mode 100644 examples/pypi-custom-registry/pixi.toml create mode 100644 examples/pypi-find-links/.gitattributes create mode 100644 examples/pypi-find-links/.gitignore create mode 100644 examples/pypi-find-links/links/requests-2.31.0-py3-none-any.whl create mode 100644 examples/pypi-find-links/pixi.lock create mode 100644 examples/pypi-find-links/pixi.toml create mode 100644 src/project/manifest/pypi_options.rs create mode 100644 src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__error_on_multiple_primary_indexes.snap create mode 100644 src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__merge_pypi_options.snap create mode 100644 src/project/manifest/snapshots/pixi__project__manifest__tests__pypi_options_default_feature.snap diff --git a/Cargo.lock b/Cargo.lock index 4f1a9d065..2133f7866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,7 +373,7 @@ dependencies = [ "http-content-range", "itertools", "memmap2 0.9.4", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "thiserror", "tokio", @@ -722,13 +722,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -1648,6 +1647,67 @@ dependencies = [ "regex-syntax 0.8.3", ] +[[package]] +name = "google-cloud-auth" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf7cb7864f08a92e77c26bb230d021ea57691788fb5dd51793f96965d19e7f9" +dependencies = [ + "async-trait", + "base64 0.21.7", + "google-cloud-metadata", + "google-cloud-token", + "home", + "jsonwebtoken", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "time", + "tokio", + "tracing", + "urlencoding", +] + +[[package]] +name = "google-cloud-metadata" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc279bfb50487d7bcd900e8688406475fc750fe474a835b2ab9ade9eb1fc90e2" +dependencies = [ + "reqwest 0.11.27", + "thiserror", + "tokio", +] + +[[package]] +name = "google-cloud-token" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c12ba8b21d128a2ce8585955246977fbce4415f680ebf9199b6f9d6d725f" +dependencies = [ + "async-trait", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1726,6 +1786,17 @@ dependencies = [ "utf8-width", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -1737,6 +1808,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1744,7 +1826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http", + "http 1.1.0", ] [[package]] @@ -1755,8 +1837,8 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -1769,7 +1851,7 @@ dependencies = [ "async-trait", "bincode", "cacache", - "http", + "http 1.1.0", "http-cache-semantics", "httpdate", "serde", @@ -1784,10 +1866,10 @@ checksum = "be3e27c4e2e510571cbcc601407b639667146aa9a4e818d5cc1d97c8b4b27d61" dependencies = [ "anyhow", "async-trait", - "http", + "http 1.1.0", "http-cache", "http-cache-semantics", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "serde", "url", @@ -1799,7 +1881,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92baf25cf0b8c9246baecf3a444546360a97b569168fdf92563ee6a47829920c" dependencies = [ - "http", + "http 1.1.0", "http-serde", "serde", "time", @@ -1817,7 +1899,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1133cafcce27ea69d35e56b3a8772e265633e04de73c5f4e1afdffc1d19b5419" dependencies = [ - "http", + "http 1.1.0", "serde", ] @@ -1854,6 +1936,30 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.6", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.3.1" @@ -1863,8 +1969,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", "httparse", "itoa", "pin-project-lite", @@ -1873,6 +1979,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "rustls 0.21.11", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.26.0" @@ -1880,16 +2000,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.1.0", + "hyper 1.3.1", "hyper-util", - "rustls", + "rustls 0.22.3", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1898,7 +2031,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-util", "native-tls", "tokio", @@ -1915,9 +2048,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.6", "tokio", @@ -2158,6 +2291,21 @@ dependencies = [ "treediff", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "junction" version = "1.0.0" @@ -2929,6 +3077,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.0", + "serde", +] + [[package]] name = "pep440_rs" version = "0.5.0" @@ -3114,7 +3272,7 @@ dependencies = [ "rattler_virtual_packages", "regex", "requirements-txt", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "reqwest-retry", "rstest", @@ -3431,9 +3589,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db9aee020b2a52dce14764a18a83a883ba5464a9cbd4d3b59ef1909dd01975a" +checksum = "f217021fa43c856d96d83a199992f969d288dfbff725aca853011a86ec3b4ba1" dependencies = [ "anyhow", "bytes", @@ -3456,7 +3614,7 @@ dependencies = [ "rattler_shell", "reflink-copy", "regex", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "smallvec", "tempfile", @@ -3514,9 +3672,9 @@ dependencies = [ [[package]] name = "rattler_lock" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058aa88ee37365fe645f272c4b650c4e1732325cdbb217c29f09c5bcd0c735c8" +checksum = "6329506655ae7257c90ef91c9a19cadef2d157d1b096f2a54e922b13f2b95147" dependencies = [ "chrono", "fxhash", @@ -3530,6 +3688,7 @@ dependencies = [ "rattler_digest", "serde", "serde_json", + "serde_repr", "serde_with", "serde_yaml", "thiserror", @@ -3548,9 +3707,9 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4586c089e479cfb541d81db67cd8b43e0036507ffbf536e1d393e8147aa685c6" +checksum = "3892df9b0816a074020239b83a8e03f49ed5e6be3f638ebb0e410630b1e4ab0d" dependencies = [ "anyhow", "async-trait", @@ -3559,11 +3718,12 @@ dependencies = [ "dirs", "fslock", "getrandom", - "http", + "google-cloud-auth", + "http 1.1.0", "itertools", "keyring", "netrc-rs", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "retry-policies", "serde", @@ -3575,9 +3735,9 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.20.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7876f7d6ac61c9f6dc5fd057a41cdc3b0adf045aa68bef95bbf6251abf4412" +checksum = "94fbb205bdb1f33e201ea59c6aa019f232785b22883aa7dce1073f9cdbbf69b6" dependencies = [ "bzip2", "chrono", @@ -3586,7 +3746,7 @@ dependencies = [ "rattler_conda_types", "rattler_digest", "rattler_networking", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "serde_json", "tar", @@ -3601,13 +3761,14 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.19.8" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb63240aadaf4c4f198f29a6de564840b63ee2eb84a1c6ecf336e5e465181816" +checksum = "99f3252f8a58e1a991c2bff138d353ffd72fa0489b701f1aff6756bf652c6434" dependencies = [ "anyhow", "async-compression", "blake2", + "bytes", "cache_control", "chrono", "futures", @@ -3623,7 +3784,7 @@ dependencies = [ "rattler_conda_types", "rattler_digest", "rattler_networking", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "serde", "serde_json", @@ -3658,9 +3819,9 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "0.20.7" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6d1812945680e75f8aceb7654c939dfed9a2560418d12c2d1e18f70acc0289" +checksum = "5425aeee32412edb497b92de3b3d6e6d1ba6079f479d8d450a4a5aa58c9d8c66" dependencies = [ "chrono", "futures", @@ -3839,6 +4000,50 @@ dependencies = [ "uv-warnings", ] +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.11", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.4" @@ -3851,12 +4056,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", @@ -3866,9 +4071,9 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.22.3", "rustls-native-certs", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "rustls-pki-types", "serde", "serde_json", @@ -3876,7 +4081,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-util", "tower-service", "url", @@ -3884,8 +4089,8 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", - "winreg", + "webpki-roots 0.26.1", + "winreg 0.52.0", ] [[package]] @@ -3896,8 +4101,8 @@ checksum = "0209efb52486ad88136190094ee214759ef7507068b27992256ed6610eb71a01" dependencies = [ "anyhow", "async-trait", - "http", - "reqwest", + "http 1.1.0", + "reqwest 0.12.4", "serde", "thiserror", "tower-service", @@ -3914,10 +4119,10 @@ dependencies = [ "chrono", "futures", "getrandom", - "http", - "hyper", + "http 1.1.0", + "hyper 1.3.1", "parking_lot 0.11.2", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "retry-policies", "tokio", @@ -4103,6 +4308,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.22.3" @@ -4112,7 +4329,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.2", "subtle", "zeroize", ] @@ -4124,12 +4341,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -4146,6 +4372,16 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.2" @@ -4193,6 +4429,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -4491,6 +4737,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4730,6 +4988,27 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tabwriter" version = "1.4.0" @@ -4791,18 +5070,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -4882,6 +5161,7 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2 0.5.6", @@ -4910,13 +5190,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.11", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls", + "rustls 0.22.3", "rustls-pki-types", "tokio", ] @@ -5239,10 +5529,10 @@ dependencies = [ "async-trait", "base64 0.22.0", "futures", - "http", + "http 1.1.0", "once-map", "once_cell", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "rust-netrc", "tokio", @@ -5319,13 +5609,13 @@ dependencies = [ "fs-err", "futures", "html-escape", - "http", + "http 1.1.0", "install-wheel-rs", "pep440_rs", "pep508_rs", "platform-tags", "pypi-types", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "reqwest-retry", "rkyv", @@ -5407,7 +5697,7 @@ dependencies = [ "pep508_rs", "platform-tags", "pypi-types", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "rmp-serde", "rustc-hash", @@ -5486,7 +5776,7 @@ dependencies = [ "hmac", "home", "rand", - "reqwest", + "reqwest 0.12.4", "sha1", "tokio", "tracing", @@ -5628,7 +5918,7 @@ dependencies = [ "once_cell", "pep440_rs", "pep508_rs", - "reqwest", + "reqwest 0.12.4", "reqwest-middleware", "tempfile", "thiserror", @@ -5850,6 +6140,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" version = "0.26.1" @@ -6131,6 +6427,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index d6623eaab..0e28d0403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ rattler = { version = "0.23.0", default-features = false, features = [ ] } rattler_conda_types = { version = "0.22.0", default-features = false } rattler_digest = { version = "0.19.3", default-features = false } -rattler_lock = { version = "0.22.3", default-features = false } +rattler_lock = { version = "0.22.4", default-features = false } rattler_networking = { version = "0.20.2", default-features = false } rattler_repodata_gateway = { version = "0.19.8", default-features = false, features = [ "sparse", @@ -89,7 +89,7 @@ rattler_repodata_gateway = { version = "0.19.8", default-features = false, featu rattler_shell = { version = "0.20.1", default-features = false, features = [ "sysinfo", ] } -rattler_solve = { version = "0.20.7", default-features = false, features = [ +rattler_solve = { version = "0.21.0", default-features = false, features = [ "resolvo", ] } rattler_virtual_packages = { version = "0.19.8", default-features = false } @@ -164,17 +164,15 @@ toml = "0.8.12" pep440_rs = { git = "https://github.com/astral-sh/uv", tag = "0.1.38" } pep508_rs = { git = "https://github.com/astral-sh/uv", tag = "0.1.38" } # deno_task_shell = { path = "../deno_task_shell" } - -# rattler = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_conda_types = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_digest = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_lock = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_networking = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_repodata_gateway = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_shell = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_solve = { git = "https://github.com/baszalmstra/rattler", branch = "main" } -# rattler_virtual_packages = { git = "https://github.com/baszalmstra/rattler", branch = "main" } - +# rattler = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_conda_types = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_digest = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_lock = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_networking = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_repodata_gateway = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_shell = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_solve = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } +# rattler_virtual_packages = { git = "https://github.com/baszalmstra/rattler", branch = "feat/pypi_indexes" } #rattler_conda_types = { path = "../rattler/crates/rattler_conda_types" } #rattler_digest = { path = "../rattler/crates/rattler_digest" } #rattler_networking = { path = "../rattler/crates/rattler_networking" } diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 69d201109..afe3d11dd 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -243,6 +243,31 @@ cuda = "11" # or any other version of cuda you want to use This informs the solver that cuda is going to be available, so it can lock it into the lock file if needed. +## The `pypi-options` table + +The `pypi-options` table is used to define options that are specific to PyPI registries. +These options can be specified either at the root level, which will add it to the default options feature, or on feature level, which will create a union of these options when the features are included in the environment. + +### Alternative registries +Currently the main reason to use this table is to define alternative registries. +We support: + +- `index-url`: replaces the main index url. +- `extra-index-urls`: adds an extra index url. +- `find-links`: which can either be a path `{path: './links'}` or a url `{url: 'https://example.com/links'}`. This is similar to the `--find-links` option in `pip`. + +An example: + +```toml +[pypi-options] +index-url = "https://pypi.org/simple" +extra-index-urls = ["https://example.com/simple"] +find-links = [{path: './links'}] +``` + +There are some examples in the pixi repository that make use of this feature. + + ## The `dependencies` table(s) This section defines what dependencies you would like to use for your project. diff --git a/examples/pypi-custom-registry/.gitattributes b/examples/pypi-custom-registry/.gitattributes new file mode 100644 index 000000000..d5799bd69 --- /dev/null +++ b/examples/pypi-custom-registry/.gitattributes @@ -0,0 +1,2 @@ +# GitHub syntax highlighting +pixi.lock linguist-language=YAML diff --git a/examples/pypi-custom-registry/.gitignore b/examples/pypi-custom-registry/.gitignore new file mode 100644 index 000000000..096b5eb54 --- /dev/null +++ b/examples/pypi-custom-registry/.gitignore @@ -0,0 +1,3 @@ +# pixi environments +.pixi +*.egg-info diff --git a/examples/pypi-custom-registry/pixi.lock b/examples/pypi-custom-registry/pixi.lock new file mode 100644 index 000000000..3a8fd1788 --- /dev/null +++ b/examples/pypi-custom-registry/pixi.lock @@ -0,0 +1,1258 @@ +version: 5 +environments: + alternative: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.tuna.tsinghua.edu.cn/simple/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.0-h47c9636_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl#sha256=4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl + - pypi: https://pypi.tuna.tsinghua.edu.cn/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.0-h47c9636_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl +packages: +- kind: conda + name: _libgcc_mutex + version: '0.1' + build: conda_forge + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + size: 2562 + timestamp: 1578324546067 +- kind: conda + name: _openmp_mutex + version: '4.5' + build: 2_gnu + build_number: 16 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + 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 +- kind: pypi + name: blinker + version: 1.7.0 + url: https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + sha256: c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 + requires_python: '>=3.8' +- kind: pypi + name: blinker + version: 1.8.1 + url: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + sha256: 5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6 + requires_python: '>=3.8' +- kind: conda + name: bzip2 + version: 1.0.8 + build: h10d778d_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + sha256: 61fb2b488928a54d9472113e1280b468a309561caa54f33825a3593da390b242 + md5: 6097a6ca9ada32699b5fc4312dd6ef18 + license: bzip2-1.0.6 + license_family: BSD + size: 127885 + timestamp: 1699280178474 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h93a5062_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda + sha256: bfa84296a638bea78a8bb29abc493ee95f2a0218775642474a840411b950fe5f + md5: 1bbc659ca658bfd49a481b5ef7a0f40f + license: bzip2-1.0.6 + license_family: BSD + size: 122325 + timestamp: 1699280294368 +- kind: conda + name: bzip2 + version: 1.0.8 + build: hcfcfb64_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda + sha256: ae5f47a5c86fd6db822931255dcf017eb12f60c77f07dc782ccb477f7808aab2 + md5: 26eb8ca6ea332b675e11704cce84a3be + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: bzip2-1.0.6 + license_family: BSD + size: 124580 + timestamp: 1699280668742 +- kind: conda + name: bzip2 + version: 1.0.8 + build: hd590300_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda + sha256: 242c0c324507ee172c0e0dd2045814e746bb303d1eb78870d182ceb0abc726a8 + md5: 69b8b6202a07720f448be700e300ccf4 + depends: + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + size: 254228 + timestamp: 1699279927352 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: h56e8100_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda + sha256: 4d587088ecccd393fec3420b64f1af4ee1a0e6897a45cfd5ef38055322cea5d0 + md5: 63da060240ab8087b60d1357051ea7d6 + license: ISC + size: 155886 + timestamp: 1706843918052 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: h8857fd0_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda + sha256: 54a794aedbb4796afeabdf54287b06b1d27f7b13b3814520925f4c2c80f58ca9 + md5: f2eacee8c33c43692f1ccfd33d0f50b1 + license: ISC + size: 155665 + timestamp: 1706843838227 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: hbcca054_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda + sha256: 91d81bfecdbb142c15066df70cc952590ae8991670198f92c66b62019b251aeb + md5: 2f4327a1cbe7f022401b236e915a5fef + license: ISC + size: 155432 + timestamp: 1706843687645 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: hf0a4a13_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda + sha256: 49bc3439816ac72d0c0e0f144b8cc870fdcc4adec2e861407ec818d8116b2204 + md5: fb416a1795f18dcc5a038bc2dc54edf9 + license: ISC + size: 155725 + timestamp: 1706844034242 +- kind: pypi + name: click + version: 8.1.7 + url: https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + sha256: ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 + requires_dist: + - colorama ; platform_system == 'Windows' + - importlib-metadata ; python_version < '3.8' + requires_python: '>=3.7' +- kind: pypi + name: click + version: 8.1.7 + url: https://pypi.tuna.tsinghua.edu.cn/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl + sha256: ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 + requires_dist: + - colorama ; platform_system == 'Windows' + - importlib-metadata ; python_version < '3.8' + requires_python: '>=3.7' +- kind: pypi + name: colorama + version: 0.4.6 + url: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + requires_python: '!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7' +- kind: pypi + name: colorama + version: 0.4.6 + url: https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl#sha256=4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + requires_python: '!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7' +- kind: pypi + name: flask + version: 3.0.3 + url: https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + sha256: 34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3 + requires_dist: + - werkzeug>=3.0.0 + - jinja2>=3.1.2 + - itsdangerous>=2.1.2 + - click>=8.1.3 + - blinker>=1.6.2 + - importlib-metadata>=3.6.0 ; python_version < '3.10' + - asgiref>=3.2 ; extra == 'async' + - python-dotenv ; extra == 'dotenv' + requires_python: '>=3.8' +- kind: pypi + name: flask + version: 3.0.3 + url: https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl + sha256: 34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3 + requires_dist: + - werkzeug>=3.0.0 + - jinja2>=3.1.2 + - itsdangerous>=2.1.2 + - click>=8.1.3 + - blinker>=1.6.2 + - importlib-metadata>=3.6.0 ; python_version < '3.10' + - asgiref>=3.2 ; extra == 'async' + - python-dotenv ; extra == 'dotenv' + requires_python: '>=3.8' +- kind: pypi + name: itsdangerous + version: 2.2.0 + url: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + sha256: c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef + requires_python: '>=3.8' +- kind: pypi + name: itsdangerous + version: 2.2.0 + url: https://pypi.tuna.tsinghua.edu.cn/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl + sha256: c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef + requires_python: '>=3.8' +- kind: pypi + name: jinja2 + version: 3.1.3 + url: https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + sha256: 7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa + requires_dist: + - markupsafe>=2.0 + - babel>=2.7 ; extra == 'i18n' + requires_python: '>=3.7' +- kind: pypi + name: jinja2 + version: 3.1.3 + url: https://pypi.tuna.tsinghua.edu.cn/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl + sha256: 7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa + requires_dist: + - markupsafe>=2.0 + - babel>=2.7 ; extra == 'i18n' + requires_python: '>=3.7' +- kind: conda + name: ld_impl_linux-64 + version: '2.40' + build: h55db66e_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + sha256: ef969eee228cfb71e55146eaecc6af065f468cb0bc0a5239bc053b39db0b5f09 + md5: 10569984e7db886e4f1abc2b47ad79a1 + constrains: + - binutils_impl_linux-64 2.40 + license: GPL-3.0-only + license_family: GPL + size: 713322 + timestamp: 1713651222435 +- kind: conda + name: libexpat + version: 2.6.2 + build: h59595ed_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + sha256: 331bb7c7c05025343ebd79f86ae612b9e1e74d2687b8f3179faec234f986ce19 + md5: e7ba12deb7020dd080c6c70e7b6f6a3d + depends: + - libgcc-ng >=12 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 73730 + timestamp: 1710362120304 +- kind: conda + name: libexpat + version: 2.6.2 + build: h63175ca_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + sha256: 79f612f75108f3e16bbdc127d4885bb74729cf66a8702fca0373dad89d40c4b7 + md5: bc592d03f62779511d392c175dcece64 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 139224 + timestamp: 1710362609641 +- kind: conda + name: libexpat + version: 2.6.2 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + sha256: a188a77b275d61159a32ab547f7d17892226e7dac4518d2c6ac3ac8fc8dfde92 + md5: 3d1d51c8f716d97c864d12f7af329526 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 69246 + timestamp: 1710362566073 +- kind: conda + name: libexpat + version: 2.6.2 + build: hebf3989_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda + sha256: ba7173ac30064ea901a4c9fb5a51846dcc25512ceb565759be7d18cbf3e5415e + md5: e3cde7cfa87f82f7cb13d482d5e0ad09 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 63655 + timestamp: 1710362424980 +- kind: conda + name: libffi + version: 3.4.2 + build: h0d85af4_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + sha256: 7a2d27a936ceee6942ea4d397f9c7d136f12549d86f7617e8b6bad51e01a941f + md5: ccb34fb14960ad8b125962d3d79b31a9 + license: MIT + license_family: MIT + size: 51348 + timestamp: 1636488394370 +- kind: conda + name: libffi + version: 3.4.2 + build: h3422bc3_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + sha256: 41b3d13efb775e340e4dba549ab5c029611ea6918703096b2eaa9c015c0750ca + md5: 086914b672be056eb70fd4285b6783b6 + license: MIT + license_family: MIT + size: 39020 + timestamp: 1636488587153 +- kind: conda + name: libffi + version: 3.4.2 + build: h7f98852_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e + md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + depends: + - libgcc-ng >=9.4.0 + license: MIT + license_family: MIT + size: 58292 + timestamp: 1636488182923 +- kind: conda + name: libffi + version: 3.4.2 + build: h8ffe710_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + sha256: 1951ab740f80660e9bc07d2ed3aefb874d78c107264fd810f24a1a6211d4b1a5 + md5: 2c96d1b6915b408893f9472569dee135 + depends: + - vc >=14.1,<15.0a0 + - vs2015_runtime >=14.16.27012 + license: MIT + license_family: MIT + size: 42063 + timestamp: 1636489106777 +- kind: conda + name: libgcc-ng + version: 13.2.0 + build: hc881cc4_6 + build_number: 6 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + sha256: 836a0057525f1414de43642d357d0ab21ac7f85e24800b010dbc17d132e6efec + md5: df88796bd09a0d2ed292e59101478ad8 + depends: + - _libgcc_mutex 0.1 conda_forge + - _openmp_mutex >=4.5 + constrains: + - libgomp 13.2.0 hc881cc4_6 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 777315 + timestamp: 1713755001744 +- kind: conda + name: libgomp + version: 13.2.0 + build: hc881cc4_6 + build_number: 6 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + sha256: e722b19b23b31a14b1592d5eceabb38dc52452ff5e4d346e330526971c22e52a + md5: aae89d3736661c36a5591788aebd0817 + depends: + - _libgcc_mutex 0.1 conda_forge + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 422363 + timestamp: 1713754915251 +- kind: conda + name: libnsl + version: 2.0.1 + build: hd590300_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 + md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 + depends: + - libgcc-ng >=12 + license: LGPL-2.1-only + license_family: GPL + size: 33408 + timestamp: 1697359010159 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h091b4b1_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + sha256: 4337f466eb55bbdc74e168b52ec8c38f598e3664244ec7a2536009036e2066cc + md5: c8c1186c7f3351f6ffddb97b1f54fc58 + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 824794 + timestamp: 1713367748819 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h2797004_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + sha256: e2273d6860eadcf714a759ffb6dc24a69cfd01f2a0ea9d6c20f86049b9334e0c + md5: b3316cbe90249da4f8e84cd66e1cc55b + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 859858 + timestamp: 1713367435849 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h92b6c6a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + sha256: 4d44b68fb29dcbc2216a8cae0b274b02ef9b4ae05d1d0f785362ed30b91c9b52 + md5: 68e462226209f35182ef66eda0f794ff + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 902546 + timestamp: 1713367776445 +- kind: conda + name: libsqlite + version: 3.45.3 + build: hcfcfb64_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + sha256: 06ec75faa51d7ec6d5db98889e869b579a9df19d7d3d9baff8359627da4a3b7e + md5: 73f5dc8e2d55d9a1e14b11f49c3b4a28 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: Unlicense + size: 870518 + timestamp: 1713367888406 +- kind: conda + name: libuuid + version: 2.38.1 + build: h0b41bf4_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + size: 33601 + timestamp: 1680112270483 +- kind: conda + name: libzlib + version: 1.2.13 + build: h53f4e23_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda + sha256: ab1c8aefa2d54322a63aaeeefe9cf877411851738616c4068e0dccc66b9c758a + md5: 1a47f5236db2e06a320ffa0392f81bd8 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 48102 + timestamp: 1686575426584 +- kind: conda + name: libzlib + version: 1.2.13 + build: h8a1eda9_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda + sha256: fc58ad7f47ffea10df1f2165369978fba0a1cc32594aad778f5eec725f334867 + md5: 4a3ad23f6e16f99c04e166767193d700 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 59404 + timestamp: 1686575566695 +- kind: conda + name: libzlib + version: 1.2.13 + build: hcfcfb64_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda + sha256: c161822ee8130b71e08b6d282b9919c1de2c5274b29921a867bca0f7d30cad26 + md5: 5fdb9c6a113b6b6cb5e517fd972d5f41 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 55800 + timestamp: 1686575452215 +- kind: conda + name: libzlib + version: 1.2.13 + build: hd590300_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda + sha256: 370c7c5893b737596fd6ca0d9190c9715d89d888b8c88537ae1ef168c25e82e4 + md5: f36c115f1ee199da648e0597ec2047ad + depends: + - libgcc-ng >=12 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 61588 + timestamp: 1686575217516 +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl + sha256: 8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl + sha256: 3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl + sha256: 823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://pypi.tuna.tsinghua.edu.cn/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl + sha256: 8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://pypi.tuna.tsinghua.edu.cn/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl + sha256: 823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://pypi.tuna.tsinghua.edu.cn/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl + sha256: 3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 + requires_python: '>=3.7' +- kind: pypi + name: markupsafe + version: 2.1.5 + url: https://pypi.tuna.tsinghua.edu.cn/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 + requires_python: '>=3.7' +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h078ce10_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda + sha256: 06f0905791575e2cd3aa961493c56e490b3d82ad9eb49f1c332bd338b0216911 + md5: 616ae8691e6608527d0071e6766dcb81 + license: X11 AND BSD-3-Clause + size: 820249 + timestamp: 1710866874348 +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h59595ed_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda + sha256: aa0f005b6727aac6507317ed490f0904430584fa8ca722657e7f0fb94741de81 + md5: 97da8860a0da5413c7c98a3b3838a645 + depends: + - libgcc-ng >=12 + license: X11 AND BSD-3-Clause + size: 895669 + timestamp: 1710866638986 +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda + sha256: 50b72acf08acbc4e5332807653e2ca6b26d4326e8af16fad1fd3f2ce9ea55503 + md5: 50f28c512e9ad78589e3eab34833f762 + license: X11 AND BSD-3-Clause + size: 823010 + timestamp: 1710866856626 +- kind: conda + name: openssl + version: 3.2.1 + build: h0d3ecfb_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda + sha256: 519dc941d7ab0ebf31a2878d85c2f444450e7c5f6f41c4d07252c6bb3417b78b + md5: eb580fb888d93d5d550c557323ac5cee + depends: + - ca-certificates + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2855250 + timestamp: 1710793435903 +- kind: conda + name: openssl + version: 3.2.1 + build: hcfcfb64_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda + sha256: 61ce4e11c3c26ed4e4d9b7e7e2483121a1741ad0f9c8db0a91a28b6e05182ce6 + md5: 958e0418e93e50c575bff70fbcaa12d8 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 8230112 + timestamp: 1710796158475 +- kind: conda + name: openssl + version: 3.2.1 + build: hd590300_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda + sha256: 2c689444ed19a603be457284cf2115ee728a3fafb7527326e96054dee7cdc1a7 + md5: 9d731343cff6ee2e5a25c4a091bf8e2a + depends: + - ca-certificates + - libgcc-ng >=12 + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2865379 + timestamp: 1710793235846 +- kind: conda + name: openssl + version: 3.2.1 + build: hd75f5a5_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda + sha256: 7ae0ac6a1673584a8a380c2ff3d46eca48ed53bc7174c0d4eaa0dd2f247a0984 + md5: 570a6f04802df580be529f3a72d2bbf7 + depends: + - ca-certificates + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2506344 + timestamp: 1710793930515 +- kind: conda + name: python + version: 3.12.0 + build: h2628c8c_0_cpython + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda + sha256: 90553586879bf328f2f9efb8d8faa958ecba822faf379f0a20c3461467b9b955 + md5: defd5d375853a2caff36a19d2d81a28e + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - openssl >=3.1.3,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 16140836 + timestamp: 1696321871976 +- kind: conda + name: python + version: 3.12.0 + build: h30d4d87_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda + sha256: 0a1ed3983acbd0528bef5216179e46170f024f4409032875b27865568fef46a1 + md5: d11dc8f4551011fb6baa2865f1ead48f + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 14529683 + timestamp: 1696323482650 +- kind: conda + name: python + version: 3.12.0 + build: h47c9636_0_cpython + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.0-h47c9636_0_cpython.conda + sha256: eb66f8f249caa9d5a956c3a407f079e4779d652ebfc2a4b4f50dcea078e84fa8 + md5: ed8ae98b1b510de68392971b9367d18c + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 13306758 + timestamp: 1696322682581 +- kind: conda + name: python + version: 3.12.0 + build: hab00c5b_0_cpython + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda + sha256: 5398ebae6a1ccbfd3f76361eac75f3ac071527a8072627c4bf9008c689034f48 + md5: 7f97faab5bebcc2580f4f299285323da + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libnsl >=2.0.0,<2.1.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 32123473 + timestamp: 1696324522323 +- kind: conda + name: readline + version: '8.2' + build: h8228510_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 + md5: 47d31b792659ce70f470b5c82fdfb7a4 + depends: + - libgcc-ng >=12 + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 281456 + timestamp: 1679532220005 +- kind: conda + name: readline + version: '8.2' + build: h92ec313_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + sha256: a1dfa679ac3f6007362386576a704ad2d0d7a02e98f5d0b115f207a2da63e884 + md5: 8cbb776a2f641b943d413b3e19df71f4 + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 250351 + timestamp: 1679532511311 +- kind: conda + name: readline + version: '8.2' + build: h9e318b2_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + sha256: 41e7d30a097d9b060037f0c6a2b1d4c4ae7e942c06c943d23f9d481548478568 + md5: f17f77f2acf4d344734bda76829ce14e + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 255870 + timestamp: 1679532707590 +- kind: conda + name: tk + version: 8.6.13 + build: h1abcd95_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + sha256: 30412b2e9de4ff82d8c2a7e5d06a15f4f4fef1809a72138b6ccb53a33b26faf5 + md5: bf830ba5afc507c6232d4ef0fb1a882d + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3270220 + timestamp: 1699202389792 +- kind: conda + name: tk + version: 8.6.13 + build: h5083fa2_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + sha256: 72457ad031b4c048e5891f3f6cb27a53cb479db68a52d965f796910e71a403a8 + md5: b50a57ba89c32b62428b71a875291c9b + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3145523 + timestamp: 1699202432999 +- kind: conda + name: tk + version: 8.6.13 + build: h5226925_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + sha256: 2c4e914f521ccb2718946645108c9bd3fc3216ba69aea20c2c3cedbd8db32bb1 + md5: fc048363eb8f03cd1737600a5d08aafe + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + size: 3503410 + timestamp: 1699202577803 +- kind: conda + name: tk + version: 8.6.13 + build: noxft_h4845f30_101 + build_number: 101 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3318875 + timestamp: 1699202167581 +- kind: conda + name: tzdata + version: 2024a + build: h0c530f3_0 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + sha256: 7b2b69c54ec62a243eb6fba2391b5e443421608c3ae5dbff938ad33ca8db5122 + md5: 161081fc7cec0bfda0d86d7cb595f8d8 + license: LicenseRef-Public-Domain + size: 119815 + timestamp: 1706886945727 +- kind: conda + name: ucrt + version: 10.0.22621.0 + build: h57928b3_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + sha256: f29cdaf8712008f6b419b8b1a403923b00ab2504bfe0fb2ba8eb60e72d4f14c6 + md5: 72608f6cd3e5898229c3ea16deb1ac43 + constrains: + - vs2015_runtime >=14.29.30037 + license: LicenseRef-Proprietary + license_family: PROPRIETARY + size: 1283972 + timestamp: 1666630199266 +- kind: conda + name: vc + version: '14.3' + build: hcf57466_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda + sha256: 447a8d8292a7b2107dcc18afb67f046824711a652725fc0f522c368e7a7b8318 + md5: 20e1e652a4c740fa719002a8449994a2 + depends: + - vc14_runtime >=14.38.33130 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + size: 16977 + timestamp: 1702511255313 +- kind: conda + name: vc14_runtime + version: 14.38.33130 + build: h82b7239_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda + sha256: bf94c9af4b2e9cba88207001197e695934eadc96a5c5e4cd7597e950aae3d8ff + md5: 8be79fdd2725ddf7bbf8a27a4c1f79ba + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.38.33130.* *_18 + license: LicenseRef-ProprietaryMicrosoft + license_family: Proprietary + size: 749868 + timestamp: 1702511239004 +- kind: conda + name: vs2015_runtime + version: 14.38.33130 + build: hcb4865c_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + sha256: a2fec221f361d6263c117f4ea6d772b21c90a2f8edc6f3eb0eadec6bfe8843db + md5: 10d42885e3ed84e575b454db30f1aa93 + depends: + - vc14_runtime >=14.38.33130 + license: BSD-3-Clause + license_family: BSD + size: 16988 + timestamp: 1702511261442 +- kind: pypi + name: werkzeug + version: 3.0.2 + url: https://files.pythonhosted.org/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + sha256: 3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795 + requires_dist: + - markupsafe>=2.1.1 + - watchdog>=2.3 ; extra == 'watchdog' + requires_python: '>=3.8' +- kind: pypi + name: werkzeug + version: 3.0.2 + url: https://pypi.tuna.tsinghua.edu.cn/packages/e3/23/c9843d7550092ae7ad380611c238f44afef66f58f76c1dab7dcf313e4339/werkzeug-3.0.2-py3-none-any.whl + sha256: 3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795 + requires_dist: + - markupsafe>=2.1.1 + - watchdog>=2.3 ; extra == 'watchdog' + requires_python: '>=3.8' +- kind: conda + name: xz + version: 5.2.6 + build: h166bdaf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 + md5: 2161070d867d1b1204ea749c8eec4ef0 + depends: + - libgcc-ng >=12 + license: LGPL-2.1 and GPL-2.0 + size: 418368 + timestamp: 1660346797927 +- kind: conda + name: xz + version: 5.2.6 + build: h57fd34a_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + sha256: 59d78af0c3e071021cfe82dc40134c19dab8cdf804324b62940f5c8cd71803ec + md5: 39c6b54e94014701dd157f4f576ed211 + license: LGPL-2.1 and GPL-2.0 + size: 235693 + timestamp: 1660346961024 +- kind: conda + name: xz + version: 5.2.6 + build: h775f41a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + sha256: eb09823f34cc2dd663c0ec4ab13f246f45dcd52e5b8c47b9864361de5204a1c8 + md5: a72f9d4ea13d55d745ff1ed594747f10 + license: LGPL-2.1 and GPL-2.0 + size: 238119 + timestamp: 1660346964847 +- kind: conda + name: xz + version: 5.2.6 + build: h8d14728_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + sha256: 54d9778f75a02723784dc63aff4126ff6e6749ba21d11a6d03c1f4775f269fe0 + md5: 515d77642eaa3639413c6b1bc3f94219 + depends: + - vc >=14.1,<15 + - vs2015_runtime >=14.16.27033 + license: LGPL-2.1 and GPL-2.0 + size: 217804 + timestamp: 1660346976440 diff --git a/examples/pypi-custom-registry/pixi.toml b/examples/pypi-custom-registry/pixi.toml new file mode 100644 index 000000000..d54a1b5bb --- /dev/null +++ b/examples/pypi-custom-registry/pixi.toml @@ -0,0 +1,21 @@ +[project] +name = "pypi-custom-registry" +authors = ["Tim de Jager "] +channels = ["conda-forge"] +platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"] + +[tasks] +start = {depends_on = ["test"]} +test = "python -c 'import importlib.metadata; print(importlib.metadata.version(\"flask\"))'" + +[dependencies] +python = "3.12" + +[pypi-dependencies] +flask = "==3.0.3" + +[feature.alternative.pypi-options] +index-url = "https://pypi.tuna.tsinghua.edu.cn/simple/" + +[environments] +alternative = ["alternative"] diff --git a/examples/pypi-find-links/.gitattributes b/examples/pypi-find-links/.gitattributes new file mode 100644 index 000000000..d5799bd69 --- /dev/null +++ b/examples/pypi-find-links/.gitattributes @@ -0,0 +1,2 @@ +# GitHub syntax highlighting +pixi.lock linguist-language=YAML diff --git a/examples/pypi-find-links/.gitignore b/examples/pypi-find-links/.gitignore new file mode 100644 index 000000000..096b5eb54 --- /dev/null +++ b/examples/pypi-find-links/.gitignore @@ -0,0 +1,3 @@ +# pixi environments +.pixi +*.egg-info diff --git a/examples/pypi-find-links/links/requests-2.31.0-py3-none-any.whl b/examples/pypi-find-links/links/requests-2.31.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..bfd5d2ea99d7b026e75e9aa393a0cad8f829efb5 GIT binary patch literal 62574 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 literal 0 HcmV?d00001 diff --git a/examples/pypi-find-links/pixi.lock b/examples/pypi-find-links/pixi.lock new file mode 100644 index 000000000..a7533d563 --- /dev/null +++ b/examples/pypi-find-links/pixi.lock @@ -0,0 +1,1043 @@ +version: 5 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + find-links: + - path: ./links + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl + - pypi: ./links/requests-2.31.0-py3-none-any.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl + - pypi: ./links/requests-2.31.0-py3-none-any.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.0-h47c9636_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl + - pypi: ./links/requests-2.31.0-py3-none-any.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl + - pypi: ./links/requests-2.31.0-py3-none-any.whl +packages: +- kind: conda + name: _libgcc_mutex + version: '0.1' + build: conda_forge + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + size: 2562 + timestamp: 1578324546067 +- kind: conda + name: _openmp_mutex + version: '4.5' + build: 2_gnu + build_number: 16 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + 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 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h10d778d_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + sha256: 61fb2b488928a54d9472113e1280b468a309561caa54f33825a3593da390b242 + md5: 6097a6ca9ada32699b5fc4312dd6ef18 + license: bzip2-1.0.6 + license_family: BSD + size: 127885 + timestamp: 1699280178474 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h93a5062_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda + sha256: bfa84296a638bea78a8bb29abc493ee95f2a0218775642474a840411b950fe5f + md5: 1bbc659ca658bfd49a481b5ef7a0f40f + license: bzip2-1.0.6 + license_family: BSD + size: 122325 + timestamp: 1699280294368 +- kind: conda + name: bzip2 + version: 1.0.8 + build: hcfcfb64_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda + sha256: ae5f47a5c86fd6db822931255dcf017eb12f60c77f07dc782ccb477f7808aab2 + md5: 26eb8ca6ea332b675e11704cce84a3be + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: bzip2-1.0.6 + license_family: BSD + size: 124580 + timestamp: 1699280668742 +- kind: conda + name: bzip2 + version: 1.0.8 + build: hd590300_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda + sha256: 242c0c324507ee172c0e0dd2045814e746bb303d1eb78870d182ceb0abc726a8 + md5: 69b8b6202a07720f448be700e300ccf4 + depends: + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + size: 254228 + timestamp: 1699279927352 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: h56e8100_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda + sha256: 4d587088ecccd393fec3420b64f1af4ee1a0e6897a45cfd5ef38055322cea5d0 + md5: 63da060240ab8087b60d1357051ea7d6 + license: ISC + size: 155886 + timestamp: 1706843918052 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: h8857fd0_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda + sha256: 54a794aedbb4796afeabdf54287b06b1d27f7b13b3814520925f4c2c80f58ca9 + md5: f2eacee8c33c43692f1ccfd33d0f50b1 + license: ISC + size: 155665 + timestamp: 1706843838227 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: hbcca054_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda + sha256: 91d81bfecdbb142c15066df70cc952590ae8991670198f92c66b62019b251aeb + md5: 2f4327a1cbe7f022401b236e915a5fef + license: ISC + size: 155432 + timestamp: 1706843687645 +- kind: conda + name: ca-certificates + version: 2024.2.2 + build: hf0a4a13_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda + sha256: 49bc3439816ac72d0c0e0f144b8cc870fdcc4adec2e861407ec818d8116b2204 + md5: fb416a1795f18dcc5a038bc2dc54edf9 + license: ISC + size: 155725 + timestamp: 1706844034242 +- kind: pypi + name: certifi + version: 2024.2.2 + url: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl + sha256: dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 + requires_python: '>=3.6' +- kind: pypi + name: charset-normalizer + version: 3.3.2 + url: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl + sha256: 55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 + requires_python: '>=3.7.0' +- kind: pypi + name: charset-normalizer + version: 3.3.2 + url: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl + sha256: 96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 + requires_python: '>=3.7.0' +- kind: pypi + name: charset-normalizer + version: 3.3.2 + url: https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + sha256: 90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b + requires_python: '>=3.7.0' +- kind: pypi + name: charset-normalizer + version: 3.3.2 + url: https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl + sha256: ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b + requires_python: '>=3.7.0' +- kind: pypi + name: idna + version: '3.7' + url: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl + sha256: 82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 + requires_python: '>=3.5' +- kind: conda + name: ld_impl_linux-64 + version: '2.40' + build: h55db66e_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + sha256: ef969eee228cfb71e55146eaecc6af065f468cb0bc0a5239bc053b39db0b5f09 + md5: 10569984e7db886e4f1abc2b47ad79a1 + constrains: + - binutils_impl_linux-64 2.40 + license: GPL-3.0-only + license_family: GPL + size: 713322 + timestamp: 1713651222435 +- kind: conda + name: libexpat + version: 2.6.2 + build: h59595ed_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + sha256: 331bb7c7c05025343ebd79f86ae612b9e1e74d2687b8f3179faec234f986ce19 + md5: e7ba12deb7020dd080c6c70e7b6f6a3d + depends: + - libgcc-ng >=12 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 73730 + timestamp: 1710362120304 +- kind: conda + name: libexpat + version: 2.6.2 + build: h63175ca_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + sha256: 79f612f75108f3e16bbdc127d4885bb74729cf66a8702fca0373dad89d40c4b7 + md5: bc592d03f62779511d392c175dcece64 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 139224 + timestamp: 1710362609641 +- kind: conda + name: libexpat + version: 2.6.2 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + sha256: a188a77b275d61159a32ab547f7d17892226e7dac4518d2c6ac3ac8fc8dfde92 + md5: 3d1d51c8f716d97c864d12f7af329526 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 69246 + timestamp: 1710362566073 +- kind: conda + name: libexpat + version: 2.6.2 + build: hebf3989_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda + sha256: ba7173ac30064ea901a4c9fb5a51846dcc25512ceb565759be7d18cbf3e5415e + md5: e3cde7cfa87f82f7cb13d482d5e0ad09 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 63655 + timestamp: 1710362424980 +- kind: conda + name: libffi + version: 3.4.2 + build: h0d85af4_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + sha256: 7a2d27a936ceee6942ea4d397f9c7d136f12549d86f7617e8b6bad51e01a941f + md5: ccb34fb14960ad8b125962d3d79b31a9 + license: MIT + license_family: MIT + size: 51348 + timestamp: 1636488394370 +- kind: conda + name: libffi + version: 3.4.2 + build: h3422bc3_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + sha256: 41b3d13efb775e340e4dba549ab5c029611ea6918703096b2eaa9c015c0750ca + md5: 086914b672be056eb70fd4285b6783b6 + license: MIT + license_family: MIT + size: 39020 + timestamp: 1636488587153 +- kind: conda + name: libffi + version: 3.4.2 + build: h7f98852_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e + md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + depends: + - libgcc-ng >=9.4.0 + license: MIT + license_family: MIT + size: 58292 + timestamp: 1636488182923 +- kind: conda + name: libffi + version: 3.4.2 + build: h8ffe710_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + sha256: 1951ab740f80660e9bc07d2ed3aefb874d78c107264fd810f24a1a6211d4b1a5 + md5: 2c96d1b6915b408893f9472569dee135 + depends: + - vc >=14.1,<15.0a0 + - vs2015_runtime >=14.16.27012 + license: MIT + license_family: MIT + size: 42063 + timestamp: 1636489106777 +- kind: conda + name: libgcc-ng + version: 13.2.0 + build: hc881cc4_6 + build_number: 6 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + sha256: 836a0057525f1414de43642d357d0ab21ac7f85e24800b010dbc17d132e6efec + md5: df88796bd09a0d2ed292e59101478ad8 + depends: + - _libgcc_mutex 0.1 conda_forge + - _openmp_mutex >=4.5 + constrains: + - libgomp 13.2.0 hc881cc4_6 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 777315 + timestamp: 1713755001744 +- kind: conda + name: libgomp + version: 13.2.0 + build: hc881cc4_6 + build_number: 6 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + sha256: e722b19b23b31a14b1592d5eceabb38dc52452ff5e4d346e330526971c22e52a + md5: aae89d3736661c36a5591788aebd0817 + depends: + - _libgcc_mutex 0.1 conda_forge + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 422363 + timestamp: 1713754915251 +- kind: conda + name: libnsl + version: 2.0.1 + build: hd590300_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 + md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 + depends: + - libgcc-ng >=12 + license: LGPL-2.1-only + license_family: GPL + size: 33408 + timestamp: 1697359010159 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h091b4b1_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + sha256: 4337f466eb55bbdc74e168b52ec8c38f598e3664244ec7a2536009036e2066cc + md5: c8c1186c7f3351f6ffddb97b1f54fc58 + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 824794 + timestamp: 1713367748819 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h2797004_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + sha256: e2273d6860eadcf714a759ffb6dc24a69cfd01f2a0ea9d6c20f86049b9334e0c + md5: b3316cbe90249da4f8e84cd66e1cc55b + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 859858 + timestamp: 1713367435849 +- kind: conda + name: libsqlite + version: 3.45.3 + build: h92b6c6a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + sha256: 4d44b68fb29dcbc2216a8cae0b274b02ef9b4ae05d1d0f785362ed30b91c9b52 + md5: 68e462226209f35182ef66eda0f794ff + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + size: 902546 + timestamp: 1713367776445 +- kind: conda + name: libsqlite + version: 3.45.3 + build: hcfcfb64_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + sha256: 06ec75faa51d7ec6d5db98889e869b579a9df19d7d3d9baff8359627da4a3b7e + md5: 73f5dc8e2d55d9a1e14b11f49c3b4a28 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: Unlicense + size: 870518 + timestamp: 1713367888406 +- kind: conda + name: libuuid + version: 2.38.1 + build: h0b41bf4_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + size: 33601 + timestamp: 1680112270483 +- kind: conda + name: libzlib + version: 1.2.13 + build: h53f4e23_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda + sha256: ab1c8aefa2d54322a63aaeeefe9cf877411851738616c4068e0dccc66b9c758a + md5: 1a47f5236db2e06a320ffa0392f81bd8 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 48102 + timestamp: 1686575426584 +- kind: conda + name: libzlib + version: 1.2.13 + build: h8a1eda9_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda + sha256: fc58ad7f47ffea10df1f2165369978fba0a1cc32594aad778f5eec725f334867 + md5: 4a3ad23f6e16f99c04e166767193d700 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 59404 + timestamp: 1686575566695 +- kind: conda + name: libzlib + version: 1.2.13 + build: hcfcfb64_5 + build_number: 5 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda + sha256: c161822ee8130b71e08b6d282b9919c1de2c5274b29921a867bca0f7d30cad26 + md5: 5fdb9c6a113b6b6cb5e517fd972d5f41 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 55800 + timestamp: 1686575452215 +- kind: conda + name: libzlib + version: 1.2.13 + build: hd590300_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda + sha256: 370c7c5893b737596fd6ca0d9190c9715d89d888b8c88537ae1ef168c25e82e4 + md5: f36c115f1ee199da648e0597ec2047ad + depends: + - libgcc-ng >=12 + constrains: + - zlib 1.2.13 *_5 + license: Zlib + license_family: Other + size: 61588 + timestamp: 1686575217516 +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h078ce10_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda + sha256: 06f0905791575e2cd3aa961493c56e490b3d82ad9eb49f1c332bd338b0216911 + md5: 616ae8691e6608527d0071e6766dcb81 + license: X11 AND BSD-3-Clause + size: 820249 + timestamp: 1710866874348 +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h59595ed_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda + sha256: aa0f005b6727aac6507317ed490f0904430584fa8ca722657e7f0fb94741de81 + md5: 97da8860a0da5413c7c98a3b3838a645 + depends: + - libgcc-ng >=12 + license: X11 AND BSD-3-Clause + size: 895669 + timestamp: 1710866638986 +- kind: conda + name: ncurses + version: 6.4.20240210 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda + sha256: 50b72acf08acbc4e5332807653e2ca6b26d4326e8af16fad1fd3f2ce9ea55503 + md5: 50f28c512e9ad78589e3eab34833f762 + license: X11 AND BSD-3-Clause + size: 823010 + timestamp: 1710866856626 +- kind: conda + name: openssl + version: 3.2.1 + build: h0d3ecfb_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda + sha256: 519dc941d7ab0ebf31a2878d85c2f444450e7c5f6f41c4d07252c6bb3417b78b + md5: eb580fb888d93d5d550c557323ac5cee + depends: + - ca-certificates + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2855250 + timestamp: 1710793435903 +- kind: conda + name: openssl + version: 3.2.1 + build: hcfcfb64_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda + sha256: 61ce4e11c3c26ed4e4d9b7e7e2483121a1741ad0f9c8db0a91a28b6e05182ce6 + md5: 958e0418e93e50c575bff70fbcaa12d8 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 8230112 + timestamp: 1710796158475 +- kind: conda + name: openssl + version: 3.2.1 + build: hd590300_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda + sha256: 2c689444ed19a603be457284cf2115ee728a3fafb7527326e96054dee7cdc1a7 + md5: 9d731343cff6ee2e5a25c4a091bf8e2a + depends: + - ca-certificates + - libgcc-ng >=12 + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2865379 + timestamp: 1710793235846 +- kind: conda + name: openssl + version: 3.2.1 + build: hd75f5a5_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda + sha256: 7ae0ac6a1673584a8a380c2ff3d46eca48ed53bc7174c0d4eaa0dd2f247a0984 + md5: 570a6f04802df580be529f3a72d2bbf7 + depends: + - ca-certificates + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2506344 + timestamp: 1710793930515 +- kind: conda + name: python + version: 3.12.0 + build: h2628c8c_0_cpython + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda + sha256: 90553586879bf328f2f9efb8d8faa958ecba822faf379f0a20c3461467b9b955 + md5: defd5d375853a2caff36a19d2d81a28e + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - openssl >=3.1.3,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 16140836 + timestamp: 1696321871976 +- kind: conda + name: python + version: 3.12.0 + build: h30d4d87_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda + sha256: 0a1ed3983acbd0528bef5216179e46170f024f4409032875b27865568fef46a1 + md5: d11dc8f4551011fb6baa2865f1ead48f + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 14529683 + timestamp: 1696323482650 +- kind: conda + name: python + version: 3.12.0 + build: h47c9636_0_cpython + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.0-h47c9636_0_cpython.conda + sha256: eb66f8f249caa9d5a956c3a407f079e4779d652ebfc2a4b4f50dcea078e84fa8 + md5: ed8ae98b1b510de68392971b9367d18c + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 13306758 + timestamp: 1696322682581 +- kind: conda + name: python + version: 3.12.0 + build: hab00c5b_0_cpython + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda + sha256: 5398ebae6a1ccbfd3f76361eac75f3ac071527a8072627c4bf9008c689034f48 + md5: 7f97faab5bebcc2580f4f299285323da + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.5.0,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libnsl >=2.0.0,<2.1.0a0 + - libsqlite >=3.43.0,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - openssl >=3.1.3,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 32123473 + timestamp: 1696324522323 +- kind: conda + name: readline + version: '8.2' + build: h8228510_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 + md5: 47d31b792659ce70f470b5c82fdfb7a4 + depends: + - libgcc-ng >=12 + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 281456 + timestamp: 1679532220005 +- kind: conda + name: readline + version: '8.2' + build: h92ec313_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + sha256: a1dfa679ac3f6007362386576a704ad2d0d7a02e98f5d0b115f207a2da63e884 + md5: 8cbb776a2f641b943d413b3e19df71f4 + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 250351 + timestamp: 1679532511311 +- kind: conda + name: readline + version: '8.2' + build: h9e318b2_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + sha256: 41e7d30a097d9b060037f0c6a2b1d4c4ae7e942c06c943d23f9d481548478568 + md5: f17f77f2acf4d344734bda76829ce14e + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 255870 + timestamp: 1679532707590 +- kind: pypi + name: requests + version: 2.31.0 + path: ./links/requests-2.31.0-py3-none-any.whl + requires_dist: + - charset-normalizer<4,>=2 + - idna<4,>=2.5 + - urllib3<3,>=1.21.1 + - certifi>=2017.4.17 + - pysocks!=1.5.7,>=1.5.6 ; extra == 'socks' + - chardet<6,>=3.0.2 ; extra == 'use_chardet_on_py3' + requires_python: '>=3.7' +- kind: conda + name: tk + version: 8.6.13 + build: h1abcd95_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + sha256: 30412b2e9de4ff82d8c2a7e5d06a15f4f4fef1809a72138b6ccb53a33b26faf5 + md5: bf830ba5afc507c6232d4ef0fb1a882d + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3270220 + timestamp: 1699202389792 +- kind: conda + name: tk + version: 8.6.13 + build: h5083fa2_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + sha256: 72457ad031b4c048e5891f3f6cb27a53cb479db68a52d965f796910e71a403a8 + md5: b50a57ba89c32b62428b71a875291c9b + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3145523 + timestamp: 1699202432999 +- kind: conda + name: tk + version: 8.6.13 + build: h5226925_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + sha256: 2c4e914f521ccb2718946645108c9bd3fc3216ba69aea20c2c3cedbd8db32bb1 + md5: fc048363eb8f03cd1737600a5d08aafe + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + size: 3503410 + timestamp: 1699202577803 +- kind: conda + name: tk + version: 8.6.13 + build: noxft_h4845f30_101 + build_number: 101 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<1.3.0a0 + license: TCL + license_family: BSD + size: 3318875 + timestamp: 1699202167581 +- kind: conda + name: tzdata + version: 2024a + build: h0c530f3_0 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + sha256: 7b2b69c54ec62a243eb6fba2391b5e443421608c3ae5dbff938ad33ca8db5122 + md5: 161081fc7cec0bfda0d86d7cb595f8d8 + license: LicenseRef-Public-Domain + size: 119815 + timestamp: 1706886945727 +- kind: conda + name: ucrt + version: 10.0.22621.0 + build: h57928b3_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + sha256: f29cdaf8712008f6b419b8b1a403923b00ab2504bfe0fb2ba8eb60e72d4f14c6 + md5: 72608f6cd3e5898229c3ea16deb1ac43 + constrains: + - vs2015_runtime >=14.29.30037 + license: LicenseRef-Proprietary + license_family: PROPRIETARY + size: 1283972 + timestamp: 1666630199266 +- kind: pypi + name: urllib3 + version: 2.2.1 + url: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl + sha256: 450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d + requires_dist: + - brotli>=1.0.9 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2<5,>=4 ; extra == 'h2' + - pysocks!=1.5.7,<2.0,>=1.5.6 ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.8' +- kind: conda + name: vc + version: '14.3' + build: hcf57466_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda + sha256: 447a8d8292a7b2107dcc18afb67f046824711a652725fc0f522c368e7a7b8318 + md5: 20e1e652a4c740fa719002a8449994a2 + depends: + - vc14_runtime >=14.38.33130 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + size: 16977 + timestamp: 1702511255313 +- kind: conda + name: vc14_runtime + version: 14.38.33130 + build: h82b7239_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda + sha256: bf94c9af4b2e9cba88207001197e695934eadc96a5c5e4cd7597e950aae3d8ff + md5: 8be79fdd2725ddf7bbf8a27a4c1f79ba + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.38.33130.* *_18 + license: LicenseRef-ProprietaryMicrosoft + license_family: Proprietary + size: 749868 + timestamp: 1702511239004 +- kind: conda + name: vs2015_runtime + version: 14.38.33130 + build: hcb4865c_18 + build_number: 18 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + sha256: a2fec221f361d6263c117f4ea6d772b21c90a2f8edc6f3eb0eadec6bfe8843db + md5: 10d42885e3ed84e575b454db30f1aa93 + depends: + - vc14_runtime >=14.38.33130 + license: BSD-3-Clause + license_family: BSD + size: 16988 + timestamp: 1702511261442 +- kind: conda + name: xz + version: 5.2.6 + build: h166bdaf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 + md5: 2161070d867d1b1204ea749c8eec4ef0 + depends: + - libgcc-ng >=12 + license: LGPL-2.1 and GPL-2.0 + size: 418368 + timestamp: 1660346797927 +- kind: conda + name: xz + version: 5.2.6 + build: h57fd34a_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + sha256: 59d78af0c3e071021cfe82dc40134c19dab8cdf804324b62940f5c8cd71803ec + md5: 39c6b54e94014701dd157f4f576ed211 + license: LGPL-2.1 and GPL-2.0 + size: 235693 + timestamp: 1660346961024 +- kind: conda + name: xz + version: 5.2.6 + build: h775f41a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + sha256: eb09823f34cc2dd663c0ec4ab13f246f45dcd52e5b8c47b9864361de5204a1c8 + md5: a72f9d4ea13d55d745ff1ed594747f10 + license: LGPL-2.1 and GPL-2.0 + size: 238119 + timestamp: 1660346964847 +- kind: conda + name: xz + version: 5.2.6 + build: h8d14728_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + sha256: 54d9778f75a02723784dc63aff4126ff6e6749ba21d11a6d03c1f4775f269fe0 + md5: 515d77642eaa3639413c6b1bc3f94219 + depends: + - vc >=14.1,<15 + - vs2015_runtime >=14.16.27033 + license: LGPL-2.1 and GPL-2.0 + size: 217804 + timestamp: 1660346976440 diff --git a/examples/pypi-find-links/pixi.toml b/examples/pypi-find-links/pixi.toml new file mode 100644 index 000000000..ae1b9e5cc --- /dev/null +++ b/examples/pypi-find-links/pixi.toml @@ -0,0 +1,21 @@ +[project] +name = "pypi-find-links" +authors = ["Tim de Jager "] +channels = ["conda-forge"] +platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"] + +[tasks] +start = {depends_on = ["test"]} +test = "python -c 'import requests; print(requests.__version__)'" + +[pypi-options] +# This is similar to the --find-links option in pip +find-links = [{ path = "./links" }] + +# We need python to resolve pypi dependencies +[dependencies] +python = "3.12" + +[pypi-dependencies] +# We should get this from the flat index +requests = "==2.31.0" diff --git a/examples/pypi-source-deps/pixi.lock b/examples/pypi-source-deps/pixi.lock index e8373ddf1..a4ac869a2 100644 --- a/examples/pypi-source-deps/pixi.lock +++ b/examples/pypi-source-deps/pixi.lock @@ -3,19 +3,21 @@ environments: default: channels: - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h807b86a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.2-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda @@ -30,7 +32,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@2c7f57ad5bd19743312eb34ea5fb838a1349474e + - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -39,9 +41,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@58844247f7ae4a7a213a73c8af2462253b3d8fc7 + - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -52,7 +54,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.2-h92b6c6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4.20240210-h73e2aa4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.1-hd75f5a5_1.conda @@ -65,7 +67,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@2c7f57ad5bd19743312eb34ea5fb838a1349474e + - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -74,9 +76,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@58844247f7ae4a7a213a73c8af2462253b3d8fc7 + - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -87,7 +89,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.2-hebf3989_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.2-h091b4b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h53f4e23_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.4.20240210-h078ce10_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.1-h0d3ecfb_1.conda @@ -100,7 +102,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@2c7f57ad5bd19743312eb34ea5fb838a1349474e + - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -109,9 +111,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@58844247f7ae4a7a213a73c8af2462253b3d8fc7 + - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -122,7 +124,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.3-h2628c8c_0_cpython.conda @@ -138,7 +140,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@2c7f57ad5bd19743312eb34ea5fb838a1349474e + - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -147,9 +149,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@58844247f7ae4a7a213a73c8af2462253b3d8fc7 + - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -308,8 +310,8 @@ packages: - kind: pypi name: charset-normalizer version: 3.3.2 - url: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl - sha256: 96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 + url: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl + sha256: 55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 requires_python: '>=3.7.0' - kind: pypi name: charset-normalizer @@ -320,8 +322,8 @@ packages: - kind: pypi name: charset-normalizer version: 3.3.2 - url: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl - sha256: 55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 + url: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl + sha256: 96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 requires_python: '>=3.7.0' - kind: pypi name: click @@ -340,7 +342,7 @@ packages: - kind: pypi name: flask version: 3.1.0.dev0 - url: git+ssh://git@github.com/pallets/flask@2c7f57ad5bd19743312eb34ea5fb838a1349474e + url: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 requires_dist: - werkzeug>=3.0.0 - jinja2>=3.1.2 @@ -381,17 +383,17 @@ packages: - kind: conda name: ld_impl_linux-64 version: '2.40' - build: h41732ed_0 + build: h55db66e_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda - sha256: f6cc89d887555912d6c61b295d398cff9ec982a3417d38025c45d5dd9b9e79cd - md5: 7aca3059a1729aa76c597603f10b0dd3 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h55db66e_0.conda + sha256: ef969eee228cfb71e55146eaecc6af065f468cb0bc0a5239bc053b39db0b5f09 + md5: 10569984e7db886e4f1abc2b47ad79a1 constrains: - binutils_impl_linux-64 2.40 license: GPL-3.0-only license_family: GPL - size: 704696 - timestamp: 1674833944779 + size: 713322 + timestamp: 1713651222435 - kind: conda name: libexpat version: 2.6.2 @@ -510,36 +512,36 @@ packages: - kind: conda name: libgcc-ng version: 13.2.0 - build: h807b86a_5 - build_number: 5 + build: hc881cc4_6 + build_number: 6 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h807b86a_5.conda - sha256: d32f78bfaac282cfe5205f46d558704ad737b8dbf71f9227788a5ca80facaba4 - md5: d4ff227c46917d3b4565302a2bbb276b + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-hc881cc4_6.conda + sha256: 836a0057525f1414de43642d357d0ab21ac7f85e24800b010dbc17d132e6efec + md5: df88796bd09a0d2ed292e59101478ad8 depends: - _libgcc_mutex 0.1 conda_forge - _openmp_mutex >=4.5 constrains: - - libgomp 13.2.0 h807b86a_5 + - libgomp 13.2.0 hc881cc4_6 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 770506 - timestamp: 1706819192021 + size: 777315 + timestamp: 1713755001744 - kind: conda name: libgomp version: 13.2.0 - build: h807b86a_5 - build_number: 5 + build: hc881cc4_6 + build_number: 6 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_5.conda - sha256: 0d3d4b1b0134283ea02d58e8eb5accf3655464cf7159abf098cc694002f8d34e - md5: d211c42b9ce49aee3734fdc828731689 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-hc881cc4_6.conda + sha256: e722b19b23b31a14b1592d5eceabb38dc52452ff5e4d346e330526971c22e52a + md5: aae89d3736661c36a5591788aebd0817 depends: - _libgcc_mutex 0.1 conda_forge license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 419751 - timestamp: 1706819107383 + size: 422363 + timestamp: 1713754915251 - kind: conda name: libnsl version: 2.0.1 @@ -556,59 +558,59 @@ packages: timestamp: 1697359010159 - kind: conda name: libsqlite - version: 3.45.2 + version: 3.45.3 build: h091b4b1_0 subdir: osx-arm64 - url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.2-h091b4b1_0.conda - sha256: 7c234320a1a2132b9cc972aaa06bb215bb220a5b1addb0bed7a5a321c805920e - md5: 9d07427ee5bd9afd1e11ce14368a48d6 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.45.3-h091b4b1_0.conda + sha256: 4337f466eb55bbdc74e168b52ec8c38f598e3664244ec7a2536009036e2066cc + md5: c8c1186c7f3351f6ffddb97b1f54fc58 depends: - libzlib >=1.2.13,<1.3.0a0 license: Unlicense - size: 825300 - timestamp: 1710255078823 + size: 824794 + timestamp: 1713367748819 - kind: conda name: libsqlite - version: 3.45.2 + version: 3.45.3 build: h2797004_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.2-h2797004_0.conda - sha256: 8cdbeb7902729e319510a82d7c642402981818702b58812af265ef55d1315473 - md5: 866983a220e27a80cb75e85cb30466a1 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda + sha256: e2273d6860eadcf714a759ffb6dc24a69cfd01f2a0ea9d6c20f86049b9334e0c + md5: b3316cbe90249da4f8e84cd66e1cc55b depends: - libgcc-ng >=12 - libzlib >=1.2.13,<1.3.0a0 license: Unlicense - size: 857489 - timestamp: 1710254744982 + size: 859858 + timestamp: 1713367435849 - kind: conda name: libsqlite - version: 3.45.2 + version: 3.45.3 build: h92b6c6a_0 subdir: osx-64 - url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.2-h92b6c6a_0.conda - sha256: 320ec73a4e3dd377757a2595770b8137ec4583df4d7782472d76377cdbdc4543 - md5: 086f56e13a96a6cfb1bf640505ae6b70 + url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda + sha256: 4d44b68fb29dcbc2216a8cae0b274b02ef9b4ae05d1d0f785362ed30b91c9b52 + md5: 68e462226209f35182ef66eda0f794ff depends: - libzlib >=1.2.13,<1.3.0a0 license: Unlicense - size: 902355 - timestamp: 1710254991672 + size: 902546 + timestamp: 1713367776445 - kind: conda name: libsqlite - version: 3.45.2 + version: 3.45.3 build: hcfcfb64_0 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda - sha256: 4bb24b986550275a6d02835150d943c4c675808d05c0efc5c2a22154d007a69f - md5: f95359f8dc5abf7da7776ece9ef10bc5 + url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda + sha256: 06ec75faa51d7ec6d5db98889e869b579a9df19d7d3d9baff8359627da4a3b7e + md5: 73f5dc8e2d55d9a1e14b11f49c3b4a28 depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: Unlicense - size: 869606 - timestamp: 1710255095740 + size: 870518 + timestamp: 1713367888406 - kind: conda name: libuuid version: 2.38.1 @@ -744,8 +746,8 @@ packages: - kind: pypi name: markupsafe version: 2.1.5 - url: https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl - sha256: 823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb + url: https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl + sha256: 8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 requires_python: '>=3.7' - kind: pypi name: markupsafe @@ -756,8 +758,8 @@ packages: - kind: pypi name: markupsafe version: 2.1.5 - url: https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - sha256: 8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 + url: https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl + sha256: 823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb requires_python: '>=3.7' - kind: pypi name: mdurl @@ -886,9 +888,9 @@ packages: requires_python: '>=3.7' - kind: pypi name: pluggy - version: 1.4.0 - url: https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl - sha256: 7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 + version: 1.5.0 + url: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl + sha256: 44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 requires_dist: - pre-commit ; extra == 'dev' - tox ; extra == 'dev' @@ -906,12 +908,12 @@ packages: requires_python: '>=3.7' - kind: pypi name: pytest - version: 8.2.0.dev90+g58844247f - url: git+https://github.com/pytest-dev/pytest.git@58844247f7ae4a7a213a73c8af2462253b3d8fc7 + version: 8.2.0.dev102+gfafab1dbf + url: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab requires_dist: - iniconfig - packaging - - pluggy<2.0,>=1.4 + - pluggy<2.0,>=1.5 - exceptiongroup>=1.0.0rc8 ; python_version < '3.11' - tomli>=1 ; python_version < '3.11' - colorama ; sys_platform == 'win32' diff --git a/schema/model.py b/schema/model.py index 7c98a9624..132e4380b 100644 --- a/schema/model.py +++ b/schema/model.py @@ -381,7 +381,25 @@ class Feature(StrictBaseModel): description="Machine-specific aspects of this feature", examples=[{"linux": {"dependencies": {"python": "3.8"}}}], ) + pypi_options: PyPIOptions | None = Field(None, alias="pypi-options", description="Options related to PyPI indexes for this feature") +################### +# PyPI section # +################### + +class FindLinksPath(StrictBaseModel): + """The path to the directory containing packages""" + path: NonEmptyStr | None = Field(None, description="Path to the directory of packages", examples=["./links"]) + +class FindLinksURL(StrictBaseModel): + """The URL to the html file containing href-links to packages""" + url: NonEmptyStr | None = Field(None, description="URL to html file with href-links to packages", examples=["https://simple-index-is-here.com"]) + +class PyPIOptions(StrictBaseModel): + """Options related to PyPI indexes""" + index_url: NonEmptyStr | None = Field(None, alias="index-url", description="Alternative PyPI registry that should be used as the main index", examples=["https://pypi.org/simple"]) + extra_index_urls: list[NonEmptyStr] | None = Field(None, alias="extra-index-urls", description="Additional PyPI registries that should be used as extra indexes", examples=[["https://pypi.org/simple"]]) + find_links: list[FindLinksPath | FindLinksURL] = Field(None, alias="find-links", description="Paths to directory containing", examples=[["https://pypi.org/simple"]]) ####################### # The Manifest itself # @@ -413,6 +431,7 @@ class Config: pypi_dependencies: dict[PyPIPackageName, PyPIRequirement] | None = Field( None, alias="pypi-dependencies", description="The PyPI dependencies" ) + pypi_options: PyPIOptions | None = Field(None, alias="pypi-options", description="Options related to PyPI indexes") tasks: dict[TaskName, TaskInlineTable | NonEmptyStr] | None = Field( None, description="The tasks of the project" ) diff --git a/schema/schema.json b/schema/schema.json index d1014ab02..95012fc6b 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -137,6 +137,10 @@ ] } }, + "pypi-options": { + "$ref": "#/$defs/PyPIOptions", + "description": "Options related to PyPI indexes" + }, "system-requirements": { "$ref": "#/$defs/SystemRequirements", "description": "The system requirements of the project" @@ -383,6 +387,10 @@ ] } }, + "pypi-options": { + "$ref": "#/$defs/PyPIOptions", + "description": "Options related to PyPI indexes for this feature" + }, "system-requirements": { "$ref": "#/$defs/SystemRequirements", "description": "The system requirements of this feature" @@ -424,6 +432,40 @@ } } }, + "FindLinksPath": { + "title": "FindLinksPath", + "description": "The path to the directory containing packages", + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "title": "Path", + "description": "Path to the directory of packages", + "type": "string", + "minLength": 1, + "examples": [ + "./links" + ] + } + } + }, + "FindLinksURL": { + "title": "FindLinksURL", + "description": "The URL to the html file containing href-links to packages", + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "title": "Url", + "description": "URL to html file with href-links to packages", + "type": "string", + "minLength": 1, + "examples": [ + "https://simple-index-is-here.com" + ] + } + } + }, "LibcFamily": { "title": "LibcFamily", "type": "object", @@ -747,6 +789,57 @@ } } }, + "PyPIOptions": { + "title": "PyPIOptions", + "description": "Options related to PyPI indexes", + "type": "object", + "additionalProperties": false, + "properties": { + "extra-index-urls": { + "title": "Extra-Index-Urls", + "description": "Additional PyPI registries that should be used as extra indexes", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "examples": [ + [ + "https://pypi.org/simple" + ] + ] + }, + "find-links": { + "title": "Find-Links", + "description": "Paths to directory containing", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/FindLinksPath" + }, + { + "$ref": "#/$defs/FindLinksURL" + } + ] + }, + "examples": [ + [ + "https://pypi.org/simple" + ] + ] + }, + "index-url": { + "title": "Index-Url", + "description": "Alternative PyPI registry that should be used as the main index", + "type": "string", + "minLength": 1, + "examples": [ + "https://pypi.org/simple" + ] + } + } + }, "PyPIPathRequirement": { "title": "PyPIPathRequirement", "type": "object", diff --git a/src/cli/add.rs b/src/cli/add.rs index 1a811ac5d..99a871e73 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -16,7 +16,7 @@ use rattler_conda_types::{ VersionBumpType, VersionSpec, }; use rattler_repodata_gateway::sparse::SparseRepoData; -use rattler_solve::{resolvo, SolverImpl}; +use rattler_solve::{resolvo, ChannelPriority, SolverImpl}; use std::{ collections::{HashMap, HashSet}, path::PathBuf, @@ -474,6 +474,7 @@ pub fn determine_best_version( pinned_packages: vec![], timeout: None, + channel_priority: ChannelPriority::Strict, }; let records = resolvo::Solver.solve(task).into_diagnostic()?; diff --git a/src/cli/global/common.rs b/src/cli/global/common.rs index 34a14eb6d..01dc006f9 100644 --- a/src/cli/global/common.rs +++ b/src/cli/global/common.rs @@ -6,7 +6,7 @@ use rattler_conda_types::{ Channel, ChannelConfig, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord, }; use rattler_repodata_gateway::sparse::SparseRepoData; -use rattler_solve::{resolvo, SolverImpl, SolverTask}; +use rattler_solve::{resolvo, ChannelPriority, SolverImpl, SolverTask}; use reqwest_middleware::ClientWithMiddleware; use crate::{ @@ -160,6 +160,7 @@ pub fn load_package_records( locked_packages: vec![], pinned_packages: vec![], timeout: None, + channel_priority: ChannelPriority::Strict, }; // Solve it diff --git a/src/cli/list.rs b/src/cli/list.rs index 341a93836..74a3846e8 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -6,6 +6,7 @@ use clap::Parser; use console::Color; use human_bytes::human_bytes; use itertools::Itertools; + use rattler_conda_types::Platform; use rattler_lock::{Package, UrlOrPath}; use serde::Serialize; @@ -134,8 +135,10 @@ pub async fn execute(args: Args) -> miette::Result<()> { let python_record = conda_records.find(|r| is_python_record(r)); let tags; let uv_context; + let index_locations; let mut registry_index = if let Some(python_record) = python_record { uv_context = UvResolutionContext::from_project(&project)?; + index_locations = environment.pypi_options().to_index_locations(); tags = get_pypi_tags( platform, &project.system_requirements(), @@ -144,7 +147,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { Some(RegistryWheelIndex::new( &uv_context.cache, &tags, - &uv_context.index_locations, + &index_locations, &uv_types::HashStrategy::None, )) } else { diff --git a/src/consts.rs b/src/consts.rs index da3ee91f5..438dca012 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,5 +1,6 @@ use console::Style; use lazy_static::lazy_static; +use url::Url; pub const PROJECT_MANIFEST: &str = "pixi.toml"; pub const PYPROJECT_MANIFEST: &str = "pyproject.toml"; @@ -17,11 +18,11 @@ pub const DEFAULT_ENVIRONMENT_NAME: &str = "default"; /// The default channels to use for a new project. pub const DEFAULT_CHANNELS: &[&str] = &["conda-forge"]; - pub const DEFAULT_FEATURE_NAME: &str = DEFAULT_ENVIRONMENT_NAME; lazy_static! { pub static ref TASK_STYLE: Style = Style::new().blue(); pub static ref PLATFORM_STYLE: Style = Style::new().yellow(); pub static ref SOLVE_GROUP_STYLE: Style = Style::new().cyan(); + pub static ref DEFAULT_PYPI_INDEX_URL: Url = Url::parse("https://pypi.org/simple").unwrap(); } diff --git a/src/environment.rs b/src/environment.rs index 5e682ddd8..271578d8b 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -2,6 +2,7 @@ use crate::lock_file::UvResolutionContext; use crate::progress::await_in_progress; use crate::project::grouped_environment::GroupedEnvironmentName; use crate::project::has_features::HasFeatures; +use crate::project::manifest::pypi_options::PypiOptions; use crate::{ consts, install, install_pypi, lock_file::UpdateLockFileOptions, @@ -251,6 +252,7 @@ pub async fn update_prefix_pypi( status: &PythonStatus, system_requirements: &SystemRequirements, uv_context: UvResolutionContext, + pypi_options: &PypiOptions, environment_variables: &HashMap, lock_file_dir: &Path, platform: Platform, @@ -272,6 +274,7 @@ pub async fn update_prefix_pypi( status, system_requirements, uv_context, + pypi_options, environment_variables, platform, ) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 675028f83..58cd47ba9 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -1,7 +1,9 @@ use crate::environment::PythonStatus; use crate::prefix::Prefix; +use crate::project::manifest::pypi_options::PypiOptions; use crate::uv_reporter::{UvReporter, UvReporterOptions}; use std::borrow::Cow; +use std::sync::Arc; use distribution_filename::DistFilename; @@ -37,7 +39,7 @@ use std::path::Path; use std::str::FromStr; use std::time::Duration; -use uv_client::FlatIndexClient; +use uv_client::{Connectivity, FlatIndexClient, RegistryClient, RegistryClientBuilder}; use uv_dispatch::BuildDispatch; use uv_distribution::RegistryWheelIndex; use uv_installer::{Downloader, ResolvedEditable, SitePackages}; @@ -567,6 +569,7 @@ async fn resolve_editables( site_packages: &SitePackages<'_>, uv_context: &UvResolutionContext, tags: &Tags, + registry_client: &RegistryClient, build_dispatch: &BuildDispatch<'_>, ) -> miette::Result { let mut to_build = vec![]; @@ -653,7 +656,7 @@ async fn resolve_editables( &uv_context.cache, tags, &uv_types::HashStrategy::None, - &uv_context.registry_client, + registry_client, build_dispatch, ) .with_reporter(UvReporter::new(options)) @@ -691,6 +694,7 @@ pub async fn update_python_distributions( status: &PythonStatus, system_requirements: &SystemRequirements, uv_context: UvResolutionContext, + pypi_options: &PypiOptions, environment_variables: &HashMap, platform: Platform, ) -> miette::Result<()> { @@ -715,11 +719,20 @@ pub async fn update_python_distributions( .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {PROJECT_MANIFEST}, or run:\n\n\tpixi add python"))?; let tags = get_pypi_tags(platform, system_requirements, &python_record.package_record)?; + let index_locations = pypi_options.to_index_locations(); + let registry_client = Arc::new( + RegistryClientBuilder::new(uv_context.cache.clone()) + .client(uv_context.client.clone()) + .index_urls(index_locations.index_urls()) + .connectivity(Connectivity::Online) + .build(), + ); + // Resolve the flat indexes from `--find-links`. let flat_index = { - let client = FlatIndexClient::new(&uv_context.registry_client, &uv_context.cache); + let client = FlatIndexClient::new(®istry_client, &uv_context.cache); let entries = client - .fetch(uv_context.index_locations.flat_index()) + .fetch(index_locations.flat_index()) .await .into_diagnostic()?; FlatIndex::from_entries( @@ -742,10 +755,10 @@ pub async fn update_python_distributions( let venv = PythonEnvironment::from_interpreter(interpreter); // Prep the build context. let build_dispatch = BuildDispatch::new( - &uv_context.registry_client, + ®istry_client, &uv_context.cache, venv.interpreter(), - &uv_context.index_locations, + &index_locations, &flat_index, &in_memory_index, &uv_context.in_flight, @@ -776,6 +789,7 @@ pub async fn update_python_distributions( &site_packages, &uv_context, &tags, + ®istry_client, &build_dispatch, ) .await?; @@ -784,7 +798,7 @@ pub async fn update_python_distributions( let mut registry_index = RegistryWheelIndex::new( &uv_context.cache, &tags, - &uv_context.index_locations, + &index_locations, &HashStrategy::None, ); @@ -867,7 +881,7 @@ pub async fn update_python_distributions( &uv_context.cache, &tags, &uv_types::HashStrategy::None, - &uv_context.registry_client, + ®istry_client, &build_dispatch, ) .with_reporter(UvReporter::new(options)); diff --git a/src/lock_file/outdated.rs b/src/lock_file/outdated.rs index 52f3aab7e..bd2606da6 100644 --- a/src/lock_file/outdated.rs +++ b/src/lock_file/outdated.rs @@ -135,7 +135,7 @@ fn find_unsatisfiable_targets<'p>( .extend(platforms); match unsat { - EnvironmentUnsat::ChannelsMismatch => { + EnvironmentUnsat::ChannelsMismatch | EnvironmentUnsat::IndexesMismatch => { // If the channels mismatched we also cannot trust any of the locked content. disregard_locked_content.insert(environment.clone()); } diff --git a/src/lock_file/resolve.rs b/src/lock_file/resolve.rs index b01d391af..2424b2909 100644 --- a/src/lock_file/resolve.rs +++ b/src/lock_file/resolve.rs @@ -5,6 +5,7 @@ use crate::config::get_cache_dir; use crate::consts::PROJECT_MANIFEST; use crate::lock_file::pypi_editables::build_editables; +use crate::project::manifest::pypi_options::PypiOptions; use crate::project::manifest::python::RequirementOrEditable; use crate::uv_reporter::{UvReporter, UvReporterOptions}; use std::collections::{BTreeMap, HashMap}; @@ -21,8 +22,8 @@ use crate::{ }; use distribution_types::{ - BuiltDist, DirectUrlSourceDist, Dist, HashPolicy, IndexLocations, Name, PrioritizedDist, - Resolution, ResolvedDist, SourceDist, + BuiltDist, DirectUrlSourceDist, Dist, FlatIndexLocation, HashPolicy, IndexLocations, IndexUrl, + Name, PrioritizedDist, Resolution, ResolvedDist, SourceDist, }; use distribution_types::{FileLocation, SourceDistCompatibility}; use futures::FutureExt; @@ -38,7 +39,7 @@ use rattler_digest::{parse_digest_from_hex, Md5, Sha256}; use rattler_lock::{ PackageHashes, PypiPackageData, PypiPackageEnvironmentData, PypiSourceTreeHashable, UrlOrPath, }; -use rattler_solve::{resolvo, SolverImpl}; +use rattler_solve::{resolvo, ChannelPriority, SolverImpl}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; @@ -48,7 +49,7 @@ use uv_configuration::{ use url::Url; use uv_cache::Cache; -use uv_client::{Connectivity, FlatIndexClient, RegistryClient, RegistryClientBuilder}; +use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_dispatch::BuildDispatch; use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter}; use uv_interpreter::Interpreter; @@ -64,12 +65,11 @@ use uv_types::{BuildContext, EmptyInstalledPackages, HashStrategy, InFlight}; #[derive(Clone)] pub struct UvResolutionContext { pub cache: Cache, - pub registry_client: Arc, pub in_flight: Arc, - pub index_locations: Arc, pub no_build: NoBuild, pub no_binary: NoBinary, pub hash_strategy: HashStrategy, + pub client: reqwest::Client, } impl UvResolutionContext { @@ -81,22 +81,14 @@ impl UvResolutionContext { ) .into_diagnostic() .context("failed to create uv cache")?; - let registry_client = Arc::new( - RegistryClientBuilder::new(cache.clone()) - .client(project.client().clone()) - .connectivity(Connectivity::Online) - .build(), - ); let in_flight = Arc::new(InFlight::default()); - let index_locations = Arc::new(project.pypi_index_locations()); Ok(Self { cache, - registry_client, in_flight, - index_locations, no_build: NoBuild::None, no_binary: NoBinary::None, hash_strategy: HashStrategy::None, + client: project.client().clone(), }) } } @@ -241,9 +233,64 @@ fn process_uv_path_url(path_url: &VerbatimUrl) -> PathBuf { } } +// Store a reference to the flat index +#[derive(Clone)] +struct FindLinksLocation { + /// Canocialized path to the flat index. + canonicalized_path: PathBuf, + /// Manifest path to flat index. + given_path: PathBuf, +} + +/// Given a flat index url and a list of flat indexes, return the path to the flat index. +/// for that specific index. +fn find_links_for( + flat_index_url: &IndexUrl, + flat_indexes_paths: &[FindLinksLocation], +) -> Option { + // Convert to file path + let flat_index_url_path = flat_index_url + .url() + .to_file_path() + .expect("invalid path-based index"); + + // Find the flat index in the list of flat indexes + // Compare with the path that we got from the `IndexUrl` + // which is absolute + flat_indexes_paths + .iter() + .find(|path| path.canonicalized_path == flat_index_url_path) + .cloned() +} + +/// Convert an absolute path to a path relative to the flat index url. +/// which is assumed to be a file:// url. +fn convert_flat_index_path( + flat_index_url: &IndexUrl, + absolute_path: &Path, + given_flat_index_path: &Path, +) -> PathBuf { + assert!( + absolute_path.is_absolute(), + "flat index package does not have an absolute path" + ); + let base = flat_index_url + .url() + .to_file_path() + .expect("invalid path-based index"); + // Strip the index from the path + // This is safe because we know the index is a prefix of the path + let path = absolute_path + .strip_prefix(&base) + .expect("base was not a prefix of the flat index path"); + // Join with the given flat index path + given_flat_index_path.join(path) +} + #[allow(clippy::too_many_arguments)] pub async fn resolve_pypi( context: UvResolutionContext, + pypi_options: &PypiOptions, dependencies: IndexMap>, system_requirements: SystemRequirements, locked_conda_records: &[RepoDataRecord], @@ -338,11 +385,21 @@ pub async fn resolve_pypi( tracing::debug!("[Resolve] Using Python Interpreter: {:?}", interpreter); + let index_locations = pypi_options.to_index_locations(); + + // TODO: create a cached registry client per index_url set? + let registry_client = Arc::new( + RegistryClientBuilder::new(context.cache.clone()) + .client(context.client.clone()) + .index_urls(index_locations.index_urls()) + .connectivity(Connectivity::Online) + .build(), + ); // Resolve the flat indexes from `--find-links`. let flat_index = { - let client = FlatIndexClient::new(&context.registry_client, &context.cache); + let client = FlatIndexClient::new(®istry_client, &context.cache); let entries = client - .fetch(context.index_locations.flat_index()) + .fetch(index_locations.flat_index()) .await .into_diagnostic()?; FlatIndex::from_entries( @@ -360,10 +417,10 @@ pub async fn resolve_pypi( // Create a shared in-memory index. let options = Options::default(); let build_dispatch = BuildDispatch::new( - &context.registry_client, + ®istry_client, &context.cache, &interpreter, - &context.index_locations, + &index_locations, &flat_index, &in_memory_index, &context.in_flight, @@ -408,8 +465,8 @@ pub async fn resolve_pypi( ); let fallback_provider = DefaultResolverProvider::new( - &context.registry_client, - DistributionDatabase::new(&context.registry_client, &build_dispatch), + ®istry_client, + DistributionDatabase::new(®istry_client, &build_dispatch), &flat_index, &tags, PythonRequirement::new(&interpreter, &marker_environment), @@ -446,7 +503,26 @@ pub async fn resolve_pypi( let resolution = Resolution::from(resolution); - let database = DistributionDatabase::new(&context.registry_client, &build_dispatch); + // Create a list of canocialized flat indexes. + let flat_index_locations = index_locations + .flat_index() + // Take only path based flat indexes + .filter_map(|i| match i { + FlatIndexLocation::Path(path) => Some(path), + FlatIndexLocation::Url(_) => None, + }) + // Canonicalize the path + .map(|path| { + let canonicalized_path = path.canonicalize()?; + Ok::<_, std::io::Error>(FindLinksLocation { + canonicalized_path, + given_path: path.clone(), + }) + }) + .collect::, _>>() + .into_diagnostic()?; + + let database = DistributionDatabase::new(®istry_client, &build_dispatch); let mut locked_packages = LockedPypiPackages::with_capacity(resolution.len()); for dist in resolution.into_distributions() { @@ -467,8 +543,22 @@ pub async fn resolve_pypi( FileLocation::AbsoluteUrl(url) => { UrlOrPath::Url(Url::from_str(url).expect("invalid absolute url")) } - FileLocation::Path(path) => UrlOrPath::Path(path.clone()), - _ => todo!("unsupported URL"), + // I (tim) thinks this only happens for flat path based indexes + FileLocation::Path(path) => { + let flat_index = find_links_for(&dist.index, &flat_index_locations) + .expect("flat index does not exist for resolved ids"); + UrlOrPath::Path(convert_flat_index_path( + &dist.index, + path, + &flat_index.given_path, + )) + } + // This happens when it is relative to the non-standard index + FileLocation::RelativeUrl(base, relative) => { + let base = Url::from_str(base).expect("invalid base url"); + let url = base.join(relative).expect("could not join urls"); + UrlOrPath::Url(url) + } }; let hash = parse_hashes_from_hash_vec(&dist.file.hashes); @@ -485,8 +575,7 @@ pub async fn resolve_pypi( } }; - let metadata = context - .registry_client + let metadata = registry_client .wheel_metadata(&dist) .await .expect("failed to get wheel metadata"); @@ -520,8 +609,22 @@ pub async fn resolve_pypi( FileLocation::AbsoluteUrl(url) => { UrlOrPath::Url(Url::from_str(url).expect("invalid absolute url")) } - FileLocation::Path(path) => UrlOrPath::Path(path.clone()), - _ => todo!("unsupported URL"), + // I (tim) thinks this only happens for flat path based indexes + FileLocation::Path(path) => { + let flat_index = find_links_for(®.index, &flat_index_locations) + .expect("flat index does not exist for resolved ids"); + UrlOrPath::Path(convert_flat_index_path( + ®.index, + path, + &flat_index.given_path, + )) + } + // This happens when it is relative to the non-standard index + FileLocation::RelativeUrl(base, relative) => { + let base = Url::from_str(base).expect("invalid base url"); + let url = base.join(relative).expect("could not join urls"); + UrlOrPath::Url(url) + } }; (url_or_path, hash, false) } @@ -624,6 +727,7 @@ pub async fn resolve_conda( pinned_packages: vec![], virtual_packages, timeout: None, + channel_priority: ChannelPriority::Strict, }; // Solve the task diff --git a/src/lock_file/satisfiability.rs b/src/lock_file/satisfiability.rs index 1240912cd..cc27d1129 100644 --- a/src/lock_file/satisfiability.rs +++ b/src/lock_file/satisfiability.rs @@ -1,4 +1,5 @@ use super::{PypiRecord, PypiRecordsByName, RepoDataRecordsByName}; +use crate::project::grouped_environment::GroupedEnvironment; use crate::project::has_features::HasFeatures; use crate::project::manifest::python::{AsPep508Error, RequirementOrEditable}; use crate::{project::Environment, pypi_marker_env::determine_marker_environment}; @@ -30,6 +31,11 @@ use uv_normalize::{ExtraName, PackageName}; pub enum EnvironmentUnsat { #[error("the channels in the lock-file do not match the environments channels")] ChannelsMismatch, + + #[error( + "the indexes used to previously solve to lock file do not match the environments indexes" + )] + IndexesMismatch, } #[derive(Debug, Error)] @@ -127,10 +133,12 @@ pub fn verify_environment_satisfiability( environment: &Environment<'_>, locked_environment: &rattler_lock::Environment, ) -> Result<(), EnvironmentUnsat> { + let grouped_env = GroupedEnvironment::from(environment.clone()); + // Check if the channels in the lock file match our current configuration. Note that the order // matters here. If channels are added in a different order, the solver might return a different // result. - let channels = environment + let channels = grouped_env .channels() .into_iter() .map(|channel| rattler_lock::Channel::from(channel.base_url().to_string())) @@ -139,6 +147,25 @@ pub fn verify_environment_satisfiability( return Err(EnvironmentUnsat::ChannelsMismatch); } + // Check if the indexes in the lock file match our current configuration. + if !environment.pypi_dependencies(None).is_empty() { + match locked_environment.pypi_indexes() { + None => { + if locked_environment + .version() + .should_pypi_indexes_be_present() + { + return Err(EnvironmentUnsat::IndexesMismatch); + } + } + Some(indexes) => { + if indexes != &rattler_lock::PypiIndexes::from(grouped_env.pypi_options()) { + return Err(EnvironmentUnsat::IndexesMismatch); + } + } + } + } + Ok(()) } diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index afb219555..20f67e6b2 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -137,6 +137,7 @@ impl<'p> LockFileDerivedData<'p> { &python_status, &environment.system_requirements(), uv_context, + &environment.pypi_options(), env_variables, self.project.root(), environment.best_platform(), @@ -1036,31 +1037,36 @@ pub async fn ensure_up_to_date_lock_file( // Iterate over all environments and add their records to the lock-file. for environment in project.environments() { + let environment_name = environment.name().to_string(); + let grouped_env = GroupedEnvironment::from(environment.clone()); + builder.set_channels( - environment.name().as_str(), - environment + &environment_name, + grouped_env .channels() .into_iter() .map(|channel| rattler_lock::Channel::from(channel.base_url().to_string())), ); + let mut has_pypi_records = false; for platform in environment.platforms() { if let Some(records) = context.take_latest_repodata_records(&environment, platform) { for record in records.into_inner() { - builder.add_conda_package(environment.name().as_str(), platform, record.into()); + builder.add_conda_package(&environment_name, platform, record.into()); } } if let Some(records) = context.take_latest_pypi_records(&environment, platform) { for (pkg_data, pkg_env_data) in records.into_inner() { - builder.add_pypi_package( - environment.name().as_str(), - platform, - pkg_data, - pkg_env_data, - ); + builder.add_pypi_package(&environment_name, platform, pkg_data, pkg_env_data); + has_pypi_records = true; } } } + + // Store the indexes that were used to solve the environment. But only if there are pypi packages. + if has_pypi_records { + builder.set_pypi_indexes(&environment_name, grouped_env.pypi_options().into()); + } } // Store the lock file @@ -1493,6 +1499,7 @@ async fn spawn_solve_pypi_task( ) .await?; + let pypi_options = environment.pypi_options(); // let (pypi_packages, duration) = tokio::spawn( let (pypi_packages, duration) = async move { let pb = SolveProgressBar::new( @@ -1511,6 +1518,7 @@ async fn spawn_solve_pypi_task( let records = lock_file::resolve_pypi( resolution_context, + &pypi_options, dependencies .into_iter() .map(|(name, requirement)| (name.as_normalized().clone(), requirement)) diff --git a/src/project/environment.rs b/src/project/environment.rs index 341eef92c..641e08c1e 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -496,4 +496,72 @@ mod tests { vec!["barry", "conda-forge", "bar"] ); } + + #[test] + fn test_pypi_options_per_environment() { + let manifest = Project::from_str( + Path::new("pixi.toml"), + r#" + [project] + name = "foobar" + channels = ["conda-forge"] + platforms = ["linux-64", "osx-64"] + + [feature.foo] + pypi-options = { index-url = "https://mypypi.org/simple", extra-index-urls = ["https://1.com"] } + + [feature.bar] + pypi-options = { extra-index-urls = ["https://2.com"] } + + [environments] + foo = ["foo"] + bar = ["bar"] + foobar = ["foo", "bar"] + "#, + ) + .unwrap(); + + let foo_opts = manifest.environment("foo").unwrap().pypi_options(); + assert_eq!( + foo_opts.index_url.unwrap().to_string(), + "https://mypypi.org/simple" + ); + assert_eq!( + foo_opts + .extra_index_urls + .unwrap() + .iter() + .map(|i| i.to_string()) + .collect_vec(), + vec!["https://1.com/"] + ); + + let bar_opts = manifest.environment("bar").unwrap().pypi_options(); + assert_eq!( + bar_opts + .extra_index_urls + .unwrap() + .iter() + .map(|i| i.to_string()) + .collect_vec(), + vec!["https://2.com/"] + ); + + let foo_bar_opts = manifest.environment("foobar").unwrap().pypi_options(); + + assert_eq!( + foo_bar_opts.index_url.unwrap().to_string(), + "https://mypypi.org/simple" + ); + + assert_eq!( + foo_bar_opts + .extra_index_urls + .unwrap() + .iter() + .map(|i| i.to_string()) + .collect_vec(), + vec!["https://1.com/", "https://2.com/"] + ) + } } diff --git a/src/project/has_features.rs b/src/project/has_features.rs index b65db3914..55b49611a 100644 --- a/src/project/has_features.rs +++ b/src/project/has_features.rs @@ -7,7 +7,10 @@ use rattler_conda_types::{Channel, Platform}; use crate::{Project, SpecType}; use super::{ - manifest::{python::PyPiPackageName, Feature, PyPiRequirement, SystemRequirements}, + manifest::{ + pypi_options::PypiOptions, python::PyPiPackageName, Feature, PyPiRequirement, + SystemRequirements, + }, Dependencies, }; @@ -137,4 +140,14 @@ pub trait HasFeatures<'p> { .reduce(|acc, deps| acc.union(&deps)) .unwrap_or_default() } + + /// Returns the pypi options for this solve group. + fn pypi_options(&self) -> PypiOptions { + self.features() + .filter_map(|f| f.pypi_options()) + .fold(PypiOptions::default(), |acc, opt| { + acc.union(opt) + .expect("pypi-options should have been validated upfront") + }) + } } diff --git a/src/project/manifest/feature.rs b/src/project/manifest/feature.rs index abd8f9372..2fc329307 100644 --- a/src/project/manifest/feature.rs +++ b/src/project/manifest/feature.rs @@ -1,3 +1,4 @@ +use super::pypi_options::PypiOptions; use super::{Activation, PyPiRequirement, SystemRequirements, Target, TargetSelector}; use crate::consts; use crate::project::manifest::channel::{PrioritizedChannel, TomlPrioritizedChannelStrOrMap}; @@ -127,6 +128,9 @@ pub struct Feature { /// Additional system requirements pub system_requirements: SystemRequirements, + /// Pypi-related options + pub pypi_options: Option, + /// Target specific configuration. pub targets: Targets, } @@ -139,6 +143,7 @@ impl Feature { platforms: None, channels: None, system_requirements: SystemRequirements::default(), + pypi_options: None, targets: ::default(), } } @@ -231,6 +236,11 @@ impl Feature { .targets() .any(|t| t.pypi_dependencies.iter().flatten().next().is_some()) } + + /// Returns any pypi_options if they are set. + pub fn pypi_options(&self) -> Option<&PypiOptions> { + self.pypi_options.as_ref() + } } impl<'de> Deserialize<'de> for Feature { @@ -270,6 +280,10 @@ impl<'de> Deserialize<'de> for Feature { /// Target specific tasks to run in the environment #[serde(default)] tasks: HashMap, + + /// Additional options for PyPi dependencies. + #[serde(default)] + pypi_options: Option, } let inner = FeatureInner::deserialize(deserializer)?; @@ -298,6 +312,7 @@ impl<'de> Deserialize<'de> for Feature { .collect() }), system_requirements: inner.system_requirements, + pypi_options: inner.pypi_options, targets: Targets::from_default_and_user_defined(default_target, inner.target), }) } @@ -410,4 +425,23 @@ mod tests { "should have selected the activation from the [linux-64] section" ); } + + #[test] + pub fn test_pypi_options_manifest() { + let manifest = Manifest::from_str( + Path::new("pixi.toml"), + r#" + [project] + name = "foo" + platforms = ["linux-64", "osx-64", "win-64"] + channels = [] + + [pypi-options] + index-url = "https://pypi.org/simple" + "#, + ) + .unwrap(); + + manifest.default_feature().pypi_options().unwrap(); + } } diff --git a/src/project/manifest/mod.rs b/src/project/manifest/mod.rs index 905cc2ed4..0e5830450 100644 --- a/src/project/manifest/mod.rs +++ b/src/project/manifest/mod.rs @@ -5,6 +5,7 @@ mod environment; mod error; mod feature; mod metadata; +pub mod pypi_options; pub mod pyproject; pub mod python; mod system_requirements; @@ -14,6 +15,7 @@ mod validation; use crate::config::Config; use crate::project::manifest::channel::PrioritizedChannel; use crate::project::manifest::environment::TomlEnvironmentMapOrSeq; +use crate::project::manifest::pypi_options::PypiOptions; use crate::project::manifest::python::PyPiPackageName; use crate::pypi_mapping::{ChannelName, MappingLocation, MappingSource}; use crate::task::TaskName; @@ -295,6 +297,7 @@ impl Manifest { system_requirements: Default::default(), targets: Default::default(), channels: None, + pypi_options: Default::default(), }); } } @@ -571,6 +574,7 @@ impl Manifest { channels: Some(vec![channel.clone()]), system_requirements: Default::default(), targets: Default::default(), + pypi_options: Default::default(), }); } } @@ -1041,6 +1045,9 @@ impl<'de> Deserialize<'de> for ProjectManifest { #[serde(default)] environments: IndexMap, + #[serde(default)] + pypi_options: Option, + /// The tool configuration which is unused by pixi #[serde(rename = "tool")] _tool: Option, @@ -1077,6 +1084,7 @@ impl<'de> Deserialize<'de> for ProjectManifest { channels: None, system_requirements: toml_manifest.system_requirements, + pypi_options: toml_manifest.pypi_options, // Combine the default target with all user specified targets targets: Targets::from_default_and_user_defined(default_target, toml_manifest.target), @@ -1145,7 +1153,7 @@ impl<'de> Deserialize<'de> for ProjectManifest { mod tests { use super::*; use crate::project::manifest::channel::PrioritizedChannel; - use insta::assert_snapshot; + use insta::{assert_snapshot, assert_yaml_snapshot}; use rattler_conda_types::{Channel, ChannelConfig, ParseStrictness}; use rstest::*; use std::str::FromStr; @@ -1474,6 +1482,29 @@ mod tests { .join("\n")); } + #[test] + fn test_pypi_options_default_feature() { + let contents = format!( + r#" + {PROJECT_BOILERPLATE} + [pypi-options] + index-url = "https://pypi.org/simple" + extra-index-urls = ["https://pypi.org/simple2"] + [[pypi-options.find-links]] + path = "../foo" + [[pypi-options.find-links]] + url = "https://example.com/bar" + "# + ); + + assert_yaml_snapshot!(toml_edit::de::from_str::(&contents) + .expect("parsing should succeed!") + .default_feature() + .pypi_options + .clone() + .unwrap()); + } + fn test_remove( file_contents: &str, name: &str, diff --git a/src/project/manifest/pypi_options.rs b/src/project/manifest/pypi_options.rs new file mode 100644 index 000000000..901b7011c --- /dev/null +++ b/src/project/manifest/pypi_options.rs @@ -0,0 +1,252 @@ +use crate::consts; +use distribution_types::{FlatIndexLocation, IndexLocations, IndexUrl}; +use indexmap::IndexSet; +use pep508_rs::VerbatimUrl; +use rattler_lock::FindLinksUrlOrPath; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::{hash::Hash, iter}; +use thiserror::Error; +use url::Url; + +/// Specific options for a PyPI registries +#[serde_as] +#[derive(Debug, Clone, PartialEq, Serialize, Eq, Deserialize, Default)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct PypiOptions { + /// The index URL to use as the primary pypi index + pub index_url: Option, + /// Any extra indexes to use, that will be searched after the primary index + pub extra_index_urls: Option>, + /// Flat indexes also called `--find-links` in pip + /// These are flat listings of distributions + pub find_links: Option>, +} + +/// Converts to the [`distribution_types::FlatIndexLocation`] +pub fn to_flat_index_location(find_links: &FindLinksUrlOrPath) -> FlatIndexLocation { + match find_links { + FindLinksUrlOrPath::Path(path) => FlatIndexLocation::Path(path.clone()), + FindLinksUrlOrPath::Url(url) => FlatIndexLocation::Url(url.clone()), + } +} + +/// Clones and deduplicates two iterators of values +fn clone_and_deduplicate<'a, I: Iterator, T: Clone + Eq + Hash + 'a>( + values: I, + other: I, +) -> Vec { + values + .cloned() + .chain(other.cloned()) + .collect::>() + .into_iter() + .collect::>() +} + +impl PypiOptions { + pub fn new( + index: Option, + extra_indexes: Option>, + flat_indexes: Option>, + ) -> Self { + Self { + index_url: index, + extra_index_urls: extra_indexes, + find_links: flat_indexes, + } + } + + /// Converts to the [`distribution_types::IndexLocations`] + pub fn to_index_locations(&self) -> IndexLocations { + // Convert the index to a `IndexUrl` + let index = self + .index_url + .clone() + .map(VerbatimUrl::from_url) + .map(IndexUrl::from); + + // Convert to list of extra indexes + let extra_indexes = self + .extra_index_urls + .clone() + .map(|urls| { + urls.into_iter() + .map(VerbatimUrl::from_url) + .map(IndexUrl::from) + .collect::>() + }) + .unwrap_or_default(); + + // Convert to list of flat indexes + let flat_indexes = self + .find_links + .clone() + .map(|indexes| { + indexes + .into_iter() + .map(|index| to_flat_index_location(&index)) + .collect::>() + }) + .unwrap_or_default(); + + // We keep the `no_index` to false for now, because I've not seen a use case for it yet + // we could change this later if needed + IndexLocations::new(index, extra_indexes, flat_indexes, false) + } + + /// Merges two `PypiOptions` together, according to the following rules + /// - There can only be one primary index + /// - Extra indexes are merged and deduplicated, in the order they are provided + /// - Flat indexes are merged and deduplicated, in the order they are provided + pub fn union(&self, other: &PypiOptions) -> Result { + let index = if let Some(other_index) = other.index_url.clone() { + // Allow only one index + if let Some(own_index) = self.index_url.clone() { + return Err(PypiOptionsMergeError::MultiplePrimaryIndexes { + first: own_index.to_string(), + second: other_index.to_string(), + }); + } else { + // Use the other index, because we don't have one + Some(other_index) + } + } else { + // Use our index, because the other doesn't have one + self.index_url.clone() + }; + + // Chain together and deduplicate the extra indexes + let extra_indexes = self + .extra_index_urls + .as_ref() + // Map for value + .map(|extra_indexes| { + clone_and_deduplicate( + extra_indexes.iter(), + other.extra_index_urls.clone().unwrap_or_default().iter(), + ) + }) + .or_else(|| other.extra_index_urls.clone()); + + // Chain together and deduplicate the flat indexes + let flat_indexes = self + .find_links + .as_ref() + .map(|flat_indexes| { + clone_and_deduplicate( + flat_indexes.iter(), + other.find_links.clone().unwrap_or_default().iter(), + ) + }) + .or_else(|| other.find_links.clone()); + + Ok(PypiOptions { + index_url: index, + extra_index_urls: extra_indexes, + find_links: flat_indexes, + }) + } +} + +impl From for rattler_lock::PypiIndexes { + fn from(value: PypiOptions) -> Self { + let primary_index = value + .index_url + .unwrap_or(consts::DEFAULT_PYPI_INDEX_URL.clone()); + Self { + indexes: iter::once(primary_index) + .chain(value.extra_index_urls.into_iter().flatten()) + .collect(), + find_links: value.find_links.into_iter().flatten().collect(), + } + } +} + +#[derive(Error, Debug)] +pub enum PypiOptionsMergeError { + #[error( + "multiple primary pypi indexes are not supported, found both {first} and {second} across multiple pypi options" + )] + MultiplePrimaryIndexes { first: String, second: String }, +} + +#[cfg(test)] +mod tests { + use crate::project::manifest::pypi_options::{FindLinksUrlOrPath, PypiOptions}; + use url::Url; + + #[test] + fn test_deserialize_pypi_options() { + let toml_str = r#" + index-url = "https://example.com/pypi" + extra-index-urls = ["https://example.com/extra"] + + [[find-links]] + path = "/path/to/flat/index" + + [[find-links]] + url = "https://flat.index" + "#; + let deserialized_options: PypiOptions = toml::from_str(toml_str).unwrap(); + assert_eq!( + deserialized_options, + PypiOptions { + index_url: Some(Url::parse("https://example.com/pypi").unwrap()), + extra_index_urls: Some(vec![Url::parse("https://example.com/extra").unwrap()]), + find_links: Some(vec![ + FindLinksUrlOrPath::Path("/path/to/flat/index".into()), + FindLinksUrlOrPath::Url(Url::parse("https://flat.index").unwrap()) + ]) + }, + ); + } + + #[test] + fn test_merge_pypi_options() { + // Create the first set of options + let opts = PypiOptions { + index_url: Some(Url::parse("https://example.com/pypi").unwrap()), + extra_index_urls: Some(vec![Url::parse("https://example.com/extra").unwrap()]), + find_links: Some(vec![ + FindLinksUrlOrPath::Path("/path/to/flat/index".into()), + FindLinksUrlOrPath::Url(Url::parse("https://flat.index").unwrap()), + ]), + }; + + // Create the second set of options + let opts2 = PypiOptions { + index_url: None, + extra_index_urls: Some(vec![Url::parse("https://example.com/extra2").unwrap()]), + find_links: Some(vec![ + FindLinksUrlOrPath::Path("/path/to/flat/index2".into()), + FindLinksUrlOrPath::Url(Url::parse("https://flat.index2").unwrap()), + ]), + }; + + // Merge the two options + let merged_opts = opts.union(&opts2).unwrap(); + insta::assert_yaml_snapshot!(merged_opts); + } + + #[test] + fn test_error_on_multiple_primary_indexes() { + // Create the first set of options + let opts = PypiOptions { + index_url: Some(Url::parse("https://example.com/pypi").unwrap()), + extra_index_urls: None, + find_links: None, + }; + + // Create the second set of options + let opts2 = PypiOptions { + index_url: Some(Url::parse("https://example.com/pypi2").unwrap()), + extra_index_urls: None, + find_links: None, + }; + + // Merge the two options + let merged_opts = opts.union(&opts2); + insta::assert_snapshot!(merged_opts.err().unwrap()); + } +} diff --git a/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__error_on_multiple_primary_indexes.snap b/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__error_on_multiple_primary_indexes.snap new file mode 100644 index 000000000..7e1536e06 --- /dev/null +++ b/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__error_on_multiple_primary_indexes.snap @@ -0,0 +1,5 @@ +--- +source: src/project/manifest/pypi_options.rs +expression: merged_opts.err().unwrap() +--- +multiple primary pypi indexes are not supported, found both https://example.com/pypi and https://example.com/pypi2 across multiple pypi options diff --git a/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__merge_pypi_options.snap b/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__merge_pypi_options.snap new file mode 100644 index 000000000..8b2dc7845 --- /dev/null +++ b/src/project/manifest/snapshots/pixi__project__manifest__pypi_options__tests__merge_pypi_options.snap @@ -0,0 +1,13 @@ +--- +source: src/project/manifest/pypi_options.rs +expression: merged_opts +--- +index-url: "https://example.com/pypi" +extra-index-urls: + - "https://example.com/extra" + - "https://example.com/extra2" +find-links: + - path: /path/to/flat/index + - url: "https://flat.index/" + - path: /path/to/flat/index2 + - url: "https://flat.index2/" diff --git a/src/project/manifest/snapshots/pixi__project__manifest__tests__invalid_key.snap b/src/project/manifest/snapshots/pixi__project__manifest__tests__invalid_key.snap index e9dbfccb2..caf2d65ee 100644 --- a/src/project/manifest/snapshots/pixi__project__manifest__tests__invalid_key.snap +++ b/src/project/manifest/snapshots/pixi__project__manifest__tests__invalid_key.snap @@ -6,7 +6,7 @@ TOML parse error at line 8, column 2 | 8 | [foobar] | ^^^^^^ -unknown field `foobar`, expected one of `project`, `system-requirements`, `target`, `dependencies`, `host-dependencies`, `build-dependencies`, `pypi-dependencies`, `activation`, `tasks`, `feature`, `environments`, `tool`, `$schema` +unknown field `foobar`, expected one of `project`, `system-requirements`, `target`, `dependencies`, `host-dependencies`, `build-dependencies`, `pypi-dependencies`, `activation`, `tasks`, `feature`, `environments`, `pypi-options`, `tool`, `$schema` TOML parse error at line 8, column 16 | diff --git a/src/project/manifest/snapshots/pixi__project__manifest__tests__pypi_options_default_feature.snap b/src/project/manifest/snapshots/pixi__project__manifest__tests__pypi_options_default_feature.snap new file mode 100644 index 000000000..cf3220867 --- /dev/null +++ b/src/project/manifest/snapshots/pixi__project__manifest__tests__pypi_options_default_feature.snap @@ -0,0 +1,10 @@ +--- +source: src/project/manifest/mod.rs +expression: "toml_edit::de::from_str::(&contents).expect(\"parsing should succeed!\").default_feature().pypi_options.clone().unwrap()" +--- +index-url: "https://pypi.org/simple" +extra-index-urls: + - "https://pypi.org/simple2" +find-links: + - path: "../foo" + - url: "https://example.com/bar" diff --git a/src/project/manifest/validation.rs b/src/project/manifest/validation.rs index be3599cff..4e7fa26c5 100644 --- a/src/project/manifest/validation.rs +++ b/src/project/manifest/validation.rs @@ -9,6 +9,8 @@ use std::{ path::{Path, PathBuf}, }; +use super::pypi_options::PypiOptions; + impl ProjectManifest { /// Validate the project manifest. pub fn validate(&self, source: NamedSource, root_folder: &Path) -> miette::Result<()> { @@ -124,7 +126,7 @@ impl ProjectManifest { // Validate the environments defined in the project for env in self.environments.environments.iter() { - if let Err(report) = self.validate_environment(env) { + if let Err(report) = self.validate_environment(env, self.default_feature()) { return Err(report.with_source_code(source)); } } @@ -133,7 +135,11 @@ impl ProjectManifest { } /// Validates that the given environment is valid. - fn validate_environment(&self, env: &Environment) -> Result<(), Report> { + fn validate_environment( + &self, + env: &Environment, + default_feature: &Feature, + ) -> Result<(), Report> { let mut features_seen = HashSet::new(); let mut features = Vec::with_capacity(env.features.len()); for feature in env.features.iter() { @@ -172,6 +178,7 @@ impl ProjectManifest { // Check if there are conflicts in system requirements between features if let Err(e) = features .iter() + .chain(std::iter::once(&default_feature)) .map(|feature| &feature.system_requirements) .try_fold(SystemRequirements::default(), |acc, req| acc.union(req)) { @@ -184,6 +191,14 @@ impl ProjectManifest { )); } + // Check if there are no conflicts in pypi options between features + features + .iter() + .chain(std::iter::once(&default_feature)) + .filter_map(|feature| feature.pypi_options()) + .try_fold(PypiOptions::default(), |acc, opts| acc.union(opts)) + .into_diagnostic()?; + Ok(()) } } diff --git a/src/project/mod.rs b/src/project/mod.rs index 2512fb847..f9d783536 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -8,7 +8,7 @@ mod solve_group; pub mod virtual_packages; use async_once_cell::OnceCell as AsyncCell; -use distribution_types::IndexLocations; + use indexmap::{Equivalent, IndexMap, IndexSet}; use miette::{IntoDiagnostic, NamedSource}; @@ -471,12 +471,6 @@ impl Project { .expect("mapping source should be ok") } - /// Returns the Python index locations to use for this project. - pub fn pypi_index_locations(&self) -> IndexLocations { - // TODO: Currently we just default to Pypi always. - IndexLocations::default() - } - /// Returns the reqwest client used for http networking pub fn client(&self) -> &reqwest::Client { &self.client diff --git a/src/project/solve_group.rs b/src/project/solve_group.rs index e8dcee649..0429074e5 100644 --- a/src/project/solve_group.rs +++ b/src/project/solve_group.rs @@ -1,7 +1,6 @@ use super::has_features::HasFeatures; use super::manifest::SystemRequirements; use super::{manifest, Environment, Project}; - use itertools::Itertools; use std::hash::Hash; use std::path::PathBuf; @@ -107,6 +106,9 @@ mod tests { [feature.foo.dependencies] b = "*" + [feature.foo.pypi-options] + index-url = "https://my-index.com/simple" + [feature.bar.dependencies] c = "*" @@ -151,6 +153,11 @@ mod tests { assert_eq!(bar_system_requirements.cuda, "12.0".parse().ok()); assert_eq!(default_system_requirements.cuda, None); + assert_eq!( + solve_group.pypi_options().index_url.unwrap(), + "https://my-index.com/simple".parse().unwrap() + ); + // Check that the solve group 'group1' contains all the dependencies of its environments let package_names: HashSet<_> = solve_group .dependencies(None, None) From 1a1fcbe1e01f029cc5ad27bafffd93fd66939a01 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Tue, 30 Apr 2024 13:36:46 +0200 Subject: [PATCH 10/15] fix: make integration test fail in ci and fix ssh issue (#1301) --- examples/pypi-source-deps/pixi.lock | 38 +++++++++---------- examples/pypi-source-deps/pixi.toml | 6 ++- .../pypi-source-deps/test/test_imports.py | 5 ++- tests/test_examples.sh | 1 + 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/examples/pypi-source-deps/pixi.lock b/examples/pypi-source-deps/pixi.lock index a4ac869a2..a121a60d2 100644 --- a/examples/pypi-source-deps/pixi.lock +++ b/examples/pypi-source-deps/pixi.lock @@ -1,4 +1,4 @@ -version: 4 +version: 5 environments: default: channels: @@ -28,11 +28,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - - pypi: https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 + - pypi: git+https://github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -43,7 +43,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab + - pypi: git+https://github.com/pytest-dev/pytest.git@feaae2fb35b17ea5da0ba587090a70a74ac947dd - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -63,11 +63,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 - - pypi: https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 + - pypi: git+https://github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -78,7 +78,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab + - pypi: git+https://github.com/pytest-dev/pytest.git@feaae2fb35b17ea5da0ba587090a70a74ac947dd - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -98,11 +98,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - - pypi: https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 + - pypi: git+https://github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -113,7 +113,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab + - pypi: git+https://github.com/pytest-dev/pytest.git@feaae2fb35b17ea5da0ba587090a70a74ac947dd - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -135,12 +135,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - - pypi: https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl - pypi: direct+https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - - pypi: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 + - pypi: git+https://github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 - pypi: https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl @@ -151,7 +151,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl - - pypi: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab + - pypi: git+https://github.com/pytest-dev/pytest.git@feaae2fb35b17ea5da0ba587090a70a74ac947dd - pypi: git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 - pypi: https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl @@ -189,9 +189,9 @@ packages: timestamp: 1650670423406 - kind: pypi name: blinker - version: 1.7.0 - url: https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl - sha256: c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 + version: 1.8.1 + url: https://files.pythonhosted.org/packages/b8/a8/e2c40dc86dfc88fea7b627123e0a8e33ad1978af1f521629ec0f9a6e5da8/blinker-1.8.1-py3-none-any.whl + sha256: 5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6 requires_python: '>=3.8' - kind: conda name: bzip2 @@ -342,7 +342,7 @@ packages: - kind: pypi name: flask version: 3.1.0.dev0 - url: git+ssh://git@github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 + url: git+https://github.com/pallets/flask@11c15ddfeb6edcb0978d3407ed972ae441013177 requires_dist: - werkzeug>=3.0.0 - jinja2>=3.1.2 @@ -908,8 +908,8 @@ packages: requires_python: '>=3.7' - kind: pypi name: pytest - version: 8.2.0.dev102+gfafab1dbf - url: git+https://github.com/pytest-dev/pytest.git@fafab1dbfd2414c2282b558f0c7af9a7ad0318ab + version: 8.3.0.dev16+gfeaae2fb3 + url: git+https://github.com/pytest-dev/pytest.git@feaae2fb35b17ea5da0ba587090a70a74ac947dd requires_dist: - iniconfig - packaging diff --git a/examples/pypi-source-deps/pixi.toml b/examples/pypi-source-deps/pixi.toml index 67a3d3bd8..bb7a4b983 100644 --- a/examples/pypi-source-deps/pixi.toml +++ b/examples/pypi-source-deps/pixi.toml @@ -17,9 +17,11 @@ python = "*" rich = "~=13.7" # Lets start with some git dependencies -# With ssh -flask = { git = "ssh://git@github.com/pallets/flask" } +# With ssh: (you need to have the correct ssh keys setup for which I (ruben) am to lazy to set up correctly) +# flask = { git = "ssh://git@github.com/pallets/flask" } + # With https +flask = { git = "https://github.com/pallets/flask" } requests = { git = "https://github.com/psf/requests.git", rev = "0106aced5faa299e6ede89d1230bd6784f2c3660" } # TODO: will support later -> or use branch = '' or tag = '' to specify a branch or tag diff --git a/examples/pypi-source-deps/test/test_imports.py b/examples/pypi-source-deps/test/test_imports.py index 0c0cb1104..7314faf3f 100644 --- a/examples/pypi-source-deps/test/test_imports.py +++ b/examples/pypi-source-deps/test/test_imports.py @@ -1,7 +1,8 @@ from importlib.metadata import version - +from importlib.util import find_spec def test_flask(): - assert version("flask") == "3.0.2" + # Don't test version, as it may change over time with lock file updates + assert find_spec("flask") is not None def test_rich(): assert version("rich").split(".")[0] == "13" diff --git a/tests/test_examples.sh b/tests/test_examples.sh index 9b657cd1f..481d4be6a 100644 --- a/tests/test_examples.sh +++ b/tests/test_examples.sh @@ -1,4 +1,5 @@ # Run from the root of the project using `bash tests/test_examples.sh` +set -e echo "Running test_examples.sh" echo "Running the polarify example:" From 42c9c8a8a7309c793673ec52210b980cec5b64d9 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Tue, 30 Apr 2024 13:42:13 +0200 Subject: [PATCH 11/15] fix: automate adding install scripts to the docs (#1302) Triggered by tip from @beenje --- install/docs_hooks.py | 19 +++++++++++++++++++ install/install.ps1 | 2 ++ install/install.sh | 1 + mkdocs.yml | 1 + tbump.toml | 9 +++++++++ 5 files changed, 32 insertions(+) create mode 100644 install/docs_hooks.py diff --git a/install/docs_hooks.py b/install/docs_hooks.py new file mode 100644 index 000000000..a78e6a0bb --- /dev/null +++ b/install/docs_hooks.py @@ -0,0 +1,19 @@ +from pathlib import Path +from mkdocs.structure.files import File, Files +from mkdocs.config.defaults import MkDocsConfig + +INSTALL_SCRIPTS = [Path(__file__).parent / "install.sh", Path(__file__).parent / "install.ps1"] + + +def on_files(files: Files, config: MkDocsConfig): + """Copy the install scripts to the site.""" + for script in INSTALL_SCRIPTS: + files.append( + File( + path=script.name, + src_dir=script.parent, + dest_dir=f"{config.site_dir}", + use_directory_urls=config.use_directory_urls, + ) + ) + return files diff --git a/install/install.ps1 b/install/install.ps1 index becb8392e..086f835bd 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -17,6 +17,8 @@ https://pixi.sh .LINK https://github.com/prefix-dev/pixi +.NOTES + Version: v0.20.1 #> param ( [string] $PixiVersion = 'latest', diff --git a/install/install.sh b/install/install.sh index 8b9905a66..2d1281b9a 100644 --- a/install/install.sh +++ b/install/install.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -euo pipefail +# Version: v0.20.1 __wrap__() { diff --git a/mkdocs.yml b/mkdocs.yml index 6a56c1587..1ebcb4b08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -134,6 +134,7 @@ nav: hooks: - schema/docs_hooks.py + - install/docs_hooks.py plugins: - redirects: diff --git a/tbump.toml b/tbump.toml index 4806ed201..e5726d17e 100644 --- a/tbump.toml +++ b/tbump.toml @@ -42,10 +42,19 @@ search = '^version = "{current_version}"' [[file]] src = "docs/advanced/github_actions.md" search = "pixi-version: v{current_version}" + [[file]] src = "schema/schema.json" search = "/pixi.sh/v{current_version}/" +[[file]] +src = "install/install.sh" +search = "Version: v{current_version}" + +[[file]] +src = "install/install.ps1" +search = "Version: v{current_version}" + [[field]] # the name of the field name = "candidate" From 404d17d8dff90064a03a6f30f9d1f852f4f85a01 Mon Sep 17 00:00:00 2001 From: Olivier Lacroix Date: Tue, 30 Apr 2024 22:23:51 +1000 Subject: [PATCH 12/15] refactor: Use IndexSet instead of Vec for collections of unique elements (#1289) --- src/cli/project/platform/remove.rs | 2 +- src/lock_file/resolve.rs | 4 +- src/project/dependencies.rs | 38 ++-- src/project/has_features.rs | 4 +- src/project/manifest/channel.rs | 14 ++ src/project/manifest/document.rs | 2 +- src/project/manifest/feature.rs | 20 +- src/project/manifest/metadata.rs | 7 +- src/project/manifest/mod.rs | 347 ++++++++++------------------- src/project/manifest/python.rs | 4 +- src/project/mod.rs | 2 +- 11 files changed, 176 insertions(+), 268 deletions(-) diff --git a/src/cli/project/platform/remove.rs b/src/cli/project/platform/remove.rs index f497a5fa2..50833f96f 100644 --- a/src/cli/project/platform/remove.rs +++ b/src/cli/project/platform/remove.rs @@ -38,7 +38,7 @@ pub async fn execute(mut project: Project, args: Args) -> miette::Result<()> { // Remove the platform(s) from the manifest project .manifest - .remove_platforms(&platforms, &feature_name)?; + .remove_platforms(platforms.clone(), &feature_name)?; get_up_to_date_prefix( &project.default_environment(), diff --git a/src/lock_file/resolve.rs b/src/lock_file/resolve.rs index 2424b2909..078c37d2b 100644 --- a/src/lock_file/resolve.rs +++ b/src/lock_file/resolve.rs @@ -27,7 +27,7 @@ use distribution_types::{ }; use distribution_types::{FileLocation, SourceDistCompatibility}; use futures::FutureExt; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use indicatif::ProgressBar; use install_wheel_rs::linker::LinkMode; use itertools::{Either, Itertools}; @@ -291,7 +291,7 @@ fn convert_flat_index_path( pub async fn resolve_pypi( context: UvResolutionContext, pypi_options: &PypiOptions, - dependencies: IndexMap>, + dependencies: IndexMap>, system_requirements: SystemRequirements, locked_conda_records: &[RepoDataRecord], platform: rattler_conda_types::Platform, diff --git a/src/project/dependencies.rs b/src/project/dependencies.rs index b3acbc252..0758a4378 100644 --- a/src/project/dependencies.rs +++ b/src/project/dependencies.rs @@ -1,6 +1,6 @@ -use indexmap::{Equivalent, IndexMap}; +use indexmap::{Equivalent, IndexMap, IndexSet}; use rattler_conda_types::{MatchSpec, NamelessMatchSpec, PackageName}; -use std::hash::Hash; +use std::{hash::Hash, iter::once}; /// Holds a list of dependencies where for each package name there can be multiple requirements. /// @@ -9,11 +9,11 @@ use std::hash::Hash; /// there can be multiple requirements for a given package. #[derive(Default, Debug, Clone)] pub struct Dependencies { - map: IndexMap>, + map: IndexMap>, } -impl From>> for Dependencies { - fn from(map: IndexMap>) -> Self { +impl From>> for Dependencies { + fn from(map: IndexMap>) -> Self { Self { map } } } @@ -21,7 +21,10 @@ impl From>> for Dependencies { impl From> for Dependencies { fn from(map: IndexMap) -> Self { Self { - map: map.into_iter().map(|(k, v)| (k, vec![v])).collect(), + map: map + .into_iter() + .map(|(k, v)| (k, once(v).collect())) + .collect(), } } } @@ -29,7 +32,7 @@ impl From> for Dependencies { impl Dependencies { /// Adds the given spec to the list of dependencies. pub fn insert(&mut self, name: PackageName, spec: NamelessMatchSpec) { - self.map.entry(name).or_default().push(spec); + self.map.entry(name).or_default().insert(spec); } /// Adds a list of specs to the list of dependencies. @@ -46,12 +49,15 @@ impl Dependencies { iter: impl IntoIterator, ) { for (name, spec) in iter { - *self.map.entry(name).or_default() = vec![spec]; + *self.map.entry(name).or_default() = once(spec).collect(); } } /// Removes all requirements for the given package and returns them. - pub fn remove(&mut self, name: &Q) -> Option<(PackageName, Vec)> + pub fn remove( + &mut self, + name: &Q, + ) -> Option<(PackageName, IndexSet)> where Q: Hash + Equivalent, { @@ -63,13 +69,7 @@ impl Dependencies { pub fn union(&self, other: &Self) -> Self { let mut map = self.map.clone(); for (name, specs) in &other.map { - let entry = map.entry(name.clone()).or_default(); - for spec in specs { - // TODO entry should be a set to avoid duplicates - if !entry.contains(spec) { - entry.push(spec.clone()); - } - } + map.entry(name.clone()).or_default().extend(specs.clone()) } Self { map } } @@ -87,7 +87,7 @@ impl Dependencies { /// Returns an iterator over the package names and their corresponding requirements. pub fn iter( &self, - ) -> impl DoubleEndedIterator)> + '_ { + ) -> impl DoubleEndedIterator)> + '_ { self.map.iter() } @@ -123,8 +123,8 @@ impl Dependencies { } impl IntoIterator for Dependencies { - type Item = (PackageName, Vec); - type IntoIter = indexmap::map::IntoIter>; + type Item = (PackageName, IndexSet); + type IntoIter = indexmap::map::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.map.into_iter() diff --git a/src/project/has_features.rs b/src/project/has_features.rs index 55b49611a..404a8d295 100644 --- a/src/project/has_features.rs +++ b/src/project/has_features.rs @@ -105,7 +105,7 @@ pub trait HasFeatures<'p> { fn pypi_dependencies( &self, platform: Option, - ) -> IndexMap> { + ) -> IndexMap> { self.features() .filter_map(|f| f.pypi_dependencies(platform)) .fold(IndexMap::default(), |mut acc, deps| { @@ -121,7 +121,7 @@ pub trait HasFeatures<'p> { // Add the requirements to the accumulator. for (name, spec) in deps_iter { - acc.entry(name).or_default().push(spec); + acc.entry(name).or_default().insert(spec); } acc diff --git a/src/project/manifest/channel.rs b/src/project/manifest/channel.rs index 35345784b..a20da841c 100644 --- a/src/project/manifest/channel.rs +++ b/src/project/manifest/channel.rs @@ -24,6 +24,20 @@ impl PrioritizedChannel { priority: None, } } + + /// If channel base is part of the default config, returns the name otherwise the base url + pub fn to_name_or_url(&self) -> String { + if self + .channel + .base_url + .as_str() + .contains(default_channel_config().channel_alias.as_str()) + { + self.channel.name().to_string() + } else { + self.channel.base_url.to_string() + } + } } pub enum TomlPrioritizedChannelStrOrMap { diff --git a/src/project/manifest/document.rs b/src/project/manifest/document.rs index f11a62d00..2de877bef 100644 --- a/src/project/manifest/document.rs +++ b/src/project/manifest/document.rs @@ -124,7 +124,7 @@ impl ManifestSource { } /// Returns a mutable reference to the specified array either in project or feature. - pub fn specific_array_mut( + pub fn get_array_mut( &mut self, array_name: &str, feature_name: &FeatureName, diff --git a/src/project/manifest/feature.rs b/src/project/manifest/feature.rs index 2fc329307..39ccb7095 100644 --- a/src/project/manifest/feature.rs +++ b/src/project/manifest/feature.rs @@ -8,7 +8,7 @@ use crate::project::manifest::{deserialize_opt_package_map, deserialize_package_ use crate::project::SpecType; use crate::task::{Task, TaskName}; use crate::utils::spanned::PixiSpanned; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use itertools::Either; use rattler_conda_types::{NamelessMatchSpec, PackageName, Platform}; use serde::de::Error; @@ -117,13 +117,13 @@ pub struct Feature { /// /// This value is `None` if this feature does not specify any platforms and the default /// platforms from the project should be used. - pub platforms: Option>>, + pub platforms: Option>>, /// Channels specific to this feature. /// /// This value is `None` if this feature does not specify any channels and the default /// channels from the project should be used. - pub channels: Option>, + pub channels: Option>, /// Additional system requirements pub system_requirements: SystemRequirements, @@ -153,6 +153,18 @@ impl Feature { self.name == FeatureName::Default } + /// Returns a mutable reference to the platforms of the feature. Create them if needed + pub fn platforms_mut(&mut self) -> &mut IndexSet { + self.platforms + .get_or_insert_with(Default::default) + .get_mut() + } + + /// Returns a mutable reference to the channels of the feature. Create them if needed + pub fn channels_mut(&mut self) -> &mut IndexSet { + self.channels.get_or_insert_with(Default::default) + } + /// Returns the dependencies of the feature for a given `spec_type` and `platform`. /// /// This function returns a [`Cow`]. If the dependencies are not combined or overwritten by @@ -253,7 +265,7 @@ impl<'de> Deserialize<'de> for Feature { #[serde(deny_unknown_fields, rename_all = "kebab-case")] struct FeatureInner { #[serde(default)] - platforms: Option>>, + platforms: Option>>, #[serde(default)] channels: Option>, #[serde(default)] diff --git a/src/project/manifest/metadata.rs b/src/project/manifest/metadata.rs index 20c721d86..f544e5d8b 100644 --- a/src/project/manifest/metadata.rs +++ b/src/project/manifest/metadata.rs @@ -1,4 +1,5 @@ use crate::utils::spanned::PixiSpanned; +use indexmap::IndexSet; use rattler_conda_types::{Platform, Version}; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; @@ -25,13 +26,13 @@ pub struct ProjectMetadata { pub authors: Vec, /// The channels used by the project - #[serde_as(as = "Vec")] - pub channels: Vec, + #[serde_as(as = "IndexSet")] + pub channels: IndexSet, /// The platforms this project supports // TODO: This is actually slightly different from the rattler_conda_types::Platform because it // should not include noarch. - pub platforms: PixiSpanned>, + pub platforms: PixiSpanned>, /// The license as a valid SPDX string (e.g. MIT AND Apache-2.0) pub license: Option, diff --git a/src/project/manifest/mod.rs b/src/project/manifest/mod.rs index 0e5830450..d96b540d6 100644 --- a/src/project/manifest/mod.rs +++ b/src/project/manifest/mod.rs @@ -19,13 +19,11 @@ use crate::project::manifest::pypi_options::PypiOptions; use crate::project::manifest::python::PyPiPackageName; use crate::pypi_mapping::{ChannelName, MappingLocation, MappingSource}; use crate::task::TaskName; -use crate::util::default_channel_config; use crate::{consts, project::SpecType, task::Task, utils::spanned::PixiSpanned}; pub use activation::Activation; use document::ManifestSource; pub use environment::{Environment, EnvironmentName}; pub use feature::{Feature, FeatureName}; -use indexmap::map::Entry; use indexmap::{Equivalent, IndexMap, IndexSet}; use itertools::Itertools; pub use metadata::ProjectMetadata; @@ -44,7 +42,7 @@ use serde::{Deserialize, Deserializer}; use serde_with::serde_as; use std::collections::HashSet; use std::ffi::OsStr; -use std::fmt; +use std::fmt::{self, Display}; use std::hash::Hash; use std::marker::PhantomData; use std::{ @@ -237,8 +235,7 @@ impl Manifest { .remove_task(name.as_str(), platform, feature_name)?; // Remove the task from the internal manifest - self.feature_mut(feature_name) - .expect("feature should exist") + self.feature_mut(feature_name)? .targets .for_opt_target_mut(platform.map(TargetSelector::from).as_ref()) .map(|target| target.tasks.remove(&name)); @@ -252,65 +249,21 @@ impl Manifest { platforms: impl Iterator + Clone, feature_name: &FeatureName, ) -> miette::Result<()> { - let mut stored_platforms = IndexSet::new(); - match feature_name { - FeatureName::Default => { - for platform in platforms { - // TODO: Make platforms a IndexSet to avoid duplicates. - if self - .parsed - .project - .platforms - .value - .iter() - .any(|x| x == platform) - { - continue; - } - self.parsed.project.platforms.value.push(*platform); + // Get current and new platforms for the feature + let current = match feature_name { + FeatureName::Default => self.parsed.project.platforms.get_mut(), + FeatureName::Named(_) => self.get_or_insert_feature_mut(feature_name).platforms_mut(), + }; + let to_add: IndexSet<_> = platforms.cloned().collect(); + let new: IndexSet<_> = to_add.difference(current).cloned().collect(); - stored_platforms.insert(platform); - } - } - FeatureName::Named(_) => { - for platform in platforms { - match self.parsed.features.entry(feature_name.clone()) { - Entry::Occupied(mut entry) => { - if let Some(platforms) = &mut entry.get_mut().platforms { - if platforms.value.iter().any(|x| x == platform) { - continue; - } - } - // If the feature already exists, just push the new platform - entry - .get_mut() - .platforms - .get_or_insert_with(Default::default) - .value - .push(*platform); - } - Entry::Vacant(entry) => { - // If the feature does not exist, insert a new feature with the new platform - entry.insert(Feature { - name: feature_name.clone(), - platforms: Some(PixiSpanned::from(vec![*platform])), - system_requirements: Default::default(), - targets: Default::default(), - channels: None, - pypi_options: Default::default(), - }); - } - } - stored_platforms.insert(platform); - } - } - } - // Then add the platforms to the toml document - let platforms_array = self - .document - .specific_array_mut("platforms", feature_name)?; - for platform in stored_platforms { - platforms_array.push(platform.to_string()); + // Add the platforms to the manifest + current.extend(new.clone()); + + // Then to the TOML document + let platforms = self.document.get_array_mut("platforms", feature_name)?; + for platform in new.iter() { + platforms.push(platform.to_string()); } Ok(()) @@ -319,55 +272,24 @@ impl Manifest { /// Remove the platform(s) from the project pub fn remove_platforms( &mut self, - platforms: &Vec, + platforms: impl IntoIterator, feature_name: &FeatureName, ) -> miette::Result<()> { - let mut removed_platforms = Vec::new(); - match feature_name { - FeatureName::Default => { - for platform in platforms { - if let Some(index) = self - .parsed - .project - .platforms - .value - .iter() - .position(|x| x == platform) - { - self.parsed.project.platforms.value.remove(index); - removed_platforms.push(platform.to_string()); - } - } - } - FeatureName::Named(_) => { - for platform in platforms { - match self.parsed.features.entry(feature_name.clone()) { - Entry::Occupied(mut entry) => { - if let Some(platforms) = &mut entry.get_mut().platforms { - if let Some(index) = - platforms.value.iter().position(|x| x == platform) - { - platforms.value.remove(index); - } - } - } - Entry::Vacant(_entry) => { - return Err(miette!( - "Feature {} does not exist", - feature_name.as_str() - )); - } - } - removed_platforms.push(platform.to_string()); - } - } - } + // Get current platforms and platform to remove for the feature + let current = match feature_name { + FeatureName::Default => self.parsed.project.platforms.get_mut(), + FeatureName::Named(_) => self.feature_mut(feature_name)?.platforms_mut(), + }; + let to_remove: IndexSet<_> = platforms.into_iter().collect(); + let retained: IndexSet<_> = current.difference(&to_remove).cloned().collect(); - // remove the channels from the toml - let platforms_array = self - .document - .specific_array_mut("platforms", feature_name)?; - platforms_array.retain(|x| !removed_platforms.contains(&x.as_str().unwrap().to_string())); + // Remove platforms from the manifest + current.retain(|p| retained.contains(p)); + + // And from the TOML document + let retained = retained.iter().map(|p| p.to_string()).collect_vec(); + let platforms = self.document.get_array_mut("platforms", feature_name)?; + platforms.retain(|x| retained.contains(&x.to_string())); Ok(()) } @@ -526,67 +448,25 @@ impl Manifest { channels: impl IntoIterator, feature_name: &FeatureName, ) -> miette::Result<()> { - // First add the channels to the manifest - let mut stored_channels = IndexSet::new(); - match feature_name { - FeatureName::Default => { - for channel in channels { - // TODO: Make channels a IndexSet to avoid duplicates. - if self.parsed.project.channels.iter().any(|x| x == &channel) { - continue; - } - self.parsed.project.channels.push(channel.clone()); - - // If channel base is part of the default config, use the name otherwise the base url. - if channel - .channel - .base_url - .as_str() - .contains(default_channel_config().channel_alias.as_str()) - { - stored_channels.insert(channel.channel.name().to_string()); - } else { - stored_channels.insert(channel.channel.base_url.to_string()); - } - } - } - FeatureName::Named(_) => { - for channel in channels { - match self.parsed.features.entry(feature_name.clone()) { - Entry::Occupied(mut entry) => { - if let Some(channels) = &mut entry.get_mut().channels { - if channels.iter().any(|x| x == &channel) { - continue; - } - } - // If the feature already exists, just push the new channel - entry - .get_mut() - .channels - .get_or_insert_with(Vec::new) - .push(channel.clone()); - } - Entry::Vacant(entry) => { - // If the feature does not exist, insert a new feature with the new channel - entry.insert(Feature { - name: feature_name.clone(), - platforms: None, - channels: Some(vec![channel.clone()]), - system_requirements: Default::default(), - targets: Default::default(), - pypi_options: Default::default(), - }); - } - } - stored_channels.insert(channel.channel.name().to_string()); - } + // Get current and new platforms for the feature + let current = match feature_name { + FeatureName::Default => &mut self.parsed.project.channels, + FeatureName::Named(_) => self.get_or_insert_feature_mut(feature_name).channels_mut(), + }; + let to_add: IndexSet<_> = channels.into_iter().collect(); + let new: IndexSet<_> = to_add.difference(current).cloned().collect(); + + // Add the channels to the manifest + current.extend(new.clone()); + + // Then to the TOML document + let channels = self.document.get_array_mut("channels", feature_name)?; + for channel in new.iter() { + match feature_name { + FeatureName::Default => channels.push(channel.to_name_or_url()), + FeatureName::Named(_) => channels.push(channel.channel.name().to_string()), } } - // Then add the channels to the toml document - let channels_array = self.document.specific_array_mut("channels", feature_name)?; - for channel in stored_channels { - channels_array.push(channel); - } Ok(()) } @@ -597,51 +477,21 @@ impl Manifest { channels: impl IntoIterator, feature_name: &FeatureName, ) -> miette::Result<()> { - let mut removed_channels = Vec::new(); - - match feature_name { - FeatureName::Default => { - for channel in channels { - // TODO: Make channels a IndexSet to simplify this. - if self.parsed.project.channels.iter().any(|x| x == &channel) { - if let Some(index) = self - .parsed - .project - .channels - .iter() - .position(|x| *x == channel) - { - self.parsed.project.channels.remove(index); - } - removed_channels.push(channel.channel.name().to_string()); - } - } - } - FeatureName::Named(_) => { - for channel in channels { - match self.parsed.features.entry(feature_name.clone()) { - Entry::Occupied(mut entry) => { - if let Some(channels) = &mut entry.get_mut().channels { - if let Some(index) = channels.iter().position(|x| *x == channel) { - channels.remove(index); - } - } - } - Entry::Vacant(_entry) => { - return Err(miette!( - "Feature {} does not exist", - feature_name.as_str() - )); - } - } - removed_channels.push(channel.channel.name().to_string()); - } - } - } + // Get current channels and channels to remove for the feature + let current = match feature_name { + FeatureName::Default => &mut self.parsed.project.channels, + FeatureName::Named(_) => self.feature_mut(feature_name)?.channels_mut(), + }; + let to_remove: IndexSet<_> = channels.into_iter().collect(); + let retained: IndexSet<_> = current.difference(&to_remove).cloned().collect(); + + // Remove channels from the manifest + current.retain(|c| retained.contains(c)); - // remove the channels from the toml - let channels_array = self.document.specific_array_mut("channels", feature_name)?; - channels_array.retain(|x| !removed_channels.contains(&x.as_str().unwrap().to_string())); + // And from the TOML document + let retained = retained.iter().map(|c| c.channel.name()).collect_vec(); + let channels = self.document.get_array_mut("channels", feature_name)?; + channels.retain(|x| retained.contains(&x.as_str().unwrap())); Ok(()) } @@ -674,11 +524,7 @@ impl Manifest { name: Option<&FeatureName>, ) -> &mut Target { let feature = match name { - Some(feature) => self - .parsed - .features - .entry(feature.clone()) - .or_insert_with(|| Feature::new(feature.clone())), + Some(feature) => self.get_or_insert_feature_mut(feature), None => self.default_feature_mut(), }; feature @@ -689,7 +535,7 @@ impl Manifest { /// Returns a mutable reference to a target pub fn target_mut(&mut self, platform: Option, name: &FeatureName) -> &mut Target { self.feature_mut(name) - .expect("feature should exist") + .unwrap() .targets .for_opt_target_mut(platform.map(TargetSelector::Platform).as_ref()) .expect("target should exist") @@ -709,11 +555,22 @@ impl Manifest { } /// Returns the mutable feature with the given name or `None` if it does not exist. - pub fn feature_mut(&mut self, name: &Q) -> Option<&mut Feature> + pub fn feature_mut(&mut self, name: &Q) -> miette::Result<&mut Feature> where - Q: Hash + Equivalent, + Q: Hash + Equivalent + Display, { - self.parsed.features.get_mut(name) + self.parsed + .features + .get_mut(name) + .ok_or_else(|| miette!("Feature `{name}` does not exist")) + } + + /// Returns the mutable feature with the given name or `None` if it does not exist. + pub fn get_or_insert_feature_mut(&mut self, name: &FeatureName) -> &mut Feature { + self.parsed + .features + .entry(name.clone()) + .or_insert_with(|| Feature::new(name.clone())) } /// Returns the feature with the given name or `None` if it does not exist. @@ -1152,7 +1009,7 @@ impl<'de> Deserialize<'de> for ProjectManifest { #[cfg(test)] mod tests { use super::*; - use crate::project::manifest::channel::PrioritizedChannel; + use crate::{project::manifest::channel::PrioritizedChannel, util::default_channel_config}; use insta::{assert_snapshot, assert_yaml_snapshot}; use rattler_conda_types::{Channel, ChannelConfig, ParseStrictness}; use rstest::*; @@ -1517,7 +1374,7 @@ mod tests { // Initially the dependency should exist assert!(manifest .feature_mut(feature_name) - .unwrap_or_else(|| panic!("feature `{}` should exist", feature_name.as_str())) + .unwrap() .targets .for_opt_target(platform.map(TargetSelector::Platform).as_ref()) .unwrap() @@ -1540,7 +1397,7 @@ mod tests { // The dependency should no longer exist assert!(manifest .feature_mut(feature_name) - .unwrap_or_else(|| panic!("feature `{}` should exist", feature_name.as_str())) + .unwrap() .targets .for_opt_target(platform.map(TargetSelector::Platform).as_ref()) .unwrap() @@ -1570,7 +1427,7 @@ mod tests { // Initially the dependency should exist assert!(manifest .feature_mut(feature_name) - .unwrap_or_else(|| panic!("feature `{}` should exist", feature_name.as_str())) + .unwrap() .targets .for_opt_target(platform.map(TargetSelector::Platform).as_ref()) .unwrap() @@ -1588,7 +1445,7 @@ mod tests { // The dependency should no longer exist assert!(manifest .feature_mut(feature_name) - .unwrap_or_else(|| panic!("feature `{}` should exist", feature_name.as_str())) + .unwrap() .targets .for_opt_target(platform.map(TargetSelector::Platform).as_ref()) .unwrap() @@ -1830,6 +1687,8 @@ feature_target_dep = "*" assert_eq!( manifest.parsed.project.platforms.value, vec![Platform::Linux64, Platform::Win64] + .into_iter() + .collect::>() ); manifest @@ -1839,6 +1698,8 @@ feature_target_dep = "*" assert_eq!( manifest.parsed.project.platforms.value, vec![Platform::Linux64, Platform::Win64, Platform::OsxArm64] + .into_iter() + .collect::>() ); manifest @@ -1857,6 +1718,8 @@ feature_target_dep = "*" .unwrap() .value, vec![Platform::LinuxAarch64, Platform::Osx64] + .into_iter() + .collect::>() ); manifest @@ -1875,6 +1738,8 @@ feature_target_dep = "*" .unwrap() .value, vec![Platform::LinuxAarch64, Platform::Osx64, Platform::Win64] + .into_iter() + .collect::>() ); } @@ -1901,15 +1766,17 @@ feature_target_dep = "*" assert_eq!( manifest.parsed.project.platforms.value, vec![Platform::Linux64, Platform::Win64] + .into_iter() + .collect::>() ); manifest - .remove_platforms(&vec![Platform::Linux64], &FeatureName::Default) + .remove_platforms(vec![Platform::Linux64], &FeatureName::Default) .unwrap(); assert_eq!( manifest.parsed.project.platforms.value, - vec![Platform::Win64] + vec![Platform::Win64].into_iter().collect::>() ); assert_eq!( @@ -1921,11 +1788,13 @@ feature_target_dep = "*" .unwrap() .value, vec![Platform::Linux64, Platform::Win64, Platform::Osx64] + .into_iter() + .collect::>() ); manifest .remove_platforms( - &vec![Platform::Linux64, Platform::Osx64], + vec![Platform::Linux64, Platform::Osx64], &FeatureName::Named("test".to_string()), ) .unwrap(); @@ -1938,7 +1807,7 @@ feature_target_dep = "*" .clone() .unwrap() .value, - vec![Platform::Win64] + vec![Platform::Win64].into_iter().collect::>() ); } @@ -1958,7 +1827,7 @@ platforms = ["linux-64", "win-64"] let mut manifest = Manifest::from_str(Path::new("pixi.toml"), file_contents).unwrap(); - assert_eq!(manifest.parsed.project.channels, vec![]); + assert_eq!(manifest.parsed.project.channels, IndexSet::new()); let conda_forge = PrioritizedChannel::from_channel( Channel::from_str("conda-forge", &channel_config()).unwrap(), @@ -1996,6 +1865,8 @@ platforms = ["linux-64", "win-64"] channel: Channel::from_str("conda-forge", &channel_config()).unwrap(), priority: None }] + .into_iter() + .collect::>() ); // Try to add again, should not add more channels @@ -2009,6 +1880,8 @@ platforms = ["linux-64", "win-64"] channel: Channel::from_str("conda-forge", &channel_config()).unwrap(), priority: None }] + .into_iter() + .collect::>() ); assert_eq!( @@ -2024,6 +1897,8 @@ platforms = ["linux-64", "win-64"] channel: Channel::from_str("nvidia", &channel_config()).unwrap(), priority: None }] + .into_iter() + .collect::>() ); // Try to add again, should not add more channels manifest @@ -2042,6 +1917,8 @@ platforms = ["linux-64", "win-64"] channel: Channel::from_str("nvidia", &channel_config()).unwrap(), priority: None }] + .into_iter() + .collect::>() ); assert_eq!( @@ -2063,6 +1940,8 @@ platforms = ["linux-64", "win-64"] priority: None } ] + .into_iter() + .collect::>() ); // Test custom channel urls @@ -2105,6 +1984,8 @@ platforms = ["linux-64", "win-64"] vec![PrioritizedChannel::from_channel( Channel::from_str("conda-forge", &channel_config()).unwrap() )] + .into_iter() + .collect::>() ); manifest @@ -2117,7 +1998,7 @@ platforms = ["linux-64", "win-64"] ) .unwrap(); - assert_eq!(manifest.parsed.project.channels, vec![]); + assert_eq!(manifest.parsed.project.channels, IndexSet::new()); manifest .remove_channels( @@ -2135,7 +2016,7 @@ platforms = ["linux-64", "win-64"] .channels .clone() .unwrap(); - assert_eq!(feature_channels, vec![]); + assert_eq!(feature_channels, IndexSet::new()); } #[test] diff --git a/src/project/manifest/python.rs b/src/project/manifest/python.rs index 2ad7b3f75..a8cfac2ed 100644 --- a/src/project/manifest/python.rs +++ b/src/project/manifest/python.rs @@ -59,7 +59,7 @@ impl PyPiPackageName { /// The pep crate does not support "*" as a version specifier, so we need to /// handle it ourselves. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum VersionOrStar { Version(VersionSpecifiers), Star, @@ -114,7 +114,7 @@ impl<'de> Deserialize<'de> for VersionOrStar { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)] #[serde(untagged, rename_all = "snake_case", deny_unknown_fields)] pub enum PyPiRequirement { Git { diff --git a/src/project/mod.rs b/src/project/mod.rs index f9d783536..c786f437b 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -447,7 +447,7 @@ impl Project { pub fn pypi_dependencies( &self, platform: Option, - ) -> IndexMap> { + ) -> IndexMap> { self.default_environment().pypi_dependencies(platform) } From 6bbd24df3d712a150821403db96bed27790298cf Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 3 May 2024 13:59:36 +0200 Subject: [PATCH 13/15] make `--platform` work and make warning appear only once (actually) --- src/cli/global/common.rs | 11 ++++------- src/cli/global/install.rs | 2 +- src/cli/global/upgrade.rs | 2 +- src/cli/global/upgrade_all.rs | 2 +- src/project/environment.rs | 10 +++++++--- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/cli/global/common.rs b/src/cli/global/common.rs index 01dc006f9..23bd5e1ab 100644 --- a/src/cli/global/common.rs +++ b/src/cli/global/common.rs @@ -177,19 +177,16 @@ pub fn load_package_records( /// The network client and the fetched sparse repodata pub(super) async fn get_client_and_sparse_repodata( channels: impl IntoIterator, + platform: Platform, config: &Config, ) -> miette::Result<( ClientWithMiddleware, IndexMap<(Channel, Platform), SparseRepoData>, )> { let authenticated_client = build_reqwest_clients(Some(config)).1; - let platform_sparse_repodata = repodata::fetch_sparse_repodata( - channels, - [Platform::current()], - &authenticated_client, - Some(config), - ) - .await?; + let platform_sparse_repodata = + repodata::fetch_sparse_repodata(channels, [platform], &authenticated_client, Some(config)) + .await?; Ok((authenticated_client, platform_sparse_repodata)) } diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index 40b9dfdd0..bd85951b8 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -257,7 +257,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fetch sparse repodata let (authenticated_client, sparse_repodata) = - get_client_and_sparse_repodata(&channels, &config).await?; + get_client_and_sparse_repodata(&channels, args.platform.clone(), &config).await?; // Install the package(s) let mut executables = vec![]; diff --git a/src/cli/global/upgrade.rs b/src/cli/global/upgrade.rs index 854cf778e..6c408704d 100644 --- a/src/cli/global/upgrade.rs +++ b/src/cli/global/upgrade.rs @@ -89,7 +89,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fetch sparse repodata let (authenticated_client, sparse_repodata) = - get_client_and_sparse_repodata(&channels, &config).await?; + get_client_and_sparse_repodata(&channels, args.platform.clone(), &config).await?; let records = load_package_records(package_matchspec, &sparse_repodata)?; let package_record = records diff --git a/src/cli/global/upgrade_all.rs b/src/cli/global/upgrade_all.rs index cab259b60..05514737b 100644 --- a/src/cli/global/upgrade_all.rs +++ b/src/cli/global/upgrade_all.rs @@ -66,7 +66,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fetch sparse repodata let (authenticated_client, sparse_repodata) = - get_client_and_sparse_repodata(&channels, &config).await?; + get_client_and_sparse_repodata(&channels, args.platform.clone(), &config).await?; let mut upgraded = false; for package_name in packages.iter() { diff --git a/src/project/environment.rs b/src/project/environment.rs index 5254256c0..37d59f201 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -13,6 +13,7 @@ use rattler_conda_types::{Arch, Platform}; use std::{ collections::{HashMap, HashSet}, fmt::Debug, + fs, hash::{Hash, Hasher}, sync::Once, }; @@ -110,17 +111,20 @@ impl<'p> Environment<'p> { return current; } - static INIT: Once = Once::new(); + static WARN_ONCE: Once = Once::new(); + // If the current platform is osx-arm64 and the environment supports osx-64, return osx-64. if current.is_osx() && self.platforms().contains(&Platform::Osx64) { - INIT.call_once(|| { + WARN_ONCE.call_once(|| { let emulation_warn = self.project.pixi_dir().join(consts::MACOS_EMULATION_WARN); if !emulation_warn.exists() { tracing::warn!( "osx-arm64 (Apple Silicon) is not supported by the pixi.toml, falling back to osx-64 (emulated with Rosetta)" ); // Create a file to prevent the warning from showing up multiple times. Also ignore the result. - std::fs::File::create(emulation_warn).ok(); + fs::create_dir_all(self.project.pixi_dir()).and_then(|_| { + std::fs::File::create(emulation_warn) + }).ok(); } }); return Platform::Osx64; From c7a88dc36adef7fb88fec65c30918903d1afab50 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 6 May 2024 10:14:11 +0200 Subject: [PATCH 14/15] stick messages into a one-time-message directory --- src/consts.rs | 3 ++- src/project/environment.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 438dca012..045517a18 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -12,7 +12,8 @@ pub const ENVIRONMENTS_DIR: &str = "envs"; pub const SOLVE_GROUP_ENVIRONMENTS_DIR: &str = "solve-group-envs"; pub const PYPI_DEPENDENCIES: &str = "pypi-dependencies"; pub const TASK_CACHE_DIR: &str = "task-cache-v0"; -pub const MACOS_EMULATION_WARN: &str = "macos-emulation-warn"; + +pub const ONE_TIME_MESSAGES_DIR: &str = "one-time-messages"; pub const DEFAULT_ENVIRONMENT_NAME: &str = "default"; diff --git a/src/project/environment.rs b/src/project/environment.rs index 37d59f201..cfceca09a 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -116,13 +116,14 @@ impl<'p> Environment<'p> { // If the current platform is osx-arm64 and the environment supports osx-64, return osx-64. if current.is_osx() && self.platforms().contains(&Platform::Osx64) { WARN_ONCE.call_once(|| { - let emulation_warn = self.project.pixi_dir().join(consts::MACOS_EMULATION_WARN); + let warn_folder = self.project.pixi_dir().join(consts::ONE_TIME_MESSAGES_DIR); + let emulation_warn = warn_folder.join("macos-emulation-warn"); if !emulation_warn.exists() { tracing::warn!( "osx-arm64 (Apple Silicon) is not supported by the pixi.toml, falling back to osx-64 (emulated with Rosetta)" ); // Create a file to prevent the warning from showing up multiple times. Also ignore the result. - fs::create_dir_all(self.project.pixi_dir()).and_then(|_| { + fs::create_dir_all(warn_folder).and_then(|_| { std::fs::File::create(emulation_warn) }).ok(); } From 364c3214e76d07991df55c076c183fc48baa7c64 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 6 May 2024 11:16:49 +0200 Subject: [PATCH 15/15] fix lint --- src/cli/global/install.rs | 2 +- src/cli/global/upgrade.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/global/install.rs b/src/cli/global/install.rs index f006483fe..799333c08 100644 --- a/src/cli/global/install.rs +++ b/src/cli/global/install.rs @@ -253,7 +253,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fetch sparse repodata let (authenticated_client, sparse_repodata) = - get_client_and_sparse_repodata(&channels, args.platform.clone(), &config).await?; + get_client_and_sparse_repodata(&channels, args.platform, &config).await?; // Install the package(s) let mut executables = vec![]; diff --git a/src/cli/global/upgrade.rs b/src/cli/global/upgrade.rs index 6f3307263..bdb7af451 100644 --- a/src/cli/global/upgrade.rs +++ b/src/cli/global/upgrade.rs @@ -84,7 +84,7 @@ pub(super) async fn upgrade_packages( // Fetch sparse repodata let (authenticated_client, sparse_repodata) = - get_client_and_sparse_repodata(&channels, platform.clone(), &config).await?; + get_client_and_sparse_repodata(&channels, *platform, &config).await?; // Upgrade each package when relevant let mut upgraded = false;