Skip to content
Draft

wip #5859

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/pixi/tests/integration_rust/build_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,12 @@ async fn test_source_dependency_inherits_exclude_newer_for_build_dependencies()
setup_tracing();

let mut package_database = MockRepoData::default();
package_database.add_package(
Package::build("in-memory", "0.1.0")
.with_timestamp("2024-01-10T00:00:00Z".parse().unwrap())
.with_materialize(true)
.finish(),
);
package_database.add_package(
Package::build("foo", "1")
.with_timestamp("2026-01-10T00:00:00Z".parse().unwrap())
Expand Down
32 changes: 32 additions & 0 deletions crates/pixi/tests/integration_rust/init_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,38 @@ async fn specific_channel() {
)
}

#[tokio::test]
async fn init_uses_global_exclude_newer_configuration() {
setup_tracing();

let pixi = PixiControl::new().unwrap();
fs_err::write(
pixi.workspace_path().join(".pixi/config.toml"),
r#"
default-channels = [
{ channel = "https://prefix.dev/internal", exclude-newer = "0d" },
"conda-forge",
]
exclude-newer = "7d"
"#,
)
.unwrap();

pixi.init().no_fast_prefix_overwrite(true).await.unwrap();

let manifest = pixi.manifest_contents().unwrap();
assert!(manifest.contains(r#"exclude-newer ="#));
assert!(manifest.contains(r#"{ channel = "https://prefix.dev/internal", exclude-newer ="#));

let workspace = pixi.workspace().unwrap();
let exclude_newer = workspace
.default_environment()
.exclude_newer_config_resolved()
.unwrap()
.unwrap();
assert!(exclude_newer.channel_cutoffs.contains_key("https://prefix.dev/internal"));
}

// Test the initialization from an existing pyproject.toml file without the pixi information
#[tokio::test]
async fn init_from_existing_pyproject_toml() {
Expand Down
49 changes: 38 additions & 11 deletions crates/pixi_api/src/workspace/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use std::{
str::FromStr,
};

use itertools::Itertools;
use miette::{Context, IntoDiagnostic};
use minijinja::{Environment, context};
use pixi_config::{Config, get_default_author, pixi_home};
use pixi_config::{Config, ConfigChannel, ExcludeNewer, get_default_author, pixi_home};
use pixi_consts::consts;
use pixi_core::{Workspace, workspace::WorkspaceMut};
use pixi_manifest::{FeatureName, pyproject::PyProjectManifest};
Expand Down Expand Up @@ -88,8 +89,9 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
name,
version,
author.as_ref(),
channels,
channels.into_iter().map(ConfigChannel::from).collect(),
&platforms,
config.exclude_newer(),
None,
&vec![],
config.s3_options,
Expand Down Expand Up @@ -118,11 +120,12 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
workspace
} else {
let channels = if let Some(channels) = options.channels {
channels
channels.into_iter().map(ConfigChannel::from).collect()
} else {
config.default_channels().to_vec()
config.default_channel_configurations()
};

let exclude_newer = config.exclude_newer();
let index_url = config.pypi_config.index_url;
let extra_index_urls = config.pypi_config.extra_index_urls;

Expand Down Expand Up @@ -165,10 +168,11 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
context! {
name,
pixi_name,
channels,
channels_toml => render_channels(&channels),
exclude_newer => exclude_newer.as_ref().map(ToString::to_string),
platforms,
environments,
s3 => relevant_s3_options(config.s3_options, channels),
s3 => relevant_s3_options(config.s3_options, channels.iter().map(|c| c.channel.clone()).collect()),
},
)
.expect("should be able to render the template");
Expand Down Expand Up @@ -231,11 +235,12 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
pypi_package_name,
version,
author,
channels,
channels_toml => render_channels(&channels),
exclude_newer => exclude_newer.as_ref().map(ToString::to_string),
platforms,
index_url => index_url.as_ref(),
extra_index_urls => &extra_index_urls,
s3 => relevant_s3_options(config.s3_options, channels),
s3 => relevant_s3_options(config.s3_options, channels.iter().map(|c| c.channel.clone()).collect()),
},
)
.expect("should be able to render the template");
Expand Down Expand Up @@ -287,6 +292,7 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
author.as_ref(),
channels,
&platforms,
exclude_newer,
index_url.as_ref(),
&extra_index_urls,
config.s3_options,
Expand Down Expand Up @@ -337,8 +343,9 @@ fn render_workspace(
name: String,
version: &str,
author: Option<&(String, String)>,
channels: Vec<NamedChannelOrUrl>,
channels: Vec<ConfigChannel>,
platforms: &Vec<String>,
exclude_newer: Option<ExcludeNewer>,
index_url: Option<&Url>,
extra_index_urls: &Vec<Url>,
s3_options: HashMap<String, pixi_config::S3Options>,
Expand All @@ -349,11 +356,15 @@ fn render_workspace(
name,
version,
author,
channels,
channels_toml => render_channels(&channels),
exclude_newer => exclude_newer.as_ref().map(ToString::to_string),
platforms,
index_url,
extra_index_urls,
s3 => relevant_s3_options(s3_options, channels),
s3 => relevant_s3_options(
s3_options,
channels.iter().map(|channel| channel.channel.clone()).collect(),
),
env_vars => {if let Some(env_vars) = env_vars {
env_vars.iter().map(|(k, v)| format!("{k} = \"{v}\"")).collect::<Vec<String>>().join(", ")
} else {String::new()}},
Expand All @@ -374,6 +385,22 @@ fn render_workspace(
.expect("should be able to render the template")
}

fn render_channels(channels: &[ConfigChannel]) -> String {
format!(
"[{}]",
channels
.iter()
.map(|channel| match &channel.exclude_newer {
Some(exclude_newer) => format!(
"{{ channel = \"{}\", exclude-newer = \"{}\" }}",
channel.channel, exclude_newer
),
None => format!("\"{}\"", channel.channel),
})
.join(", ")
)
}

fn relevant_s3_options(
s3_options: HashMap<String, pixi_config::S3Options>,
channels: Vec<NamedChannelOrUrl>,
Expand Down
15 changes: 12 additions & 3 deletions crates/pixi_api/src/workspace/init/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ pub const WORKSPACE_TEMPLATE: &str = r#"[workspace]
{%- if author %}
authors = ["{{ author[0] }} <{{ author[1] }}>"]
{%- endif %}
channels = {{ channels }}
channels = {{ channels_toml }}
{%- if exclude_newer %}
exclude-newer = "{{ exclude_newer }}"
{%- endif %}
name = "{{ name }}"
platforms = {{ platforms }}
version = "{{ version }}"
Expand Down Expand Up @@ -59,7 +62,10 @@ pub const PYROJECT_TEMPLATE_EXISTING: &str = r#"
{%- if pixi_name %}
name = "{{ name }}"
{%- endif %}
channels = {{ channels }}
channels = {{ channels_toml }}
{%- if exclude_newer %}
exclude-newer = "{{ exclude_newer }}"
{%- endif %}
platforms = {{ platforms }}

[tool.pixi.pypi-dependencies]
Expand Down Expand Up @@ -113,7 +119,10 @@ build-backend = "hatchling.build"
requires = ["hatchling"]

[tool.pixi.workspace]
channels = {{ channels }}
channels = {{ channels_toml }}
{%- if exclude_newer %}
exclude-newer = "{{ exclude_newer }}"
{%- endif %}
platforms = {{ platforms }}


Expand Down
22 changes: 12 additions & 10 deletions crates/pixi_cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use pixi_consts::consts::{
WORKSPACE_MANIFEST,
};
use pixi_core::{WorkspaceLocator, environment::sanity_check_workspace, workspace::DiscoveryStart};
use pixi_manifest::FeaturesExt;
use pixi_path::AbsPathBuf;
use pixi_progress::global_multi_progress;
use pixi_record::{PinnedPathSpec, PinnedSourceSpec};
Expand Down Expand Up @@ -266,11 +265,18 @@ pub async fn execute(args: Args) -> miette::Result<()> {
pathdiff::diff_paths(&package_manifest_path_canonical, workspace.root())
.unwrap_or_else(|| package_manifest_path_canonical.to_path_buf());

let channel_config = workspace.channel_config();
let channel_config = workspace.config().global_channel_config().clone();
let channels = workspace
.default_environment()
.channel_urls(&channel_config)
.config()
.default_channels()
.into_iter()
.map(|channel| channel.into_base_url(&channel_config))
.collect::<Result<Vec<_>, _>>()
.into_diagnostic()?;
let exclude_newer = workspace
.config()
.exclude_newer_cutoff()
.map(pixi_spec::ResolvedExcludeNewer::from_datetime);

let manifest_source: PinnedSourceSpec = PinnedPathSpec {
path: manifest_path_spec.to_string_lossy().into_owned().into(),
Expand All @@ -283,9 +289,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
preferred_build_source: None,
channels: channels.clone(),
channel_config: channel_config.clone(),
// When running `pixi build`, the exclude_newer config will be ignored.
// It will only be used when using the package as a source dependency.
exclude_newer: None,
exclude_newer: exclude_newer.clone(),
build_environment: build_environment.clone(),
variant_configuration: Some(variant_configuration.clone()),
variant_files: Some(variant_files.clone()),
Expand Down Expand Up @@ -316,9 +320,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
output_directory: None,
source: PinnedSourceCodeLocation::new(manifest_source.clone(), None),
channels: channels.clone(),
// When running `pixi build`, the exclude_newer config will be ignored.
// It will only be used when using the package as a source dependency.
exclude_newer: None,
exclude_newer: exclude_newer.clone(),
channel_config: channel_config.clone(),
build_environment: build_environment.clone(),
variant_configuration: Some(variant_configuration.clone()),
Expand Down
8 changes: 5 additions & 3 deletions crates/pixi_cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::cli_config::WorkspaceConfig;
use clap::Parser;
use miette::{IntoDiagnostic, WrapErr};
use pixi_config;
use pixi_config::Config;
use pixi_config::{Config, ConfigChannel};
use pixi_consts::consts;
use pixi_core::WorkspaceLocator;
use pixi_core::workspace::WorkspaceLocatorError;
Expand Down Expand Up @@ -289,9 +289,9 @@ fn alter_config(
.context("invalid channel name")?;
let mut new_channels = config.default_channels.clone();
if is_prepend {
new_channels.insert(0, channel);
new_channels.insert(0, ConfigChannel::from(channel));
} else {
new_channels.push(channel);
new_channels.push(ConfigChannel::from(channel));
}
config.default_channels = new_channels;
}
Expand Down Expand Up @@ -331,6 +331,7 @@ fn partial_config(config: &mut Config, key: &str) -> miette::Result<()> {

match key {
"default-channels" => new.default_channels = config.default_channels.clone(),
"exclude-newer" => new.exclude_newer = config.exclude_newer.clone(),
"shell" => new.shell = config.shell.clone(),
"tls-no-verify" => new.tls_no_verify = config.tls_no_verify,
"authentication-override-file" => {
Expand All @@ -343,6 +344,7 @@ fn partial_config(config: &mut Config, key: &str) -> miette::Result<()> {
_ => {
let keys = [
"default-channels",
"exclude-newer",
"tls-no-verify",
"authentication-override-file",
"mirrors",
Expand Down
11 changes: 10 additions & 1 deletion crates/pixi_cli/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,13 @@ pub async fn create_exec_prefix(
.collect();

let environment_hash =
EnvironmentHash::new(command.clone(), specs.clone(), channels, args.platform);
EnvironmentHash::new(
command.clone(),
specs.clone(),
channels,
args.platform,
config.exclude_newer_cutoff().map(|cutoff| cutoff.to_rfc3339()),
);

let prefix = Prefix::new(
cache_dir
Expand Down Expand Up @@ -252,6 +258,9 @@ pub async fn create_exec_prefix(
Solver.solve(SolverTask {
specs: specs.clone(),
virtual_packages: virtual_packages.clone(),
exclude_newer: config
.exclude_newer_cutoff()
.map(rattler_solve::ExcludeNewer::from_datetime),
..SolverTask::from_iter(&repodata.clone())
})
});
Expand Down
Loading
Loading