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:
Родитель
6138ff8f0c
Коммит
daebb5cb77
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -48,5 +48,4 @@ dist/**
|
|||
*.xlf
|
||||
package.nls.*.json
|
||||
l10n/
|
||||
native_locator/target/**
|
||||
native_locator/Cargo.lock
|
||||
python-env-tools/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@
|
|||
10.1.1
|
|
@ -1 +0,0 @@
|
|||
10.1.1
|
|
@ -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
|
124
noxfile.py
124
noxfile.py
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче