Skip to content

Commit

Permalink
feat: implement global config options (#960)
Browse files Browse the repository at this point in the history
Co-authored-by: Ruben Arts <[email protected]>
  • Loading branch information
wolfv and ruben-arts authored Mar 14, 2024
1 parent 713d94b commit cf548aa
Show file tree
Hide file tree
Showing 28 changed files with 589 additions and 191 deletions.
32 changes: 32 additions & 0 deletions docs/advanced/global_configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Global configuration in pixi

Pixi supports some global configuration options, as well as project-scoped configuration (that does not belong into the project file).
The configuration is loaded in the following order:

1. Global configuration folder (e.g. `~/.config/pixi/config.toml` on Linux, dependent on XDG_CONFIG_HOME)
2. Global .pixi folder: `~/.pixi/config.toml` (or `$PIXI_HOME/config.toml` if the `PIXI_HOME` environment variable is set)
3. Project-local .pixi folder: `$PIXI_PROJECT/.pixi/config.toml`
4. Command line arguments (`--tls-no-verify`, `--change-ps1=false` etc.)

!!! note
To find the locations where `pixi` looks for configuration files, run `pixi` with `-v` or `--verbose`.

## Reference

The following reference describes all available configuration options.

```toml
# The default channels to select when running `pixi init` or `pixi global install`.
# This defaults to only conda-forge.
default_channels = ["conda-forge"]

# When set to false, the `(pixi)` prefix in the shell prompt is removed.
# This applies to the `pixi shell` subcommand.
# You can override this from the CLI with `--change-ps1`.
change_ps1 = true

# When set to true, the TLS certificates are not verified. Note that this is a
# security risk and should only be used for testing purposes or internal networks.
# You can override this from the CLI with `--tls-no-verify`.
tls_no_verify = false
```
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,7 @@ test = {features = ["test"], solve-group = "test"}
prod = {features = ["prod"], solve-group = "test"}
lint = ["lint"]
```

## Global configuration

The global configuration options are documented in the [global configuration](advanced/global_configuration.md) section.
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ nav:
- Authentication: advanced/authentication.md
- Tasks: advanced/advanced_tasks.md
- Multi Platform: advanced/multi_platform_configuration.md
- Info command: advanced/explain_info_command.md
- Info Command: advanced/explain_info_command.md
- Channel Logic: advanced/channel_priority.md
- GitHub Actions: advanced/github_actions.md
- Global Configuration: advanced/global_configuration.md
- Examples:
- C++/Cmake: examples/cpp-sdl.md
- OpenCV: examples/opencv.md
Expand Down
7 changes: 6 additions & 1 deletion src/cli/add.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
config::ConfigCli,
environment::{get_up_to_date_prefix, verify_prefix_location_unchanged, LockFileUsage},
project::{manifest::PyPiRequirement, DependencyType, Project, SpecType},
FeatureName,
Expand Down Expand Up @@ -91,6 +92,9 @@ pub struct Args {
/// The feature for which the dependency should be added
#[arg(long, short)]
pub feature: Option<String>,

#[clap(flatten)]
pub config: ConfigCli,
}

impl DependencyType {
Expand All @@ -108,7 +112,8 @@ impl DependencyType {
}

pub async fn execute(args: Args) -> miette::Result<()> {
let mut project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
let mut project = Project::load_or_else_discover(args.manifest_path.as_deref())?
.with_cli_config(args.config.clone());
let dependency_type = DependencyType::from_args(&args);
let spec_platforms = &args.platform;

Expand Down
53 changes: 22 additions & 31 deletions src/cli/global/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ use rattler_repodata_gateway::sparse::SparseRepoData;
use rattler_solve::{resolvo, SolverImpl, SolverTask};
use reqwest_middleware::ClientWithMiddleware;

use crate::{prefix::Prefix, repodata};
use crate::{config::home_path, prefix::Prefix, repodata};

/// Global binaries directory, default to `$HOME/.pixi/bin`
pub struct BinDir(pub PathBuf);

impl BinDir {
/// Create the Binary Executable directory
pub async fn create() -> miette::Result<Self> {
let bin_dir = bin_dir()?;
let bin_dir = bin_dir().ok_or(miette::miette!(
"could not determine global binary executable directory"
))?;
tokio::fs::create_dir_all(&bin_dir)
.await
.into_diagnostic()?;
Expand All @@ -28,7 +30,9 @@ impl BinDir {

/// Get the Binary Executable directory, erroring if it doesn't already exist.
pub async fn from_existing() -> miette::Result<Self> {
let bin_dir = bin_dir()?;
let bin_dir = bin_dir().ok_or(miette::miette!(
"could not find global binary executable directory"
))?;
if tokio::fs::try_exists(&bin_dir).await.into_diagnostic()? {
Ok(Self(bin_dir))
} else {
Expand All @@ -39,39 +43,17 @@ impl BinDir {
}
}

/// Get pixi home directory, default to `$HOME/.pixi`
///
/// It may be overridden by the `PIXI_HOME` environment variable.
///
/// # Returns
///
/// The pixi home directory
pub fn home_path() -> miette::Result<PathBuf> {
if let Some(path) = std::env::var_os("PIXI_HOME") {
Ok(PathBuf::from(path))
} else {
dirs::home_dir()
.map(|path| path.join(".pixi"))
.ok_or_else(|| miette::miette!("could not find home directory"))
}
}

/// Global binaries directory, default to `$HOME/.pixi/bin`
///
/// # Returns
///
/// The global binaries directory
pub fn bin_dir() -> miette::Result<PathBuf> {
home_path().map(|path| path.join("bin"))
}

/// Global binary environments directory, default to `$HOME/.pixi/envs`
pub struct BinEnvDir(pub PathBuf);

impl BinEnvDir {
/// Construct the path to the env directory for the binary package `package_name`.
fn package_bin_env_dir(package_name: &PackageName) -> miette::Result<PathBuf> {
Ok(bin_env_dir()?.join(package_name.as_normalized()))
Ok(bin_env_dir()
.ok_or(miette::miette!(
"could not find global binary environment directory"
))?
.join(package_name.as_normalized()))
}

/// Get the Binary Environment directory, erroring if it doesn't already exist.
Expand Down Expand Up @@ -100,12 +82,21 @@ impl BinEnvDir {
}
}

/// Global binaries directory, default to `$HOME/.pixi/bin`
///
/// # Returns
///
/// The global binaries directory
pub fn bin_dir() -> Option<PathBuf> {
home_path().map(|path| path.join("bin"))
}

/// Global binary environments directory, default to `$HOME/.pixi/envs`
///
/// # Returns
///
/// The global binary environments directory
pub fn bin_env_dir() -> miette::Result<PathBuf> {
pub fn bin_env_dir() -> Option<PathBuf> {
home_path().map(|path| path.join("envs"))
}

Expand Down
17 changes: 6 additions & 11 deletions src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::config::Config;
use crate::install::execute_transaction;
use crate::{config, prefix::Prefix, progress::await_in_progress};
use clap::Parser;
Expand All @@ -10,8 +11,7 @@ use miette::IntoDiagnostic;
use rattler::install::Transaction;
use rattler::package_cache::PackageCache;
use rattler_conda_types::{
Channel, ChannelConfig, MatchSpec, PackageName, ParseStrictness, Platform, PrefixRecord,
RepoDataRecord,
MatchSpec, PackageName, ParseStrictness, Platform, PrefixRecord, RepoDataRecord,
};
use rattler_shell::{
activation::{ActivationVariables, Activator, PathModificationBehavior},
Expand Down Expand Up @@ -41,7 +41,7 @@ pub struct Args {
/// For example: `pixi global install --channel conda-forge --channel bioconda`.
///
/// By default, if no channel is provided, `conda-forge` is used.
#[clap(short, long, default_values = ["conda-forge"])]
#[clap(short, long)]
channel: Vec<String>,
}

Expand Down Expand Up @@ -231,13 +231,8 @@ pub(super) async fn create_executable_scripts(
/// Install a global command
pub async fn execute(args: Args) -> miette::Result<()> {
// Figure out what channels we are using
let channel_config = ChannelConfig::default();
let channels = args
.channel
.iter()
.map(|c| Channel::from_str(c, &channel_config))
.collect::<Result<Vec<Channel>, _>>()
.into_diagnostic()?;
let config = Config::load_global();
let channels = config.compute_channels(&args.channel).into_diagnostic()?;

// Find the MatchSpec we want to install
let specs = args
Expand All @@ -258,7 +253,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {

let (prefix_package, scripts, _) =
globally_install_package(&package_name, records, authenticated_client.clone()).await?;
let channel_name = channel_name_from_prefix(&prefix_package, &channel_config);
let channel_name = channel_name_from_prefix(&prefix_package, config.channel_config());
let record = &prefix_package.repodata_record.package_record;

// Warn if no executables were created for the package
Expand Down
9 changes: 6 additions & 3 deletions src/cli/global/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::PackageName;

use crate::config::home_path;
use crate::prefix::Prefix;

use super::common::{bin_env_dir, find_designated_package, home_path, BinDir, BinEnvDir};
use super::common::{bin_env_dir, find_designated_package, BinDir, BinEnvDir};
use super::install::{find_and_map_executable_scripts, BinScriptMapping};

/// Lists all packages previously installed into a globally accessible location via `pixi global install`.
Expand Down Expand Up @@ -90,7 +91,7 @@ pub async fn execute(_args: Args) -> miette::Result<()> {
if package_info.is_empty() {
print_no_packages_found_message();
} else {
let path = home_path()?;
let path = home_path().ok_or(miette::miette!("Could not determine home directory"))?;
let len = package_info.len();
let mut message = String::new();
for (idx, pkgi) in package_info.into_iter().enumerate() {
Expand Down Expand Up @@ -139,7 +140,9 @@ pub async fn execute(_args: Args) -> miette::Result<()> {
/// A list of all globally installed packages represented as [`PackageName`]s
pub(super) async fn list_global_packages() -> miette::Result<Vec<PackageName>> {
let mut packages = vec![];
let Ok(mut dir_contents) = tokio::fs::read_dir(bin_env_dir()?).await else {
let bin_env_dir =
bin_env_dir().ok_or(miette::miette!("Could not determine global envs directory"))?;
let Ok(mut dir_contents) = tokio::fs::read_dir(bin_env_dir).await else {
return Ok(vec![]);
};

Expand Down
12 changes: 7 additions & 5 deletions src/cli/global/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use clap::Parser;
use indicatif::ProgressBar;
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, PackageName, Version};
use rattler_conda_types::{Channel, MatchSpec, PackageName, Version};
use rattler_conda_types::{ParseStrictness, RepoDataRecord};
use reqwest_middleware::ClientWithMiddleware;

use crate::config::Config;
use crate::progress::{global_multi_progress, long_running_progress_style};

use super::common::{
Expand All @@ -32,7 +33,7 @@ pub struct Args {
///
/// By default, if no channel is provided, `conda-forge` is used, the channel
/// the package was installed from will always be used.
#[clap(short, long, default_values = ["conda-forge"])]
#[clap(short, long)]
channel: Vec<String>,
}

Expand Down Expand Up @@ -62,19 +63,20 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.version
.into_version();

let config = Config::load_global();

// Figure out what channels we are using
let channel_config = ChannelConfig::default();
let last_installed_channel = Channel::from_str(
prefix_record.repodata_record.channel.clone(),
&channel_config,
config.channel_config(),
)
.into_diagnostic()?;

let mut channels = vec![last_installed_channel];
let input_channels = args
.channel
.iter()
.map(|c| Channel::from_str(c, &channel_config))
.map(|c| Channel::from_str(c, config.channel_config()))
.collect::<Result<Vec<Channel>, _>>()
.into_diagnostic()?;
channels.extend(input_channels);
Expand Down
17 changes: 7 additions & 10 deletions src/cli/global/upgrade_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::collections::HashMap;
use clap::Parser;
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, ParseStrictness};
use rattler_conda_types::{Channel, MatchSpec, ParseStrictness};

use crate::config::Config;

use super::{
common::{find_installed_package, get_client_and_sparse_repodata, load_package_records},
Expand All @@ -23,27 +25,22 @@ pub struct Args {
///
/// By default, if no channel is provided, `conda-forge` is used, the channel
/// the package was installed from will always be used.
#[clap(short, long, default_values = ["conda-forge"])]
#[clap(short, long)]
channel: Vec<String>,
}

pub async fn execute(args: Args) -> miette::Result<()> {
let packages = list_global_packages().await?;
let channel_config = ChannelConfig::default();
let mut channels = args
.channel
.iter()
.map(|c| Channel::from_str(c, &channel_config))
.collect::<Result<Vec<Channel>, _>>()
.into_diagnostic()?;
let config = Config::load_global();
let mut channels = config.compute_channels(&args.channel).into_diagnostic()?;

let mut installed_versions = HashMap::with_capacity(packages.len());

for package_name in packages.iter() {
let prefix_record = find_installed_package(package_name).await?;
let last_installed_channel = Channel::from_str(
prefix_record.repodata_record.channel.clone(),
&channel_config,
config.channel_config(),
)
.into_diagnostic()?;

Expand Down
16 changes: 15 additions & 1 deletion src/cli/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct ProjectInfo {
last_updated: Option<String>,
pixi_folder_size: Option<String>,
version: Option<String>,
configuration: Vec<PathBuf>,
}

#[derive(Serialize)]
Expand Down Expand Up @@ -184,7 +185,6 @@ impl Display for Info {
}

writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Cache dir"), cache_dir)?;

if let Some(cache_size) = &self.cache_size {
writeln!(f, "{:>WIDTH$}: {}", bold.apply_to("Cache size"), cache_size)?;
}
Expand All @@ -208,6 +208,19 @@ impl Display for Info {
pi.manifest_path.to_string_lossy()
)?;

let config_locations = pi
.configuration
.iter()
.map(|p| p.to_string_lossy())
.join(", ");

writeln!(
f,
"{:>WIDTH$}: {}",
bold.apply_to("Config locations"),
config_locations
)?;

if let Some(update_time) = &pi.last_updated {
writeln!(
f,
Expand Down Expand Up @@ -282,6 +295,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
manifest_path: p.root().to_path_buf().join("pixi.toml"),
last_updated: last_updated(p.lock_file_path()).ok(),
pixi_folder_size,
configuration: p.config().loaded_from.clone(),
version: p.version().clone().map(|v| v.to_string()),
});

Expand Down
Loading

0 comments on commit cf548aa

Please sign in to comment.