Add support for Python Environment Tools (#23643)

Closes https://github.com/microsoft/vscode-python/issues/23564

---------

Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com>
This commit is contained in:
Karthik Nadig 2024-06-20 22:20:04 -07:00 коммит произвёл GitHub
Родитель 6138ff8f0c
Коммит daebb5cb77
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
89 изменённых файлов: 528 добавлений и 4359 удалений

33
.github/workflows/build.yml поставляемый
Просмотреть файл

@ -84,6 +84,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: 'python-env-tools'
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Build VSIX
uses: ./.github/actions/build-vsix
with:
@ -201,6 +212,17 @@ jobs:
with:
path: ${{ env.special-working-directory-relative }}
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: ${{ env.special-working-directory-relative }}/python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Install Node
uses: actions/setup-node@v4
with:
@ -387,6 +409,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: ${{ env.special-working-directory-relative }}/python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Smoke tests
uses: ./.github/actions/smoke-tests
with:

70
.github/workflows/pr-check.yml поставляемый
Просмотреть файл

@ -57,6 +57,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: 'python-env-tools'
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Build VSIX
uses: ./.github/actions/build-vsix
with:
@ -90,6 +101,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: 'python-env-tools'
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Install base Python requirements
uses: brettcannon/pip-secure-install@v1
with:
@ -186,6 +208,17 @@ jobs:
with:
path: ${{ env.special-working-directory-relative }}
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: ${{ env.special-working-directory-relative }}/python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Install Node
uses: actions/setup-node@v4
with:
@ -363,9 +396,20 @@ jobs:
with:
path: ${{ env.special-working-directory-relative }}
- name: Native Locator tests
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: ${{ env.special-working-directory-relative }}/python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Python Environment Tools tests
run: cargo test -- --nocapture
working-directory: ${{ env.special-working-directory }}/native_locator
working-directory: ${{ env.special-working-directory }}/python-env-tools
smoke-tests:
name: Smoke tests
@ -388,6 +432,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Smoke tests
uses: ./.github/actions/smoke-tests
with:
@ -409,6 +464,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout Python Environment Tools
uses: actions/checkout@v4
with:
repository: 'microsoft/python-environment-tools'
path: python-env-tools
sparse-checkout: |
crates
Cargo.toml
Cargo.lock
sparse-checkout-cone-mode: false
- name: Install Node
uses: actions/setup-node@v4
with:

3
.gitignore поставляемый
Просмотреть файл

@ -48,5 +48,4 @@ dist/**
*.xlf
package.nls.*.json
l10n/
native_locator/target/**
native_locator/Cargo.lock
python-env-tools/**

2
.vscode/settings.json поставляемый
Просмотреть файл

@ -73,6 +73,6 @@
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"rust-analyzer.linkedProjects": [
".\\native_locator\\Cargo.toml"
".\\python-env-tools\\Cargo.toml"
]
}

Просмотреть файл

@ -67,9 +67,8 @@ test/**
tmp/**
typings/**
types/**
native_locator/.vscode/**
native_locator/src/**
native_locator/tests/**
native_locator/bin/**
native_locator/target/**
native_locator/Cargo.*
python-env-tools/.github/**
python-env-tools/.vscode/**
python-env-tools/crates/**
python-env-tools/target/**
python-env-tools/Cargo.*

Просмотреть файл

@ -18,6 +18,13 @@ resources:
ref: main
endpoint: Monaco
- repository: python-environment-tools
type: github
name: microsoft/python-environment-tools
ref: main
endpoint: Monaco
parameters:
- name: publishExtension
displayName: 🚀 Publish Extension
@ -30,7 +37,48 @@ extends:
publishExtension: ${{ parameters.publishExtension }}
ghCreateTag: false
l10nSourcePaths: ./src/client
sourceRepositoriesToScan:
include:
- repository: python-environment-tools
exclude:
- repository: translations
buildPlatforms:
- name: Linux
vsceTarget: 'web'
# - name: Linux
# packageArch: arm64
# vsceTarget: linux-arm64
# - name: Linux
# packageArch: arm
# vsceTarget: linux-armhf
- name: Linux
packageArch: x64
vsceTarget: linux-x64
# - name: Linux
# packageArch: arm64
# vsceTarget: alpine-arm64
- name: Linux
packageArch: x64
vsceTarget: alpine-x64
- name: MacOS
packageArch: arm64
vsceTarget: darwin-arm64
- name: MacOS
packageArch: x64
vsceTarget: darwin-x64
- name: Windows
packageArch: arm
vsceTarget: win32-arm64
- name: Windows
packageArch: x64
vsceTarget: win32-x64
buildSteps:
- checkout: self
displayName: Checkout Python Extension
path: ./s
- task: NodeTool@0
inputs:
versionSpec: '18.17.1'
@ -43,37 +91,54 @@ extends:
architecture: 'x64'
displayName: Select Python version
- script: npm ci
displayName: Install NPM dependencies
- script: python -m pip install -U pip
displayName: Upgrade pip
- script: python -m pip install wheel nox
displayName: Install wheel and nox
- script: |
nox --session install_python_libs
- script: npm ci
displayName: Install NPM dependencies
- script: nox --session install_python_libs
displayName: Install Jedi, get-pip, etc
- script: |
python ./build/update_ext_version.py --for-publishing
- script: python ./build/update_ext_version.py --for-publishing
displayName: Update build number
- script: |
python ./build/update_package_file.py
- script: python ./build/update_package_file.py
displayName: Update telemetry in package.json
- script: npm run addExtensionPackDependencies
displayName: Update optional extension dependencies
- script: gulp prePublishBundle
- script: npx gulp prePublishBundle
displayName: Build
- checkout: python-environment-tools
displayName: Checkout python-environment-tools
path: ./s/python-env-tools
- script: nox --session azure_pet_build_before
displayName: Enable cargo config for azure
- template: azure-pipelines/extension/templates/steps/build-extension-rust-package.yml@templates
parameters:
vsceTarget: $(vsceTarget)
binaryName: pet
signing: true
workingDirectory: $(Build.SourcesDirectory)/python-env-tools
buildWasm: false
runTest: false
- script: nox --session azure_pet_build_after
displayName: Move bin to final location
- script: python -c "import shutil; shutil.rmtree('.nox', ignore_errors=True)"
displayName: Clean up Nox
tsa:
config:
areaPath: 'Visual Studio Code Python Extensions'
serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46'
enabled: true
config:
areaPath: 'Visual Studio Code Python Extensions'
serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46'
enabled: true

3
native_locator/.vscode/settings.json поставляемый
Просмотреть файл

@ -1,3 +0,0 @@
{
"git.openRepositoryInParentFolders": "always"
}

Просмотреть файл

@ -1,23 +0,0 @@
[package]
name = "python-finder"
version = "0.1.0"
edition = "2021"
[target.'cfg(windows)'.dependencies]
winreg = "0.52.0"
[dependencies]
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
serde_repr = "0.1.10"
regex = "1.10.4"
log = "0.4.21"
env_logger = "0.10.2"
[lib]
doctest = false
[profile.release]
strip = true
lto = true
codegen-units = 1

Просмотреть файл

@ -1,89 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use crate::known::Environment;
use crate::locator::{Locator, LocatorResult};
use crate::messaging::PythonEnvironment;
use crate::utils::{self, PythonEnv};
use std::env;
use std::path::{Path, PathBuf};
fn get_env_path(python_executable_path: &PathBuf) -> Option<PathBuf> {
let parent = python_executable_path.parent()?;
if parent.file_name()? == "Scripts" {
return Some(parent.parent()?.to_path_buf());
} else {
return Some(parent.to_path_buf());
}
}
pub struct PythonOnPath<'a> {
pub environment: &'a dyn Environment,
}
impl PythonOnPath<'_> {
pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath {
PythonOnPath { environment }
}
}
impl Locator for PythonOnPath<'_> {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
let bin = if cfg!(windows) {
"python.exe"
} else {
"python"
};
if env.executable.file_name().unwrap().to_ascii_lowercase() != bin {
return None;
}
Some(PythonEnvironment {
display_name: None,
python_executable_path: Some(env.executable.clone()),
version: env.version.clone(),
category: crate::messaging::PythonEnvironmentCategory::System,
env_path: env.path.clone(),
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
..Default::default()
})
}
fn find(&mut self) -> Option<LocatorResult> {
let paths = self.environment.get_env_var("PATH".to_string())?;
let bin = if cfg!(windows) {
"python.exe"
} else {
"python"
};
// Exclude files from this folder, as they would have been discovered elsewhere (widows_store)
// Also the exe is merely a pointer to another file.
let home = self.environment.get_user_home()?;
let apps_path = Path::new(&home)
.join("AppData")
.join("Local")
.join("Microsoft")
.join("WindowsApps");
let mut environments: Vec<PythonEnvironment> = vec![];
env::split_paths(&paths)
.filter(|p| !p.starts_with(apps_path.clone()))
.map(|p| p.join(bin))
.filter(|p| p.exists())
.for_each(|full_path| {
let version = utils::get_version(&full_path);
let env_path = get_env_path(&full_path);
if let Some(env) = self.resolve(&PythonEnv::new(full_path, env_path, version)) {
environments.push(env);
}
});
if environments.is_empty() {
None
} else {
Some(LocatorResult {
environments,
managers: vec![],
})
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,69 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::{
known,
utils::{find_python_binary_path, get_version, PythonEnv},
};
use std::{fs, path::PathBuf};
fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec<PathBuf> {
let mut venv_dirs: Vec<PathBuf> = vec![];
if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) {
if let Ok(work_on_home) = fs::canonicalize(work_on_home) {
if work_on_home.exists() {
venv_dirs.push(work_on_home);
}
}
}
if let Some(home) = environment.get_user_home() {
let home = PathBuf::from(home);
for dir in [
PathBuf::from("envs"),
PathBuf::from(".direnv"),
PathBuf::from(".venvs"),
PathBuf::from(".virtualenvs"),
PathBuf::from(".local").join("share").join("virtualenvs"),
] {
let venv_dir = home.join(dir);
if venv_dir.exists() {
venv_dirs.push(venv_dir);
}
}
if cfg!(target_os = "linux") {
let envs = PathBuf::from("Envs");
if envs.exists() {
venv_dirs.push(envs);
}
}
}
venv_dirs
}
pub fn list_global_virtual_envs(environment: &impl known::Environment) -> Vec<PythonEnv> {
let mut python_envs: Vec<PythonEnv> = vec![];
for root_dir in get_global_virtualenv_dirs(environment).iter() {
if let Ok(dirs) = fs::read_dir(root_dir) {
for venv_dir in dirs {
if let Ok(venv_dir) = venv_dir {
let venv_dir = venv_dir.path();
if !venv_dir.is_dir() {
continue;
}
if let Some(executable) = find_python_binary_path(&venv_dir) {
python_envs.push(PythonEnv::new(
executable.clone(),
Some(venv_dir),
get_version(&executable),
));
}
}
}
}
}
python_envs
}

Просмотреть файл

@ -1,283 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::{
known::Environment,
locator::{Locator, LocatorResult},
messaging::PythonEnvironment,
utils::PythonEnv,
};
use regex::Regex;
use std::{collections::HashSet, path::PathBuf};
fn is_symlinked_python_executable(path: &PathBuf) -> Option<PathBuf> {
let name = path.file_name()?.to_string_lossy();
if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") {
return None;
}
let metadata = std::fs::symlink_metadata(&path).ok()?;
if metadata.is_file() || !metadata.file_type().is_symlink() {
return None;
}
Some(std::fs::canonicalize(path).ok()?)
}
fn get_homebrew_prefix_env_var(environment: &dyn Environment) -> Option<PathBuf> {
if let Some(homebrew_prefix) = environment.get_env_var("HOMEBREW_PREFIX".to_string()) {
let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin");
if homebrew_prefix_bin.exists() {
return Some(homebrew_prefix_bin);
}
}
None
}
fn get_homebrew_prefix_bin(environment: &dyn Environment) -> Option<PathBuf> {
if let Some(homebrew_prefix) = get_homebrew_prefix_env_var(environment) {
return Some(homebrew_prefix);
}
// Homebrew install folders documented here https://docs.brew.sh/Installation
// /opt/homebrew for Apple Silicon,
// /usr/local for macOS Intel
// /home/linuxbrew/.linuxbrew for Linux
[
"/home/linuxbrew/.linuxbrew/bin",
"/opt/homebrew/bin",
"/usr/local/bin",
]
.iter()
.map(|p| PathBuf::from(p))
.find(|p| p.exists())
}
fn get_env_path(python_exe_from_bin_dir: &PathBuf, resolved_file: &PathBuf) -> Option<PathBuf> {
// If the fully resolved file path contains the words `/homebrew/` or `/linuxbrew/`
// Then we know this is definitely a home brew version of python.
// And in these cases we can compute the sysprefix.
let resolved_file = resolved_file.to_str()?;
// 1. MacOS Silicon
if python_exe_from_bin_dir
.to_string_lossy()
.to_lowercase()
.starts_with("/opt/homebrew/bin/python")
{
// Resolved exe is something like `/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12`
let reg_ex = Regex::new("/opt/homebrew/Cellar/python@((\\d+\\.?)*)/(\\d+\\.?)*/Frameworks/Python.framework/Versions/(\\d+\\.?)*/bin/python(\\d+\\.?)*").unwrap();
let captures = reg_ex.captures(&resolved_file)?;
let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
// SysPrefix- /opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12
let sys_prefix = PathBuf::from(format!(
"/opt/homebrew/opt/python@{}/Frameworks/Python.framework/Versions/{}",
version, version
));
return if sys_prefix.exists() {
Some(sys_prefix)
} else {
None
};
}
// 2. Linux
if python_exe_from_bin_dir
.to_string_lossy()
.to_lowercase()
.starts_with("/usr/local/bin/python")
{
// Resolved exe is something like `/home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3/bin/python3.12`
let reg_ex = Regex::new("/home/linuxbrew/.linuxbrew/Cellar/python@(\\d+\\.?\\d+\\.?)/(\\d+\\.?\\d+\\.?\\d+\\.?)/bin/python.*").unwrap();
let captures = reg_ex.captures(&resolved_file)?;
let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
let full_version = captures.get(2).map(|m| m.as_str()).unwrap_or_default();
// SysPrefix- /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3
let sys_prefix = PathBuf::from(format!(
"/home/linuxbrew/.linuxbrew/Cellar/python@{}/{}",
version, full_version
));
return if sys_prefix.exists() {
Some(sys_prefix)
} else {
None
};
}
// 3. MacOS Intel
if python_exe_from_bin_dir
.to_string_lossy()
.to_lowercase()
.starts_with("/usr/local/bin/python")
{
// Resolved exe is something like `/usr/local/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12`
let reg_ex = Regex::new("/usr/local/Cellar/python@(\\d+\\.?\\d+\\.?)/(\\d+\\.?\\d+\\.?\\d+\\.?)/Frameworks/Python.framework/Versions/(\\d+\\.?\\d+\\.?)/bin/python.*").unwrap();
let captures = reg_ex.captures(&resolved_file)?;
let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
let full_version = captures.get(2).map(|m| m.as_str()).unwrap_or_default();
// SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8
let sys_prefix = PathBuf::from(format!(
"/usr/local/Cellar/python@{}/{}/Frameworks/Python.framework/Versions/{}",
version, full_version, version
));
return if sys_prefix.exists() {
Some(sys_prefix)
} else {
None
};
}
None
}
fn get_python_info(
python_exe_from_bin_dir: &PathBuf,
reported: &mut HashSet<String>,
python_version_regex: &Regex,
) -> Option<PythonEnvironment> {
// Possible we do not have python3.12 or the like in bin directory
// & we have only python3, in that case we should add python3 to the list
if let Some(resolved_exe) = is_symlinked_python_executable(python_exe_from_bin_dir) {
let user_friendly_exe = python_exe_from_bin_dir;
let python_version = resolved_exe.to_string_lossy().to_string();
let version = match python_version_regex.captures(&python_version) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => None,
};
if reported.contains(&resolved_exe.to_string_lossy().to_string()) {
return None;
}
reported.insert(resolved_exe.to_string_lossy().to_string());
return Some(PythonEnvironment::new(
None,
None,
Some(user_friendly_exe.clone()),
crate::messaging::PythonEnvironmentCategory::Homebrew,
version,
get_env_path(python_exe_from_bin_dir, &resolved_exe),
None,
Some(vec![user_friendly_exe.to_string_lossy().to_string()]),
));
}
None
}
pub struct Homebrew<'a> {
pub environment: &'a dyn Environment,
}
impl Homebrew<'_> {
#[cfg(unix)]
pub fn with<'a>(environment: &'a impl Environment) -> Homebrew {
Homebrew { environment }
}
}
impl Locator for Homebrew<'_> {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap();
let exe = env.executable.clone();
let exe_file_name = exe.file_name()?;
let mut reported: HashSet<String> = HashSet::new();
if exe.starts_with("/opt/homebrew/bin/python")
|| exe.starts_with("/opt/homebrew/Cellar/python@")
|| exe.starts_with("/opt/homebrew/opt/python@")
|| exe.starts_with("/opt/homebrew/opt/python")
|| exe.starts_with("/opt/homebrew/Frameworks/Python.framework/Versions/")
{
// Symlink - /opt/homebrew/bin/python3.12
// Symlink - /opt/homebrew/opt/python3/bin/python3.12
// Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/bin/python3.12
// Symlink - /opt/homebrew/opt/python@3.12/bin/python3.12
// Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12
// Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/Current/bin/python3.12
// Symlink - /opt/homebrew/Frameworks/Python.framework/Versions/3.12/bin/python3.12
// Symlink - /opt/homebrew/Frameworks/Python.framework/Versions/Current/bin/python3.12
// Real exe - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12
// SysPrefix- /opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12
get_python_info(
&PathBuf::from("/opt/homebrew/bin").join(exe_file_name),
&mut reported,
&python_regex,
)
} else if exe.starts_with("/usr/local/bin/python")
|| exe.starts_with("/usr/local/opt/python@")
|| exe.starts_with("/usr/local/Cellar/python@")
{
// Symlink - /usr/local/bin/python3.8
// Symlink - /usr/local/opt/python@3.8/bin/python3.8
// Symlink - /usr/local/Cellar/python@3.8/3.8.19/bin/python3.8
// Real exe - /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/bin/python3.8
// SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8
get_python_info(
&PathBuf::from("/usr/local/bin").join(exe_file_name),
&mut reported,
&python_regex,
)
} else if exe.starts_with("/usr/local/bin/python")
|| exe.starts_with("/home/linuxbrew/.linuxbrew/bin/python")
|| exe.starts_with("/home/linuxbrew/.linuxbrew/opt/python@")
|| exe.starts_with("/home/linuxbrew/.linuxbrew/Cellar/python")
{
// Symlink - /usr/local/bin/python3.12
// Symlink - /home/linuxbrew/.linuxbrew/bin/python3.12
// Symlink - /home/linuxbrew/.linuxbrew/opt/python@3.12/bin/python3.12
// Real exe - /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3/bin/python3.12
// SysPrefix- /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3
get_python_info(
&PathBuf::from("/usr/local/bin").join(exe_file_name),
&mut reported,
&python_regex,
)
} else {
None
}
}
fn find(&mut self) -> Option<LocatorResult> {
let homebrew_prefix_bin = get_homebrew_prefix_bin(self.environment)?;
let mut reported: HashSet<String> = HashSet::new();
let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap();
let mut environments: Vec<PythonEnvironment> = vec![];
for file in std::fs::read_dir(&homebrew_prefix_bin)
.ok()?
.filter_map(Result::ok)
{
// If this file name is `python3`, then ignore this for now.
// We would prefer to use `python3.x` instead of `python3`.
// That way its more consistent and future proof
if let Some(file_name) = file.file_name().to_str() {
if file_name.to_lowercase() == "python3" {
continue;
}
}
if let Some(env) = get_python_info(&file.path(), &mut reported, &python_regex) {
environments.push(env);
}
}
// Possible we do not have python3.12 or the like in bin directory
// & we have only python3, in that case we should add python3 to the list
if let Some(env) = get_python_info(
&homebrew_prefix_bin.join("python3"),
&mut reported,
&python_regex,
) {
environments.push(env);
}
if environments.is_empty() {
None
} else {
Some(LocatorResult {
managers: vec![],
environments,
})
}
}
}

Просмотреть файл

@ -1,81 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use std::{env, path::PathBuf};
pub trait Environment {
fn get_user_home(&self) -> Option<PathBuf>;
/**
* Only used in tests, this is the root `/`.
*/
#[allow(dead_code)]
fn get_root(&self) -> Option<PathBuf>;
fn get_env_var(&self, key: String) -> Option<String>;
fn get_know_global_search_locations(&self) -> Vec<PathBuf>;
}
pub struct EnvironmentApi {}
impl EnvironmentApi {
pub fn new() -> Self {
EnvironmentApi {}
}
}
#[cfg(windows)]
impl Environment for EnvironmentApi {
fn get_user_home(&self) -> Option<PathBuf> {
get_user_home()
}
fn get_root(&self) -> Option<PathBuf> {
None
}
fn get_env_var(&self, key: String) -> Option<String> {
get_env_var(key)
}
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
vec![]
}
}
#[cfg(unix)]
impl Environment for EnvironmentApi {
fn get_user_home(&self) -> Option<PathBuf> {
get_user_home()
}
fn get_root(&self) -> Option<PathBuf> {
None
}
fn get_env_var(&self, key: String) -> Option<String> {
get_env_var(key)
}
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
vec![
PathBuf::from("/usr/bin"),
PathBuf::from("/usr/local/bin"),
PathBuf::from("/bin"),
PathBuf::from("/home/bin"),
PathBuf::from("/sbin"),
PathBuf::from("/usr/sbin"),
PathBuf::from("/usr/local/sbin"),
PathBuf::from("/home/sbin"),
PathBuf::from("/opt"),
PathBuf::from("/opt/bin"),
PathBuf::from("/opt/sbin"),
PathBuf::from("/opt/homebrew/bin"),
]
}
}
fn get_user_home() -> Option<PathBuf> {
let home = env::var("HOME").or_else(|_| env::var("USERPROFILE"));
match home {
Ok(home) => Some(PathBuf::from(home)),
Err(_) => None,
}
}
fn get_env_var(key: String) -> Option<String> {
match env::var(key) {
Ok(path) => Some(path),
Err(_) => None,
}
}

Просмотреть файл

@ -1,198 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use global_virtualenvs::list_global_virtual_envs;
use known::EnvironmentApi;
use locator::{Locator, LocatorResult};
use messaging::{create_dispatcher, JsonRpcDispatcher, MessageDispatcher};
use std::thread::{self, JoinHandle};
use utils::PythonEnv;
pub mod common_python;
pub mod conda;
pub mod global_virtualenvs;
pub mod homebrew;
pub mod known;
pub mod locator;
pub mod logging;
pub mod messaging;
pub mod pipenv;
pub mod pyenv;
pub mod utils;
pub mod venv;
pub mod virtualenv;
pub mod virtualenvwrapper;
pub mod windows_registry;
pub mod windows_store;
pub fn find_and_report_envs() {
let mut dispatcher: JsonRpcDispatcher = create_dispatcher();
// 1. Find using known global locators.
find_using_global_finders(&mut dispatcher);
// Step 2: Search in some global locations for virtual envs.
find_in_global_virtual_env_dirs(&mut dispatcher);
// Step 3: Finally find in the current PATH variable
let environment = EnvironmentApi::new();
let mut path_locator = common_python::PythonOnPath::with(&environment);
report_result(path_locator.find(), &mut dispatcher)
}
fn find_using_global_finders(dispatcher: &mut JsonRpcDispatcher) {
// Step 1: These environments take precedence over all others.
// As they are very specific and guaranteed to be specific type.
#[cfg(windows)]
fn find() -> Vec<JoinHandle<std::option::Option<LocatorResult>>> {
// The order matters,
// Windows store can sometimes get detected via registry locator (but we want to avoid that),
// difficult to repro, but we have see this on Karthiks machine
// Windows registry can contain conda envs (e.g. installing Ananconda will result in registry entries).
// Conda is best done last, as Windows Registry and Pyenv can also contain conda envs,
// Thus lets leave the generic conda locator to last to find all remaining conda envs.
// pyenv can be treated as a virtualenvwrapper environment, hence virtualenvwrapper needs to be detected first
vec![
// 1. windows store
thread::spawn(|| {
let environment = EnvironmentApi::new();
let mut windows_store = windows_store::WindowsStore::with(&environment);
windows_store.find()
}),
// 2. windows registry
thread::spawn(|| {
let environment = EnvironmentApi::new();
let mut conda_locator = conda::Conda::with(&environment);
windows_registry::WindowsRegistry::with(&mut conda_locator).find()
}),
// 3. virtualenvwrapper
thread::spawn(|| {
let environment = EnvironmentApi::new();
virtualenvwrapper::VirtualEnvWrapper::with(&environment).find()
}),
// 4. pyenv
thread::spawn(|| {
let environment = EnvironmentApi::new();
let mut conda_locator = conda::Conda::with(&environment);
pyenv::PyEnv::with(&environment, &mut conda_locator).find()
}),
// 5. conda
thread::spawn(|| {
let environment = EnvironmentApi::new();
conda::Conda::with(&environment).find()
}),
]
}
#[cfg(unix)]
fn find() -> Vec<JoinHandle<std::option::Option<LocatorResult>>> {
// The order matters,
// pyenv can be treated as a virtualenvwrapper environment, hence virtualenvwrapper needs to be detected first
// Homebrew can happen anytime
// Conda is best done last, as pyenv can also contain conda envs,
// Thus lets leave the generic conda locator to last to find all remaining conda envs.
vec![
// 1. virtualenvwrapper
thread::spawn(|| {
let environment = EnvironmentApi::new();
virtualenvwrapper::VirtualEnvWrapper::with(&environment).find()
}),
// 2. pyenv
thread::spawn(|| {
let environment = EnvironmentApi::new();
let mut conda_locator = conda::Conda::with(&environment);
pyenv::PyEnv::with(&environment, &mut conda_locator).find()
}),
// 3. homebrew
thread::spawn(|| {
let environment = EnvironmentApi::new();
homebrew::Homebrew::with(&environment).find()
}),
// 4. conda
thread::spawn(|| {
let environment = EnvironmentApi::new();
conda::Conda::with(&environment).find()
}),
]
}
for handle in find() {
if let Ok(result) = handle.join() {
report_result(result, dispatcher);
} else {
log::error!("Error getting result from thread.");
}
}
}
fn find_in_global_virtual_env_dirs(dispatcher: &mut JsonRpcDispatcher) -> Option<LocatorResult> {
// Step 1: These environments take precedence over all others.
// As they are very specific and guaranteed to be specific type.
let environment = EnvironmentApi::new();
let virtualenv_locator = virtualenv::VirtualEnv::new();
let venv_locator = venv::Venv::new();
let virtualenvwrapper = virtualenvwrapper::VirtualEnvWrapper::with(&environment);
let pipenv_locator = pipenv::PipEnv::new();
#[cfg(unix)]
let homebrew_locator = homebrew::Homebrew::with(&environment);
let venv_type_locators = vec![
Box::new(pipenv_locator) as Box<dyn Locator>,
Box::new(virtualenvwrapper) as Box<dyn Locator>,
Box::new(venv_locator) as Box<dyn Locator>,
Box::new(virtualenv_locator) as Box<dyn Locator>,
];
// Step 2: Search in some global locations for virtual envs.
for env in list_global_virtual_envs(&environment) {
if dispatcher.was_environment_reported(&env) {
continue;
}
// 1. First must be homebrew, as it is the most specific and supports symlinks
#[cfg(unix)]
if resolve_and_report_environment(&homebrew_locator, &env, dispatcher) {
continue;
}
// 3. Finally Check if these are some kind of virtual env or pipenv.
// Pipeenv before virtualenvwrapper as it is more specific.
// Because pipenv environments are also virtualenvwrapper environments.
// Before venv, as all venvs are also virtualenvwrapper environments.
// Before virtualenv as this is more specific.
// All venvs are also virtualenvs environments.
for locator in &venv_type_locators {
if resolve_and_report_environment(locator.as_ref(), &env, dispatcher) {
break;
}
}
}
None
}
fn resolve_and_report_environment(
locator: &dyn Locator,
env: &PythonEnv,
dispatcher: &mut JsonRpcDispatcher,
) -> bool {
if let Some(env) = locator.resolve(env) {
dispatcher.report_environment(env);
return true;
}
false
}
fn report_result(result: Option<LocatorResult>, dispatcher: &mut JsonRpcDispatcher) {
if let Some(result) = result {
result
.environments
.iter()
.for_each(|e| dispatcher.report_environment(e.clone()));
result
.managers
.iter()
.for_each(|m| dispatcher.report_environment_manager(m.clone()));
}
}

Просмотреть файл

@ -1,27 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::{
messaging::{EnvManager, PythonEnvironment},
utils::PythonEnv,
};
#[derive(Debug, Clone)]
pub struct LocatorResult {
pub managers: Vec<EnvManager>,
pub environments: Vec<PythonEnvironment>,
}
pub trait Locator {
/**
* Given a Python environment, this will convert it to a PythonEnvironment that can be supported by this locator.
* If an environment is not supported by this locator, this will return None.
*
* I.e. use this to test whether an environment is of a specific type.
*/
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment>;
/**
* Finds all environments specific to this locator.
*/
fn find(&mut self) -> Option<LocatorResult>;
}

Просмотреть файл

@ -1,41 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Eq, Clone)]
pub enum LogLevel {
#[serde(rename = "debug")]
Debug,
#[serde(rename = "info")]
Info,
#[serde(rename = "warning")]
Warning,
#[serde(rename = "error")]
Error,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Log {
pub message: String,
pub level: LogLevel,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogMessage {
pub jsonrpc: String,
pub method: String,
pub params: Log,
}
impl LogMessage {
pub fn new(message: String, level: LogLevel) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: "log".to_string(),
params: Log { message, level },
}
}
}

Просмотреть файл

@ -1,46 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use crate::messaging::initialize_logger;
use log::LevelFilter;
use messaging::{create_dispatcher, MessageDispatcher};
use python_finder::find_and_report_envs;
use std::time::SystemTime;
mod common_python;
mod conda;
mod global_virtualenvs;
mod homebrew;
mod known;
mod locator;
mod logging;
mod messaging;
mod pipenv;
mod pyenv;
mod utils;
mod venv;
mod virtualenv;
mod virtualenvwrapper;
mod windows_registry;
mod windows_store;
fn main() {
initialize_logger(LevelFilter::Trace);
log::info!("Starting Native Locator");
let now = SystemTime::now();
let mut dispatcher = create_dispatcher();
find_and_report_envs();
match now.elapsed() {
Ok(elapsed) => {
log::info!("Native Locator took {} milliseconds.", elapsed.as_millis());
}
Err(e) => {
log::error!("Error getting elapsed time: {:?}", e);
}
}
dispatcher.exit();
}

Просмотреть файл

@ -1,297 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use crate::{
logging::{LogLevel, LogMessage},
utils::{get_environment_key, get_environment_manager_key, PythonEnv},
};
use env_logger::Builder;
use log::LevelFilter;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, path::PathBuf, time::UNIX_EPOCH};
pub trait MessageDispatcher {
fn was_environment_reported(&self, env: &PythonEnv) -> bool;
fn report_environment_manager(&mut self, env: EnvManager) -> ();
fn report_environment(&mut self, env: PythonEnvironment) -> ();
fn exit(&mut self) -> ();
}
#[derive(Serialize, Deserialize, Copy, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub enum EnvManagerType {
Conda,
Pyenv,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub struct EnvManager {
pub executable_path: PathBuf,
pub version: Option<String>,
pub tool: EnvManagerType,
pub company: Option<String>,
pub company_display_name: Option<String>,
}
impl EnvManager {
pub fn new(executable_path: PathBuf, version: Option<String>, tool: EnvManagerType) -> Self {
Self {
executable_path,
version,
tool,
company: None,
company_display_name: None,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub struct EnvManagerMessage {
pub jsonrpc: String,
pub method: String,
pub params: EnvManager,
}
impl EnvManagerMessage {
pub fn new(params: EnvManager) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: "envManager".to_string(),
params,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub enum PythonEnvironmentCategory {
System,
Homebrew,
Conda,
Pyenv,
PyenvVirtualEnv,
WindowsStore,
WindowsRegistry,
Pipenv,
VirtualEnvWrapper,
Venv,
VirtualEnv,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub enum Architecture {
X64,
X86,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub struct PythonEnvironment {
pub display_name: Option<String>,
pub name: Option<String>,
pub python_executable_path: Option<PathBuf>,
pub category: PythonEnvironmentCategory,
pub version: Option<String>,
pub env_path: Option<PathBuf>,
pub env_manager: Option<EnvManager>,
pub python_run_command: Option<Vec<String>>,
/**
* The project path for the Pipenv environment.
*/
pub project_path: Option<PathBuf>,
pub arch: Option<Architecture>,
pub symlinks: Option<Vec<PathBuf>>,
pub creation_time: Option<u128>,
pub modified_time: Option<u128>,
pub company: Option<String>,
pub company_display_name: Option<String>,
}
impl Default for PythonEnvironment {
fn default() -> Self {
Self {
display_name: None,
name: None,
python_executable_path: None,
category: PythonEnvironmentCategory::System,
version: None,
env_path: None,
env_manager: None,
python_run_command: None,
project_path: None,
arch: None,
symlinks: None,
creation_time: None,
modified_time: None,
company: None,
company_display_name: None,
}
}
}
impl PythonEnvironment {
pub fn new(
display_name: Option<String>,
name: Option<String>,
python_executable_path: Option<PathBuf>,
category: PythonEnvironmentCategory,
version: Option<String>,
env_path: Option<PathBuf>,
env_manager: Option<EnvManager>,
python_run_command: Option<Vec<String>>,
) -> Self {
Self {
display_name,
name,
python_executable_path,
category,
version,
env_path,
env_manager,
python_run_command,
project_path: None,
arch: None,
symlinks: None,
creation_time: None,
modified_time: None,
company: None,
company_display_name: None,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub struct PythonEnvironmentMessage {
pub jsonrpc: String,
pub method: String,
pub params: PythonEnvironment,
}
impl PythonEnvironmentMessage {
pub fn new(params: PythonEnvironment) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: "pythonEnvironment".to_string(),
params,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
pub struct ExitMessage {
pub jsonrpc: String,
pub method: String,
pub params: Option<()>,
}
impl ExitMessage {
pub fn new() -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: "exit".to_string(),
params: None,
}
}
}
pub struct JsonRpcDispatcher {
pub reported_managers: HashSet<String>,
pub reported_environments: HashSet<String>,
}
pub fn send_message<T: serde::Serialize>(message: T) -> () {
let message = serde_json::to_string(&message).unwrap();
print!(
"Content-Length: {}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{}",
message.len(),
message
);
}
pub fn initialize_logger(log_level: LevelFilter) {
Builder::new()
.format(|_, record| {
let level = match record.level() {
log::Level::Debug => LogLevel::Debug,
log::Level::Error => LogLevel::Error,
log::Level::Info => LogLevel::Info,
log::Level::Warn => LogLevel::Warning,
_ => LogLevel::Debug,
};
send_message(LogMessage::new(
format!("{}", record.args()).to_string(),
level,
));
Ok(())
})
.filter(None, log_level)
.init();
}
impl JsonRpcDispatcher {}
impl MessageDispatcher for JsonRpcDispatcher {
fn was_environment_reported(&self, env: &PythonEnv) -> bool {
if let Some(key) = env.executable.as_os_str().to_str() {
return self.reported_environments.contains(key);
}
false
}
fn report_environment_manager(&mut self, env: EnvManager) -> () {
let key = get_environment_manager_key(&env);
if !self.reported_managers.contains(&key) {
self.reported_managers.insert(key);
send_message(EnvManagerMessage::new(env));
}
}
fn report_environment(&mut self, env: PythonEnvironment) -> () {
if let Some(key) = get_environment_key(&env) {
if let Some(ref manager) = env.env_manager {
self.report_environment_manager(manager.clone());
}
if !self.reported_environments.contains(&key) {
self.reported_environments.insert(key);
// Get the creation and modified times.
let mut env = env.clone();
if let Some(ref exe) = env.python_executable_path {
if let Ok(metadata) = exe.metadata() {
if let Ok(ctime) = metadata.created() {
if let Ok(ctime) = ctime.duration_since(UNIX_EPOCH) {
env.creation_time = Some(ctime.as_millis());
}
}
if let Ok(mtime) = metadata.modified() {
if let Ok(mtime) = mtime.duration_since(UNIX_EPOCH) {
env.modified_time = Some(mtime.as_millis());
}
}
}
}
send_message(PythonEnvironmentMessage::new(env));
}
}
}
fn exit(&mut self) -> () {
send_message(ExitMessage::new());
}
}
pub fn create_dispatcher() -> JsonRpcDispatcher {
JsonRpcDispatcher {
reported_managers: HashSet::new(),
reported_environments: HashSet::new(),
}
}

Просмотреть файл

@ -1,60 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::locator::{Locator, LocatorResult};
use crate::messaging::PythonEnvironment;
use crate::utils::PythonEnv;
use std::fs;
use std::path::PathBuf;
fn get_pipenv_project(env: &PythonEnv) -> Option<PathBuf> {
let project_file = env.path.clone()?.join(".project");
if let Ok(contents) = fs::read_to_string(project_file) {
let project_folder = PathBuf::from(contents.trim().to_string());
if project_folder.exists() {
return Some(project_folder);
}
}
None
}
fn is_pipenv(env: &PythonEnv) -> bool {
// If we have a Pipfile, then this is a pipenv environment.
// Else likely a virtualenvwrapper or the like.
if let Some(project_path) = get_pipenv_project(env) {
if project_path.join("Pipfile").exists() {
return true;
}
}
false
}
pub struct PipEnv {}
impl PipEnv {
pub fn new() -> PipEnv {
PipEnv {}
}
}
impl Locator for PipEnv {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if !is_pipenv(env) {
return None;
}
let project_path = get_pipenv_project(env)?;
Some(PythonEnvironment {
python_executable_path: Some(env.executable.clone()),
category: crate::messaging::PythonEnvironmentCategory::Pipenv,
version: env.version.clone(),
env_path: env.path.clone(),
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
project_path: Some(project_path),
..Default::default()
})
}
fn find(&mut self) -> Option<LocatorResult> {
None
}
}

Просмотреть файл

@ -1,290 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::conda::CondaLocator;
use crate::known;
use crate::known::Environment;
use crate::locator::Locator;
use crate::locator::LocatorResult;
use crate::messaging;
use crate::messaging::EnvManager;
use crate::messaging::EnvManagerType;
use crate::messaging::PythonEnvironment;
use crate::utils::find_and_parse_pyvenv_cfg;
use crate::utils::find_python_binary_path;
use crate::utils::PythonEnv;
use regex::Regex;
use std::fs;
use std::path::PathBuf;
#[cfg(windows)]
fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option<PathBuf> {
let home = environment.get_user_home()?;
Some(PathBuf::from(home).join(".pyenv").join("pyenv-win"))
}
#[cfg(unix)]
fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option<PathBuf> {
let home = environment.get_user_home()?;
Some(PathBuf::from(home).join(".pyenv"))
}
fn get_binary_from_known_paths(environment: &dyn known::Environment) -> Option<PathBuf> {
for known_path in environment.get_know_global_search_locations() {
let bin = known_path.join("pyenv");
if bin.exists() {
return Some(bin);
}
}
None
}
fn get_pyenv_dir(environment: &dyn known::Environment) -> Option<PathBuf> {
// Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix.
// They contain the path to pyenv's installation folder.
// If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix.
// If the interpreter path starts with the path to the pyenv folder, then it is a pyenv environment.
// See https://github.com/pyenv/pyenv#locating-the-python-installation for general usage,
// And https://github.com/pyenv-win/pyenv-win for Windows specifics.
match environment.get_env_var("PYENV_ROOT".to_string()) {
Some(dir) => Some(PathBuf::from(dir)),
None => match environment.get_env_var("PYENV".to_string()) {
Some(dir) => Some(PathBuf::from(dir)),
None => get_home_pyenv_dir(environment),
},
}
}
fn get_pyenv_binary(environment: &dyn known::Environment) -> Option<PathBuf> {
let dir = get_pyenv_dir(environment)?;
let exe = PathBuf::from(dir).join("bin").join("pyenv");
if fs::metadata(&exe).is_ok() {
Some(exe)
} else {
get_binary_from_known_paths(environment)
}
}
fn get_version(folder_name: &String) -> Option<String> {
// Stable Versions = like 3.10.10
let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => {
// Dev Versions = like 3.10-dev
let python_regex = Regex::new(r"^(\d+\.\d+-dev)$").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => {
// Alpha, rc Versions = like 3.10.0a3
let python_regex = Regex::new(r"^(\d+\.\d+.\d+\w\d+)").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => {
// win32 versions, rc Versions = like 3.11.0a-win32
let python_regex = Regex::new(r"^(\d+\.\d+.\d+\w\d+)-win32").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => None,
}
}
}
}
}
}
}
}
fn get_pure_python_environment(
executable: &PathBuf,
path: &PathBuf,
manager: &Option<EnvManager>,
) -> Option<PythonEnvironment> {
let file_name = path.file_name()?.to_string_lossy().to_string();
let version = get_version(&file_name)?;
let mut env = messaging::PythonEnvironment::new(
None,
None,
Some(executable.clone()),
messaging::PythonEnvironmentCategory::Pyenv,
Some(version),
Some(path.clone()),
manager.clone(),
Some(vec![executable
.clone()
.into_os_string()
.into_string()
.unwrap()]),
);
if file_name.ends_with("-win32") {
env.arch = Some(messaging::Architecture::X86);
}
Some(env)
}
fn is_conda_environment(path: &PathBuf) -> bool {
if let Some(name) = path.file_name() {
let name = name.to_ascii_lowercase().to_string_lossy().to_string();
return name.starts_with("anaconda")
|| name.starts_with("miniconda")
|| name.starts_with("miniforge");
}
false
}
fn get_virtual_env_environment(
executable: &PathBuf,
path: &PathBuf,
manager: &Option<EnvManager>,
) -> Option<messaging::PythonEnvironment> {
let pyenv_cfg = find_and_parse_pyvenv_cfg(executable)?;
let folder_name = path.file_name().unwrap().to_string_lossy().to_string();
Some(messaging::PythonEnvironment::new(
None,
Some(folder_name),
Some(executable.clone()),
messaging::PythonEnvironmentCategory::PyenvVirtualEnv,
Some(pyenv_cfg.version),
Some(path.clone()),
manager.clone(),
Some(vec![executable
.clone()
.into_os_string()
.into_string()
.unwrap()]),
))
}
pub fn list_pyenv_environments(
manager: &Option<EnvManager>,
environment: &dyn known::Environment,
conda_locator: &mut dyn CondaLocator,
) -> Option<Vec<messaging::PythonEnvironment>> {
let pyenv_dir = get_pyenv_dir(environment)?;
let mut envs: Vec<messaging::PythonEnvironment> = vec![];
let versions_dir = PathBuf::from(&pyenv_dir)
.join("versions")
.into_os_string()
.into_string()
.ok()?;
for entry in fs::read_dir(&versions_dir).ok()?.filter_map(Result::ok) {
let path = entry.path();
if !path.is_dir() {
continue;
}
if let Some(executable) = find_python_binary_path(&path) {
if let Some(env) = get_pure_python_environment(&executable, &path, manager) {
envs.push(env);
} else if let Some(env) = get_virtual_env_environment(&executable, &path, manager) {
envs.push(env);
} else if is_conda_environment(&path) {
if let Some(result) = conda_locator.find_in(&path) {
result.environments.iter().for_each(|e| {
envs.push(e.clone());
});
}
}
}
}
Some(envs)
}
#[cfg(windows)]
fn get_pyenv_manager_version(
_pyenv_binary_path: &PathBuf,
environment: &dyn known::Environment,
) -> Option<String> {
// In windows, the version is stored in the `.pyenv/.version` file
let pyenv_dir = get_pyenv_dir(environment)?;
let mut version_file = PathBuf::from(&pyenv_dir).join(".version");
if !version_file.exists() {
// We might have got the path `~/.pyenv/pyenv-win`
version_file = pyenv_dir.parent()?.join(".version");
if !version_file.exists() {
return None;
}
}
let version = fs::read_to_string(version_file).ok()?;
let version_regex = Regex::new(r"(\d+\.\d+\.\d+)").unwrap();
let captures = version_regex.captures(&version)?.get(1)?;
Some(captures.as_str().to_string())
}
#[cfg(unix)]
fn get_pyenv_manager_version(
pyenv_binary_path: &PathBuf,
_environment: &dyn known::Environment,
) -> Option<String> {
// Look for version in path
// Sample /opt/homebrew/Cellar/pyenv/2.4.0/libexec/pyenv
if !pyenv_binary_path.to_string_lossy().contains("/pyenv/") {
return None;
}
// Find the real path, generally we have a symlink.
let real_path = fs::read_link(pyenv_binary_path)
.ok()?
.to_string_lossy()
.to_string();
let version_regex = Regex::new(r"pyenv/(\d+\.\d+\.\d+)/").unwrap();
let captures = version_regex.captures(&real_path)?.get(1)?;
Some(captures.as_str().to_string())
}
pub struct PyEnv<'a> {
pub environment: &'a dyn Environment,
pub conda_locator: &'a mut dyn CondaLocator,
}
impl PyEnv<'_> {
pub fn with<'a>(
environment: &'a impl Environment,
conda_locator: &'a mut impl CondaLocator,
) -> PyEnv<'a> {
PyEnv {
environment,
conda_locator,
}
}
}
impl Locator for PyEnv<'_> {
fn resolve(&self, _env: &PythonEnv) -> Option<PythonEnvironment> {
// We will find everything in gather
None
}
fn find(&mut self) -> Option<LocatorResult> {
let pyenv_binary = get_pyenv_binary(self.environment)?;
let version = get_pyenv_manager_version(&pyenv_binary, self.environment);
let manager = messaging::EnvManager::new(pyenv_binary, version, EnvManagerType::Pyenv);
let mut environments: Vec<PythonEnvironment> = vec![];
if let Some(envs) =
list_pyenv_environments(&Some(manager.clone()), self.environment, self.conda_locator)
{
for env in envs {
environments.push(env);
}
}
Some(LocatorResult {
managers: vec![manager],
environments,
})
}
}

Просмотреть файл

@ -1,148 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use crate::messaging::{EnvManager, PythonEnvironment};
use regex::Regex;
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Debug)]
pub struct PythonEnv {
pub executable: PathBuf,
pub path: Option<PathBuf>,
pub version: Option<String>,
}
impl PythonEnv {
pub fn new(executable: PathBuf, path: Option<PathBuf>, version: Option<String>) -> Self {
Self {
executable,
path,
version,
}
}
}
#[derive(Debug)]
pub struct PyEnvCfg {
pub version: String,
}
const PYVENV_CONFIG_FILE: &str = "pyvenv.cfg";
pub fn find_pyvenv_config_path(python_executable: &PathBuf) -> Option<PathBuf> {
// Check if the pyvenv.cfg file is in the parent directory relative to the interpreter.
// env
// |__ pyvenv.cfg <--- check if this file exists
// |__ bin or Scripts
// |__ python <--- interpreterPath
let cfg = python_executable.parent()?.join(PYVENV_CONFIG_FILE);
if fs::metadata(&cfg).is_ok() {
return Some(cfg);
}
// Check if the pyvenv.cfg file is in the directory as the interpreter.
// env
// |__ pyvenv.cfg <--- check if this file exists
// |__ python <--- interpreterPath
let cfg = python_executable
.parent()?
.parent()?
.join(PYVENV_CONFIG_FILE);
if fs::metadata(&cfg).is_ok() {
return Some(cfg);
}
None
}
pub fn find_and_parse_pyvenv_cfg(python_executable: &PathBuf) -> Option<PyEnvCfg> {
let cfg = find_pyvenv_config_path(&PathBuf::from(python_executable))?;
if !fs::metadata(&cfg).is_ok() {
return None;
}
let contents = fs::read_to_string(&cfg).ok()?;
let version_regex = Regex::new(r"^version\s*=\s*(\d+\.\d+\.\d+)$").unwrap();
let version_info_regex = Regex::new(r"^version_info\s*=\s*(\d+\.\d+\.\d+.*)$").unwrap();
for line in contents.lines() {
if !line.contains("version") {
continue;
}
if let Some(captures) = version_regex.captures(line) {
if let Some(value) = captures.get(1) {
return Some(PyEnvCfg {
version: value.as_str().to_string(),
});
}
}
if let Some(captures) = version_info_regex.captures(line) {
if let Some(value) = captures.get(1) {
return Some(PyEnvCfg {
version: value.as_str().to_string(),
});
}
}
}
None
}
pub fn get_version(python_executable: &PathBuf) -> Option<String> {
if let Some(parent_folder) = python_executable.parent() {
if let Some(pyenv_cfg) = find_and_parse_pyvenv_cfg(&parent_folder.to_path_buf()) {
return Some(pyenv_cfg.version);
}
}
None
}
pub fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
let python_bin_name = if cfg!(windows) {
"python.exe"
} else {
"python"
};
let path_1 = env_path.join("bin").join(python_bin_name);
let path_2 = env_path.join("Scripts").join(python_bin_name);
let path_3 = env_path.join(python_bin_name);
let paths = vec![path_1, path_2, path_3];
paths.into_iter().find(|path| path.exists())
}
pub fn list_python_environments(path: &PathBuf) -> Option<Vec<PythonEnv>> {
let mut python_envs: Vec<PythonEnv> = vec![];
for venv_dir in fs::read_dir(path).ok()? {
if let Ok(venv_dir) = venv_dir {
let venv_dir = venv_dir.path();
if !venv_dir.is_dir() {
continue;
}
if let Some(executable) = find_python_binary_path(&venv_dir) {
python_envs.push(PythonEnv::new(
executable.clone(),
Some(venv_dir),
get_version(&executable),
));
}
}
}
Some(python_envs)
}
pub fn get_environment_key(env: &PythonEnvironment) -> Option<String> {
if let Some(ref path) = env.python_executable_path {
return Some(path.to_string_lossy().to_string());
}
if let Some(ref path) = env.env_path {
return Some(path.to_string_lossy().to_string());
}
None
}
pub fn get_environment_manager_key(env: &EnvManager) -> String {
return env.executable_path.to_string_lossy().to_string();
}

Просмотреть файл

@ -1,54 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::{
locator::{Locator, LocatorResult},
messaging::PythonEnvironment,
utils::{self, PythonEnv},
};
pub fn is_venv(env: &PythonEnv) -> bool {
// env path cannot be empty.
if env.path.is_none() {
return false;
}
return utils::find_pyvenv_config_path(&env.executable).is_some();
}
pub struct Venv {}
impl Venv {
pub fn new() -> Venv {
Venv {}
}
}
impl Locator for Venv {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if is_venv(&env) {
return Some(PythonEnvironment {
name: Some(
env.path
.clone()
.expect("env.path can never be empty for venvs")
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
),
python_executable_path: Some(env.executable.clone()),
version: env.version.clone(),
category: crate::messaging::PythonEnvironmentCategory::Venv,
env_path: env.path.clone(),
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
..Default::default()
});
}
None
}
fn find(&mut self) -> Option<LocatorResult> {
// There are no common global locations for virtual environments.
// We expect the user of this class to call `is_compatible`
None
}
}

Просмотреть файл

@ -1,85 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::locator::{Locator, LocatorResult};
use crate::messaging::PythonEnvironment;
use crate::utils::PythonEnv;
pub fn is_virtualenv(env: &PythonEnv) -> bool {
if env.path.is_none() {
return false;
}
if let Some(file_path) = env.executable.parent() {
// Check if there are any activate.* files in the same directory as the interpreter.
//
// env
// |__ activate, activate.* <--- check if any of these files exist
// |__ python <--- interpreterPath
// if let Some(parent_path) = PathBuf::from(env.)
// const directory = path.dirname(interpreterPath);
// const files = await fsapi.readdir(directory);
// const regex = /^activate(\.([A-z]|\d)+)?$/i;
if file_path.join("activate").exists() || file_path.join("activate.bat").exists() {
return true;
}
// Support for activate.ps, etc.
match std::fs::read_dir(file_path) {
Ok(files) => {
for file in files {
if let Ok(file) = file {
if let Some(file_name) = file.file_name().to_str() {
if file_name.starts_with("activate") {
return true;
}
}
}
}
return false;
}
Err(_) => return false,
};
}
false
}
pub struct VirtualEnv {}
impl VirtualEnv {
pub fn new() -> VirtualEnv {
VirtualEnv {}
}
}
impl Locator for VirtualEnv {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if is_virtualenv(env) {
return Some(PythonEnvironment {
name: Some(
env.path
.clone()
.expect("env.path can never be empty for virtualenvs")
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
),
python_executable_path: Some(env.executable.clone()),
version: env.version.clone(),
category: crate::messaging::PythonEnvironmentCategory::VirtualEnv,
env_path: env.path.clone(),
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
..Default::default()
});
}
None
}
fn find(&mut self) -> Option<LocatorResult> {
// There are no common global locations for virtual environments.
// We expect the user of this class to call `is_compatible`
None
}
}

Просмотреть файл

@ -1,135 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::locator::{Locator, LocatorResult};
use crate::messaging::PythonEnvironment;
use crate::utils::list_python_environments;
use crate::virtualenv;
use crate::{known::Environment, utils::PythonEnv};
use std::fs;
use std::path::PathBuf;
#[cfg(windows)]
fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option<PathBuf> {
// In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs.
// If 'Envs' is not available we should default to '.virtualenvs'. Since that
// is also valid for windows.
if let Some(home) = environment.get_user_home() {
let home = PathBuf::from(home).join("Envs");
if home.exists() {
return Some(home);
}
let home = PathBuf::from(home).join("virtualenvs");
if home.exists() {
return Some(home);
}
}
None
}
#[cfg(unix)]
fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option<PathBuf> {
if let Some(home) = environment.get_user_home() {
let home = PathBuf::from(home).join(".virtualenvs");
if home.exists() {
return Some(home);
}
}
None
}
pub fn get_work_on_home_path(environment: &dyn Environment) -> Option<PathBuf> {
// The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments.
// If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment.
if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) {
if let Ok(work_on_home) = std::fs::canonicalize(work_on_home) {
if work_on_home.exists() {
return Some(work_on_home);
}
}
}
get_default_virtualenvwrapper_path(environment)
}
pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> bool {
if env.path.is_none() {
return false;
}
// For environment to be a virtualenvwrapper based it has to follow these two rules:
// 1. It should be in a sub-directory under the WORKON_HOME
// 2. It should be a valid virtualenv environment
if let Some(work_on_home_dir) = get_work_on_home_path(environment) {
if env.executable.starts_with(&work_on_home_dir) && virtualenv::is_virtualenv(env) {
return true;
}
}
false
}
fn get_project(env: &PythonEnv) -> Option<PathBuf> {
let project_file = env.path.clone()?.join(".project");
if let Ok(contents) = fs::read_to_string(project_file) {
let project_folder = PathBuf::from(contents.trim().to_string());
if project_folder.exists() {
return Some(project_folder);
}
}
None
}
pub struct VirtualEnvWrapper<'a> {
pub environment: &'a dyn Environment,
}
impl VirtualEnvWrapper<'_> {
pub fn with<'a>(environment: &'a impl Environment) -> VirtualEnvWrapper {
VirtualEnvWrapper { environment }
}
}
impl Locator for VirtualEnvWrapper<'_> {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if !is_virtualenvwrapper(env, self.environment) {
return None;
}
Some(PythonEnvironment {
name: Some(
env.path
.clone()
.expect("env.path cannot be empty for virtualenv rapper")
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
),
python_executable_path: Some(env.executable.clone()),
version: env.version.clone(),
category: crate::messaging::PythonEnvironmentCategory::VirtualEnvWrapper,
env_path: env.path.clone(),
project_path: get_project(env),
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
..Default::default()
})
}
fn find(&mut self) -> Option<LocatorResult> {
let work_on_home = get_work_on_home_path(self.environment)?;
let envs = list_python_environments(&work_on_home)?;
let mut environments: Vec<PythonEnvironment> = vec![];
envs.iter().for_each(|env| {
if let Some(env) = self.resolve(env) {
environments.push(env);
}
});
if environments.is_empty() {
None
} else {
Some(LocatorResult {
managers: vec![],
environments,
})
}
}
}

Просмотреть файл

@ -1,254 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#[cfg(windows)]
use crate::conda::CondaLocator;
#[cfg(windows)]
use crate::locator::{Locator, LocatorResult};
#[cfg(windows)]
use crate::messaging::EnvManager;
#[cfg(windows)]
use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory};
#[cfg(windows)]
use crate::utils::PythonEnv;
#[cfg(windows)]
use crate::windows_store::is_windows_app_folder_in_program_files;
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use winreg::RegKey;
#[cfg(windows)]
fn get_registry_pythons_from_key_for_company(
key_container: &str,
company_key: &RegKey,
company: &str,
conda_locator: &mut dyn CondaLocator,
) -> Option<LocatorResult> {
use log::{trace, warn};
use crate::messaging::Architecture;
let mut managers: Vec<EnvManager> = vec![];
let mut environments = vec![];
let company_display_name: Option<String> = company_key.get_value("DisplayName").ok();
for installed_python in company_key.enum_keys().filter_map(Result::ok) {
match company_key.open_subkey(installed_python.clone()) {
Ok(installed_python_key) => {
match installed_python_key.open_subkey("InstallPath") {
Ok(install_path_key) => {
let env_path: String =
install_path_key.get_value("").ok().unwrap_or_default();
let env_path = PathBuf::from(env_path);
if is_windows_app_folder_in_program_files(&env_path) {
trace!(
"Found Python ({}) in {}\\Software\\Python\\{}\\{}, but skipping as this is a Windows Store Python",
env_path.to_str().unwrap_or_default(),
key_container,
company,
installed_python,
);
continue;
}
trace!(
"Found Python ({}) in {}\\Software\\Python\\{}\\{}",
env_path.to_str().unwrap_or_default(),
key_container,
company,
installed_python,
);
// Possible this is a conda install folder.
if let Some(conda_result) = conda_locator.find_in(&env_path) {
for manager in conda_result.managers {
let mut mgr = manager.clone();
mgr.company = Some(company.to_string());
mgr.company_display_name = company_display_name.clone();
managers.push(mgr)
}
for env in conda_result.environments {
let mut env = env.clone();
env.company = Some(company.to_string());
env.company_display_name = company_display_name.clone();
if let Some(mgr) = env.env_manager {
let mut mgr = mgr.clone();
mgr.company = Some(company.to_string());
mgr.company_display_name = company_display_name.clone();
env.env_manager = Some(mgr);
}
environments.push(env);
}
continue;
}
let env_path = if env_path.exists() {
Some(env_path)
} else {
None
};
let executable: String = install_path_key
.get_value("ExecutablePath")
.ok()
.unwrap_or_default();
if executable.len() == 0 {
warn!(
"Executable is empty {}\\Software\\Python\\{}\\{}\\ExecutablePath",
key_container, company, installed_python
);
continue;
}
let executable = PathBuf::from(executable);
if !executable.exists() {
warn!(
"Python executable ({}) file not found for {}\\Software\\Python\\{}\\{}",
executable.to_str().unwrap_or_default(),
key_container,
company,
installed_python
);
continue;
}
let version: String = installed_python_key
.get_value("Version")
.ok()
.unwrap_or_default();
let architecture: String = installed_python_key
.get_value("SysArchitecture")
.ok()
.unwrap_or_default();
let display_name: String = installed_python_key
.get_value("DisplayName")
.ok()
.unwrap_or_default();
let mut env = PythonEnvironment::new(
Some(display_name),
None,
Some(executable.clone()),
PythonEnvironmentCategory::WindowsRegistry,
if version.len() > 0 {
Some(version)
} else {
None
},
env_path,
None,
Some(vec![executable.to_string_lossy().to_string()]),
);
if architecture.contains("32") {
env.arch = Some(Architecture::X86);
} else if architecture.contains("64") {
env.arch = Some(Architecture::X64);
}
env.company = Some(company.to_string());
env.company_display_name = company_display_name.clone();
environments.push(env);
}
Err(err) => {
warn!(
"Failed to open {}\\Software\\Python\\{}\\{}\\InstallPath, {:?}",
key_container, company, installed_python, err
);
}
}
}
Err(err) => {
warn!(
"Failed to open {}\\Software\\Python\\{}\\{}, {:?}",
key_container, company, installed_python, err
);
}
}
}
Some(LocatorResult {
environments,
managers,
})
}
#[cfg(windows)]
fn get_registry_pythons(conda_locator: &mut dyn CondaLocator) -> Option<LocatorResult> {
use log::{trace, warn};
let mut environments = vec![];
let mut managers: Vec<EnvManager> = vec![];
struct RegistryKey {
pub name: &'static str,
pub key: winreg::RegKey,
}
let search_keys = [
RegistryKey {
name: "HKLM",
key: winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE),
},
RegistryKey {
name: "HKCU",
key: winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER),
},
];
for (name, key) in search_keys.iter().map(|f| (f.name, &f.key)) {
match key.open_subkey("Software\\Python") {
Ok(python_key) => {
for company in python_key.enum_keys().filter_map(Result::ok) {
trace!("Searching {}\\Software\\Python\\{}", name, company);
match python_key.open_subkey(&company) {
Ok(company_key) => {
if let Some(result) = get_registry_pythons_from_key_for_company(
name,
&company_key,
&company,
conda_locator,
) {
managers.extend(result.managers);
environments.extend(result.environments);
}
}
Err(err) => {
warn!(
"Failed to open {}\\Software\\Python\\{}, {:?}",
name, company, err
);
}
}
}
}
Err(err) => {
warn!("Failed to open {}\\Software\\Python, {:?}", name, err)
}
}
}
Some(LocatorResult {
environments,
managers,
})
}
#[cfg(windows)]
pub struct WindowsRegistry<'a> {
pub conda_locator: &'a mut dyn CondaLocator,
}
#[cfg(windows)]
impl WindowsRegistry<'_> {
#[allow(dead_code)]
pub fn with<'a>(conda_locator: &'a mut impl CondaLocator) -> WindowsRegistry<'a> {
WindowsRegistry { conda_locator }
}
}
#[cfg(windows)]
impl Locator for WindowsRegistry<'_> {
fn resolve(&self, _env: &PythonEnv) -> Option<PythonEnvironment> {
None
}
fn find(&mut self) -> Option<LocatorResult> {
if let Some(result) = get_registry_pythons(self.conda_locator) {
if !result.environments.is_empty() || !result.managers.is_empty() {
return Some(result);
}
}
None
}
}

Просмотреть файл

@ -1,249 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#[cfg(windows)]
use crate::known;
#[cfg(windows)]
use crate::known::Environment;
#[cfg(windows)]
use crate::locator::{Locator, LocatorResult};
#[cfg(windows)]
use crate::messaging::PythonEnvironment;
#[cfg(windows)]
use crate::utils::PythonEnv;
#[cfg(windows)]
use log::{trace, warn};
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use winreg::RegKey;
#[cfg(windows)]
pub fn is_windows_python_executable(path: &PathBuf) -> bool {
let name = path.file_name().unwrap().to_string_lossy().to_lowercase();
// TODO: Is it safe to assume the number 3?
name.starts_with("python3.") && name.ends_with(".exe")
}
#[cfg(windows)]
pub fn is_windows_app_folder_in_program_files(path: &PathBuf) -> bool {
path.to_str().unwrap_or_default().to_string().to_lowercase()[1..].starts_with(":\\program files\\windowsapps")
}
#[cfg(windows)]
fn list_windows_store_python_executables(
environment: &dyn known::Environment,
) -> Option<Vec<PythonEnvironment>> {
use crate::messaging::Architecture;
use regex::Regex;
use std::collections::HashMap;
let mut python_envs: Vec<PythonEnvironment> = vec![];
let home = environment.get_user_home()?;
let apps_path = Path::new(&home)
.join("AppData")
.join("Local")
.join("Microsoft")
.join("WindowsApps");
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
trace!("Searching for Windows Store Python in {:?}", apps_path);
let folder_version_regex =
Regex::new("PythonSoftwareFoundation.Python.(\\d+\\.\\d+)_.*").unwrap();
let exe_version_regex = Regex::new("python(\\d+\\.\\d+).exe").unwrap();
#[derive(Default)]
struct PotentialPython {
path: Option<PathBuf>,
name: Option<String>,
exe: Option<PathBuf>,
version: String,
}
let mut potential_matches: HashMap<String, PotentialPython> = HashMap::new();
for path in std::fs::read_dir(apps_path)
.ok()?
.filter_map(Result::ok)
.map(|f| f.path())
{
if let Some(name) = path.file_name() {
let name = name.to_string_lossy().to_string();
if name.starts_with("PythonSoftwareFoundation.Python.") {
let simple_version = folder_version_regex.captures(&name)?;
let simple_version = simple_version
.get(1)
.map(|m| m.as_str())
.unwrap_or_default();
if simple_version.len() == 0 {
continue;
}
if let Some(existing) = potential_matches.get_mut(&simple_version.to_string()) {
existing.path = Some(path.clone());
existing.name = Some(name.clone());
} else {
let item = PotentialPython {
path: Some(path.clone()),
name: Some(name.clone()),
version: simple_version.to_string(),
..Default::default()
};
potential_matches.insert(simple_version.to_string(), item);
}
} else if name.starts_with("python") && name.ends_with(".exe") {
if name == "python.exe" || name == "python3.exe" {
// Unfortunately we have no idea what these point to.
// Even old python code didn't report these, hopefully users will not use these.
// If they do, we might have to spawn Python to find the real path and match it to one of the items discovered.
continue;
}
if let Some(simple_version) = exe_version_regex.captures(&name) {
let simple_version = simple_version
.get(1)
.map(|m| m.as_str())
.unwrap_or_default();
if simple_version.len() == 0 {
continue;
}
if let Some(existing) = potential_matches.get_mut(&simple_version.to_string()) {
existing.exe = Some(path.clone());
} else {
let item = PotentialPython {
exe: Some(path.clone()),
version: simple_version.to_string(),
..Default::default()
};
potential_matches.insert(simple_version.to_string(), item);
}
}
}
}
}
for (_, item) in potential_matches {
if item.exe.is_none() {
warn!(
"Did not find a Windows Store exe for version {:?} that coresponds to path {:?}",
item.version, item.path
);
continue;
}
if item.path.is_none() {
warn!(
"Did not find a Windows Store path for version {:?} that coresponds to exe {:?}",
item.version, item.exe
);
continue;
}
let name = item.name.unwrap_or_default();
let path = item.path.unwrap_or_default();
let exe = item.exe.unwrap_or_default();
let parent = path.parent()?.to_path_buf(); // This dir definitely exists.
if let Some(result) = get_package_display_name_and_location(&name, &hkcu) {
let env_path = PathBuf::from(result.env_path);
let env = PythonEnvironment {
display_name: Some(result.display_name),
python_executable_path: Some(exe.clone()),
category: crate::messaging::PythonEnvironmentCategory::WindowsStore,
env_path: Some(env_path.clone()),
python_run_command: Some(vec![exe.to_string_lossy().to_string()]),
arch: if result.is64_bit {
Some(Architecture::X64)
} else {
None
},
version: Some(item.version.clone()),
symlinks: Some(vec![
parent.join(format!("python{:?}.exe", item.version)),
path.join("python.exe"),
path.join("python3.exe"),
path.join(format!("python{:?}.exe", item.version)),
env_path.join("python.exe"),
env_path.join(format!("python{:?}.exe", item.version)),
]),
..Default::default()
};
python_envs.push(env);
} else {
warn!(
"Failed to get package display name & location for Windows Store Package {:?}",
path
);
}
}
Some(python_envs)
}
#[cfg(windows)]
fn get_package_full_name_from_registry(name: &String, hkcu: &RegKey) -> Option<String> {
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\SystemAppData\\{}\\Schemas", name);
trace!("Opening registry key {:?}", key);
let package_key = hkcu.open_subkey(key).ok()?;
let value = package_key.get_value("PackageFullName").ok()?;
Some(value)
}
#[derive(Debug)]
#[cfg(windows)]
struct StorePythonInfo {
display_name: String,
env_path: String,
is64_bit: bool,
}
#[cfg(windows)]
fn get_package_display_name_and_location(name: &String, hkcu: &RegKey) -> Option<StorePythonInfo> {
if let Some(name) = get_package_full_name_from_registry(name, &hkcu) {
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\Repository\\Packages\\{}", name);
trace!("Opening registry key {:?}", key);
let package_key = hkcu.open_subkey(key).ok()?;
let display_name = package_key.get_value("DisplayName").ok()?;
let env_path = package_key.get_value("PackageRootFolder").ok()?;
return Some(StorePythonInfo {
display_name,
env_path,
is64_bit: name.contains("_x64_"),
});
}
None
}
#[cfg(windows)]
pub struct WindowsStore<'a> {
pub environment: &'a dyn Environment,
}
#[cfg(windows)]
impl WindowsStore<'_> {
#[allow(dead_code)]
pub fn with<'a>(environment: &'a impl Environment) -> WindowsStore {
WindowsStore { environment }
}
}
#[cfg(windows)]
impl Locator for WindowsStore<'_> {
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if is_windows_python_executable(&env.executable) {
return Some(PythonEnvironment {
python_executable_path: Some(env.executable.clone()),
category: crate::messaging::PythonEnvironmentCategory::WindowsStore,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
..Default::default()
});
}
None
}
fn find(&mut self) -> Option<LocatorResult> {
let environments = list_windows_store_python_executables(self.environment)?;
if environments.is_empty() {
None
} else {
Some(LocatorResult {
managers: vec![],
environments,
})
}
}
}

Просмотреть файл

@ -1,147 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use python_finder::known::Environment;
use serde_json::Value;
use std::{collections::HashMap, path::PathBuf};
#[allow(dead_code)]
pub fn test_file_path(paths: &[&str]) -> PathBuf {
let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
paths.iter().for_each(|p| root.push(p));
root
}
#[allow(dead_code)]
pub fn join_test_paths(paths: &[&str]) -> PathBuf {
let path: PathBuf = paths.iter().map(|p| p.to_string()).collect();
path
}
#[allow(dead_code)]
pub trait TestMessages {
fn get_messages(&self) -> Vec<String>;
}
#[allow(dead_code)]
pub struct TestEnvironment {
vars: HashMap<String, String>,
home: Option<PathBuf>,
root: Option<PathBuf>,
globals_locations: Vec<PathBuf>,
}
#[allow(dead_code)]
pub fn create_test_environment(
vars: HashMap<String, String>,
home: Option<PathBuf>,
globals_locations: Vec<PathBuf>,
root: Option<PathBuf>,
) -> TestEnvironment {
impl Environment for TestEnvironment {
fn get_env_var(&self, key: String) -> Option<String> {
self.vars.get(&key).cloned()
}
fn get_root(&self) -> Option<PathBuf> {
self.root.clone()
}
fn get_user_home(&self) -> Option<PathBuf> {
self.home.clone()
}
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
self.globals_locations.clone()
}
}
TestEnvironment {
vars,
home,
root,
globals_locations,
}
}
fn compare_json(expected: &Value, actual: &Value) -> bool {
if expected == actual {
return true;
}
if expected.is_object() {
if expected.as_object().is_none() && actual.as_object().is_none() {
return true;
}
if expected.as_object().is_none() && actual.as_object().is_some() {
return false;
}
if expected.as_object().is_some() && actual.as_object().is_none() {
return false;
}
let expected = expected.as_object().unwrap();
let actual = actual.as_object().unwrap();
for (key, value) in expected.iter() {
if !actual.contains_key(key) {
return false;
}
if !compare_json(value, actual.get(key).unwrap()) {
return false;
}
}
return true;
}
if expected.is_array() {
let expected = expected.as_array().unwrap();
let actual = actual.as_array().unwrap();
if expected.len() != actual.len() {
return false;
}
for (i, value) in expected.iter().enumerate() {
if !compare_json(value, actual.get(i).unwrap()) {
return false;
}
}
return true;
}
false
}
#[allow(dead_code)]
pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) {
let mut expected_json = expected_json.to_vec();
assert_eq!(
expected_json.len(),
actual_json.len(),
"Incorrect number of messages"
);
if expected_json.len() == 0 {
return;
}
// Ignore the order of the json items when comparing.
for actual in actual_json.iter() {
let mut valid_index: Option<usize> = None;
for (i, expected) in expected_json.iter().enumerate() {
if !compare_json(expected, &actual) {
continue;
}
// Ensure we verify using standard assert_eq!, just in case the code is faulty..
valid_index = Some(i);
assert_eq!(expected, actual);
}
if let Some(index) = valid_index {
// This is to ensure we don't compare the same item twice.
expected_json.remove(index);
} else {
// Use traditional assert so we can see the fully output in the test results.
assert_eq!(&expected_json[0], actual);
}
}
}

Просмотреть файл

@ -1,55 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod common;
#[test]
#[cfg(unix)]
fn find_python_in_path_this() {
use crate::common::{
assert_messages, create_test_environment, join_test_paths, test_file_path,
};
use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment};
use serde_json::json;
use std::collections::HashMap;
let user_home = test_file_path(&["tests/unix/known/user_home"]);
let unix_python_exe = join_test_paths(&[user_home.clone().to_str().unwrap(), "python"]);
let known = create_test_environment(
HashMap::from([(
"PATH".to_string(),
user_home.clone().to_string_lossy().to_string(),
)]),
Some(user_home.clone()),
Vec::new(),
None,
);
let mut locator = common_python::PythonOnPath::with(&known);
let result = locator.find().unwrap();
assert_eq!(result.environments.len(), 1);
let env = PythonEnvironment {
display_name: None,
env_manager: None,
project_path: None,
name: None,
python_executable_path: Some(unix_python_exe.clone()),
category: python_finder::messaging::PythonEnvironmentCategory::System,
version: None,
python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]),
env_path: Some(user_home.clone()),
arch: None,
..Default::default()
};
assert_messages(
&[json!(env)],
&result
.environments
.iter()
.map(|e| json!(e))
.collect::<Vec<_>>(),
);
}

Просмотреть файл

@ -1,302 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod common;
#[test]
#[cfg(unix)]
fn does_not_find_any_conda_envs() {
use crate::common::create_test_environment;
use python_finder::{conda, locator::Locator};
use std::{collections::HashMap, path::PathBuf};
let known = create_test_environment(
HashMap::from([("PATH".to_string(), "".to_string())]),
Some(PathBuf::from("SOME_BOGUS_HOME_DIR")),
Vec::new(),
None,
);
let mut locator = conda::Conda::with(&known);
let result = locator.find();
assert_eq!(result.is_none(), true);
}
#[test]
#[cfg(unix)]
fn no_paths_from_conda_rc_if_conda_rc_does_not_exist() {
use crate::common::{create_test_environment, test_file_path};
use python_finder::conda::get_conda_environment_paths_from_conda_rc;
use std::collections::HashMap;
let user_home = test_file_path(&["tests/unix/no_conda_rc/user_home"]);
let root = test_file_path(&["tests/unix/no_conda_rc/root"]);
let known = create_test_environment(
HashMap::from([("PATH".to_string(), "".to_string())]),
Some(user_home),
Vec::new(),
Some(root),
);
let result = get_conda_environment_paths_from_conda_rc(&known);
assert_eq!(result.len(), 0);
}
#[test]
#[cfg(unix)]
fn paths_from_conda_rc() {
use crate::common::{create_test_environment, test_file_path};
use python_finder::conda::get_conda_environment_paths_from_conda_rc;
use std::{collections::HashMap, fs, path::PathBuf};
fn create_conda_rc(file: &PathBuf, paths: &Vec<PathBuf>) {
use std::fs::File;
use std::io::Write;
let mut file = File::create(file).unwrap();
writeln!(file, "envs_dirs:").unwrap();
for path in paths {
writeln!(file, " - {}", path.to_string_lossy()).unwrap();
}
}
fn test_with(conda_rc_file: &PathBuf) {
let home = test_file_path(&["tests/unix/conda_rc/user_home"]);
let root = test_file_path(&["tests/unix/conda_rc/root"]);
let conda_dir = home.join(".conda");
let conda_envs = conda_dir.join("envs");
let known = create_test_environment(
HashMap::from([("PATH".to_string(), "".to_string())]),
Some(home.clone()),
Vec::new(),
Some(root.clone()),
);
fs::remove_dir_all(home.clone()).unwrap_or_default();
fs::remove_dir_all(root.clone()).unwrap_or_default();
fs::create_dir_all(home.clone()).unwrap_or_default();
fs::create_dir_all(root.clone()).unwrap_or_default();
fs::create_dir_all(conda_envs.clone()).unwrap_or_default();
fs::create_dir_all(conda_rc_file.parent().unwrap()).unwrap_or_default();
create_conda_rc(conda_rc_file, &vec![conda_dir.clone()]);
let result = get_conda_environment_paths_from_conda_rc(&known);
assert_eq!(result.len(), 1);
assert_eq!(result[0], conda_envs);
fs::remove_dir_all(home.clone()).unwrap_or_default();
fs::remove_dir_all(root.clone()).unwrap_or_default();
}
let home = test_file_path(&["tests/unix/conda_rc/user_home"]);
let root = test_file_path(&["tests/unix/conda_rc/root"]);
test_with(&root.join("etc/conda/.condarc"));
test_with(&home.join(".condarc"));
}
#[test]
#[cfg(unix)]
fn find_conda_exe_and_empty_envs() {
use crate::common::{create_test_environment, join_test_paths, test_file_path};
use python_finder::messaging::{EnvManager, EnvManagerType};
use python_finder::{conda, locator::Locator};
use serde_json::json;
use std::collections::HashMap;
let user_home = test_file_path(&["tests/unix/conda_without_envs/user_home"]);
let conda_dir = test_file_path(&["tests/unix/conda_without_envs/user_home"]);
let known = create_test_environment(
HashMap::from([(
"PATH".to_string(),
conda_dir.clone().to_str().unwrap().to_string(),
)]),
Some(user_home),
Vec::new(),
None,
);
let mut locator = conda::Conda::with(&known);
let result = locator.find().unwrap();
let managers = result.managers;
assert_eq!(managers.len(), 1);
let conda_exe = join_test_paths(&[
conda_dir.clone().to_str().unwrap(),
"anaconda3",
"bin",
"conda",
]);
let expected_conda_manager = EnvManager {
executable_path: conda_exe.clone(),
version: Some("4.0.2".to_string()),
tool: EnvManagerType::Conda,
company: None,
company_display_name: None,
};
assert_eq!(managers.len(), 1);
assert_eq!(json!(expected_conda_manager), json!(managers[0]));
}
#[test]
#[cfg(unix)]
fn find_conda_from_custom_install_location() {
use crate::common::{create_test_environment, test_file_path};
use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment};
use python_finder::{conda, locator::Locator};
use serde_json::json;
use std::collections::HashMap;
use std::fs;
let home = test_file_path(&["tests/unix/conda_custom_install_path/user_home"]);
let conda_dir =
test_file_path(&["tests/unix/conda_custom_install_path/user_home/some_location/anaconda3"]);
let environments_txt =
test_file_path(&["tests/unix/conda_custom_install_path/user_home/.conda/environments.txt"]);
fs::create_dir_all(environments_txt.parent().unwrap()).unwrap_or_default();
fs::write(
environments_txt.clone(),
format!("{}", conda_dir.clone().to_str().unwrap().to_string()),
)
.unwrap();
let known = create_test_environment(HashMap::new(), Some(home), Vec::new(), None);
let mut locator = conda::Conda::with(&known);
let result = locator.find().unwrap();
assert_eq!(result.managers.len(), 1);
assert_eq!(result.environments.len(), 1);
let conda_exe = conda_dir.clone().join("bin").join("conda");
let expected_conda_manager = EnvManager {
executable_path: conda_exe.clone(),
version: Some("4.0.2".to_string()),
tool: EnvManagerType::Conda,
company: None,
company_display_name: None,
};
assert_eq!(json!(expected_conda_manager), json!(result.managers[0]));
let expected_conda_env = PythonEnvironment {
display_name: None,
name: None,
project_path: None,
python_executable_path: Some(conda_dir.clone().join("bin").join("python")),
category: python_finder::messaging::PythonEnvironmentCategory::Conda,
version: Some("10.0.1".to_string()),
env_path: Some(conda_dir.clone()),
env_manager: Some(expected_conda_manager.clone()),
python_run_command: Some(vec![
conda_exe.clone().to_str().unwrap().to_string(),
"run".to_string(),
"-p".to_string(),
conda_dir.to_string_lossy().to_string(),
"python".to_string(),
]),
arch: None,
..Default::default()
};
assert_eq!(json!(expected_conda_env), json!(result.environments[0]));
// Reset environments.txt
fs::write(environments_txt.clone(), "").unwrap();
}
#[test]
#[cfg(unix)]
fn finds_two_conda_envs_from_known_location() {
use crate::common::{
assert_messages, create_test_environment, join_test_paths, test_file_path,
};
use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment};
use python_finder::{conda, locator::Locator};
use serde_json::json;
use std::collections::HashMap;
let home = test_file_path(&["tests/unix/conda/user_home"]);
let conda_dir = test_file_path(&["tests/unix/conda/user_home/anaconda3"]);
let conda_1 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/one"]);
let conda_2 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/two"]);
let known = create_test_environment(
HashMap::from([(
"PATH".to_string(),
conda_dir.clone().to_str().unwrap().to_string(),
)]),
Some(home),
Vec::new(),
None,
);
let mut locator = conda::Conda::with(&known);
let result = locator.find().unwrap();
let managers = result.managers;
let environments = result.environments;
assert_eq!(managers.len(), 1);
let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "bin", "conda"]);
let conda_1_exe = join_test_paths(&[conda_1.clone().to_str().unwrap(), "python"]);
let conda_2_exe = join_test_paths(&[conda_2.clone().to_str().unwrap(), "python"]);
let expected_conda_manager = EnvManager {
executable_path: conda_exe.clone(),
version: Some("4.0.2".to_string()),
tool: EnvManagerType::Conda,
company: None,
company_display_name: None,
};
assert_eq!(managers.len(), 1);
assert_eq!(json!(expected_conda_manager), json!(managers[0]));
let expected_conda_1 = PythonEnvironment {
display_name: None,
name: Some("one".to_string()),
project_path: None,
python_executable_path: Some(conda_1_exe.clone()),
category: python_finder::messaging::PythonEnvironmentCategory::Conda,
version: Some("10.0.1".to_string()),
env_path: Some(conda_1.clone()),
env_manager: Some(expected_conda_manager.clone()),
python_run_command: Some(vec![
conda_exe.clone().to_str().unwrap().to_string(),
"run".to_string(),
"-n".to_string(),
"one".to_string(),
"python".to_string(),
]),
arch: None,
..Default::default()
};
let expected_conda_2 = PythonEnvironment {
display_name: None,
name: Some("two".to_string()),
project_path: None,
python_executable_path: Some(conda_2_exe.clone()),
category: python_finder::messaging::PythonEnvironmentCategory::Conda,
version: None,
env_path: Some(conda_2.clone()),
env_manager: Some(expected_conda_manager.clone()),
python_run_command: Some(vec![
conda_exe.clone().to_str().unwrap().to_string(),
"run".to_string(),
"-n".to_string(),
"two".to_string(),
"python".to_string(),
]),
arch: None,
..Default::default()
};
assert_messages(
&[json!(expected_conda_1), json!(expected_conda_2)],
&environments.iter().map(|e| json!(e)).collect::<Vec<_>>(),
);
}

Просмотреть файл

@ -1,255 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod common;
#[test]
#[cfg(unix)]
fn does_not_find_any_pyenv_envs() {
use crate::common::create_test_environment;
use python_finder::{conda::Conda, locator::Locator, pyenv};
use std::{collections::HashMap, path::PathBuf};
let known = create_test_environment(
HashMap::new(),
Some(PathBuf::from("SOME_BOGUS_HOME_DIR")),
Vec::new(),
None,
);
let mut conda = Conda::with(&known);
let mut locator = pyenv::PyEnv::with(&known, &mut conda);
let result = locator.find();
assert_eq!(result.is_none(), true);
}
#[test]
#[cfg(unix)]
fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() {
use crate::common::{
assert_messages, create_test_environment, join_test_paths, test_file_path,
};
use python_finder::messaging::{EnvManager, EnvManagerType};
use python_finder::pyenv;
use python_finder::{conda::Conda, locator::Locator};
use serde_json::json;
use std::{collections::HashMap, path::PathBuf};
let home = test_file_path(&["tests", "unix", "pyenv_without_envs", "user_home"]);
let homebrew_bin = test_file_path(&[
"tests",
"unix",
"pyenv_without_envs",
"home",
"opt",
"homebrew",
"bin",
]);
let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]);
let known = create_test_environment(
HashMap::new(),
Some(home.clone()),
vec![PathBuf::from(homebrew_bin)],
None,
);
let mut conda = Conda::with(&known);
let mut locator = pyenv::PyEnv::with(&known, &mut conda);
let result = locator.find().unwrap();
let managers = result.clone().managers;
assert_eq!(managers.len(), 1);
let expected_manager = EnvManager {
executable_path: pyenv_exe.clone(),
version: None,
tool: EnvManagerType::Pyenv,
company: None,
company_display_name: None,
};
assert_eq!(json!(expected_manager), json!(result.managers[0]));
}
#[test]
#[cfg(unix)]
fn find_pyenv_envs() {
use crate::common::{
assert_messages, create_test_environment, join_test_paths, test_file_path,
};
use python_finder::conda::Conda;
use python_finder::locator::Locator;
use python_finder::{
messaging::{EnvManager, EnvManagerType, PythonEnvironment},
pyenv,
};
use serde_json::json;
use std::{collections::HashMap, path::PathBuf};
let home = test_file_path(&["tests", "unix", "pyenv", "user_home"]);
let homebrew_bin =
test_file_path(&["tests", "unix", "pyenv", "home", "opt", "homebrew", "bin"]);
let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]);
let known = create_test_environment(
HashMap::new(),
Some(home.clone()),
vec![PathBuf::from(homebrew_bin)],
None,
);
let mut conda = Conda::with(&known);
let mut locator = pyenv::PyEnv::with(&known, &mut conda);
let result = locator.find().unwrap();
assert_eq!(result.managers.len(), 1);
let expected_manager = EnvManager {
executable_path: pyenv_exe.clone(),
version: None,
tool: EnvManagerType::Pyenv,
company: None,
company_display_name: None,
};
assert_eq!(json!(expected_manager), json!(result.managers[0]));
let expected_3_9_9 = json!(PythonEnvironment {
display_name: None,
project_path: None,
name: None,
python_executable_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.9.9/bin/python"
])),
python_run_command: Some(vec![join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.9.9/bin/python"
])
.to_str()
.unwrap()
.to_string()]),
category: python_finder::messaging::PythonEnvironmentCategory::Pyenv,
version: Some("3.9.9".to_string()),
env_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.9.9"
])),
env_manager: Some(expected_manager.clone()),
arch: None,
..Default::default()
});
let expected_virtual_env = PythonEnvironment {
display_name: None,
project_path: None,
name: Some("my-virtual-env".to_string()),
python_executable_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/my-virtual-env/bin/python",
])),
python_run_command: Some(vec![join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/my-virtual-env/bin/python",
])
.to_str()
.unwrap()
.to_string()]),
category: python_finder::messaging::PythonEnvironmentCategory::PyenvVirtualEnv,
version: Some("3.10.13".to_string()),
env_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/my-virtual-env",
])),
env_manager: Some(expected_manager.clone()),
arch: None,
..Default::default()
};
let expected_3_12_1 = PythonEnvironment {
display_name: None,
project_path: None,
name: None,
python_executable_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1/bin/python",
])),
python_run_command: Some(vec![join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1/bin/python",
])
.to_str()
.unwrap()
.to_string()]),
category: python_finder::messaging::PythonEnvironmentCategory::Pyenv,
version: Some("3.12.1".to_string()),
env_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1",
])),
env_manager: Some(expected_manager.clone()),
arch: None,
..Default::default()
};
let expected_3_13_dev = PythonEnvironment {
display_name: None,
project_path: None,
name: None,
python_executable_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.13-dev/bin/python",
])),
python_run_command: Some(vec![join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.13-dev/bin/python",
])
.to_str()
.unwrap()
.to_string()]),
category: python_finder::messaging::PythonEnvironmentCategory::Pyenv,
version: Some("3.13-dev".to_string()),
env_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.13-dev",
])),
env_manager: Some(expected_manager.clone()),
arch: None,
..Default::default()
};
let expected_3_12_1a3 = PythonEnvironment {
display_name: None,
project_path: None,
name: None,
python_executable_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1a3/bin/python",
])),
python_run_command: Some(vec![join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1a3/bin/python",
])
.to_str()
.unwrap()
.to_string()]),
category: python_finder::messaging::PythonEnvironmentCategory::Pyenv,
version: Some("3.12.1a3".to_string()),
env_path: Some(join_test_paths(&[
home.to_str().unwrap(),
".pyenv/versions/3.12.1a3",
])),
env_manager: Some(expected_manager.clone()),
arch: None,
..Default::default()
};
assert_messages(
&[
json!(expected_3_9_9),
json!(expected_virtual_env),
json!(expected_3_12_1),
json!(expected_3_13_dev),
json!(expected_3_12_1a3),
],
&result
.environments
.iter()
.map(|e| json!(e))
.collect::<Vec<_>>(),
)
}

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1 +0,0 @@
10.1.1

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1 +0,0 @@
12.0.0

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1,3 +0,0 @@
home = /Users/donjayamanne/.pyenv/versions/3.10.13/bin
include-system-site-packages = false
version = 3.10.13

Просмотреть файл

Просмотреть файл

@ -6,6 +6,7 @@ import pathlib
import nox
import shutil
import sysconfig
import uuid
EXT_ROOT = pathlib.Path(__file__).parent
@ -48,13 +49,47 @@ def install_python_libs(session: nox.Session):
@nox.session()
def native_build(session: nox.Session):
with session.cd("./native_locator"):
if not pathlib.Path(pathlib.Path.cwd() / "bin").exists():
pathlib.Path(pathlib.Path.cwd() / "bin").mkdir()
def azure_pet_build_before(session: nox.Session):
source_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve()
config_toml_disabled = source_dir / ".cargo" / "config.toml.disabled"
config_toml = source_dir / ".cargo" / "config.toml"
if config_toml_disabled.exists() and not config_toml.exists():
config_toml.write_bytes(config_toml_disabled.read_bytes())
if not pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").exists():
pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").write_text(
@nox.session()
def azure_pet_build_after(session: nox.Session):
source_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve()
ext = sysconfig.get_config_var("EXE") or ""
bin_name = f"pet{ext}"
abs_bin_path = None
for root, _, files in os.walk(os.fspath(source_dir / "target")):
bin_path = pathlib.Path(root) / "release" / bin_name
if bin_path.exists():
abs_bin_path = bin_path.absolute()
break
assert abs_bin_path
dest_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve()
if not pathlib.Path(dest_dir / "bin").exists():
pathlib.Path(dest_dir / "bin").mkdir()
bin_dest = dest_dir / "bin" / bin_name
shutil.copyfile(abs_bin_path, bin_dest)
@nox.session()
def native_build(session: nox.Session):
source_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve()
dest_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve()
with session.cd(source_dir):
if not pathlib.Path(dest_dir / "bin").exists():
pathlib.Path(dest_dir / "bin").mkdir()
if not pathlib.Path(dest_dir / "bin" / ".gitignore").exists():
pathlib.Path(dest_dir / "bin" / ".gitignore").write_text(
"*\n", encoding="utf-8"
)
@ -70,37 +105,88 @@ def native_build(session: nox.Session):
"--release",
"--target",
target,
"--package",
"python-finder",
external=True,
)
source = f"./target/{target}/release/python-finder{ext}"
dest = f"./bin/python-finder{ext}"
shutil.copy(source, dest)
source = source_dir / "target" / target / "release" / f"pet{ext}"
else:
session.run(
"cargo",
"build",
"--frozen",
"--release",
"--package",
"python-finder",
external=True,
)
source = source_dir / "target" / "release" / f"pet{ext}"
dest = dest_dir / "bin" / f"pet{ext}"
shutil.copy(source, dest)
source = f"./target/release/python-finder{ext}"
dest = f"./bin/python-finder{ext}"
shutil.copy(source, dest)
# Remove native_locator/bin exclusion from .vscodeignore
# Remove python-env-tools/bin exclusion from .vscodeignore
vscode_ignore = EXT_ROOT / ".vscodeignore"
remove_patterns = ("native_locator/bin/**",)
remove_patterns = ("python-env-tools/bin/**",)
lines = vscode_ignore.read_text(encoding="utf-8").splitlines()
filtered_lines = [line for line in lines if not line.startswith(remove_patterns)]
vscode_ignore.write_text("\n".join(filtered_lines) + "\n", encoding="utf-8")
def delete_dir(path: pathlib.Path, ignore_errors=None):
attempt = 0
known = []
while attempt < 5:
try:
shutil.rmtree(os.fspath(path), ignore_errors=ignore_errors)
return
except PermissionError as pe:
if os.fspath(pe.filename) in known:
break
print(f"Changing permissions on {pe.filename}")
os.chmod(pe.filename, 0o666)
shutil.rmtree(os.fspath(path))
@nox.session()
def checkout_native(session: nox.Session):
dest = (pathlib.Path.cwd() / "python-env-tools").resolve()
if dest.exists():
shutil.rmtree(os.fspath(dest))
tempdir = os.getenv("TEMP") or os.getenv("TMP") or "/tmp"
tempdir = pathlib.Path(tempdir) / str(uuid.uuid4()) / "python-env-tools"
tempdir.mkdir(0o666, parents=True)
session.log(f"Temp dir: {tempdir}")
session.log(f"Cloning python-environment-tools to {tempdir}")
try:
with session.cd(tempdir):
session.run("git", "init", external=True)
session.run(
"git",
"remote",
"add",
"origin",
"https://github.com/microsoft/python-environment-tools",
external=True,
)
session.run("git", "fetch", "origin", "main", external=True)
session.run(
"git", "checkout", "--force", "-B", "main", "origin/main", external=True
)
delete_dir(tempdir / ".git")
delete_dir(tempdir / ".github")
delete_dir(tempdir / ".vscode")
(tempdir / "CODE_OF_CONDUCT.md").unlink()
shutil.move(os.fspath(tempdir), os.fspath(dest))
except PermissionError as e:
print(f"Permission error: {e}")
if not dest.exists():
raise
finally:
delete_dir(tempdir.parent, ignore_errors=True)
@nox.session()
def setup_repo(session: nox.Session):
install_python_libs(session)
checkout_native(session)
native_build(session)

243
package-lock.json сгенерированный
Просмотреть файл

@ -65,7 +65,7 @@
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^2.26.1",
"@vscode/vsce": "^2.27.0",
"bent": "^7.3.12",
"chai": "^4.1.2",
"chai-arrays": "^2.0.0",
@ -1998,12 +1998,13 @@
}
},
"node_modules/@vscode/vsce": {
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz",
"integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==",
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz",
"integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==",
"dev": true,
"dependencies": {
"@azure/identity": "^4.1.0",
"@vscode/vsce-sign": "^2.0.0",
"azure-devops-node-api": "^12.5.0",
"chalk": "^2.4.2",
"cheerio": "^1.0.0-rc.9",
@ -2037,6 +2038,141 @@
"keytar": "^7.7.0"
}
},
"node_modules/@vscode/vsce-sign": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz",
"integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==",
"dev": true,
"hasInstallScript": true,
"optionalDependencies": {
"@vscode/vsce-sign-alpine-arm64": "2.0.2",
"@vscode/vsce-sign-alpine-x64": "2.0.2",
"@vscode/vsce-sign-darwin-arm64": "2.0.2",
"@vscode/vsce-sign-darwin-x64": "2.0.2",
"@vscode/vsce-sign-linux-arm": "2.0.2",
"@vscode/vsce-sign-linux-arm64": "2.0.2",
"@vscode/vsce-sign-linux-x64": "2.0.2",
"@vscode/vsce-sign-win32-arm64": "2.0.2",
"@vscode/vsce-sign-win32-x64": "2.0.2"
}
},
"node_modules/@vscode/vsce-sign-alpine-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz",
"integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"alpine"
]
},
"node_modules/@vscode/vsce-sign-alpine-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz",
"integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"alpine"
]
},
"node_modules/@vscode/vsce-sign-darwin-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz",
"integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@vscode/vsce-sign-darwin-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz",
"integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@vscode/vsce-sign-linux-arm": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz",
"integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@vscode/vsce-sign-linux-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz",
"integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@vscode/vsce-sign-linux-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz",
"integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@vscode/vsce-sign-win32-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz",
"integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@vscode/vsce-sign-win32-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz",
"integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@vscode/vsce/node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
@ -13612,9 +13748,9 @@
}
},
"node_modules/ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true,
"engines": {
"node": ">=8.3.0"
@ -15399,12 +15535,13 @@
}
},
"@vscode/vsce": {
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz",
"integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==",
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz",
"integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==",
"dev": true,
"requires": {
"@azure/identity": "^4.1.0",
"@vscode/vsce-sign": "^2.0.0",
"azure-devops-node-api": "^12.5.0",
"chalk": "^2.4.2",
"cheerio": "^1.0.0-rc.9",
@ -15465,6 +15602,86 @@
}
}
},
"@vscode/vsce-sign": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz",
"integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==",
"dev": true,
"requires": {
"@vscode/vsce-sign-alpine-arm64": "2.0.2",
"@vscode/vsce-sign-alpine-x64": "2.0.2",
"@vscode/vsce-sign-darwin-arm64": "2.0.2",
"@vscode/vsce-sign-darwin-x64": "2.0.2",
"@vscode/vsce-sign-linux-arm": "2.0.2",
"@vscode/vsce-sign-linux-arm64": "2.0.2",
"@vscode/vsce-sign-linux-x64": "2.0.2",
"@vscode/vsce-sign-win32-arm64": "2.0.2",
"@vscode/vsce-sign-win32-x64": "2.0.2"
}
},
"@vscode/vsce-sign-alpine-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz",
"integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-alpine-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz",
"integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-darwin-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz",
"integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-darwin-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz",
"integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-linux-arm": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz",
"integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-linux-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz",
"integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-linux-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz",
"integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-win32-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz",
"integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==",
"dev": true,
"optional": true
},
"@vscode/vsce-sign-win32-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz",
"integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==",
"dev": true,
"optional": true
},
"@webassemblyjs/ast": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@ -24390,9 +24607,9 @@
}
},
"ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true,
"requires": {}
},

Просмотреть файл

@ -1568,7 +1568,7 @@
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^2.26.1",
"@vscode/vsce": "^2.27.0",
"bent": "^7.3.12",
"chai": "^4.1.2",
"chai-arrays": "^2.0.0",

Просмотреть файл

@ -19,9 +19,9 @@ import { getUserHomeDir } from '../../../../common/utils/platform';
const untildify = require('untildify');
const NATIVE_LOCATOR = isWindows()
? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet.exe')
: path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet');
const PYTHON_ENV_TOOLS_PATH = isWindows()
? path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet.exe')
: path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet');
export interface NativeEnvInfo {
displayName?: string;
@ -153,8 +153,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
// eslint-disable-next-line class-methods-use-this
private start(): rpc.MessageConnection {
this.outputChannel.info(`Starting Python Locator ${NATIVE_LOCATOR} server`);
const proc = ch.spawn(NATIVE_LOCATOR, ['server'], { env: process.env });
this.outputChannel.info(`Starting Python Locator ${PYTHON_ENV_TOOLS_PATH} server`);
const proc = ch.spawn(PYTHON_ENV_TOOLS_PATH, ['server'], { env: process.env });
const disposables: Disposable[] = [];
// jsonrpc package cannot handle messages coming through too quickly.
// Lets handle the messages and close the stream only when