Skip to content

Commit

Permalink
Add Pixi locator
Browse files Browse the repository at this point in the history
  • Loading branch information
renan-r-santos committed Nov 13, 2024
1 parent ffcbf3f commit 82c89df
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 25 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

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

5 changes: 0 additions & 5 deletions crates/pet-conda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@
- Thus using the `history` file we can find the conda installation folder.
This is useful in cases where conda environments are created using `-p` option.

## Known issues

- Note: pixi seems to use conda envs internall, hence its possible to falsely identify a pixi env as a conda env.
- However pixi is not supported by this tool, hence thats not a concern.

## Miscellanous

- What if conda is installed in some custom locations that we have no idea about?
Expand Down
1 change: 1 addition & 0 deletions crates/pet-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub enum LocatorKind {
MacPythonOrg,
MacXCode,
PipEnv,
Pixi,
Poetry,
PyEnv,
Venv,
Expand Down
1 change: 1 addition & 0 deletions crates/pet-core/src/python_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{arch::Architecture, manager::EnvManager};
#[derive(Parser, ValueEnum, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum PythonEnvironmentKind {
Conda,
Pixi,
Homebrew,
Pyenv,
GlobalPaths, // Python found in global locations like PATH, /usr/bin etc.
Expand Down
13 changes: 13 additions & 0 deletions crates/pet-pixi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "pet-pixi"
version = "0.1.0"
edition = "2021"

[target.'cfg(target_os = "windows")'.dependencies]
msvc_spectre_libs = { version = "0.1.1", features = ["error"] }

[dependencies]
pet-conda = { path = "../pet-conda" }
pet-core = { path = "../pet-core" }
pet-python-utils = { path = "../pet-python-utils" }
log = "0.4.21"
11 changes: 11 additions & 0 deletions crates/pet-pixi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Pixi

## Notes

- Pixi environments are detected by:
- Searching for Python interpreters in `.pixi/envs` subdirectories within workspace folders
- Checking for a `conda-meta/pixi` file in potential Pixi environment directories (`.pixi/envs/{env_name}`)
- Determining the version of the Python interpreter from the `conda-meta/python-{version}.json` file

This process ensures fast detection without spawning processes.
Note that the Pixi locator should run before Conda since Conda could incorrectly identify Pixi environments as Conda environments.
87 changes: 87 additions & 0 deletions crates/pet-pixi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::path::{Path, PathBuf};

use pet_conda::package::{CondaPackageInfo, Package};
use pet_core::{
env::PythonEnv,
python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentKind},
reporter::Reporter,
Locator, LocatorKind,
};
use pet_python_utils::executable::find_executables;

pub fn is_pixi_env(path: &Path) -> bool {
path.join("conda-meta").join("pixi").is_file()
}

fn get_pixi_prefix(env: &PythonEnv) -> Option<PathBuf> {
env.prefix.clone().or_else(|| {
env.executable.parent().and_then(|parent_dir| {
if is_pixi_env(parent_dir) {
Some(parent_dir.to_path_buf())
} else if parent_dir.ends_with("bin") || parent_dir.ends_with("Scripts") {
parent_dir
.parent()
.filter(|parent| is_pixi_env(parent))
.map(|parent| parent.to_path_buf())
} else {
None
}
})
})
}

pub struct Pixi {}

impl Pixi {
pub fn new() -> Pixi {
Pixi {}
}
}
impl Default for Pixi {
fn default() -> Self {
Self::new()
}
}

impl Locator for Pixi {
fn get_kind(&self) -> LocatorKind {
LocatorKind::Pixi
}
fn supported_categories(&self) -> Vec<PythonEnvironmentKind> {
vec![PythonEnvironmentKind::Pixi]
}

fn try_from(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
get_pixi_prefix(env).and_then(|prefix| {
if !is_pixi_env(&prefix) {
return None;
}

let name = prefix
.file_name()
.and_then(|name| name.to_str())
.unwrap_or_default()
.to_string();

let symlinks = find_executables(&prefix);

let version = CondaPackageInfo::from(&prefix, &Package::Python)
.map(|package_info| package_info.version);

Some(
PythonEnvironmentBuilder::new(Some(PythonEnvironmentKind::Pixi))
.executable(Some(env.executable.clone()))
.name(Some(name))
.prefix(Some(prefix))
.symlinks(Some(symlinks))
.version(version)
.build(),
)
})
}

fn find(&self, _reporter: &dyn Reporter) {}
}
1 change: 1 addition & 0 deletions crates/pet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pet-homebrew = { path = "../pet-homebrew" }
[dependencies]
pet-core = { path = "../pet-core" }
pet-conda = { path = "../pet-conda" }
pet-pixi = { path = "../pet-pixi" }
pet-jsonrpc = { path = "../pet-jsonrpc" }
pet-fs = { path = "../pet-fs" }
pet-pyenv = { path = "../pet-pyenv" }
Expand Down
26 changes: 14 additions & 12 deletions crates/pet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,53 +66,55 @@ It could have been created by other tools like `poetry`, `pipenv`, etc. Hence we

4. Homebrew
These are always stored in a custom (global) lcoation.
5. Conda

5. Pixi
Pixi should run before Conda as its environments could be falsely identified as Conda environments.

6. Conda
- These are always stored in a custom (global) location.

- At this stage Conda envs cannot be treated as anything else
- Note: pixi seems to use conda envs internall, hence its possible to falsely identify a pixi env as a conda env.
- However pixi is not supported by this tool, hence thats not a concern.

6. Poetry
7. Poetry

- These are always stored in a custom (global) location.
- Environments are created with a special name based on the hash of the project folder.
- This needs to happen before others as these environments are general virtual environments.

7. Pipenv
8. Pipenv

- These are always stored in a custom (global) location.
- This needs to happen before others as these environments are general virtual environments.

8. Virtualenvwrapper
9. Virtualenvwrapper

- These are always stored in a custom (global) location.
- This needs to happen before others as these environments are general virtual environments.

9. Vevn
10. Vevn

- A virtual environment that has a `pyvenv.cfg` file.
- Note, this is a fallback for all other virtual environments.

10. Virtualenv
11. Virtualenv

- A virtual environment that does not have a `pyvenv.cfg` file but has activation scripts such as `/bin/activate` or `Scripts/activate.bat` or the like.
- Note, this is a fallback for all other environments.
- This must happen after conda env discovery, as conda envs have activation scripts as well.

11. Mac XCode
12. Mac XCode

- These are always stored in a custom (global) location.

12. Mac Command Line Tools
13. Mac Command Line Tools

- These are always stored in a custom (global) location.

13. Mac Python Org
14. Mac Python Org

- These are always stored in a custom (global) location.

14. Linux Python
15. Linux Python

- These are always stored in a custom (global) location.
</details>
18 changes: 16 additions & 2 deletions crates/pet/src/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use pet_core::reporter::Reporter;
use pet_core::{Configuration, Locator, LocatorKind};
use pet_env_var_path::get_search_paths_from_env_variables;
use pet_global_virtualenvs::list_global_virtual_envs_paths;
use pet_pixi::is_pixi_env;
use pet_python_utils::executable::{
find_executable, find_executables, should_search_for_environments_in_path,
};
Expand Down Expand Up @@ -254,7 +255,7 @@ pub fn find_python_environments_in_workspace_folder_recursive(
environment_directories: &[PathBuf],
) {
// When searching in a directory, give preference to some paths.
let paths_to_search_first = vec![
let mut paths_to_search_first = vec![
// Possible this is a virtual env
workspace_folder.to_path_buf(),
// Optimize for finding these first.
Expand All @@ -264,6 +265,15 @@ pub fn find_python_environments_in_workspace_folder_recursive(
workspace_folder.join("venv"),
];

// Add all subdirectories of .pixi/envs/**
if let Ok(reader) = fs::read_dir(workspace_folder.join(".pixi").join("envs")) {
reader
.filter_map(Result::ok)
.filter(|d| d.file_type().is_ok_and(|f| f.is_dir()))
.map(|p| p.path())
.for_each(|p| paths_to_search_first.push(p));
}

// Possible this is an environment.
find_python_environments_in_paths_with_locators(
paths_to_search_first.clone(),
Expand All @@ -274,7 +284,11 @@ pub fn find_python_environments_in_workspace_folder_recursive(
);

// If this is a virtual env folder, no need to scan this.
if is_virtualenv_dir(workspace_folder) || is_conda_env(workspace_folder) {
// Note: calling is_pixi_env after is_conda_env is redundant but kept for consistency.
if is_virtualenv_dir(workspace_folder)
|| is_conda_env(workspace_folder)
|| is_pixi_env(workspace_folder)
{
return;
}
if let Ok(reader) = fs::read_dir(workspace_folder) {
Expand Down
16 changes: 10 additions & 6 deletions crates/pet/src/locators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use pet_mac_commandlinetools::MacCmdLineTools;
use pet_mac_python_org::MacPythonOrg;
use pet_mac_xcode::MacXCode;
use pet_pipenv::PipEnv;
use pet_pixi::Pixi;
use pet_poetry::Poetry;
use pet_pyenv::PyEnv;
use pet_python_utils::env::ResolvedPythonEnv;
Expand Down Expand Up @@ -48,10 +49,13 @@ pub fn create_locators(
// 3. Pyenv Python
locators.push(Arc::new(PyEnv::from(environment, conda_locator.clone())));

// 4. Conda Python
// 4. Pixi
locators.push(Arc::new(Pixi::new()));

// 5. Conda Python
locators.push(conda_locator);

// 5. Support for Virtual Envs
// 6. Support for Virtual Envs
// The order of these matter.
// Basically PipEnv is a superset of VirtualEnvWrapper, which is a superset of Venv, which is a superset of VirtualEnv.
locators.push(poetry_locator);
Expand All @@ -61,7 +65,7 @@ pub fn create_locators(
// VirtualEnv is the most generic, hence should be the last.
locators.push(Arc::new(VirtualEnv::new()));

// 6. Homebrew Python
// 7. Homebrew Python
if cfg!(unix) {
#[cfg(unix)]
use pet_homebrew::Homebrew;
Expand All @@ -71,14 +75,14 @@ pub fn create_locators(
locators.push(Arc::new(homebrew_locator));
}

// 7. Global Mac Python
// 8. CommandLineTools Python & xcode
// 8. Global Mac Python
// 9. CommandLineTools Python & xcode
if std::env::consts::OS == "macos" {
locators.push(Arc::new(MacXCode::new()));
locators.push(Arc::new(MacCmdLineTools::new()));
locators.push(Arc::new(MacPythonOrg::new()));
}
// 9. Global Linux Python
// 10. Global Linux Python
// All other Linux (not mac, & not windows)
// THIS MUST BE LAST
if std::env::consts::OS != "macos" && std::env::consts::OS != "windows" {
Expand Down
1 change: 1 addition & 0 deletions docs/JSONRPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ interface ResolveParams {

enum PythonEnvironmentKind {
Conda,
Pixi,
Homebrew,
Pyenv,
GlobalPaths, // Python found in global locations like PATH, /usr/bin etc.
Expand Down

0 comments on commit 82c89df

Please sign in to comment.