Skip to content
260 changes: 246 additions & 14 deletions crates/pixi_config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,45 @@ pub fn get_cache_dir() -> miette::Result<PathBuf> {
}
#[derive(Parser, Debug, Default, Clone)]
pub struct ConfigCli {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the liberty to order this alphabetically 😄

/// Do not verify the TLS certificate of the server.
#[arg(long, action = ArgAction::SetTrue, help_heading = consts::CLAP_CONFIG_OPTIONS)]
tls_no_verify: bool,

/// Path to the file containing the authentication token.
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
auth_file: Option<PathBuf>,

/// Max concurrent network requests, default is `50`
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
concurrent_downloads: Option<usize>,

/// Max concurrent solves, default is the number of CPUs
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
concurrent_solves: Option<usize>,

/// Set detached environments path
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
detached_environments: Option<PathBuf>,
Comment thread
zelosleone marked this conversation as resolved.
Outdated

/// Set pinning strategy
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS, value_enum)]
pinning_strategy: Option<PinningStrategy>,

/// Specifies whether to use the keyring to look up credentials for PyPI.
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
pypi_keyring_provider: Option<KeyringProvider>,

/// Max concurrent solves, default is the number of CPUs
/// Run post-link scripts (insecure)
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
pub concurrent_solves: Option<usize>,
run_post_link_scripts: bool,

/// Max concurrent network requests, default is `50`
/// The platform to use to install tools
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
tool_platform: Option<Platform>,
Comment thread
zelosleone marked this conversation as resolved.
Outdated

/// Do not verify the TLS certificate of the server.
#[arg(long, action = ArgAction::SetTrue, help_heading = consts::CLAP_CONFIG_OPTIONS)]
tls_no_verify: bool,

/// Use environment activation cache (experimental)
#[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)]
pub concurrent_downloads: Option<usize>,
use_environment_activation_cache: bool,
}

#[derive(Parser, Debug, Clone, Default)]
Expand Down Expand Up @@ -483,7 +503,7 @@ impl PyPIConfig {
}

/// The strategy for that will be used for pinning a version of a package.
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, Copy)]
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, Copy, clap::ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum PinningStrategy {
/// Default semver strategy e.g. "1.2.3" becomes ">=1.2.3, <2" but "0.1.0"
Expand Down Expand Up @@ -758,7 +778,7 @@ impl From<ConfigCli> for Config {
.pypi_keyring_provider
.map(|val| PyPIConfig::default().with_keyring(val))
.unwrap_or_default(),
detached_environments: None,
detached_environments: cli.detached_environments.map(DetachedEnvironments::Path),
concurrency: ConcurrencyConfig {
solves: cli
.concurrent_solves
Expand All @@ -767,6 +787,20 @@ impl From<ConfigCli> for Config {
.concurrent_downloads
.unwrap_or(ConcurrencyConfig::default().downloads),
},
tool_platform: cli.tool_platform,
run_post_link_scripts: if cli.run_post_link_scripts {
Some(RunPostLinkScripts::Insecure)
} else {
None
},
experimental: ExperimentalConfig {
use_environment_activation_cache: if cli.use_environment_activation_cache {
Some(true)
} else {
None
},
},
pinning_strategy: cli.pinning_strategy,
..Default::default()
}
}
Expand Down Expand Up @@ -1262,10 +1296,13 @@ impl Config {
pub fn get_keys(&self) -> &[&str] {
&[
"authentication-override-file",
"concurrency",
"concurrency.downloads",
"concurrency.solves",
"default-channels",
"detached-environments",
"experimental",
"experimental.use-environment-activation-cache",
"max-concurrent-solves",
"mirrors",
"pinning-strategy",
"proxy-config",
Expand All @@ -1282,6 +1319,7 @@ impl Config {
"repodata-config.disable-jlap",
"repodata-config.disable-sharded",
"repodata-config.disable-zstd",
"run-post-link-scripts",
"s3-options",
"s3-options.<bucket>",
"s3-options.<bucket>.endpoint-url",
Expand Down Expand Up @@ -1934,26 +1972,53 @@ UNUSED = "unused"

#[test]
fn test_config_from_cli() {
// Test with all CLI options enabled
let cli = ConfigCli {
tls_no_verify: true,
auth_file: None,
pypi_keyring_provider: Some(KeyringProvider::Subprocess),
concurrent_solves: None,
concurrent_downloads: None,
concurrent_solves: Some(8),
concurrent_downloads: Some(100),
tool_platform: Some(Platform::Linux64),
run_post_link_scripts: true,
use_environment_activation_cache: true,
detached_environments: Some(PathBuf::from("/custom/envs")),
pinning_strategy: Some(PinningStrategy::Semver),
};
let config = Config::from(cli);
assert_eq!(config.tls_no_verify, Some(true));
assert_eq!(
config.pypi_config().keyring_provider,
Some(KeyringProvider::Subprocess)
);
assert_eq!(config.concurrency.solves, 8);
assert_eq!(config.concurrency.downloads, 100);
assert_eq!(config.tool_platform, Some(Platform::Linux64));
assert_eq!(
config.run_post_link_scripts,
Some(RunPostLinkScripts::Insecure)
);
assert_eq!(
config.experimental.use_environment_activation_cache,
Some(true)
);
assert!(matches!(
config.detached_environments,
Some(DetachedEnvironments::Path(_))
));
assert_eq!(config.pinning_strategy, Some(PinningStrategy::Semver));

let cli = ConfigCli {
tls_no_verify: false,
auth_file: Some(PathBuf::from("path.json")),
pypi_keyring_provider: None,
concurrent_solves: None,
concurrent_downloads: None,
tool_platform: None,
run_post_link_scripts: false,
use_environment_activation_cache: false,
detached_environments: None,
pinning_strategy: None,
};

let config = Config::from(cli);
Expand All @@ -1962,7 +2027,11 @@ UNUSED = "unused"
config.authentication_override_file,
Some(PathBuf::from("path.json"))
);
assert!(!config.experimental.use_environment_activation_cache());
assert_eq!(config.tool_platform, None);
assert_eq!(config.run_post_link_scripts, None);
assert_eq!(config.experimental.use_environment_activation_cache, None);
assert_eq!(config.detached_environments, None);
assert_eq!(config.pinning_strategy, None);
}

#[test]
Expand Down Expand Up @@ -2404,6 +2473,169 @@ UNUSED = "unused"
assert!(s3_options.force_path_style);
assert_eq!(s3_options.region, "auto");

// Test tool-platform
config
.set("tool-platform", Some("linux-64".to_string()))
.unwrap();
assert_eq!(config.tool_platform, Some(Platform::Linux64));

// Test run-post-link-scripts
config
.set("run-post-link-scripts", Some("insecure".to_string()))
.unwrap();
assert_eq!(
config.run_post_link_scripts,
Some(RunPostLinkScripts::Insecure)
);

// Test shell.force-activate
config
.set("shell.force-activate", Some("true".to_string()))
.unwrap();
assert_eq!(config.shell.force_activate, Some(true));

// Test shell.source-completion-scripts
config
.set("shell.source-completion-scripts", Some("false".to_string()))
.unwrap();
assert_eq!(config.shell.source_completion_scripts, Some(false));

// Test experimental.use-environment-activation-cache
config
.set(
"experimental.use-environment-activation-cache",
Some("true".to_string()),
)
.unwrap();
assert_eq!(
config.experimental.use_environment_activation_cache,
Some(true)
);

// Test more repodata-config options
config
.set("repodata-config.disable-bzip2", Some("true".to_string()))
.unwrap();
let repodata_config = config.repodata_config();
assert_eq!(repodata_config.default.disable_bzip2, Some(true));

config
.set("repodata-config.disable-zstd", Some("false".to_string()))
.unwrap();
let repodata_config = config.repodata_config();
assert_eq!(repodata_config.default.disable_zstd, Some(false));

config
.set("repodata-config.disable-sharded", Some("true".to_string()))
.unwrap();
let repodata_config = config.repodata_config();
assert_eq!(repodata_config.default.disable_sharded, Some(true));

// Test pypi-config.allow-insecure-host
config
.set(
"pypi-config.allow-insecure-host",
Some(r#"["pypi.example.com"]"#.to_string()),
)
.unwrap();
assert_eq!(config.pypi_config().allow_insecure_host.len(), 1);

// Test proxy-config
config
.set(
"proxy-config.http",
Some("http://proxy.example.com:8080".to_string()),
)
.unwrap();
assert_eq!(
config.proxy_config.http,
Some(Url::parse("http://proxy.example.com:8080").unwrap())
);

config
.set(
"proxy-config.https",
Some("https://proxy.example.com:8080".to_string()),
)
.unwrap();
assert_eq!(
config.proxy_config.https,
Some(Url::parse("https://proxy.example.com:8080").unwrap())
);

config
.set(
"proxy-config.non-proxy-hosts",
Some(r#"["localhost", "127.0.0.1"]"#.to_string()),
)
.unwrap();
assert_eq!(config.proxy_config.non_proxy_hosts.len(), 2);

// Test s3-options with individual keys
config
.set(
"s3-options.test-bucket.endpoint-url",
Some("http://localhost:9000".to_string()),
)
.unwrap();
config
.set(
"s3-options.test-bucket.region",
Some("us-east-1".to_string()),
)
.unwrap();
config
.set(
"s3-options.test-bucket.force-path-style",
Some("false".to_string()),
)
.unwrap();

// Test concurrency configuration
config
.set("concurrency.solves", Some("5".to_string()))
.unwrap();
assert_eq!(config.concurrency.solves, 5);

config
.set("concurrency.downloads", Some("25".to_string()))
.unwrap();
assert_eq!(config.concurrency.downloads, 25);

// Test max-concurrent-solves (legacy accessor)
assert_eq!(config.max_concurrent_solves(), 5);
assert_eq!(config.max_concurrent_downloads(), 25);

// Test tls-no-verify
config
.set("tls-no-verify", Some("true".to_string()))
.unwrap();
assert_eq!(config.tls_no_verify, Some(true));

// Test mirrors
config
.set(
"mirrors",
Some(r#"{"https://conda.anaconda.org/conda-forge": ["https://prefix.dev/conda-forge"]}"#.to_string()),
)
.unwrap();
assert_eq!(config.mirrors.len(), 1);

// Test detached-environments
config
.set("detached-environments", Some("/custom/path".to_string()))
.unwrap();
assert!(matches!(
config.detached_environments,
Some(DetachedEnvironments::Path(_))
));

// Test pinning-strategy
config
.set("pinning-strategy", Some("semver".to_string()))
.unwrap();
assert_eq!(config.pinning_strategy, Some(PinningStrategy::Semver));

config.set("unknown-key", None).unwrap_err();
}

Expand Down
10 changes: 10 additions & 0 deletions docs/reference/cli/pixi/add.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ pixi add [OPTIONS] <SPEC>...
: Max concurrent solves, default is the number of CPUs
- <a id="arg---concurrent-downloads" href="#arg---concurrent-downloads">`--concurrent-downloads <CONCURRENT_DOWNLOADS>`</a>
: Max concurrent network requests, default is `50`
- <a id="arg---tool-platform" href="#arg---tool-platform">`--tool-platform <TOOL_PLATFORM>`</a>
: The platform to use to install tools
- <a id="arg---run-post-link-scripts" href="#arg---run-post-link-scripts">`--run-post-link-scripts`</a>
: Run post-link scripts (insecure)
- <a id="arg---use-environment-activation-cache" href="#arg---use-environment-activation-cache">`--use-environment-activation-cache`</a>
: Use environment activation cache (experimental)
- <a id="arg---detached-environments" href="#arg---detached-environments">`--detached-environments <DETACHED_ENVIRONMENTS>`</a>
: Set detached environments path
- <a id="arg---pinning-strategy" href="#arg---pinning-strategy">`--pinning-strategy <PINNING_STRATEGY>`</a>
: Set pinning strategy

## Git Options
- <a id="arg---git" href="#arg---git">`--git (-g) <GIT>`</a>
Expand Down
10 changes: 10 additions & 0 deletions docs/reference/cli/pixi/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ pixi build [OPTIONS]
: Max concurrent solves, default is the number of CPUs
- <a id="arg---concurrent-downloads" href="#arg---concurrent-downloads">`--concurrent-downloads <CONCURRENT_DOWNLOADS>`</a>
: Max concurrent network requests, default is `50`
- <a id="arg---tool-platform" href="#arg---tool-platform">`--tool-platform <TOOL_PLATFORM>`</a>
: The platform to use to install tools
- <a id="arg---run-post-link-scripts" href="#arg---run-post-link-scripts">`--run-post-link-scripts`</a>
: Run post-link scripts (insecure)
- <a id="arg---use-environment-activation-cache" href="#arg---use-environment-activation-cache">`--use-environment-activation-cache`</a>
: Use environment activation cache (experimental)
- <a id="arg---detached-environments" href="#arg---detached-environments">`--detached-environments <DETACHED_ENVIRONMENTS>`</a>
: Set detached environments path
- <a id="arg---pinning-strategy" href="#arg---pinning-strategy">`--pinning-strategy <PINNING_STRATEGY>`</a>
: Set pinning strategy

## Global Options
- <a id="arg---manifest-path" href="#arg---manifest-path">`--manifest-path <MANIFEST_PATH>`</a>
Expand Down
Loading
Loading