diff --git a/Cargo.lock b/Cargo.lock index f1e652f78..19fb72e56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4785,9 +4785,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.46.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67961cc1ccb170208f10169542752dbe730353a874a13cb0749c13b960d72d5b" +checksum = "3e3120ba76d85c4750f21d2ee72245a358eed7a66c076f3a67c72383595e46ba" dependencies = [ "anyhow", "astral-reqwest-middleware", @@ -5262,9 +5262,9 @@ dependencies = [ [[package]] name = "rattler_cache" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24205a6af611e71f346e5e170c32f00ce1a9b49077f699f18d8404e711dc2209" +checksum = "d2be7994388cfd959c35e7e31eeb0a206b0b389f426511f08d004bf7ba103897" dependencies = [ "ahash", "anyhow", @@ -5296,9 +5296,9 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.47.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385575f3cfb4b9ce40c13d90b37964c80d8b527b62514118e4783ae0717b3ebd" +checksum = "017a8ae50486c176ed5dcd2b2118883e1eb89abcacb6fdb434b6f5cb5aa2fdf7" dependencies = [ "ahash", "core-foundation 0.10.1", @@ -5339,15 +5339,16 @@ dependencies = [ [[package]] name = "rattler_config" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afcf9763975148e4cc805653de9146ff3542535895e37cdb1a06b2dd3d681af" +checksum = "59903c0728da7418179f5d5e5516339ea279acee06247319dc6da234f6c43dd7" dependencies = [ - "console", + "dirs", "fs-err", "indexmap 2.14.0", "rattler_conda_types", "serde", + "serde_ignored", "serde_json", "thiserror 2.0.18", "toml", @@ -5398,9 +5399,9 @@ dependencies = [ [[package]] name = "rattler_index" -version = "0.30.7" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d078770093f35bfdd275aa0c34689ef989b76dcd37d4e3b31a668e98852e61" +checksum = "11cc76cfe51931dcbade151e2e2643fd465b41e259248c1beb9cc97ee8417b74" dependencies = [ "ahash", "anyhow", @@ -5448,9 +5449,9 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957dbd0e8fb9ed66dce0bd6922914fb8bf795a4334d92e2190f34314a0b1f170" +checksum = "b832bfe293a9f7912e9ecb5fefdfaf0e5858420a238b8efe10533982a3b7bb75" dependencies = [ "configparser", "dirs", @@ -5479,9 +5480,9 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5014e4f5aedcbe01a83a5be0e8669b305adc82cc10985fc83f51c5fe07e0d198" +checksum = "5a976a1ab13c2a706bd09ae5376358b2cc062d815a06089a8127ce59fbdce0c9" dependencies = [ "ambient-id", "anyhow", @@ -5504,6 +5505,7 @@ dependencies = [ "keyring-core", "netrc-rs", "rattler_config", + "regex", "reqwest", "retry-policies", "serde", @@ -5517,9 +5519,9 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4440ad7368a66ddd370a8160d8fb2a4d0a825b7a050f9c421dac350b37bed2" +checksum = "66c4eab28b2bb0132d281612f23192d2335356b20ea538fa40dfd0dd5ddc7c65" dependencies = [ "astral-reqwest-middleware", "astral-tokio-tar", @@ -5595,9 +5597,9 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.29.7" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd05b4c6fbad5fae62d1d67c22c2bb411a7d21a026aeb079893df1d6d157a61" +checksum = "5660f0974ffeef30389d01b5b8a390ca064dad75999d625305a8b07db3d21cbb" dependencies = [ "ahash", "anyhow", @@ -5658,9 +5660,9 @@ dependencies = [ [[package]] name = "rattler_s3" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2a55757ecde8f53a17bc176d13061dda08485e3d1197dd7d9338e04ec7a3ca" +checksum = "aaf41b431123d3c1a79c6802152d23fb896213691e058a1d636d822c9613af2f" dependencies = [ "aws-config", "aws-credential-types", @@ -5675,9 +5677,9 @@ dependencies = [ [[package]] name = "rattler_shell" -version = "0.27.8" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9794276d154c19579fe7982ab2c1e9a960f0d51e5e56cd8d9fafe36373a71dd3" +checksum = "c10583e7532955b7cd4579ddd457e91b648bf4a1232c4192384c61a08b8ff03c" dependencies = [ "anyhow", "enum_dispatch", @@ -5697,9 +5699,9 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9bbdbf299cc4f9b363bd54a4e315be7e96c7645f0a75df5853405d0189ead6" +checksum = "fb5972bed20c404d943da0ed351bbd97cd91b01086620bc68563e9dfe173c198" dependencies = [ "futures", "hex", @@ -5716,9 +5718,9 @@ dependencies = [ [[package]] name = "rattler_upload" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1e75ffc1d23661ac5ad6b25836f6918fe46dfe065c013123d08e97f63e6f57" +checksum = "c0aec8ed35b75444dca1fbea81b51d5fb437552918e4bd5c85cbb98c05ca5a11" dependencies = [ "astral-reqwest-middleware", "astral-reqwest-retry", @@ -5755,9 +5757,9 @@ dependencies = [ [[package]] name = "rattler_virtual_packages" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312c7e886ca90e83d562cadfb3d88af9dcef42f7c2489dae569df84c4bd07890" +checksum = "c8ff8fc437511c1ade556cc9753c99f64d4d6097fa82e6ab3008ca935abd8a48" dependencies = [ "archspec", "libloading", @@ -6505,6 +6507,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" diff --git a/Cargo.toml b/Cargo.toml index 8b278ae1c..4e031c1e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,39 +133,38 @@ tracing-subscriber = { version = "0.3", features = [ ] } tracing-test = "0.2.6" -# rattler crates # Rattler crates -rattler_conda_types = { version = "0.47.2", default-features = false } +rattler_conda_types = { version = "0.48", default-features = false } rattler_digest = { version = "1.3.1", default-features = false, features = [ "serde", ] } -rattler_networking = { version = "0.30.0", default-features = false } -rattler_package_streaming = { version = "0.26.5", default-features = false } -rattler_shell = { version = "0.27.7", default-features = false, features = [ +rattler_networking = { version = "0.30", default-features = false } +rattler_package_streaming = { version = "0.26", default-features = false } +rattler_shell = { version = "0.27", default-features = false, features = [ "sysinfo", ] } rattler_git = { version = "0.1.3" } rattler_prefix_guard = { version = "0.1.1" } -rattler_config = { version = "0.5.2" } -rattler = { version = "0.46.0", default-features = false, features = [ +rattler_config = { version = "0.6" } +rattler = { version = "0.47", default-features = false, features = [ "cli-tools", "indicatif", ] } -rattler_cache = { version = "0.10.0", default-features = false } -rattler_index = { version = "0.30.5", default-features = false } -rattler_upload = { version = "0.8.0", default-features = false } +rattler_cache = { version = "0.10", default-features = false } +rattler_index = { version = "0.30", default-features = false } +rattler_upload = { version = "0.8", default-features = false } rattler_s3 = { version = "0.2.5" } rattler_redaction = { version = "0.2.1", default-features = false } -rattler_repodata_gateway = { version = "0.29.6", default-features = false, features = [ +rattler_repodata_gateway = { version = "0.30", default-features = false, features = [ "gateway", ] } -rattler_solve = { version = "7.1.4", default-features = false, features = [ +rattler_solve = { version = "7.2", default-features = false, features = [ "resolvo", "serde", ] } -rattler_virtual_packages = { version = "3.0.2", default-features = false } -rattler_menuinst = "0.2.67" +rattler_virtual_packages = { version = "3.0", default-features = false } +rattler_menuinst = "0.2" # Internal rattler-build crates diff --git a/crates/rattler_build_core/src/build.rs b/crates/rattler_build_core/src/build.rs index 035f89729..1cf1e5b86 100644 --- a/crates/rattler_build_core/src/build.rs +++ b/crates/rattler_build_core/src/build.rs @@ -57,7 +57,8 @@ pub async fn skip_existing( let channels = if only_local { vec![ - Channel::from_directory(&first_output.build_configuration.directories.output_dir) + Channel::try_from_directory(&first_output.build_configuration.directories.output_dir) + .expect("could not create channel from directory") .base_url, ] } else { diff --git a/crates/rattler_build_core/src/package_test/run_test.rs b/crates/rattler_build_core/src/package_test/run_test.rs index 010189b89..a47a22bc0 100644 --- a/crates/rattler_build_core/src/package_test/run_test.rs +++ b/crates/rattler_build_core/src/package_test/run_test.rs @@ -507,7 +507,12 @@ pub async fn run_test( let package_folder = cache_metadata.path().to_path_buf(); let mut channels = config.channels.clone(); - channels.insert(0, Channel::from_directory(tmp_repo.path()).base_url); + channels.insert( + 0, + Channel::try_from_directory(tmp_repo.path()) + .expect("could not create channel from directory") + .base_url, + ); let host_platform = config.host_platform.clone().unwrap_or_else(|| { if target_platform == Platform::NoArch { diff --git a/crates/rattler_build_core/src/publish.rs b/crates/rattler_build_core/src/publish.rs index 10188666b..9a128fb4b 100644 --- a/crates/rattler_build_core/src/publish.rs +++ b/crates/rattler_build_core/src/publish.rs @@ -144,6 +144,11 @@ pub async fn fetch_highest_build_numbers( match result { Ok(repo_data) => { + // Surface any non-fatal warnings gathered during the query + // instead of dropping them. + for warning in &repo_data.warnings { + tracing::warn!("{warning}"); + } for repo in repo_data { for record in repo.iter() { let name = &record.package_record.name; diff --git a/crates/rattler_build_core/src/render/solver.rs b/crates/rattler_build_core/src/render/solver.rs index 115144717..2dd5a3ac9 100644 --- a/crates/rattler_build_core/src/render/solver.rs +++ b/crates/rattler_build_core/src/render/solver.rs @@ -208,7 +208,15 @@ pub async fn load_repodatas( .clear() .unwrap(); - Ok(result) + // `query` returns a `RepoDataQueryOutput`: repodata records plus any + // non-fatal warnings gathered during the query (e.g. CEP-42 channel + // relations). Surface the warnings to the user instead of dropping them, + // then return the records. + for warning in &result.warnings { + tracing::warn!("{warning}"); + } + + Ok(result.repodata) } pub async fn install_packages( diff --git a/crates/rattler_build_core/src/types/mod.rs b/crates/rattler_build_core/src/types/mod.rs index 316d65ffe..df73f103b 100644 --- a/crates/rattler_build_core/src/types/mod.rs +++ b/crates/rattler_build_core/src/types/mod.rs @@ -161,7 +161,8 @@ pub async fn build_reindexed_channels( return Ok(build_configuration.channels.clone()); } - let output_channel = Channel::from_directory(output_dir); + let output_channel = + Channel::try_from_directory(output_dir).expect("could not create channel from directory"); // Clear the repodata gateway of any cached values for the output channel. // Clear all subdirs so that packages from other platforms (e.g. a linux-64 diff --git a/crates/rattler_build_playground/Cargo.toml b/crates/rattler_build_playground/Cargo.toml index 22e9d7cf7..6a8750c4d 100644 --- a/crates/rattler_build_playground/Cargo.toml +++ b/crates/rattler_build_playground/Cargo.toml @@ -26,7 +26,7 @@ rattler_build_jinja = { path = "../rattler_build_jinja" } rattler_build_types = { path = "../rattler_build_types" } rattler_build_yaml_parser = { path = "../rattler_build_yaml_parser" } indexmap = "2" -rattler_conda_types = { version = "0.47.2", default-features = false } +rattler_conda_types = { version = "0.48", default-features = false } serde_yaml = "0.9" arborium = { version = "2", default-features = false, features = ["lang-yaml"] } diff --git a/docs/config.md b/docs/config.md index c37700a39..1acebe76d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,7 +1,33 @@ # Rattler-Build configuration -Rattler-Build can be configured by specifying `--config-file ~/.pixi/config.toml`. -The config file is of the same format as pixi's [global configuration file](https://pixi.sh/latest/reference/pixi_configuration/). +Rattler-Build shares its configuration format with pixi: the config file is of the same format as pixi's [global configuration file](https://pixi.sh/latest/reference/pixi_configuration/). + +By default (when no `--config-file` is passed), Rattler-Build automatically loads and merges configuration from the standard locations shared by all rattler based tools. Discovery is provided by `rattler_config`'s common `locations` helper, so the search order is identical to the one pixi and other tools use. The locations, in ascending order of precedence (values from later files override values from earlier files): + +1. The system-wide configuration of each tool: `/etc/pixi/config.toml` followed by `/etc/rattler-build/config.toml` (on Windows: `C:\ProgramData\\config.toml`) +2. The per-user configuration of each tool: the platform config directory (`$XDG_CONFIG_HOME//config.toml`, e.g. `~/.config/pixi/config.toml`) and the tool home (`$PIXI_HOME` / `$RATTLER_BUILD_HOME`, defaulting to `~/.pixi/config.toml` / `~/.rattler-build/config.toml`), for `pixi` first and then `rattler-build` + +In other words all system-wide files are read before any per-user file, and within each group pixi's files are overridden by Rattler-Build's own files. This means that settings such as default channels, mirrors, or S3 options that you have configured for pixi are picked up by Rattler-Build automatically, and can be overridden in Rattler-Build's own configuration files. + +Alternatively, a single configuration file can be specified explicitly with `--config-file` (e.g. `--config-file ~/.pixi/config.toml`), which disables the automatic discovery and loads only that file. To disable configuration entirely — so that only built-in defaults and command-line arguments apply — pass `--no-config` (mutually exclusive with `--config-file`). + +!!! note "Behavior change" + Earlier versions of Rattler-Build only loaded configuration when `--config-file` was passed. Automatic discovery of the locations above is new: if you already have a pixi configuration, Rattler-Build now picks it up by default. Pass `--no-config` to restore the old behavior and skip all configuration loading, or `--config-file` to load only a specific file. + +## Seeing which configuration was loaded + +On startup Rattler-Build logs its version and the configuration files it actually loaded, so you can always trace where a setting came from: + +``` +rattler-build 0.68.0 +Loaded configuration from: /etc/pixi/config.toml, /home/user/.pixi/config.toml +``` + +If no configuration file was found — either because none of the default locations exist, or because `--config-file` was not given — it logs `No configuration file loaded` instead. These lines appear at the default log level; run with `-v` to additionally see the full list of candidate paths that were considered (useful when a file you expected is not picked up). + +## Programmatic use + +Automatic discovery happens **only when you run the `rattler-build` command-line tool**. When Rattler-Build is used as a library (for example from [pixi](https://pixi.sh), or through the Python bindings), no configuration is loaded implicitly — the embedding application constructs the configuration and passes it in. This guarantees that using Rattler-Build programmatically never silently reads your global pixi or Rattler-Build configuration from disk. ## Channels diff --git a/docs/reference/cli/rattler-build.md b/docs/reference/cli/rattler-build.md index 19a2bb5e7..31b986dd6 100644 --- a/docs/reference/cli/rattler-build.md +++ b/docs/reference/cli/rattler-build.md @@ -41,7 +41,9 @@ rattler-build [OPTIONS] [COMMAND]
**env**: `RATTLER_BUILD_WRAP_LOG_LINES`
**options**: `true`, `false` - `--config-file ` -: The Rattler-Build configuration file to use +: The Rattler-Build configuration file to use. If not set, configuration is loaded from the default locations (the system-wide and global pixi configuration files as well as rattler-build's own configuration files) +- `--no-config` +: Do not load any configuration. Disables the automatic discovery of pixi/rattler-build configuration files so that only built-in defaults and command-line arguments are used (the behavior of rattler-build versions before default config discovery was added). Mutually exclusive with `--config-file` - `--color ` : Enable or disable colored output from Rattler-Build. Also honors the `CLICOLOR` and `CLICOLOR_FORCE` environment variable
**env**: `RATTLER_BUILD_COLOR` diff --git a/docs/reference/cli/rattler-build/auth/status.md b/docs/reference/cli/rattler-build/auth/status.md index 2eab6c926..711b9821f 100644 --- a/docs/reference/cli/rattler-build/auth/status.md +++ b/docs/reference/cli/rattler-build/auth/status.md @@ -12,5 +12,5 @@ rattler-build auth status [OPTIONS] ``` ## Options -- `--verbose (-v)` +- `--details` : Show endpoint URLs, client ID, and other IdP-introspection fields that are only useful for debugging diff --git a/py-rattler-build/rust/Cargo.lock b/py-rattler-build/rust/Cargo.lock index 728d403ae..9bdcf4faf 100644 --- a/py-rattler-build/rust/Cargo.lock +++ b/py-rattler-build/rust/Cargo.lock @@ -4612,9 +4612,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.46.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67961cc1ccb170208f10169542752dbe730353a874a13cb0749c13b960d72d5b" +checksum = "3e3120ba76d85c4750f21d2ee72245a358eed7a66c076f3a67c72383595e46ba" dependencies = [ "anyhow", "astral-reqwest-middleware", @@ -5035,9 +5035,9 @@ dependencies = [ [[package]] name = "rattler_cache" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24205a6af611e71f346e5e170c32f00ce1a9b49077f699f18d8404e711dc2209" +checksum = "d2be7994388cfd959c35e7e31eeb0a206b0b389f426511f08d004bf7ba103897" dependencies = [ "ahash", "anyhow", @@ -5069,9 +5069,9 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.47.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385575f3cfb4b9ce40c13d90b37964c80d8b527b62514118e4783ae0717b3ebd" +checksum = "017a8ae50486c176ed5dcd2b2118883e1eb89abcacb6fdb434b6f5cb5aa2fdf7" dependencies = [ "ahash", "core-foundation 0.10.1", @@ -5112,15 +5112,16 @@ dependencies = [ [[package]] name = "rattler_config" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afcf9763975148e4cc805653de9146ff3542535895e37cdb1a06b2dd3d681af" +checksum = "59903c0728da7418179f5d5e5516339ea279acee06247319dc6da234f6c43dd7" dependencies = [ - "console", + "dirs", "fs-err", "indexmap 2.14.0", "rattler_conda_types", "serde", + "serde_ignored", "serde_json", "thiserror 2.0.18", "toml", @@ -5171,9 +5172,9 @@ dependencies = [ [[package]] name = "rattler_index" -version = "0.30.7" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d078770093f35bfdd275aa0c34689ef989b76dcd37d4e3b31a668e98852e61" +checksum = "11cc76cfe51931dcbade151e2e2643fd465b41e259248c1beb9cc97ee8417b74" dependencies = [ "ahash", "anyhow", @@ -5221,9 +5222,9 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957dbd0e8fb9ed66dce0bd6922914fb8bf795a4334d92e2190f34314a0b1f170" +checksum = "b832bfe293a9f7912e9ecb5fefdfaf0e5858420a238b8efe10533982a3b7bb75" dependencies = [ "configparser", "dirs", @@ -5252,9 +5253,9 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5014e4f5aedcbe01a83a5be0e8669b305adc82cc10985fc83f51c5fe07e0d198" +checksum = "5a976a1ab13c2a706bd09ae5376358b2cc062d815a06089a8127ce59fbdce0c9" dependencies = [ "ambient-id", "anyhow", @@ -5277,6 +5278,7 @@ dependencies = [ "keyring-core", "netrc-rs", "rattler_config", + "regex", "reqwest", "retry-policies", "serde", @@ -5290,9 +5292,9 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4440ad7368a66ddd370a8160d8fb2a4d0a825b7a050f9c421dac350b37bed2" +checksum = "66c4eab28b2bb0132d281612f23192d2335356b20ea538fa40dfd0dd5ddc7c65" dependencies = [ "astral-reqwest-middleware", "astral-tokio-tar", @@ -5368,9 +5370,9 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.29.7" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd05b4c6fbad5fae62d1d67c22c2bb411a7d21a026aeb079893df1d6d157a61" +checksum = "5660f0974ffeef30389d01b5b8a390ca064dad75999d625305a8b07db3d21cbb" dependencies = [ "ahash", "anyhow", @@ -5431,9 +5433,9 @@ dependencies = [ [[package]] name = "rattler_s3" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2a55757ecde8f53a17bc176d13061dda08485e3d1197dd7d9338e04ec7a3ca" +checksum = "aaf41b431123d3c1a79c6802152d23fb896213691e058a1d636d822c9613af2f" dependencies = [ "aws-config", "aws-credential-types", @@ -5448,9 +5450,9 @@ dependencies = [ [[package]] name = "rattler_shell" -version = "0.27.8" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9794276d154c19579fe7982ab2c1e9a960f0d51e5e56cd8d9fafe36373a71dd3" +checksum = "c10583e7532955b7cd4579ddd457e91b648bf4a1232c4192384c61a08b8ff03c" dependencies = [ "anyhow", "enum_dispatch", @@ -5470,9 +5472,9 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9bbdbf299cc4f9b363bd54a4e315be7e96c7645f0a75df5853405d0189ead6" +checksum = "fb5972bed20c404d943da0ed351bbd97cd91b01086620bc68563e9dfe173c198" dependencies = [ "futures", "hex", @@ -5489,9 +5491,9 @@ dependencies = [ [[package]] name = "rattler_upload" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1e75ffc1d23661ac5ad6b25836f6918fe46dfe065c013123d08e97f63e6f57" +checksum = "c0aec8ed35b75444dca1fbea81b51d5fb437552918e4bd5c85cbb98c05ca5a11" dependencies = [ "astral-reqwest-middleware", "astral-reqwest-retry", @@ -5528,9 +5530,9 @@ dependencies = [ [[package]] name = "rattler_virtual_packages" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312c7e886ca90e83d562cadfb3d88af9dcef42f7c2489dae569df84c4bd07890" +checksum = "c8ff8fc437511c1ade556cc9753c99f64d4d6097fa82e6ab3008ca935abd8a48" dependencies = [ "archspec", "libloading", @@ -6218,6 +6220,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" diff --git a/py-rattler-build/rust/Cargo.toml b/py-rattler-build/rust/Cargo.toml index 341735131..d7cb983f6 100644 --- a/py-rattler-build/rust/Cargo.toml +++ b/py-rattler-build/rust/Cargo.toml @@ -35,17 +35,17 @@ tokio = { version = "1.50.0", features = [ "rt-multi-thread", "process", ] } -rattler_conda_types = "0.47.2" -rattler_config = "0.5.2" -rattler_networking = { version = "0.30.0" } -rattler_solve = "7.1.4" -rattler_virtual_packages = "3.0.2" +rattler_conda_types = "0.48" +rattler_config = "0.6" +rattler_networking = { version = "0.30.1" } +rattler_solve = "7.2" +rattler_virtual_packages = "3.0.3" clap = "4.6.0" url = "2.5.8" jiff = "0.2.27" hex = "0.4.3" -rattler_upload = "0.8.2" -rattler_package_streaming = { version = "0.26.6", default-features = false } +rattler_upload = "0.8.3" +rattler_package_streaming = { version = "0.26.7", default-features = false } miette = "7.6.0" tempfile = "3.27.0" serde_yaml = "0.9" diff --git a/py-rattler-build/rust/src/cli_api.rs b/py-rattler-build/rust/src/cli_api.rs index 6790979b0..63bf31edc 100644 --- a/py-rattler-build/rust/src/cli_api.rs +++ b/py-rattler-build/rust/src/cli_api.rs @@ -4,6 +4,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use ::rattler_build::config::Config; use ::rattler_build::{ build_recipes, opt::{BuildData, ChannelPriorityWrapper, CommonData, TestData}, @@ -14,7 +15,7 @@ use clap::ValueEnum; use pyo3::prelude::*; use rattler_build_script::EnvironmentIsolation; use rattler_conda_types::{NamedChannelOrUrl, Platform}; -use rattler_config::config::{ConfigBase, build::PackageFormatAndCompression}; +use rattler_config::config::build::PackageFormatAndCompression; use crate::error::RattlerBuildError; use crate::repodata_revision::PyRepodataRevision; @@ -64,7 +65,7 @@ pub fn build_recipes_py( .map(|c| ChannelPriorityWrapper::from_str(&c).map(|c| c.value)) .transpose() .map_err(|e| RattlerBuildError::ChannelPriority(e.to_string()))?; - let config = ConfigBase::<()>::default(); + let config = Config::default(); let v3 = matches!( repodata_revision.unwrap_or_default(), PyRepodataRevision::V3 @@ -176,7 +177,7 @@ pub fn test_package_py( .map(|c| ChannelPriorityWrapper::from_str(&c).map(|c| c.value)) .transpose() .map_err(|e| RattlerBuildError::ChannelPriority(e.to_string()))?; - let config = ConfigBase::<()>::default(); + let config = Config::default(); let common = CommonData::new( None, false, diff --git a/py-rattler-build/rust/src/package.rs b/py-rattler-build/rust/src/package.rs index a4f350c73..c7e3f2f18 100644 --- a/py-rattler-build/rust/src/package.rs +++ b/py-rattler-build/rust/src/package.rs @@ -24,6 +24,7 @@ use rattler_conda_types::{ use rattler_package_streaming::seek::read_package_file; // Imports for rebuild functionality +use ::rattler_build::config::Config; use ::rattler_build::{ console_utils::LoggingOutputHandler, opt::{CommonData, PackageSource, RebuildData}, @@ -31,7 +32,6 @@ use ::rattler_build::{ tool_configuration::TestStrategy, }; use clap::ValueEnum; -use rattler_config::config::ConfigBase; /// A loaded conda package for inspection and testing. #[pyclass(name = "Package")] @@ -349,7 +349,7 @@ impl PyPackage { .unwrap_or_default(); // Create common data - let config = ConfigBase::<()>::default(); + let config = Config::default(); let common = CommonData::new( output_dir, false, @@ -494,17 +494,17 @@ impl PyPackage { progress_callback: Option>, ) -> PyResult> { use ::rattler_build::{ + config::Config, opt::{ChannelPriorityWrapper, CommonData, TestData}, run_test, }; - use rattler_config::config::ConfigBase; let channel_priority = channel_priority .map(|c| ChannelPriorityWrapper::from_str(&c).map(|c| c.value)) .transpose() .map_err(|e| RattlerBuildError::ChannelPriority(e.to_string()))?; - let config = ConfigBase::<()>::default(); + let config = Config::default(); let common = CommonData::new( None, false, diff --git a/py-rattler-build/rust/src/tool_config.rs b/py-rattler-build/rust/src/tool_config.rs index 7d993eb80..7ba40f165 100644 --- a/py-rattler-build/rust/src/tool_config.rs +++ b/py-rattler-build/rust/src/tool_config.rs @@ -87,7 +87,7 @@ impl PyToolConfiguration { }) .transpose()?; - let config = rattler_config::config::ConfigBase::<()>::default(); + let config = rattler_build::config::Config::default(); let mut builder = Configuration::builder() .with_keep_build(keep_build) @@ -100,7 +100,7 @@ impl PyToolConfiguration { .with_zstd_repodata_enabled(use_zstd) .with_bz2_repodata_enabled(use_bz2) .with_sharded_repodata_enabled(use_sharded) - .with_channel_config(config.channel_config); + .with_channel_config(config.common.channel_config); if let Some(threads) = compression_threads { builder = builder.with_compression_threads(Some(threads)); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..4e520fddf --- /dev/null +++ b/src/config.rs @@ -0,0 +1,191 @@ +//! Configuration for `rattler-build`. +//! +//! `rattler-build` shares its configuration format with +//! [pixi](https://pixi.sh) and the other rattler based tools: the common keys +//! (default channels, mirrors, S3 options, …) come from +//! [`rattler_config::config::ConfigBase`], while everything that only makes +//! sense for `rattler-build` lives in the [`RattlerBuildConfig`] extension. +//! +//! ## When configuration is loaded +//! +//! Configuration is discovered and loaded **only by the command-line +//! interface** ([`load_default_config`]), and only when no explicit +//! `--config-file` is given. On startup the CLI logs its version and the +//! files it loaded, so config resolution is easy to trace. +//! +//! Programmatic/library consumers of `rattler-build` — including pixi via +//! `rattler_build_core`, and the Python bindings — never load configuration +//! implicitly. They must construct a [`Config`] themselves (e.g. +//! [`Config::default`] or [`ConfigBase::load_from_files`]) and pass it in. +//! This keeps library use free of surprising reads of the user's global +//! pixi/rattler-build configuration; the embedding application stays in full +//! control of where configuration comes from. + +use std::path::PathBuf; + +use rattler_config::config::{ConfigBase, LoadError, MergeError}; + +/// rattler-build specific configuration keys. +/// Extend this struct to add configuration that only makes sense for +/// rattler-build. +#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct RattlerBuildConfig {} + +impl rattler_config::config::Config for RattlerBuildConfig { + fn merge_config(self, _other: &Self) -> Result { + // There are no rattler-build specific keys yet, so there is nothing + // to merge. `validate`, `keys` and `is_default` use the trait's + // default implementations. + Ok(self) + } +} + +/// The `rattler-build` configuration: the configuration shared with pixi and +/// other rattler based tools, extended with rattler-build specific keys. +pub type Config = ConfigBase; + +/// The tools whose configuration `rattler-build` reads, in ascending order of +/// precedence: pixi's configuration is picked up automatically and can be +/// overridden by rattler-build specific files. +const CONFIG_TOOLS: &[&str] = &["pixi", "rattler-build"]; + +/// All default configuration file locations, in ascending order of precedence +/// (values from later files override values from earlier files). +/// +/// This is a thin wrapper around +/// [`rattler_config::locations::config_search_paths`], the shared discovery +/// logic used by all rattler based tools. For the tools +/// `["pixi", "rattler-build"]` it yields, lowest precedence first: +/// +/// 1. the system-wide configuration of every tool +/// (`/etc/pixi/config.toml`, `/etc/rattler-build/config.toml`, or the +/// `C:\ProgramData\\config.toml` equivalents on Windows), +/// 2. the per-user configuration of every tool: the platform config directory +/// (`$XDG_CONFIG_HOME//config.toml`) followed by the tool home +/// (`$PIXI_HOME` / `$RATTLER_BUILD_HOME`, defaulting to `~/.pixi` / +/// `~/.rattler-build`). +/// +/// Within each group the tools are ordered as listed, so rattler-build's +/// configuration overrides pixi's. +pub fn default_config_paths() -> Vec { + rattler_config::locations::config_search_paths(CONFIG_TOOLS) +} + +/// Load the configuration from the default locations (see +/// [`default_config_paths`]), merging all files that exist. Files later in +/// the list override values from earlier files. +/// +/// Returns `Ok(None)` if none of the default configuration files exist. +/// +/// This is the command-line interface's discovery entry point. It is +/// intentionally **not** called by any library code path: programmatic +/// consumers construct and pass their own [`Config`] instead of having one +/// discovered from the user's environment (see the module docs). +/// +/// The full candidate list (in precedence order) is logged at debug level so +/// that `-v` runs can explain why a particular file was or was not picked up; +/// the CLI separately logs the files that were actually loaded at the default +/// level on startup. +pub fn load_default_config() -> Result, LoadError> { + let candidates = default_config_paths(); + tracing::debug!("Configuration search paths (lowest precedence first): {candidates:?}"); + + let paths = candidates + .into_iter() + .filter(|p| p.is_file()) + .collect::>(); + + if paths.is_empty() { + tracing::debug!("No configuration file found in any default location"); + return Ok(None); + } + + Config::load_from_files(&paths).map(Some) +} + +#[cfg(test)] +mod tests { + use super::*; + use rattler_conda_types::NamedChannelOrUrl; + use std::str::FromStr; + + /// The `default_config_paths` wrapper must preserve the precedence + /// guaranteed by the shared `locations` helper (lowest precedence first): + /// all system-wide files come before all per-user files, and within each + /// group pixi's file is overridden by rattler-build's. We assert on the + /// positions of the paths reported by the upstream helpers rather than + /// depending on the real home directory. + #[test] + fn test_default_config_paths_ordering() { + use rattler_config::locations::{system_config_path, user_config_paths}; + + let paths = default_config_paths(); + let position = |needle: &std::path::Path| paths.iter().position(|p| p == needle); + + let system_pixi = system_config_path("pixi"); + let system_rb = system_config_path("rattler-build"); + let pos_system_pixi = position(&system_pixi).expect("system pixi config present"); + let pos_system_rb = position(&system_rb).expect("system rattler-build config present"); + + // Within the system group, rattler-build overrides pixi. + assert!( + pos_system_pixi < pos_system_rb, + "system rattler-build config must override system pixi config" + ); + + // All system-wide files come before all per-user files. + if let Some(first_user) = user_config_paths("pixi").first().and_then(|p| position(p)) { + assert!( + pos_system_rb < first_user, + "system configs must come before per-user configs" + ); + } + + // Within the per-user group, rattler-build overrides pixi. + if let (Some(last_user_pixi), Some(first_user_rb)) = ( + user_config_paths("pixi").last().and_then(|p| position(p)), + user_config_paths("rattler-build") + .first() + .and_then(|p| position(p)), + ) { + assert!( + last_user_pixi < first_user_rb, + "per-user rattler-build config must override per-user pixi config" + ); + } + } + + #[test] + fn test_load_from_files_later_files_win() { + let dir = tempfile::tempdir().unwrap(); + let low = dir.path().join("low.toml"); + let high = dir.path().join("high.toml"); + fs_err::write( + &low, + "default-channels = [\"conda-forge\"]\ntls-no-verify = true\n", + ) + .unwrap(); + fs_err::write(&high, "default-channels = [\"bioconda\"]\n").unwrap(); + + let config = Config::load_from_files([&low, &high]).unwrap(); + + // The value from the later file wins… + assert_eq!( + config.default_channels, + Some(vec![NamedChannelOrUrl::from_str("bioconda").unwrap()]) + ); + // …while values only present in the earlier file are kept. + assert_eq!(config.tls_no_verify, Some(true)); + } + + #[test] + fn test_extension_is_parsed() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + fs_err::write(&path, "default-channels = [\"conda-forge\"]\n").unwrap(); + + let config = Config::load_from_files([&path]).unwrap(); + assert_eq!(config.extensions, RattlerBuildConfig::default()); + } +} diff --git a/src/debug.rs b/src/debug.rs index abbe8637b..9675c9087 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -4,12 +4,12 @@ use std::{path::PathBuf, str::FromStr}; use miette::IntoDiagnostic; use rattler_build::{ + config::Config, console_utils::LoggingOutputHandler, debug as core_debug, opt::{CreatePatchOpts, DebugEnvAddOpts, DebugRunOpts, DebugShellOpts, DebugWorkdirOpts}, source::create_patch, }; -use rattler_config::config::ConfigBase; /// Open a debug shell from DebugShellOpts. pub fn debug_shell(opts: DebugShellOpts) -> std::io::Result<()> { @@ -137,7 +137,7 @@ pub fn debug_create_patch(opts: CreatePatchOpts) -> miette::Result<()> { pub async fn debug_env_add( env_name: &str, opts: DebugEnvAddOpts, - _config: Option>, + _config: Option, log_handler: &Option, ) -> miette::Result<()> { let (_work_dir, directories_json) = diff --git a/src/lib.rs b/src/lib.rs index 473ad9bff..edc0435e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub use rattler_build_core::tool_configuration; pub use rattler_build_core::types; pub use rattler_build_core::utils; +pub mod config; pub mod opt; // Re-export recipe generator diff --git a/src/main.rs b/src/main.rs index 5bd110a62..349c85e22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use std::{ use clap::{CommandFactory, Parser}; use miette::IntoDiagnostic; +use rattler_build::config::{Config, load_default_config}; use rattler_build::{ build_recipes, bump_recipe, console_utils::init_logging, @@ -26,7 +27,6 @@ use rattler_build::{ publish_packages, rebuild, run_test, show_package_info, tool_configuration::APP_USER_AGENT, }; -use rattler_config::config::ConfigBase; use rattler_upload::upload_from_args; use tempfile::{TempDir, tempdir}; @@ -152,12 +152,39 @@ async fn async_main() -> miette::Result<()> { ) .into_diagnostic()?; - let config = if let Some(config_path) = app.config_file { - Some(ConfigBase::<()>::load_from_files(&[config_path]).into_diagnostic()?) - } else { + let config = if app.no_config { + // `--no-config` disables all loading and discovery: only built-in + // defaults and command-line arguments apply (the behavior before + // default config discovery was added). None + } else if let Some(config_path) = app.config_file { + // An explicitly passed configuration file disables the automatic + // discovery and is used as-is. + Some(Config::load_from_files(&[config_path]).into_diagnostic()?) + } else { + // Otherwise, load and merge the configuration from the default + // locations (system-wide and global pixi configuration as well as + // rattler-build's own configuration files). + load_default_config().into_diagnostic()? }; + // Print a startup line so users can always see which version is running + // and exactly which configuration files were loaded (if any), making + // config resolution easy to trace. Shown at the default log level. + tracing::info!("rattler-build {}", env!("CARGO_PKG_VERSION")); + match config.as_ref() { + Some(config) if !config.loaded_from.is_empty() => tracing::info!( + "Loaded configuration from: {}", + config + .loaded_from + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(", ") + ), + _ => tracing::info!("No configuration file loaded"), + } + match app.subcommand { Some(SubCommands::Completion(ShellCompletion { shell })) => { let mut cmd = App::command(); diff --git a/src/opt.rs b/src/opt.rs index 1d30913ec..14b8aac94 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -10,7 +10,6 @@ use rattler_build_script::{SandboxArguments, SandboxConfiguration}; use rattler_conda_types::{ NamedChannelOrUrl, Platform, compression_level::CompressionLevel, package::CondaArchiveType, }; -use rattler_config::config::ConfigBase; use rattler_config::config::build::PackageFormatAndCompression; use rattler_networking::mirror_middleware; #[cfg(feature = "s3")] @@ -21,6 +20,7 @@ use serde_json::{Value, json}; use url::Url; use crate::{ + config::Config, console_utils::{Color, LogStyle}, tool_configuration::{ContinueOnFailure, SkipExisting, TestStrategy}, }; @@ -377,10 +377,21 @@ pub struct App { )] pub wrap_log_lines: Option, - /// The Rattler-Build configuration file to use + /// The Rattler-Build configuration file to use. + /// If not set, configuration is loaded from the default locations + /// (the system-wide and global pixi configuration files as well as + /// rattler-build's own configuration files) #[arg(long, global = true)] pub config_file: Option, + /// Do not load any configuration. + /// Disables the automatic discovery of pixi/rattler-build configuration + /// files so that only built-in defaults and command-line arguments are + /// used (the behavior of rattler-build versions before default config + /// discovery was added). Mutually exclusive with `--config-file`. + #[arg(long, global = true, conflicts_with = "config_file")] + pub no_config: bool, + /// Enable or disable colored output from Rattler-Build. /// Also honors the `CLICOLOR` and `CLICOLOR_FORCE` environment variable. #[clap( @@ -462,7 +473,7 @@ impl CommonData { experimental: bool, v3: bool, auth_file: Option, - config: ConfigBase<()>, + config: Config, channel_priority: Option, allow_insecure_host: Option>, use_zstd: bool, @@ -518,7 +529,7 @@ impl CommonData { } /// Create from CLI options and config file - pub fn from_opts_and_config(value: CommonOpts, config: ConfigBase<()>) -> Self { + pub fn from_opts_and_config(value: CommonOpts, config: Config) -> Self { Self::new( value.output_dir, value.experimental, @@ -789,7 +800,7 @@ pub struct PublishData { impl PublishData { /// Generate a new PublishData struct from PublishOpts and an optional config. - pub fn from_opts_and_config(opts: PublishOpts, config: Option>) -> Self { + pub fn from_opts_and_config(opts: PublishOpts, config: Option) -> Self { // Separate package files from recipe paths based on file extension let mut package_files = Vec::new(); let mut recipe_paths = Vec::new(); @@ -994,7 +1005,7 @@ impl BuildData { impl BuildData { /// Generate a new BuildData struct from BuildOpts and an optional pixi config. /// BuildOpts have higher priority than the pixi config. - pub fn from_opts_and_config(opts: BuildOpts, config: Option>) -> Self { + pub fn from_opts_and_config(opts: BuildOpts, config: Option) -> Self { Self::new( opts.up_to, opts.build_platform, @@ -1121,7 +1132,7 @@ pub struct TestData { impl TestData { /// Generate a new TestData struct from TestOpts and an optional pixi config. /// TestOpts have higher priority than the pixi config. - pub fn from_opts_and_config(value: TestOpts, config: Option>) -> Self { + pub fn from_opts_and_config(value: TestOpts, config: Option) -> Self { Self::new( value.package_file, value.channels, @@ -1191,7 +1202,7 @@ pub struct RebuildData { impl RebuildData { /// Generate a new RebuildData struct from RebuildOpts and an optional pixi config. /// RebuildOpts have higher priority than the pixi config. - pub fn from_opts_and_config(value: RebuildOpts, config: Option>) -> Self { + pub fn from_opts_and_config(value: RebuildOpts, config: Option) -> Self { Self::new( value.package_file, value.test.unwrap_or(if value.no_test { @@ -1250,10 +1261,7 @@ pub struct DebugData { impl DebugData { /// Generate a new DebugData struct from DebugSetupOpts and an optional /// config. - pub fn from_setup_opts_and_config( - opts: DebugSetupOpts, - config: Option>, - ) -> Self { + pub fn from_setup_opts_and_config(opts: DebugSetupOpts, config: Option) -> Self { Self { recipe_path: opts.recipe, output_dir: opts