From fefa07022599b59b6104a67d9570f5f27d03ec1d Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 3 May 2024 11:39:48 -0700 Subject: [PATCH 001/106] Add native locator for finding pythons (#23208) --- .github/actions/build-vsix/action.yml | 7 + .gitignore | 2 + .vscode/settings.json | 5 +- .vscodeignore | 3 + native_locator/Cargo.toml | 13 + native_locator/src/conda.rs | 439 ++++++++++++++++++ native_locator/src/known.rs | 37 ++ native_locator/src/main.rs | 15 + native_locator/src/messaging.rs | 119 +++++ noxfile.py | 29 ++ package.json | 8 +- src/client/pythonEnvironments/base/locator.ts | 4 +- .../base/locators/composite/resolverUtils.ts | 9 +- .../base/locators/lowLevel/nativeLocator.ts | 108 +++++ .../common/environmentManagers/conda.ts | 4 + src/client/pythonEnvironments/index.ts | 6 +- 16 files changed, 795 insertions(+), 13 deletions(-) create mode 100644 native_locator/Cargo.toml create mode 100644 native_locator/src/conda.rs create mode 100644 native_locator/src/known.rs create mode 100644 native_locator/src/main.rs create mode 100644 native_locator/src/messaging.rs create mode 100644 src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts diff --git a/.github/actions/build-vsix/action.yml b/.github/actions/build-vsix/action.yml index 03279fa5fbdd..16a1af28363c 100644 --- a/.github/actions/build-vsix/action.yml +++ b/.github/actions/build-vsix/action.yml @@ -21,6 +21,9 @@ runs: node-version: ${{ inputs.node_version }} cache: 'npm' + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + # Jedi LS depends on dataclasses which is not in the stdlib in Python 3.7. - name: Use Python 3.8 for JediLSP uses: actions/setup-python@v5 @@ -44,6 +47,10 @@ runs: run: nox --session install_python_libs shell: bash + - name: Build Native Binaries + run: nox --session native_build + shell: bash + - name: Run npm ci run: npm ci --prefer-offline shell: bash diff --git a/.gitignore b/.gitignore index 0556cbb2df0e..192e293bb50a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ dist/** *.xlf package.nls.*.json l10n/ +native_locator/target/** +native_locator/Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index 89959f33b6b1..4869c4535290 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,5 +67,8 @@ "python_files/tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "rust-analyzer.linkedProjects": [ + ".\\native_locator\\Cargo.toml" + ] } diff --git a/.vscodeignore b/.vscodeignore index b96d8fcb6610..93e26af08b72 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -67,3 +67,6 @@ test/** tmp/** typings/** types/** +native_locator/src/** +native_locator/target/** +native_locator/Cargo.* diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml new file mode 100644 index 000000000000..f20396b09a9d --- /dev/null +++ b/native_locator/Cargo.toml @@ -0,0 +1,13 @@ +[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" diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs new file mode 100644 index 000000000000..e3134754a7a8 --- /dev/null +++ b/native_locator/src/conda.rs @@ -0,0 +1,439 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use crate::known; +use crate::messaging; +use regex::Regex; +use std::env; +use std::path::{Path, PathBuf}; + +/// relative to the interpreter. This layout is common on linux/Mac. +/// +/// ``` +/// env // <--- Input can be this path +/// |-- conda-meta // <--- Returns this directory +/// |-- bin // <--- Input can be this path +/// |-- python // <--- Input can be this path +/// ``` +#[cfg(unix)] +fn get_conda_meta_path(any_path: &Path) -> Option { + if any_path.ends_with("bin/python") { + match any_path.parent() { + Some(parent) => match parent.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + }, + None => None, + } + } else if any_path.ends_with("bin") { + match any_path.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + } + } else { + Some(any_path.to_path_buf().join("conda-meta")) + } +} + +/// Get the conda-meta directory. For windows 'conda-meta' is in the same directory as the interpreter. +/// This layout is common in Windows. +/// +/// ``` +/// env // <--- Input can be this path +/// |-- conda-meta // <--- Returns this directory +/// |-- python.exe // <--- Input can be this path +/// ``` +#[cfg(windows)] +fn get_conda_meta_path(any_path: &Path) -> Option { + if any_path.ends_with("python.exe") { + match any_path.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + } + } else { + Some(any_path.to_path_buf().join("conda-meta")) + } +} + +/// Check if a given path is a conda environment. A conda environment is a directory that contains +/// a 'conda-meta' directory as child. This will find 'conda-meta' in a platform agnostic way. +pub fn is_conda_environment(any_path: &Path) -> bool { + let conda_meta_path = get_conda_meta_path(any_path); + match conda_meta_path { + Some(path) => path.exists(), + None => false, + } +} + +/// Get the version of a package in a conda environment. This will find the version +/// from the 'conda-meta' directory in a platform agnostic way. +fn get_version_from_meta_json(json_file: &Path) -> Option { + match Regex::new(r"(?m)([\d\w\-]*)-([\d\.]*)-.*\.json") { + Ok(re) => match json_file.file_name() { + Some(file_name) => { + let file_name = file_name.to_string_lossy(); + match re.captures(&file_name) { + Some(captures) => match captures.get(2) { + Some(version) => Some(version.as_str().to_string()), + None => None, + }, + None => None, + } + } + None => None, + }, + Err(_) => None, + } +} + +/// Get the path to the json file of a package in the conda environment from the 'conda-meta' directory. +fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { + let package_name = format!("{}-", package); + let conda_meta_path = get_conda_meta_path(any_path)?; + + std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { + let path = entry.ok()?.path(); + let file_name = path.file_name()?.to_string_lossy(); + if file_name.starts_with(&package_name) && file_name.ends_with(".json") { + Some(path) + } else { + None + } + }) +} + +/// Checks if the `python` package is installed in the conda environment +pub fn is_python_conda_env(any_path: &Path) -> bool { + let conda_python_json_path = get_conda_package_json_path(any_path, "python"); + match conda_python_json_path { + Some(path) => path.exists(), + None => false, + } +} + +/// Get the version of the `python` package in the conda environment +pub fn get_conda_python_version(any_path: &Path) -> Option { + let conda_python_json_path = get_conda_package_json_path(any_path, "python"); + match conda_python_json_path { + Some(path) => get_version_from_meta_json(&path), + None => None, + } +} + +/// Specifically returns the file names that are valid for 'conda' on windows +#[cfg(windows)] +fn get_conda_bin_names() -> Vec<&'static str> { + vec!["conda.exe", "conda.bat"] +} + +/// Specifically returns the file names that are valid for 'conda' on linux/Mac +#[cfg(unix)] +fn get_conda_bin_names() -> Vec<&'static str> { + vec!["conda"] +} + +/// Find the conda binary on the PATH environment variable +fn find_conda_binary_on_path() -> Option { + let paths = env::var("PATH"); + match paths { + Ok(paths) => { + let paths = env::split_paths(&paths); + for path in paths { + let path = Path::new(&path); + for bin in get_conda_bin_names() { + let conda_path = path.join(bin); + let metadata = std::fs::metadata(&conda_path); + match metadata { + Ok(metadata) => { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); + } + } + Err(_) => (), + } + } + } + None + } + Err(_) => None, + } +} + +fn find_python_binary_path(env_path: &Path) -> Option { + 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); + if path_1.exists() { + Some(path_1) + } else if path_2.exists() { + Some(path_2) + } else if path_3.exists() { + Some(path_3) + } else { + None + } +} + +#[cfg(windows)] +fn get_known_conda_locations() -> Vec { + let user_profile = env::var("USERPROFILE").unwrap(); + let program_data = env::var("PROGRAMDATA").unwrap(); + let all_user_profile = env::var("ALLUSERSPROFILE").unwrap(); + let home_drive = env::var("HOMEDRIVE").unwrap(); + let mut known_paths = vec![ + Path::new(&user_profile).join("Anaconda3\\Scripts"), + Path::new(&program_data).join("Anaconda3\\Scripts"), + Path::new(&all_user_profile).join("Anaconda3\\Scripts"), + Path::new(&home_drive).join("Anaconda3\\Scripts"), + Path::new(&user_profile).join("Miniconda3\\Scripts"), + Path::new(&program_data).join("Miniconda3\\Scripts"), + Path::new(&all_user_profile).join("Miniconda3\\Scripts"), + Path::new(&home_drive).join("Miniconda3\\Scripts"), + ]; + known_paths.append(&mut known::get_know_global_search_locations()); + known_paths +} + +#[cfg(unix)] +fn get_known_conda_locations() -> Vec { + let mut known_paths = vec![ + Path::new("/opt/anaconda3/bin").to_path_buf(), + Path::new("/opt/miniconda3/bin").to_path_buf(), + Path::new("/usr/local/anaconda3/bin").to_path_buf(), + Path::new("/usr/local/miniconda3/bin").to_path_buf(), + Path::new("/usr/anaconda3/bin").to_path_buf(), + Path::new("/usr/miniconda3/bin").to_path_buf(), + Path::new("/home/anaconda3/bin").to_path_buf(), + Path::new("/home/miniconda3/bin").to_path_buf(), + Path::new("/anaconda3/bin").to_path_buf(), + Path::new("/miniconda3/bin").to_path_buf(), + Path::new("/usr/local/anaconda3/bin").to_path_buf(), + Path::new("/usr/local/miniconda3/bin").to_path_buf(), + Path::new("/usr/anaconda3/bin").to_path_buf(), + Path::new("/usr/miniconda3/bin").to_path_buf(), + Path::new("/home/anaconda3/bin").to_path_buf(), + Path::new("/home/miniconda3/bin").to_path_buf(), + Path::new("/anaconda3/bin").to_path_buf(), + Path::new("/miniconda3/bin").to_path_buf(), + ]; + known_paths.append(&mut known::get_know_global_search_locations()); + known_paths +} + +/// Find conda binary in known locations +fn find_conda_binary_in_known_locations() -> Option { + let conda_bin_names = get_conda_bin_names(); + let known_locations = get_known_conda_locations(); + for location in known_locations { + for bin in &conda_bin_names { + let conda_path = location.join(bin); + let metadata = std::fs::metadata(&conda_path); + match metadata { + Ok(metadata) => { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); + } + } + Err(_) => (), + } + } + } + None +} + +/// Find the conda binary on the system +pub fn find_conda_binary() -> Option { + let conda_binary_on_path = find_conda_binary_on_path(); + match conda_binary_on_path { + Some(conda_binary_on_path) => Some(conda_binary_on_path), + None => find_conda_binary_in_known_locations(), + } +} + +fn get_conda_envs_from_environment_txt() -> Vec { + let mut envs = vec![]; + let home = known::get_user_home(); + match home { + Some(home) => { + let home = Path::new(&home); + let environment_txt = home.join(".conda").join("environments.txt"); + match std::fs::read_to_string(environment_txt) { + Ok(reader) => { + for line in reader.lines() { + envs.push(line.to_string()); + } + } + Err(_) => (), + } + } + None => (), + } + envs +} + +fn get_known_env_locations(conda_bin: PathBuf) -> Vec { + let mut paths = vec![]; + let home = known::get_user_home(); + match home { + Some(home) => { + let home = Path::new(&home); + let conda_envs = home.join(".conda").join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + } + None => (), + } + + match conda_bin.parent() { + Some(parent) => { + paths.push(parent.to_string_lossy().to_string()); + let conda_envs = parent.join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + match parent.parent() { + Some(parent) => { + paths.push(parent.to_string_lossy().to_string()); + let conda_envs = parent.join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + } + None => (), + } + } + None => (), + } + + paths +} + +fn get_conda_envs_from_known_env_locations(conda_bin: PathBuf) -> Vec { + let mut envs = vec![]; + for location in get_known_env_locations(conda_bin) { + if is_conda_environment(&Path::new(&location)) { + envs.push(location.to_string()); + } + match std::fs::read_dir(location) { + Ok(reader) => { + for entry in reader { + match entry { + Ok(entry) => { + let metadata = entry.metadata(); + match metadata { + Ok(metadata) => { + if metadata.is_dir() { + let path = entry.path(); + if is_conda_environment(&path) { + envs.push(path.to_string_lossy().to_string()); + } + } + } + Err(_) => (), + } + } + Err(_) => (), + } + } + } + Err(_) => (), + } + } + envs +} + +struct CondaEnv { + named: bool, + name: String, + path: PathBuf, +} + +fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec { + let mut envs = get_conda_envs_from_environment_txt(); + let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin.to_path_buf()); + envs.append(&mut known_envs); + envs.sort(); + envs.dedup(); + + let locations = get_known_env_locations(conda_bin); + let mut conda_envs = vec![]; + for env in envs.clone() { + let env = Path::new(&env); + let mut named = false; + let mut name = "".to_string(); + for location in &locations { + let location = Path::new(location); + match env.strip_prefix(location) { + Ok(prefix) => { + named = true; + name = match prefix.to_str() { + Some(name) => { + let name = name.to_string(); + if name == "" { + "base".to_string() + } else { + name.to_string() + } + } + None => "base".to_string(), + }; + break; + } + Err(_) => (), + } + } + conda_envs.push(CondaEnv { + named, + name, + path: PathBuf::from(env), + }); + } + conda_envs +} + +pub fn find_and_report_conda_envs() { + let conda_binary = find_conda_binary(); + match conda_binary { + Some(conda_binary) => { + let params = + messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None); + let message = messaging::EnvManagerMessage::new(params); + messaging::send_message(message); + + let envs = get_distinct_conda_envs(conda_binary.to_path_buf()); + for env in envs { + let executable = find_python_binary_path(Path::new(&env.path)); + let params = messaging::PythonEnvironment::new( + env.name.to_string(), + match executable { + Some(executable) => vec![executable.to_string_lossy().to_string()], + None => vec![], + }, + "conda".to_string(), + get_conda_python_version(&env.path), + if env.named { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-n".to_string(), + env.name.to_string(), + "python".to_string(), + ]) + } else { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-p".to_string(), + env.path.to_string_lossy().to_string(), + "python".to_string(), + ]) + }, + Some(env.path.to_string_lossy().to_string()), + ); + let message = messaging::PythonEnvironmentMessage::new(params); + messaging::send_message(message); + } + } + None => (), + } +} diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs new file mode 100644 index 000000000000..c68340f6c03e --- /dev/null +++ b/native_locator/src/known.rs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +use std::{env, path::PathBuf}; + + +#[cfg(windows)] +pub fn get_know_global_search_locations() -> Vec { + vec![] +} + +#[cfg(unix)] +pub fn get_know_global_search_locations() -> Vec { + vec![ + Path::new("/usr/bin").to_path_buf(), + Path::new("/usr/local/bin").to_path_buf(), + Path::new("/bin").to_path_buf(), + Path::new("/home/bin").to_path_buf(), + Path::new("/sbin").to_path_buf(), + Path::new("/usr/sbin").to_path_buf(), + Path::new("/usr/local/sbin").to_path_buf(), + Path::new("/home/sbin").to_path_buf(), + Path::new("/opt").to_path_buf(), + Path::new("/opt/bin").to_path_buf(), + Path::new("/opt/sbin").to_path_buf(), + Path::new("/opt/homebrew/bin").to_path_buf(), + ] +} + + + +pub fn get_user_home() -> Option { + let home = env::var("HOME").or_else(|_| env::var("USERPROFILE")); + match home { + Ok(home) => Some(home), + Err(_) => None, + } +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs new file mode 100644 index 000000000000..8b4e615a9531 --- /dev/null +++ b/native_locator/src/main.rs @@ -0,0 +1,15 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +pub use known::*; +pub use conda::*; +pub use messaging::*; +mod conda; +mod known; +mod messaging; + +fn main() { + conda::find_and_report_conda_envs(); + messaging::send_message(messaging::ExitMessage::new()); +} diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs new file mode 100644 index 000000000000..b67318d551fe --- /dev/null +++ b/native_locator/src/messaging.rs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use serde::{Deserialize, Serialize}; + + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EnvManager { + pub executable_path: Vec, + pub version: Option, +} + +impl EnvManager { + pub fn new(executable_path: Vec, version: Option) -> Self { + Self { + executable_path, + version, + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +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)] +#[serde(rename_all = "camelCase")] +pub struct PythonEnvironment { + pub name: String, + pub python_executable_path: Vec, + pub category: String, + pub version: Option, + pub activated_run: Option>, + pub env_path: Option, +} + +impl PythonEnvironment { + pub fn new( + name: String, + python_executable_path: Vec, + category: String, + version: Option, + activated_run: Option>, + env_path: Option, + ) -> Self { + Self { + name, + python_executable_path: python_executable_path, + category, + version, + activated_run, + env_path, + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +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")] +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, + } + } +} + +fn send_rpc_message(message: String) -> () { + print!( + "Content-Length: {}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{}", + message.len(), + message + ); +} + +pub fn send_message(message: T) -> () { + let message = serde_json::to_string(&message).unwrap(); + send_rpc_message(message); +} diff --git a/noxfile.py b/noxfile.py index 7eb2da93cfe3..a53828df51c4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -4,6 +4,7 @@ import pathlib import nox import shutil +import sys @nox.session() @@ -41,3 +42,31 @@ def install_python_libs(session: nox.Session): if pathlib.Path("./python_files/lib/temp").exists(): shutil.rmtree("./python_files/lib/temp") + + +@nox.session() +def native_build(session:nox.Session): + with session.cd("./native_locator"): + session.run("cargo", "build", "--release", "--package", "python-finder", external=True) + if not pathlib.Path(pathlib.Path.cwd() / "bin").exists(): + pathlib.Path(pathlib.Path.cwd() / "bin").mkdir() + + if not pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").exists(): + pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").write_text("*\n", encoding="utf-8") + + if sys.platform == "win32": + shutil.copy( + "./target/release/python-finder.exe", + "./bin/python-finder.exe", + ) + else: + shutil.copy( + "./target/release/python-finder", + "./bin/python-finder", + ) + + +@nox.session() +def setup_repo(session: nox.Session): + install_python_libs(session) + native_build(session) diff --git a/package.json b/package.json index 46c24ab31d01..dae04d3d6956 100644 --- a/package.json +++ b/package.json @@ -1156,10 +1156,10 @@ ], "menus": { "issue/reporter": [ - { - "command": "python.reportIssue" - } - ], + { + "command": "python.reportIssue" + } + ], "commandPalette": [ { "category": "Python", diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 58798627678e..8524c03536c5 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -5,7 +5,7 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, PythonVersion } from './info'; import { IPythonEnvsWatcher, PythonEnvCollectionChangedEvent, @@ -145,6 +145,8 @@ export type BasicEnvInfo = { source?: PythonEnvSource[]; envPath?: string; searchLocation?: Uri; + version?: PythonVersion; + name?: string; }; /** diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 9a665a85391a..4fd1e2f68d5d 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -186,19 +186,18 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise { } else { executable = await conda.getInterpreterPathForEnvironment({ prefix: envPath }); } + const version = env.version ?? (executable ? await getPythonVersionFromPath(executable) : undefined); const info = buildEnvInfo({ executable, kind: PythonEnvKind.Conda, org: AnacondaCompanyName, location: envPath, source: [], - version: executable ? await getPythonVersionFromPath(executable) : undefined, + version, type: PythonEnvType.Conda, + name: env.name ?? (await conda?.getName(envPath)), }); - const name = await conda?.getName(envPath); - if (name) { - info.name = name; - } + if (env.envPath && path.basename(executable) === executable) { // For environments without python, set ID using the predicted executable path after python is installed. // Another alternative could've been to set ID of all conda environments to the environment path, as that diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts new file mode 100644 index 000000000000..8aa4631b8e94 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -0,0 +1,108 @@ +import { Event, EventEmitter } from 'vscode'; +import * as ch from 'child_process'; +import * as path from 'path'; +import * as rpc from 'vscode-jsonrpc/node'; +import { EXTENSION_ROOT_DIR } from '../../../../constants'; +import { isWindows } from '../../../../common/platform/platformService'; +import { IDisposable } from '../../../../common/types'; +import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { PythonEnvsChangedEvent } from '../../watcher'; +import { createDeferred } from '../../../../common/utils/async'; +import { PythonEnvKind, PythonVersion } from '../../info'; +import { Conda } from '../../../common/environmentManagers/conda'; + +const NATIVE_LOCATOR = isWindows() + ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe') + : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder'); + +interface NativeEnvInfo { + name: string; + pythonExecutablePath: string[]; + category: string; + version?: string; + activatedRun?: string[]; + envPath?: string; +} + +interface EnvManager { + executablePath: string[]; + version?: string; +} + +function categoryToKind(category: string): PythonEnvKind { + if (category === 'conda') { + return PythonEnvKind.Conda; + } + return PythonEnvKind.Unknown; +} + +function parseVersion(version?: string): PythonVersion | undefined { + if (!version) { + return undefined; + } + + try { + const [major, minor, micro] = version.split('.').map((v) => parseInt(v, 10)); + return { + major, + minor, + micro, + sysVersion: version, + }; + } catch { + return undefined; + } +} + +export class NativeLocator implements ILocator, IDisposable { + public readonly providerId: string = 'native-locator'; + + private readonly onChangedEmitter = new EventEmitter(); + + private readonly disposables: IDisposable[] = []; + + constructor() { + this.onChanged = this.onChangedEmitter.event; + this.disposables.push(this.onChangedEmitter); + } + + public readonly onChanged: Event; + + public async dispose(): Promise { + this.disposables.forEach((d) => d.dispose()); + return Promise.resolve(); + } + + public iterEnvs(): IPythonEnvsIterator { + const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' }); + const envs: BasicEnvInfo[] = []; + const deferred = createDeferred(); + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(proc.stdout), + new rpc.StreamMessageWriter(proc.stdin), + ); + this.disposables.push(connection); + connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { + envs.push({ + kind: categoryToKind(data.category), + executablePath: data.pythonExecutablePath[0], + envPath: data.envPath, + version: parseVersion(data.version), + name: data.name === '' ? undefined : data.name, + }); + }); + connection.onNotification('envManager', (data: EnvManager) => { + Conda.setConda(data.executablePath[0]); + }); + connection.onNotification('exit', () => { + deferred.resolve(); + }); + connection.listen(); + + const iterator = async function* (): IPythonEnvsIterator { + await deferred.promise; + yield* envs; + }; + return iterator(); + } +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/conda.ts b/src/client/pythonEnvironments/common/environmentManagers/conda.ts index 2ec5844f4708..9f107a737dc3 100644 --- a/src/client/pythonEnvironments/common/environmentManagers/conda.ts +++ b/src/client/pythonEnvironments/common/environmentManagers/conda.ts @@ -287,6 +287,10 @@ export class Conda { return Conda.condaPromise.get(shellPath); } + public static setConda(condaPath: string): void { + Conda.condaPromise.set(undefined, Promise.resolve(new Conda(condaPath))); + } + /** * Locates the preferred "conda" utility on this system by considering user settings, * binaries on PATH, Python interpreters in the registry, and known install locations. diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index d3f6166295d9..b33425905a15 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -19,7 +19,7 @@ import { } from './common/externalDependencies'; import { ExtensionLocators, WatchRootsArgs, WorkspaceLocators } from './base/locators/wrappers'; import { CustomVirtualEnvironmentLocator } from './base/locators/lowLevel/customVirtualEnvLocator'; -import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator'; +// import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator'; import { GlobalVirtualEnvironmentLocator } from './base/locators/lowLevel/globalVirtualEnvronmentLocator'; import { PosixKnownPathsLocator } from './base/locators/lowLevel/posixKnownPathsLocator'; import { PyenvLocator } from './base/locators/lowLevel/pyenvLocator'; @@ -39,6 +39,7 @@ import { IDisposable } from '../common/types'; import { traceError } from '../logging'; import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; +import { NativeLocator } from './base/locators/lowLevel/nativeLocator'; const PYTHON_ENV_INFO_CACHE_KEY = 'PYTHON_ENV_INFO_CACHEv2'; @@ -141,10 +142,11 @@ function createNonWorkspaceLocators(ext: ExtensionState): ILocator locators.push( // OS-independent locators go here. new PyenvLocator(), - new CondaEnvironmentLocator(), + // new CondaEnvironmentLocator(), new ActiveStateLocator(), new GlobalVirtualEnvironmentLocator(), new CustomVirtualEnvironmentLocator(), + new NativeLocator(), ); if (getOSType() === OSType.Windows) { From f5fae81414cd15d475a62bbe0377c6d2572b00e4 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 3 May 2024 12:11:28 -0700 Subject: [PATCH 002/106] Add `python.nativeLocator` experimental setting (#23324) Adds a setting to switch between `native` and `js` locators. --- package.json | 13 +++++++ package.nls.json | 3 +- src/client/pythonEnvironments/index.ts | 49 +++++++++++++++----------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index dae04d3d6956..b471ec654bdc 100644 --- a/package.json +++ b/package.json @@ -550,6 +550,19 @@ "experimental" ] }, + "python.nativeLocator": { + "default": "js", + "description": "%python.nativeLocator.description%", + "enum": [ + "js", + "native" + ], + "tags": [ + "experimental" + ], + "scope": "machine", + "type": "string" + }, "python.pipenvPath": { "default": "pipenv", "description": "%python.pipenvPath.description%", diff --git a/package.nls.json b/package.nls.json index b88b04ab241f..536eab3e800d 100644 --- a/package.nls.json +++ b/package.nls.json @@ -57,6 +57,7 @@ "python.logging.level.description": "The logging level the extension logs at, defaults to 'error'", "python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.", "python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml", + "python.nativeLocator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.", "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", @@ -162,4 +163,4 @@ "walkthrough.step.python.createNewNotebook.altText": "Creating a new Jupyter notebook", "walkthrough.step.python.openInteractiveWindow.altText": "Opening Python interactive window", "walkthrough.step.python.dataScienceLearnMore.altText": "Image representing our documentation page and mailing list resources." -} \ No newline at end of file +} diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index b33425905a15..2b341dab0e9c 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -19,7 +19,7 @@ import { } from './common/externalDependencies'; import { ExtensionLocators, WatchRootsArgs, WorkspaceLocators } from './base/locators/wrappers'; import { CustomVirtualEnvironmentLocator } from './base/locators/lowLevel/customVirtualEnvLocator'; -// import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator'; +import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator'; import { GlobalVirtualEnvironmentLocator } from './base/locators/lowLevel/globalVirtualEnvronmentLocator'; import { PosixKnownPathsLocator } from './base/locators/lowLevel/posixKnownPathsLocator'; import { PyenvLocator } from './base/locators/lowLevel/pyenvLocator'; @@ -40,6 +40,7 @@ import { traceError } from '../logging'; import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; import { NativeLocator } from './base/locators/lowLevel/nativeLocator'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; const PYTHON_ENV_INFO_CACHE_KEY = 'PYTHON_ENV_INFO_CACHEv2'; @@ -137,30 +138,38 @@ async function createLocator( return caching; } +function useNativeLocator(): boolean { + const config = getConfiguration('python'); + return config.get('nativeLocator', 'js') === 'native'; +} + function createNonWorkspaceLocators(ext: ExtensionState): ILocator[] { const locators: (ILocator & Partial)[] = []; - locators.push( - // OS-independent locators go here. - new PyenvLocator(), - // new CondaEnvironmentLocator(), - new ActiveStateLocator(), - new GlobalVirtualEnvironmentLocator(), - new CustomVirtualEnvironmentLocator(), - new NativeLocator(), - ); - - if (getOSType() === OSType.Windows) { - locators.push( - // Windows specific locators go here. - new WindowsRegistryLocator(), - new MicrosoftStoreLocator(), - new WindowsPathEnvVarLocator(), - ); + if (useNativeLocator()) { + locators.push(new NativeLocator()); } else { locators.push( - // Linux/Mac locators go here. - new PosixKnownPathsLocator(), + // OS-independent locators go here. + new PyenvLocator(), + new CondaEnvironmentLocator(), + new ActiveStateLocator(), + new GlobalVirtualEnvironmentLocator(), + new CustomVirtualEnvironmentLocator(), ); + + if (getOSType() === OSType.Windows) { + locators.push( + // Windows specific locators go here. + new WindowsRegistryLocator(), + new MicrosoftStoreLocator(), + new WindowsPathEnvVarLocator(), + ); + } else { + locators.push( + // Linux/Mac locators go here. + new PosixKnownPathsLocator(), + ); + } } const disposables = locators.filter((d) => d.dispose !== undefined) as IDisposable[]; From 93f19f728618a67a5ceeeec659eb4fbc52e26c79 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 3 May 2024 14:55:12 -0700 Subject: [PATCH 003/106] Add more Windows based locators (#23325) --- .vscode/settings.json | 4 ++ native_locator/src/common_python.rs | 62 ++++++++++++++++++++++ native_locator/src/conda.rs | 78 ++++++++++------------------ native_locator/src/main.rs | 18 +++++-- native_locator/src/utils.rs | 16 ++++++ native_locator/src/windows_python.rs | 63 ++++++++++++++++++++++ 6 files changed, 185 insertions(+), 56 deletions(-) create mode 100644 native_locator/src/common_python.rs create mode 100644 native_locator/src/utils.rs create mode 100644 native_locator/src/windows_python.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 4869c4535290..76501f1f6d1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,6 +30,10 @@ }, "editor.defaultFormatter": "charliermarsh.ruff", }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs new file mode 100644 index 000000000000..f9c67f433fbf --- /dev/null +++ b/native_locator/src/common_python.rs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use crate::messaging; +use crate::utils; +use std::env; +use std::path::Path; + +fn get_env_path(path: &str) -> Option { + let path = Path::new(path); + match path.parent() { + Some(parent) => { + if parent.file_name()? == "Scripts" { + return Some(parent.parent()?.to_string_lossy().to_string()); + } else { + return Some(parent.to_string_lossy().to_string()); + } + } + None => None, + } +} + +fn report_path_python(path: &str) { + let version = utils::get_version(path); + let env_path = get_env_path(path); + messaging::send_message(messaging::PythonEnvironment::new( + "Python".to_string(), + vec![path.to_string()], + "System".to_string(), + version, + None, + env_path, + )); +} + +fn report_python_on_path() { + let paths = env::var("PATH"); + let bin = if cfg!(windows) { + "python.exe" + } else { + "python" + }; + match paths { + Ok(paths) => { + let paths = env::split_paths(&paths); + for path in paths { + let full_path = path.join(bin); + if full_path.exists() { + match full_path.to_str() { + Some(full_path) => report_path_python(full_path), + None => (), + } + } + } + } + Err(_) => (), + } +} + +pub fn find_and_report() { + report_python_on_path(); +} diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index e3134754a7a8..0aadd559b390 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -68,21 +68,15 @@ pub fn is_conda_environment(any_path: &Path) -> bool { /// Get the version of a package in a conda environment. This will find the version /// from the 'conda-meta' directory in a platform agnostic way. fn get_version_from_meta_json(json_file: &Path) -> Option { - match Regex::new(r"(?m)([\d\w\-]*)-([\d\.]*)-.*\.json") { - Ok(re) => match json_file.file_name() { - Some(file_name) => { - let file_name = file_name.to_string_lossy(); - match re.captures(&file_name) { - Some(captures) => match captures.get(2) { - Some(version) => Some(version.as_str().to_string()), - None => None, - }, - None => None, - } - } - None => None, - }, - Err(_) => None, + let file_name = json_file.file_name()?.to_string_lossy(); + + match Regex::new(r"(?m)([\d\w\-]*)-([\d\.]*)-.*\.json") + .ok()? + .captures(&file_name)? + .get(2) + { + Some(version) => Some(version.as_str().to_string()), + None => None, } } @@ -134,29 +128,23 @@ fn get_conda_bin_names() -> Vec<&'static str> { /// Find the conda binary on the PATH environment variable fn find_conda_binary_on_path() -> Option { - let paths = env::var("PATH"); - match paths { - Ok(paths) => { - let paths = env::split_paths(&paths); - for path in paths { - let path = Path::new(&path); - for bin in get_conda_bin_names() { - let conda_path = path.join(bin); - let metadata = std::fs::metadata(&conda_path); - match metadata { - Ok(metadata) => { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - Err(_) => (), + let paths = env::var("PATH").ok()?; + let paths = env::split_paths(&paths); + for path in paths { + let path = Path::new(&path); + for bin in get_conda_bin_names() { + let conda_path = path.join(bin); + match std::fs::metadata(&conda_path) { + Ok(metadata) => { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); } } + Err(_) => (), } - None } - Err(_) => None, } + None } fn find_python_binary_path(env_path: &Path) -> Option { @@ -168,15 +156,8 @@ fn find_python_binary_path(env_path: &Path) -> Option { 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); - if path_1.exists() { - Some(path_1) - } else if path_2.exists() { - Some(path_2) - } else if path_3.exists() { - Some(path_3) - } else { - None - } + let paths = vec![path_1, path_2, path_3]; + paths.into_iter().find(|path| path.exists()) } #[cfg(windows)] @@ -232,14 +213,9 @@ fn find_conda_binary_in_known_locations() -> Option { for location in known_locations { for bin in &conda_bin_names { let conda_path = location.join(bin); - let metadata = std::fs::metadata(&conda_path); - match metadata { - Ok(metadata) => { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - Err(_) => (), + let metadata = std::fs::metadata(&conda_path).ok()?; + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); } } } @@ -391,7 +367,7 @@ fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec { conda_envs } -pub fn find_and_report_conda_envs() { +pub fn find_and_report() { let conda_binary = find_conda_binary(); match conda_binary { Some(conda_binary) => { diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 8b4e615a9531..51f9a5b3d016 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,15 +1,23 @@ - // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -pub use known::*; -pub use conda::*; -pub use messaging::*; +mod common_python; mod conda; mod known; mod messaging; +mod utils; +mod windows_python; fn main() { - conda::find_and_report_conda_envs(); + // Finds python on PATH + common_python::find_and_report(); + + // finds conda binary and conda environments + conda::find_and_report(); + + // Finds Windows Store, Known Path, and Registry pythons + #[cfg(windows)] + windows_python::find_and_report(); + messaging::send_message(messaging::ExitMessage::new()); } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs new file mode 100644 index 000000000000..001f56c815ca --- /dev/null +++ b/native_locator/src/utils.rs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use std::process::Command; + +pub fn get_version(path: &str) -> Option { + let output = Command::new(path) + .arg("-c") + .arg("import sys; print(sys.version)") + .output() + .ok()?; + let output = String::from_utf8(output.stdout).ok()?; + let output = output.trim(); + let output = output.split_whitespace().next().unwrap_or(output); + Some(output.to_string()) +} diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs new file mode 100644 index 000000000000..0cb49d975685 --- /dev/null +++ b/native_locator/src/windows_python.rs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use crate::known; +use crate::messaging; +use crate::utils; +use std::path::Path; + +fn report_path_python(path: &str) { + let version = utils::get_version(path); + messaging::send_message(messaging::PythonEnvironment::new( + "Python".to_string(), + vec![path.to_string()], + "WindowsStore".to_string(), + version, + None, + None, + )); +} + +fn report_windows_store_python() { + let home = known::get_user_home(); + match home { + Some(home) => { + let apps_path = Path::new(&home) + .join("AppData") + .join("Local") + .join("Microsoft") + .join("WindowsApps"); + let files = std::fs::read_dir(apps_path); + match files { + Ok(files) => { + for file in files { + match file { + Ok(file) => { + let path = file.path(); + match path.file_name() { + Some(name) => { + let name = name.to_string_lossy().to_lowercase(); + if name.starts_with("python3.") && name.ends_with(".exe") { + report_path_python(&path.to_string_lossy()); + } + } + None => {} + } + } + Err(_) => {} + } + } + } + Err(_) => {} + } + } + None => {} + } +} + +fn report_registry_pythons() {} + +pub fn find_and_report() { + report_windows_store_python(); + report_registry_pythons(); +} From 32d2651daaf2f128abf8fa6bee1b0e247085a4bc Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 3 May 2024 15:23:13 -0700 Subject: [PATCH 004/106] Add logging over JSON-RPC (#23326) --- native_locator/src/logging.rs | 62 +++++++++++++++++++++++++++++++++ native_locator/src/main.rs | 20 ++++++++++- native_locator/src/messaging.rs | 1 - 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 native_locator/src/logging.rs diff --git a/native_locator/src/logging.rs b/native_locator/src/logging.rs new file mode 100644 index 000000000000..7dc2b495442a --- /dev/null +++ b/native_locator/src/logging.rs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use crate::messaging; +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 }, + } + } +} + +pub fn log_debug(message: &str) { + messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); +} + +pub fn log_info(message: &str) { + messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Info)); +} + +pub fn log_warning(message: &str) { + messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); +} + +pub fn log_error(message: &str) { + messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Error)); +} + +pub fn log_msg(message: &str, level: LogLevel) { + messaging::send_message(LogMessage::new(message.to_string(), level)); +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 51f9a5b3d016..aa339e4e2b6d 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,23 +1,41 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use std::time::SystemTime; + mod common_python; mod conda; mod known; +mod logging; mod messaging; mod utils; mod windows_python; fn main() { + let now = SystemTime::now(); + logging::log_info("Starting Native Locator"); + // Finds python on PATH common_python::find_and_report(); - // finds conda binary and conda environments + // Finds conda binary and conda environments conda::find_and_report(); // Finds Windows Store, Known Path, and Registry pythons #[cfg(windows)] windows_python::find_and_report(); + match now.elapsed() { + Ok(elapsed) => { + logging::log_info(&format!( + "Native Locator took {} milliseconds.", + elapsed.as_millis() + )); + } + Err(e) => { + logging::log_error(&format!("Error getting elapsed time: {:?}", e)); + } + } + messaging::send_message(messaging::ExitMessage::new()); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index b67318d551fe..540ba0595988 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; - #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { From b9d5baf118b300c67f7b95a1b7e6901c712bb0ea Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 6 May 2024 14:23:55 +1000 Subject: [PATCH 005/106] Fix compilation error and use PathBuf::from (#23339) --- native_locator/src/common_python.rs | 20 ++++----------- native_locator/src/conda.rs | 38 ++++++++++++++--------------- native_locator/src/known.rs | 27 +++++++++----------- 3 files changed, 36 insertions(+), 49 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index f9c67f433fbf..c5482bbfc16a 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -34,26 +34,16 @@ fn report_path_python(path: &str) { } fn report_python_on_path() { - let paths = env::var("PATH"); let bin = if cfg!(windows) { "python.exe" } else { "python" }; - match paths { - Ok(paths) => { - let paths = env::split_paths(&paths); - for path in paths { - let full_path = path.join(bin); - if full_path.exists() { - match full_path.to_str() { - Some(full_path) => report_path_python(full_path), - None => (), - } - } - } - } - Err(_) => (), + if let Ok(paths) = env::var("PATH") { + env::split_paths(&paths) + .map(|p| p.join(bin)) + .filter(|p| p.exists()) + .for_each(|full_path| report_path_python(full_path.to_str().unwrap())); } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 0aadd559b390..db0bc3bc10f1 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -183,24 +183,24 @@ fn get_known_conda_locations() -> Vec { #[cfg(unix)] fn get_known_conda_locations() -> Vec { let mut known_paths = vec![ - Path::new("/opt/anaconda3/bin").to_path_buf(), - Path::new("/opt/miniconda3/bin").to_path_buf(), - Path::new("/usr/local/anaconda3/bin").to_path_buf(), - Path::new("/usr/local/miniconda3/bin").to_path_buf(), - Path::new("/usr/anaconda3/bin").to_path_buf(), - Path::new("/usr/miniconda3/bin").to_path_buf(), - Path::new("/home/anaconda3/bin").to_path_buf(), - Path::new("/home/miniconda3/bin").to_path_buf(), - Path::new("/anaconda3/bin").to_path_buf(), - Path::new("/miniconda3/bin").to_path_buf(), - Path::new("/usr/local/anaconda3/bin").to_path_buf(), - Path::new("/usr/local/miniconda3/bin").to_path_buf(), - Path::new("/usr/anaconda3/bin").to_path_buf(), - Path::new("/usr/miniconda3/bin").to_path_buf(), - Path::new("/home/anaconda3/bin").to_path_buf(), - Path::new("/home/miniconda3/bin").to_path_buf(), - Path::new("/anaconda3/bin").to_path_buf(), - Path::new("/miniconda3/bin").to_path_buf(), + PathBuf::from("/opt/anaconda3/bin"), + PathBuf::from("/opt/miniconda3/bin"), + PathBuf::from("/usr/local/anaconda3/bin"), + PathBuf::from("/usr/local/miniconda3/bin"), + PathBuf::from("/usr/anaconda3/bin"), + PathBuf::from("/usr/miniconda3/bin"), + PathBuf::from("/home/anaconda3/bin"), + PathBuf::from("/home/miniconda3/bin"), + PathBuf::from("/anaconda3/bin"), + PathBuf::from("/miniconda3/bin"), + PathBuf::from("/usr/local/anaconda3/bin"), + PathBuf::from("/usr/local/miniconda3/bin"), + PathBuf::from("/usr/anaconda3/bin"), + PathBuf::from("/usr/miniconda3/bin"), + PathBuf::from("/home/anaconda3/bin"), + PathBuf::from("/home/miniconda3/bin"), + PathBuf::from("/anaconda3/bin"), + PathBuf::from("/miniconda3/bin"), ]; known_paths.append(&mut known::get_know_global_search_locations()); known_paths @@ -333,7 +333,7 @@ fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec { let locations = get_known_env_locations(conda_bin); let mut conda_envs = vec![]; - for env in envs.clone() { + for env in envs { let env = Path::new(&env); let mut named = false; let mut name = "".to_string(); diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index c68340f6c03e..d1d09e8aeda6 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use std::{env, path::PathBuf}; - #[cfg(windows)] pub fn get_know_global_search_locations() -> Vec { vec![] @@ -11,23 +10,21 @@ pub fn get_know_global_search_locations() -> Vec { #[cfg(unix)] pub fn get_know_global_search_locations() -> Vec { vec![ - Path::new("/usr/bin").to_path_buf(), - Path::new("/usr/local/bin").to_path_buf(), - Path::new("/bin").to_path_buf(), - Path::new("/home/bin").to_path_buf(), - Path::new("/sbin").to_path_buf(), - Path::new("/usr/sbin").to_path_buf(), - Path::new("/usr/local/sbin").to_path_buf(), - Path::new("/home/sbin").to_path_buf(), - Path::new("/opt").to_path_buf(), - Path::new("/opt/bin").to_path_buf(), - Path::new("/opt/sbin").to_path_buf(), - Path::new("/opt/homebrew/bin").to_path_buf(), + 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"), ] } - - pub fn get_user_home() -> Option { let home = env::var("HOME").or_else(|_| env::var("USERPROFILE")); match home { From 225e45f9a066009b1e6aa50605086ef09e824808 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 10:19:04 +1000 Subject: [PATCH 006/106] Conda and Known Python Tests (#23349) --- .github/workflows/pr-check.yml | 24 +++++ native_locator/Cargo.toml | 8 +- native_locator/src/common_python.rs | 31 +++--- native_locator/src/conda.rs | 81 +++++++++------- native_locator/src/known.rs | 67 +++++++++---- native_locator/src/lib.rs | 9 ++ native_locator/src/logging.rs | 21 ---- native_locator/src/main.rs | 20 ++-- native_locator/src/messaging.rs | 44 +++++++-- native_locator/src/utils.rs | 20 +++- native_locator/src/windows_python.rs | 21 ++-- native_locator/tests/common.rs | 95 +++++++++++++++++++ native_locator/tests/common_python_test.rs | 32 +++++++ native_locator/tests/conda_test.rs | 90 ++++++++++++++++++ .../tests/unix/conda/.conda/environments.txt | 2 + native_locator/tests/unix/conda/conda | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 + .../tests/unix/conda/envs/one/python | 0 .../tests/unix/conda/envs/two/python | 0 .../.conda/environments.txt | 0 .../tests/unix/conda_without_envs/conda | 0 native_locator/tests/unix/known/python | 0 .../tests/unix/known/python.version | 1 + 23 files changed, 456 insertions(+), 111 deletions(-) create mode 100644 native_locator/src/lib.rs create mode 100644 native_locator/tests/common.rs create mode 100644 native_locator/tests/common_python_test.rs create mode 100644 native_locator/tests/conda_test.rs create mode 100644 native_locator/tests/unix/conda/.conda/environments.txt create mode 100644 native_locator/tests/unix/conda/conda create mode 100644 native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json create mode 100644 native_locator/tests/unix/conda/envs/one/python create mode 100644 native_locator/tests/unix/conda/envs/two/python create mode 100644 native_locator/tests/unix/conda_without_envs/.conda/environments.txt create mode 100644 native_locator/tests/unix/conda_without_envs/conda create mode 100644 native_locator/tests/unix/known/python create mode 100644 native_locator/tests/unix/known/python.version diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index f7102dd5342a..19556a38e30c 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -311,6 +311,30 @@ jobs: run: npm run test:functional if: matrix.test-suite == 'functional' + native-tests: + name: Native Tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. + os: [ubuntu-latest, windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: ${{ env.special-working-directory-relative }} + + - name: Native Locator tests + run: cargo test + working-directory: ${{ env.special-working-directory }}/native_locator + smoke-tests: name: Smoke tests # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml index f20396b09a9d..29fdba160ce9 100644 --- a/native_locator/Cargo.toml +++ b/native_locator/Cargo.toml @@ -7,7 +7,13 @@ edition = "2021" winreg = "0.52.0" [dependencies] -serde = {version ="1.0.152", features = ["derive"]} +serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" serde_repr = "0.1.10" regex = "1.10.4" + +[features] +test = [] + +[lib] +doctest = false diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index c5482bbfc16a..f3fd8d682009 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::known; use crate::messaging; use crate::utils; use std::env; @@ -20,10 +21,10 @@ fn get_env_path(path: &str) -> Option { } } -fn report_path_python(path: &str) { +fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) { let version = utils::get_version(path); let env_path = get_env_path(path); - messaging::send_message(messaging::PythonEnvironment::new( + dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], "System".to_string(), @@ -33,20 +34,26 @@ fn report_path_python(path: &str) { )); } -fn report_python_on_path() { - let bin = if cfg!(windows) { - "python.exe" - } else { - "python" - }; - if let Ok(paths) = env::var("PATH") { +fn report_python_on_path( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) { + if let Some(paths) = environment.get_env_var("PATH".to_string()) { + let bin = if cfg!(windows) { + "python.exe" + } else { + "python" + }; env::split_paths(&paths) .map(|p| p.join(bin)) .filter(|p| p.exists()) - .for_each(|full_path| report_path_python(full_path.to_str().unwrap())); + .for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap())); } } -pub fn find_and_report() { - report_python_on_path(); +pub fn find_and_report( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) { + report_python_on_path(dispatcher, environment); } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index db0bc3bc10f1..1bb579e5d218 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -84,7 +84,6 @@ fn get_version_from_meta_json(json_file: &Path) -> Option { fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { let package_name = format!("{}-", package); let conda_meta_path = get_conda_meta_path(any_path)?; - std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { let path = entry.ok()?.path(); let file_name = path.file_name()?.to_string_lossy(); @@ -97,6 +96,7 @@ fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option bool { let conda_python_json_path = get_conda_package_json_path(any_path, "python"); match conda_python_json_path { @@ -127,11 +127,9 @@ fn get_conda_bin_names() -> Vec<&'static str> { } /// Find the conda binary on the PATH environment variable -fn find_conda_binary_on_path() -> Option { - let paths = env::var("PATH").ok()?; - let paths = env::split_paths(&paths); - for path in paths { - let path = Path::new(&path); +fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option { + let paths = environment.get_env_var("PATH".to_string())?; + for path in env::split_paths(&paths) { for bin in get_conda_bin_names() { let conda_path = path.join(bin); match std::fs::metadata(&conda_path) { @@ -161,11 +159,13 @@ fn find_python_binary_path(env_path: &Path) -> Option { } #[cfg(windows)] -fn get_known_conda_locations() -> Vec { - let user_profile = env::var("USERPROFILE").unwrap(); - let program_data = env::var("PROGRAMDATA").unwrap(); - let all_user_profile = env::var("ALLUSERSPROFILE").unwrap(); - let home_drive = env::var("HOMEDRIVE").unwrap(); +fn get_known_conda_locations(environment: &impl known::Environment) -> Vec { + let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); + let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); + let all_user_profile = environment + .get_env_var("ALLUSERSPROFILE".to_string()) + .unwrap(); + let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); let mut known_paths = vec![ Path::new(&user_profile).join("Anaconda3\\Scripts"), Path::new(&program_data).join("Anaconda3\\Scripts"), @@ -176,12 +176,12 @@ fn get_known_conda_locations() -> Vec { Path::new(&all_user_profile).join("Miniconda3\\Scripts"), Path::new(&home_drive).join("Miniconda3\\Scripts"), ]; - known_paths.append(&mut known::get_know_global_search_locations()); + known_paths.append(&mut environment.get_know_global_search_locations()); known_paths } #[cfg(unix)] -fn get_known_conda_locations() -> Vec { +fn get_known_conda_locations(environment: &impl known::Environment) -> Vec { let mut known_paths = vec![ PathBuf::from("/opt/anaconda3/bin"), PathBuf::from("/opt/miniconda3/bin"), @@ -202,14 +202,14 @@ fn get_known_conda_locations() -> Vec { PathBuf::from("/anaconda3/bin"), PathBuf::from("/miniconda3/bin"), ]; - known_paths.append(&mut known::get_know_global_search_locations()); + known_paths.append(&mut environment.get_know_global_search_locations()); known_paths } /// Find conda binary in known locations -fn find_conda_binary_in_known_locations() -> Option { +fn find_conda_binary_in_known_locations(environment: &impl known::Environment) -> Option { let conda_bin_names = get_conda_bin_names(); - let known_locations = get_known_conda_locations(); + let known_locations = get_known_conda_locations(environment); for location in known_locations { for bin in &conda_bin_names { let conda_path = location.join(bin); @@ -223,17 +223,17 @@ fn find_conda_binary_in_known_locations() -> Option { } /// Find the conda binary on the system -pub fn find_conda_binary() -> Option { - let conda_binary_on_path = find_conda_binary_on_path(); +pub fn find_conda_binary(environment: &impl known::Environment) -> Option { + let conda_binary_on_path = find_conda_binary_on_path(environment); match conda_binary_on_path { Some(conda_binary_on_path) => Some(conda_binary_on_path), - None => find_conda_binary_in_known_locations(), + None => find_conda_binary_in_known_locations(environment), } } -fn get_conda_envs_from_environment_txt() -> Vec { +fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec { let mut envs = vec![]; - let home = known::get_user_home(); + let home = environment.get_user_home(); match home { Some(home) => { let home = Path::new(&home); @@ -252,9 +252,12 @@ fn get_conda_envs_from_environment_txt() -> Vec { envs } -fn get_known_env_locations(conda_bin: PathBuf) -> Vec { +fn get_known_env_locations( + conda_bin: PathBuf, + environment: &impl known::Environment, +) -> Vec { let mut paths = vec![]; - let home = known::get_user_home(); + let home = environment.get_user_home(); match home { Some(home) => { let home = Path::new(&home); @@ -284,9 +287,12 @@ fn get_known_env_locations(conda_bin: PathBuf) -> Vec { paths } -fn get_conda_envs_from_known_env_locations(conda_bin: PathBuf) -> Vec { +fn get_conda_envs_from_known_env_locations( + conda_bin: PathBuf, + environment: &impl known::Environment, +) -> Vec { let mut envs = vec![]; - for location in get_known_env_locations(conda_bin) { + for location in get_known_env_locations(conda_bin, environment) { if is_conda_environment(&Path::new(&location)) { envs.push(location.to_string()); } @@ -324,14 +330,18 @@ struct CondaEnv { path: PathBuf, } -fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec { - let mut envs = get_conda_envs_from_environment_txt(); - let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin.to_path_buf()); +fn get_distinct_conda_envs( + conda_bin: PathBuf, + environment: &impl known::Environment, +) -> Vec { + let mut envs = get_conda_envs_from_environment_txt(environment); + let mut known_envs = + get_conda_envs_from_known_env_locations(conda_bin.to_path_buf(), environment); envs.append(&mut known_envs); envs.sort(); envs.dedup(); - let locations = get_known_env_locations(conda_bin); + let locations = get_known_env_locations(conda_bin, environment); let mut conda_envs = vec![]; for env in envs { let env = Path::new(&env); @@ -367,16 +377,19 @@ fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec { conda_envs } -pub fn find_and_report() { - let conda_binary = find_conda_binary(); +pub fn find_and_report( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) { + let conda_binary = find_conda_binary(environment); match conda_binary { Some(conda_binary) => { let params = messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None); let message = messaging::EnvManagerMessage::new(params); - messaging::send_message(message); + dispatcher.send_message(message); - let envs = get_distinct_conda_envs(conda_binary.to_path_buf()); + let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment); for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); let params = messaging::PythonEnvironment::new( @@ -407,7 +420,7 @@ pub fn find_and_report() { Some(env.path.to_string_lossy().to_string()), ); let message = messaging::PythonEnvironmentMessage::new(params); - messaging::send_message(message); + dispatcher.send_message(message); } } None => (), diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index d1d09e8aeda6..8c2fdb4386e1 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -2,33 +2,64 @@ // Licensed under the MIT License. use std::{env, path::PathBuf}; +pub trait Environment { + fn get_user_home(&self) -> Option; + fn get_env_var(&self, key: String) -> Option; + fn get_know_global_search_locations(&self) -> Vec; +} + +pub struct EnvironmentApi {} + #[cfg(windows)] -pub fn get_know_global_search_locations() -> Vec { - vec![] +impl Environment for EnvironmentApi { + fn get_user_home(&self) -> Option { + get_user_home() + } + fn get_env_var(&self, key: String) -> Option { + get_env_var(key) + } + fn get_know_global_search_locations(&self) -> Vec { + vec![] + } } #[cfg(unix)] -pub fn get_know_global_search_locations() -> Vec { - 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"), - ] +impl Environment for EnvironmentApi { + fn get_user_home(&self) -> Option { + get_user_home() + } + fn get_env_var(&self, key: String) -> Option { + get_env_var(key) + } + fn get_know_global_search_locations(&self) -> Vec { + 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"), + ] + } } -pub fn get_user_home() -> Option { +fn get_user_home() -> Option { let home = env::var("HOME").or_else(|_| env::var("USERPROFILE")); match home { Ok(home) => Some(home), Err(_) => None, } } + +fn get_env_var(key: String) -> Option { + match env::var(key) { + Ok(path) => Some(path), + Err(_) => None, + } +} diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs new file mode 100644 index 000000000000..d95a4300d253 --- /dev/null +++ b/native_locator/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod messaging; +pub mod utils; +pub mod common_python; +pub mod logging; +pub mod conda; +pub mod known; diff --git a/native_locator/src/logging.rs b/native_locator/src/logging.rs index 7dc2b495442a..66532ff67eff 100644 --- a/native_locator/src/logging.rs +++ b/native_locator/src/logging.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::messaging; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug, Eq, Clone)] @@ -40,23 +39,3 @@ impl LogMessage { } } } - -pub fn log_debug(message: &str) { - messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); -} - -pub fn log_info(message: &str) { - messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Info)); -} - -pub fn log_warning(message: &str) { - messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); -} - -pub fn log_error(message: &str) { - messaging::send_message(LogMessage::new(message.to_string(), LogLevel::Error)); -} - -pub fn log_msg(message: &str, level: LogLevel) { - messaging::send_message(LogMessage::new(message.to_string(), level)); -} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index aa339e4e2b6d..8a11101404dd 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -3,6 +3,9 @@ use std::time::SystemTime; +use known::EnvironmentApi; +use messaging::{create_dispatcher, MessageDispatcher}; + mod common_python; mod conda; mod known; @@ -12,30 +15,33 @@ mod utils; mod windows_python; fn main() { + let mut dispatcher = create_dispatcher(); + let environment = EnvironmentApi {}; + + dispatcher.log_info("Starting Native Locator"); let now = SystemTime::now(); - logging::log_info("Starting Native Locator"); // Finds python on PATH - common_python::find_and_report(); + common_python::find_and_report(&mut dispatcher, &environment); // Finds conda binary and conda environments - conda::find_and_report(); + conda::find_and_report(&mut dispatcher, &environment); // Finds Windows Store, Known Path, and Registry pythons #[cfg(windows)] - windows_python::find_and_report(); + windows_python::find_and_report(&mut dispatcher, &known_paths); match now.elapsed() { Ok(elapsed) => { - logging::log_info(&format!( + dispatcher.log_info(&format!( "Native Locator took {} milliseconds.", elapsed.as_millis() )); } Err(e) => { - logging::log_error(&format!("Error getting elapsed time: {:?}", e)); + dispatcher.log_error(&format!("Error getting elapsed time: {:?}", e)); } } - messaging::send_message(messaging::ExitMessage::new()); + dispatcher.send_message(messaging::ExitMessage::new()); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 540ba0595988..a433604f059a 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,8 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::logging::{LogLevel, LogMessage}; use serde::{Deserialize, Serialize}; +pub trait MessageDispatcher { + fn send_message(&mut self, message: T) -> (); + fn log_debug(&mut self, message: &str) -> (); + fn log_info(&mut self, message: &str) -> (); + fn log_warning(&mut self, message: &str) -> (); + fn log_error(&mut self, message: &str) -> (); +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { @@ -59,7 +68,7 @@ impl PythonEnvironment { ) -> Self { Self { name, - python_executable_path: python_executable_path, + python_executable_path, category, version, activated_run, @@ -104,15 +113,30 @@ impl ExitMessage { } } -fn send_rpc_message(message: String) -> () { - print!( - "Content-Length: {}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{}", - message.len(), - message - ); +pub struct JsonRpcDispatcher {} +impl MessageDispatcher for JsonRpcDispatcher { + fn send_message(&mut self, 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 + ); + } + fn log_debug(&mut self, message: &str) -> () { + self.send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); + } + fn log_error(&mut self, message: &str) -> () { + self.send_message(LogMessage::new(message.to_string(), LogLevel::Error)); + } + fn log_info(&mut self, message: &str) -> () { + self.send_message(LogMessage::new(message.to_string(), LogLevel::Info)); + } + fn log_warning(&mut self, message: &str) -> () { + self.send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); + } } -pub fn send_message(message: T) -> () { - let message = serde_json::to_string(&message).unwrap(); - send_rpc_message(message); +pub fn create_dispatcher() -> JsonRpcDispatcher { + JsonRpcDispatcher {} } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 001f56c815ca..d3573e3190a1 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -3,7 +3,7 @@ use std::process::Command; -pub fn get_version(path: &str) -> Option { +fn get_version_impl(path: &str) -> Option { let output = Command::new(path) .arg("-c") .arg("import sys; print(sys.version)") @@ -14,3 +14,21 @@ pub fn get_version(path: &str) -> Option { let output = output.split_whitespace().next().unwrap_or(output); Some(output.to_string()) } + +#[cfg(not(feature = "test"))] +pub fn get_version(path: &str) -> Option { + get_version_impl(path) +} + +// Tests + +#[cfg(feature = "test")] +pub fn get_version(path: &str) -> Option { + use std::path::PathBuf; + let version_file = PathBuf::from(path.to_owned() + ".version"); + if version_file.exists() { + let version = std::fs::read_to_string(version_file).ok()?; + return Some(version.trim().to_string()); + } + get_version_impl(path) +} diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 0cb49d975685..5f5d53fafa2e 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -6,9 +6,9 @@ use crate::messaging; use crate::utils; use std::path::Path; -fn report_path_python(path: &str) { +fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { let version = utils::get_version(path); - messaging::send_message(messaging::PythonEnvironment::new( + dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], "WindowsStore".to_string(), @@ -18,8 +18,11 @@ fn report_path_python(path: &str) { )); } -fn report_windows_store_python() { - let home = known::get_user_home(); +fn report_windows_store_python( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) { + let home = environment.get_user_home(); match home { Some(home) => { let apps_path = Path::new(&home) @@ -38,7 +41,7 @@ fn report_windows_store_python() { Some(name) => { let name = name.to_string_lossy().to_lowercase(); if name.starts_with("python3.") && name.ends_with(".exe") { - report_path_python(&path.to_string_lossy()); + report_path_python(&path.to_string_lossy(), dispatcher); } } None => {} @@ -57,7 +60,11 @@ fn report_windows_store_python() { fn report_registry_pythons() {} -pub fn find_and_report() { - report_windows_store_python(); +#[allow(dead_code)] +pub fn find_and_report( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) { + report_windows_store_python(dispatcher, environment); report_registry_pythons(); } diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs new file mode 100644 index 000000000000..5ff67224d258 --- /dev/null +++ b/native_locator/tests/common.rs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{collections::HashMap, path::PathBuf}; + +use python_finder::{known::Environment, messaging::MessageDispatcher}; +use serde_json::Value; + +#[allow(dead_code)] +pub fn test_file_path(paths: &[&str]) -> String { + // let parts: Vec = paths.iter().map(|p| p.to_string()).collect(); + let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + paths.iter().for_each(|p| root.push(p)); + + root.to_string_lossy().to_string() +} + +#[allow(dead_code)] +pub fn join_test_paths(paths: &[&str]) -> String { + let path: PathBuf = paths.iter().map(|p| p.to_string()).collect(); + path.to_string_lossy().to_string() +} + +pub struct TestDispatcher { + pub messages: Vec, +} +pub trait TestMessages { + fn get_messages(&self) -> Vec; +} + +#[allow(dead_code)] +pub fn create_test_dispatcher() -> TestDispatcher { + impl MessageDispatcher for TestDispatcher { + fn send_message(&mut self, message: T) -> () { + self.messages.push(serde_json::to_string(&message).unwrap()); + } + fn log_debug(&mut self, _message: &str) -> () {} + fn log_error(&mut self, _message: &str) -> () {} + fn log_info(&mut self, _message: &str) -> () {} + fn log_warning(&mut self, _message: &str) -> () {} + } + impl TestMessages for TestDispatcher { + fn get_messages(&self) -> Vec { + self.messages.clone() + } + } + TestDispatcher { + messages: Vec::new(), + } +} +pub struct TestEnvironment { + vars: HashMap, + home: Option, + globals_locations: Vec, +} +#[allow(dead_code)] +pub fn create_test_environment( + vars: HashMap, + home: Option, + globals_locations: Vec, +) -> TestEnvironment { + impl Environment for TestEnvironment { + fn get_env_var(&self, key: String) -> Option { + self.vars.get(&key).cloned() + } + fn get_user_home(&self) -> Option { + self.home.clone() + } + fn get_know_global_search_locations(&self) -> Vec { + self.globals_locations.clone() + } + } + TestEnvironment { + vars, + home, + globals_locations, + } +} + +#[allow(dead_code)] +pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { + assert_eq!( + expected_json.len(), + dispatcher.messages.len(), + "Incorrect number of messages" + ); + + if expected_json.len() == 0 { + return; + } + + let actual: serde_json::Value = serde_json::from_str(dispatcher.messages[0].as_str()).unwrap(); + assert_eq!(expected_json[0], actual); +} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs new file mode 100644 index 000000000000..a05be51e7218 --- /dev/null +++ b/native_locator/tests/common_python_test.rs @@ -0,0 +1,32 @@ +// 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_dispatcher, create_test_environment, join_test_paths, + test_file_path, + }; + use python_finder::common_python; + use serde_json::json; + use std::collections::HashMap; + + let unix_python = test_file_path(&["tests/unix/known"]); + let unix_python_exe = join_test_paths(&[unix_python.as_str(), "python"]); + + let mut dispatcher = create_test_dispatcher(); + let known = create_test_environment( + HashMap::from([("PATH".to_string(), unix_python.clone())]), + Some(unix_python.clone()), + Vec::new(), + ); + + common_python::find_and_report(&mut dispatcher, &known); + + assert_eq!(dispatcher.messages.len(), 1); + let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"System","version":null,"activatedRun":null,"envPath":unix_python.clone()}); + assert_messages(&[expected_json], &dispatcher); +} diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs new file mode 100644 index 000000000000..ef4d72c6c552 --- /dev/null +++ b/native_locator/tests/conda_test.rs @@ -0,0 +1,90 @@ +// 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_dispatcher, create_test_environment}; + use python_finder::conda; + use std::collections::HashMap; + + let mut dispatcher = create_test_dispatcher(); + let known = create_test_environment( + HashMap::from([("PATH".to_string(), "".to_string())]), + Some("SOME_BOGUS_HOME_DIR".to_string()), + Vec::new(), + ); + + conda::find_and_report(&mut dispatcher, &known); + + assert_eq!(dispatcher.messages.len(), 0); +} + +#[test] +#[cfg(unix)] +fn find_conda_exe_and_empty_envs() { + use crate::common::{ + assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + test_file_path, + }; + use python_finder::conda; + use serde_json::json; + use std::collections::HashMap; + let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); + + let mut dispatcher = create_test_dispatcher(); + let known = create_test_environment( + HashMap::from([("PATH".to_string(), conda_dir.clone())]), + Some("SOME_BOGUS_HOME_DIR".to_string()), + Vec::new(), + ); + + conda::find_and_report(&mut dispatcher, &known); + + let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); + let expected_json = json!({"jsonrpc":"2.0","method":"envManager","params":{"executablePath":[conda_exe.clone()],"version":null}}); + assert_messages(&[expected_json], &dispatcher) +} +#[test] +#[cfg(unix)] +fn finds_two_conda_envs_from_txt() { + use crate::common::{ + assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + test_file_path, + }; + use python_finder::conda; + use serde_json::json; + use std::collections::HashMap; + use std::fs; + + let conda_dir = test_file_path(&["tests/unix/conda"]); + let conda_1 = join_test_paths(&[conda_dir.clone().as_str(), "envs/one"]); + let conda_2 = join_test_paths(&[conda_dir.clone().as_str(), "envs/two"]); + let _ = fs::write( + "tests/unix/conda/.conda/environments.txt", + format!("{}\n{}", conda_1.clone(), conda_2.clone()), + ); + + let mut dispatcher = create_test_dispatcher(); + let known = create_test_environment( + HashMap::from([("PATH".to_string(), conda_dir.clone())]), + Some(conda_dir.clone()), + Vec::new(), + ); + + conda::find_and_report(&mut dispatcher, &known); + + let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); + let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); + let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); + + let expected_conda_env = json!({"jsonrpc":"2.0","method":"envManager","params":{"executablePath":[conda_exe.clone()],"version":null}}); + let expected_conda_1 = json!({"jsonrpc":"2.0","method":"pythonEnvironment","params":{"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone()}}); + let expected_conda_2 = json!({"jsonrpc":"2.0","method":"pythonEnvironment","params":{"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone()}}); + assert_messages( + &[expected_conda_env, expected_conda_1, expected_conda_2], + &dispatcher, + ) +} diff --git a/native_locator/tests/unix/conda/.conda/environments.txt b/native_locator/tests/unix/conda/.conda/environments.txt new file mode 100644 index 000000000000..908019719b55 --- /dev/null +++ b/native_locator/tests/unix/conda/.conda/environments.txt @@ -0,0 +1,2 @@ +/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/envs/one +/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/envs/two \ No newline at end of file diff --git a/native_locator/tests/unix/conda/conda b/native_locator/tests/unix/conda/conda new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json new file mode 100644 index 000000000000..23127993ac05 --- /dev/null +++ b/native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json @@ -0,0 +1 @@ +10.1.1 diff --git a/native_locator/tests/unix/conda/envs/one/python b/native_locator/tests/unix/conda/envs/one/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda/envs/two/python b/native_locator/tests/unix/conda/envs/two/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/.conda/environments.txt b/native_locator/tests/unix/conda_without_envs/.conda/environments.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/conda b/native_locator/tests/unix/conda_without_envs/conda new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/known/python b/native_locator/tests/unix/known/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/known/python.version b/native_locator/tests/unix/known/python.version new file mode 100644 index 000000000000..4044f90867df --- /dev/null +++ b/native_locator/tests/unix/known/python.version @@ -0,0 +1 @@ +12.0.0 From 9c29fd1d7d27afbe877798806177902d2460e506 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 10:48:19 +1000 Subject: [PATCH 007/106] Fix typo in windows locator (#23355) --- native_locator/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 8a11101404dd..fac7050259de 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -29,7 +29,7 @@ fn main() { // Finds Windows Store, Known Path, and Registry pythons #[cfg(windows)] - windows_python::find_and_report(&mut dispatcher, &known_paths); + windows_python::find_and_report(&mut dispatcher, &environment); match now.elapsed() { Ok(elapsed) => { From 7546f1e05fb9bacc072d8efbb436938ab48aa59a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 12:02:20 +1000 Subject: [PATCH 008/106] Support for pyenv locator (#23356) --- native_locator/src/conda.rs | 14 +---- native_locator/src/main.rs | 3 + native_locator/src/pyenv.rs | 115 ++++++++++++++++++++++++++++++++++++ native_locator/src/utils.rs | 18 +++++- 4 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 native_locator/src/pyenv.rs diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 1bb579e5d218..12e8276de474 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -3,6 +3,7 @@ use crate::known; use crate::messaging; +use crate::utils::find_python_binary_path; use regex::Regex; use std::env; use std::path::{Path, PathBuf}; @@ -145,19 +146,6 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option Option { - 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()) -} - #[cfg(windows)] fn get_known_conda_locations(environment: &impl known::Environment) -> Vec { let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index fac7050259de..0d61547d081f 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -13,6 +13,7 @@ mod logging; mod messaging; mod utils; mod windows_python; +mod pyenv; fn main() { let mut dispatcher = create_dispatcher(); @@ -31,6 +32,8 @@ fn main() { #[cfg(windows)] windows_python::find_and_report(&mut dispatcher, &environment); + pyenv::find_and_report(&mut dispatcher, &environment); + match now.elapsed() { Ok(elapsed) => { dispatcher.log_info(&format!( diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs new file mode 100644 index 000000000000..2c34f8848381 --- /dev/null +++ b/native_locator/src/pyenv.rs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fs; +use std::path::PathBuf; + +use crate::known; +use crate::messaging; +use crate::utils::find_python_binary_path; + +#[cfg(windows)] +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { + let home = environment.get_user_home()?; + PathBuf::from(home) + .join(".pyenv") + .join("pyenv-win") + .into_os_string() + .into_string() + .ok() +} + +#[cfg(unix)] +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { + let home = environment.get_user_home()?; + PathBuf::from(home) + .join(".pyenv") + .into_os_string() + .into_string() + .ok() +} + +fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { + for known_path in environment.get_know_global_search_locations() { + let bin = known_path.join("pyenv"); + if bin.exists() { + return bin.into_os_string().into_string().ok(); + } + } + None +} + +fn get_pyenv_dir(environment: &impl known::Environment) -> Option { + // 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(dir), + None => match environment.get_env_var("PYENV".to_string()) { + Some(dir) => Some(dir), + None => get_home_pyenv_dir(environment), + }, + } +} + +fn get_pyenv_binary(environment: &impl known::Environment) -> Option { + let dir = get_pyenv_dir(environment)?; + let exe = PathBuf::from(dir).join("bin").join("pyenv"); + if fs::metadata(&exe).is_ok() { + exe.into_os_string().into_string().ok() + } else { + get_binary_from_known_paths(environment) + } +} + +pub fn find_and_report( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) -> Option<()> { + let pyenv_dir = get_pyenv_dir(environment)?; + + if let Some(pyenv_binary) = get_pyenv_binary(environment) { + let params = messaging::EnvManager::new(vec![pyenv_binary], None); + let message = messaging::EnvManagerMessage::new(params); + dispatcher.send_message(message); + } + + let versions_dir = PathBuf::from(&pyenv_dir) + .join("versions") + .into_os_string() + .into_string() + .ok()?; + + let pyenv_binary_for_activation = match get_pyenv_binary(environment) { + Some(binary) => binary, + None => "pyenv".to_string(), + }; + for entry in fs::read_dir(&versions_dir).ok()? { + if let Ok(path) = entry { + let path = path.path(); + if path.is_dir() { + if let Some(executable) = find_python_binary_path(&path) { + let version = path.file_name().unwrap().to_string_lossy().to_string(); + dispatcher.send_message(messaging::PythonEnvironment::new( + "Python".to_string(), + vec![executable.into_os_string().into_string().unwrap()], + "Pyenv".to_string(), + Some(version.clone()), + Some(vec![ + pyenv_binary_for_activation.clone(), + "shell".to_string(), + version, + ]), + Some(path.into_os_string().into_string().unwrap()), + )); + } + } + } + } + + None +} diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index d3573e3190a1..7a235a251942 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::process::Command; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; fn get_version_impl(path: &str) -> Option { let output = Command::new(path) @@ -32,3 +35,16 @@ pub fn get_version(path: &str) -> Option { } get_version_impl(path) } + +pub fn find_python_binary_path(env_path: &Path) -> Option { + 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()) +} From b5b0e7ed8e329a6245835be2cbb3d8cb0cda3be5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 12:55:47 +1000 Subject: [PATCH 009/106] Use enum for category (#23357) --- native_locator/src/common_python.rs | 2 +- native_locator/src/conda.rs | 2 +- native_locator/src/messaging.rs | 13 +++++++++++-- native_locator/src/pyenv.rs | 2 +- native_locator/src/windows_python.rs | 2 +- native_locator/tests/common_python_test.rs | 2 +- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index f3fd8d682009..f1a1bdb1d430 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -27,7 +27,7 @@ fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], - "System".to_string(), + messaging::PythonEnvironmentCategory::System, version, None, env_path, diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 12e8276de474..d7c2995db89e 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -386,7 +386,7 @@ pub fn find_and_report( Some(executable) => vec![executable.to_string_lossy().to_string()], None => vec![], }, - "conda".to_string(), + messaging::PythonEnvironmentCategory::Conda, get_conda_python_version(&env.path), if env.named { Some(vec![ diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index a433604f059a..a079e0a39f98 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -46,12 +46,21 @@ impl EnvManagerMessage { } } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum PythonEnvironmentCategory { + System, + Conda, + Pyenv, + WindowsStore, +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { pub name: String, pub python_executable_path: Vec, - pub category: String, + pub category: PythonEnvironmentCategory, pub version: Option, pub activated_run: Option>, pub env_path: Option, @@ -61,7 +70,7 @@ impl PythonEnvironment { pub fn new( name: String, python_executable_path: Vec, - category: String, + category: PythonEnvironmentCategory, version: Option, activated_run: Option>, env_path: Option, diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 2c34f8848381..1787aa8ef841 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -97,7 +97,7 @@ pub fn find_and_report( dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![executable.into_os_string().into_string().unwrap()], - "Pyenv".to_string(), + messaging::PythonEnvironmentCategory::Pyenv, Some(version.clone()), Some(vec![ pyenv_binary_for_activation.clone(), diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 5f5d53fafa2e..860cec1f7ff3 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -11,7 +11,7 @@ fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispat dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], - "WindowsStore".to_string(), + messaging::PythonEnvironmentCategory::WindowsStore, version, None, None, diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index a05be51e7218..0f5b1853e7f8 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -27,6 +27,6 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"System","version":null,"activatedRun":null,"envPath":unix_python.clone()}); + let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"system","version":null,"activatedRun":null,"envPath":unix_python.clone()}); assert_messages(&[expected_json], &dispatcher); } From 06839d136f5fc824166ba9c8d45cda85565b2338 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 13:16:16 +1000 Subject: [PATCH 010/106] Adopt native pyenv locator in TS land (#23359) --- .../base/locators/lowLevel/nativeLocator.ts | 46 +++++++++++++++++-- .../common/environmentManagers/pyenv.ts | 10 ++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 8aa4631b8e94..6421a5705b02 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -10,6 +10,9 @@ import { PythonEnvsChangedEvent } from '../../watcher'; import { createDeferred } from '../../../../common/utils/async'; import { PythonEnvKind, PythonVersion } from '../../info'; import { Conda } from '../../../common/environmentManagers/conda'; +import { traceError } from '../../../../logging'; +import type { KnownEnvironmentTools } from '../../../../api/types'; +import { setPyEnvBinary } from '../../../common/environmentManagers/pyenv'; const NATIVE_LOCATOR = isWindows() ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe') @@ -25,15 +28,38 @@ interface NativeEnvInfo { } interface EnvManager { + tool: string; executablePath: string[]; version?: string; } function categoryToKind(category: string): PythonEnvKind { - if (category === 'conda') { - return PythonEnvKind.Conda; + switch (category.toLowerCase()) { + case 'conda': + return PythonEnvKind.Conda; + case 'system': + return PythonEnvKind.System; + case 'pyenv': + return PythonEnvKind.Pyenv; + case 'windowsstore': + return PythonEnvKind.MicrosoftStore; + default: { + traceError(`Unknown Python Environment category '${category}' from Native Locator.`); + return PythonEnvKind.Unknown; + } + } +} +function toolToKnownEnvironmentTool(tool: string): KnownEnvironmentTools { + switch (tool.toLowerCase()) { + case 'conda': + return 'Conda'; + case 'pyenv': + return 'Pyenv'; + default: { + traceError(`Unknown Python Tool '${tool}' from Native Locator.`); + return 'Unknown'; + } } - return PythonEnvKind.Unknown; } function parseVersion(version?: string): PythonVersion | undefined { @@ -92,7 +118,19 @@ export class NativeLocator implements ILocator, IDisposable { }); }); connection.onNotification('envManager', (data: EnvManager) => { - Conda.setConda(data.executablePath[0]); + switch (toolToKnownEnvironmentTool(data.tool)) { + case 'Conda': { + Conda.setConda(data.executablePath[0]); + break; + } + case 'Pyenv': { + setPyEnvBinary(data.executablePath[0]); + break; + } + default: { + break; + } + } }); connection.onNotification('exit', () => { deferred.resolve(); diff --git a/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts b/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts index 3d6c276cc868..8556e6f19f90 100644 --- a/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts +++ b/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts @@ -21,7 +21,17 @@ export function getPyenvDir(): string { return pyenvDir; } +let pyenvBinary: string | undefined; + +export function setPyEnvBinary(pyenvBin: string): void { + pyenvBinary = pyenvBin; +} + async function getPyenvBinary(): Promise { + if (pyenvBinary && (await pathExists(pyenvBinary))) { + return pyenvBinary; + } + const pyenvDir = getPyenvDir(); const pyenvBin = path.join(pyenvDir, 'bin', 'pyenv'); if (await pathExists(pyenvBin)) { From 9f4ed59cedeea12a838375585367a09617a0c2ec Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 13:29:41 +1000 Subject: [PATCH 011/106] Add sysPrefixPath to native locator (#23360) --- native_locator/src/common_python.rs | 1 + native_locator/src/conda.rs | 4 +++- native_locator/src/messaging.rs | 3 +++ native_locator/src/pyenv.rs | 4 +++- native_locator/src/windows_python.rs | 1 + native_locator/tests/common_python_test.rs | 2 +- .../base/locators/lowLevel/nativeLocator.ts | 1 + 7 files changed, 13 insertions(+), 3 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index f1a1bdb1d430..a83afaa2bc84 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -30,6 +30,7 @@ fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: messaging::PythonEnvironmentCategory::System, version, None, + env_path.clone(), env_path, )); } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index d7c2995db89e..6bf50ab96b89 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -380,6 +380,7 @@ pub fn find_and_report( let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment); for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); + let env_path = env.path.to_string_lossy().to_string(); let params = messaging::PythonEnvironment::new( env.name.to_string(), match executable { @@ -405,7 +406,8 @@ pub fn find_and_report( "python".to_string(), ]) }, - Some(env.path.to_string_lossy().to_string()), + Some(env_path.clone()), + Some(env_path), ); let message = messaging::PythonEnvironmentMessage::new(params); dispatcher.send_message(message); diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index a079e0a39f98..dee13bae0f87 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -64,6 +64,7 @@ pub struct PythonEnvironment { pub version: Option, pub activated_run: Option>, pub env_path: Option, + pub sys_prefix_path: Option, } impl PythonEnvironment { @@ -74,6 +75,7 @@ impl PythonEnvironment { version: Option, activated_run: Option>, env_path: Option, + sys_prefix_path: Option, ) -> Self { Self { name, @@ -82,6 +84,7 @@ impl PythonEnvironment { version, activated_run, env_path, + sys_prefix_path } } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 1787aa8ef841..bce5b4dfa19c 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -94,6 +94,7 @@ pub fn find_and_report( if path.is_dir() { if let Some(executable) = find_python_binary_path(&path) { let version = path.file_name().unwrap().to_string_lossy().to_string(); + let env_path = path.to_string_lossy().to_string(); dispatcher.send_message(messaging::PythonEnvironment::new( "Python".to_string(), vec![executable.into_os_string().into_string().unwrap()], @@ -104,7 +105,8 @@ pub fn find_and_report( "shell".to_string(), version, ]), - Some(path.into_os_string().into_string().unwrap()), + Some(env_path.clone()), + Some(env_path), )); } } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 860cec1f7ff3..122f4eabd832 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -15,6 +15,7 @@ fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispat version, None, None, + None, )); } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 0f5b1853e7f8..31d8b8c52f63 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -27,6 +27,6 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"system","version":null,"activatedRun":null,"envPath":unix_python.clone()}); + let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"system","version":null,"activatedRun":null,"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); assert_messages(&[expected_json], &dispatcher); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 6421a5705b02..a77b45494c3d 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -25,6 +25,7 @@ interface NativeEnvInfo { version?: string; activatedRun?: string[]; envPath?: string; + sysPrefixPath?: string; } interface EnvManager { From d9143998bcc953a872912e1a9ac8c47728013ba2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 13:44:05 +1000 Subject: [PATCH 012/106] Fix discovery of native conda locator (#23361) --- native_locator/src/conda.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 6bf50ab96b89..0e5177606767 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -181,15 +181,11 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec Date: Tue, 7 May 2024 15:09:07 +1000 Subject: [PATCH 013/106] Ensure all messages are JSON rpc payloads (#23362) --- native_locator/src/common_python.rs | 2 +- native_locator/src/conda.rs | 6 ++--- native_locator/src/main.rs | 2 +- native_locator/src/messaging.rs | 37 ++++++++++++++++++---------- native_locator/src/pyenv.rs | 6 ++--- native_locator/src/windows_python.rs | 2 +- native_locator/tests/common.rs | 12 ++++++--- native_locator/tests/conda_test.rs | 8 +++--- 8 files changed, 44 insertions(+), 31 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index a83afaa2bc84..7317b9c52c7f 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -24,7 +24,7 @@ fn get_env_path(path: &str) -> Option { fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) { let version = utils::get_version(path); let env_path = get_env_path(path); - dispatcher.send_message(messaging::PythonEnvironment::new( + dispatcher.report_environment(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], messaging::PythonEnvironmentCategory::System, diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 0e5177606767..8e64bacf13b1 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -371,8 +371,7 @@ pub fn find_and_report( Some(conda_binary) => { let params = messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None); - let message = messaging::EnvManagerMessage::new(params); - dispatcher.send_message(message); + dispatcher.report_environment_manager(params); let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment); for env in envs { @@ -406,8 +405,7 @@ pub fn find_and_report( Some(env_path.clone()), Some(env_path), ); - let message = messaging::PythonEnvironmentMessage::new(params); - dispatcher.send_message(message); + dispatcher.report_environment(params); } } None => (), diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 0d61547d081f..1c533a7fefec 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -46,5 +46,5 @@ fn main() { } } - dispatcher.send_message(messaging::ExitMessage::new()); + dispatcher.exit(); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index dee13bae0f87..7fe24fd8b4c8 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -5,7 +5,9 @@ use crate::logging::{LogLevel, LogMessage}; use serde::{Deserialize, Serialize}; pub trait MessageDispatcher { - fn send_message(&mut self, message: T) -> (); + fn report_environment_manager(&mut self, env: EnvManager) -> (); + fn report_environment(&mut self, env: PythonEnvironment) -> (); + fn exit(&mut self) -> (); fn log_debug(&mut self, message: &str) -> (); fn log_info(&mut self, message: &str) -> (); fn log_warning(&mut self, message: &str) -> (); @@ -84,7 +86,7 @@ impl PythonEnvironment { version, activated_run, env_path, - sys_prefix_path + sys_prefix_path, } } } @@ -126,26 +128,35 @@ impl ExitMessage { } pub struct JsonRpcDispatcher {} +fn send_message(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 + ); +} impl MessageDispatcher for JsonRpcDispatcher { - fn send_message(&mut self, 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 - ); + fn report_environment_manager(&mut self, env: EnvManager) -> () { + send_message(EnvManagerMessage::new(env)); + } + fn report_environment(&mut self, env: PythonEnvironment) -> () { + send_message(PythonEnvironmentMessage::new(env)); + } + fn exit(&mut self) -> () { + send_message(ExitMessage::new()); } fn log_debug(&mut self, message: &str) -> () { - self.send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); + send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); } fn log_error(&mut self, message: &str) -> () { - self.send_message(LogMessage::new(message.to_string(), LogLevel::Error)); + send_message(LogMessage::new(message.to_string(), LogLevel::Error)); } fn log_info(&mut self, message: &str) -> () { - self.send_message(LogMessage::new(message.to_string(), LogLevel::Info)); + send_message(LogMessage::new(message.to_string(), LogLevel::Info)); } fn log_warning(&mut self, message: &str) -> () { - self.send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); + send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index bce5b4dfa19c..6d66b906c82c 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -73,9 +73,7 @@ pub fn find_and_report( let pyenv_dir = get_pyenv_dir(environment)?; if let Some(pyenv_binary) = get_pyenv_binary(environment) { - let params = messaging::EnvManager::new(vec![pyenv_binary], None); - let message = messaging::EnvManagerMessage::new(params); - dispatcher.send_message(message); + dispatcher.report_environment_manager(messaging::EnvManager::new(vec![pyenv_binary], None)); } let versions_dir = PathBuf::from(&pyenv_dir) @@ -95,7 +93,7 @@ pub fn find_and_report( if let Some(executable) = find_python_binary_path(&path) { let version = path.file_name().unwrap().to_string_lossy().to_string(); let env_path = path.to_string_lossy().to_string(); - dispatcher.send_message(messaging::PythonEnvironment::new( + dispatcher.report_environment(messaging::PythonEnvironment::new( "Python".to_string(), vec![executable.into_os_string().into_string().unwrap()], messaging::PythonEnvironmentCategory::Pyenv, diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 122f4eabd832..c4c9ae8f388c 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -8,7 +8,7 @@ use std::path::Path; fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { let version = utils::get_version(path); - dispatcher.send_message(messaging::PythonEnvironment::new( + dispatcher.report_environment(messaging::PythonEnvironment::new( "Python".to_string(), vec![path.to_string()], messaging::PythonEnvironmentCategory::WindowsStore, diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 5ff67224d258..107e649e3ae5 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf}; -use python_finder::{known::Environment, messaging::MessageDispatcher}; +use python_finder::{known::Environment, messaging::{EnvManager, MessageDispatcher, PythonEnvironment}}; use serde_json::Value; #[allow(dead_code)] @@ -32,8 +32,14 @@ pub trait TestMessages { #[allow(dead_code)] pub fn create_test_dispatcher() -> TestDispatcher { impl MessageDispatcher for TestDispatcher { - fn send_message(&mut self, message: T) -> () { - self.messages.push(serde_json::to_string(&message).unwrap()); + fn report_environment_manager(&mut self, env: EnvManager) -> () { + self.messages.push(serde_json::to_string(&env).unwrap()); + } + fn report_environment(&mut self, env: PythonEnvironment) -> () { + self.messages.push(serde_json::to_string(&env).unwrap()); + } + fn exit(&mut self) -> () { + // } fn log_debug(&mut self, _message: &str) -> () {} fn log_error(&mut self, _message: &str) -> () {} diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index ef4d72c6c552..c049f2be98b2 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -44,7 +44,7 @@ fn find_conda_exe_and_empty_envs() { conda::find_and_report(&mut dispatcher, &known); let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); - let expected_json = json!({"jsonrpc":"2.0","method":"envManager","params":{"executablePath":[conda_exe.clone()],"version":null}}); + let expected_json = json!({"executablePath":[conda_exe.clone()],"version":null}); assert_messages(&[expected_json], &dispatcher) } #[test] @@ -80,9 +80,9 @@ fn finds_two_conda_envs_from_txt() { let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); - let expected_conda_env = json!({"jsonrpc":"2.0","method":"envManager","params":{"executablePath":[conda_exe.clone()],"version":null}}); - let expected_conda_1 = json!({"jsonrpc":"2.0","method":"pythonEnvironment","params":{"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone()}}); - let expected_conda_2 = json!({"jsonrpc":"2.0","method":"pythonEnvironment","params":{"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone()}}); + let expected_conda_env = json!({"executablePath":[conda_exe.clone()],"version":null}); + let expected_conda_1 = json!({"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone()}); + let expected_conda_2 = json!({"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone()}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, From 5a617d4d83b8e3091d8bd294068826a4b295e1ab Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 15:48:10 +1000 Subject: [PATCH 014/106] Add native homebrew locator on mac (#23363) --- native_locator/src/homebrew.rs | 63 +++++++++++++++++++ native_locator/src/main.rs | 5 +- native_locator/src/messaging.rs | 1 + .../base/locators/lowLevel/nativeLocator.ts | 1 + 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 native_locator/src/homebrew.rs diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs new file mode 100644 index 000000000000..e78043619bc9 --- /dev/null +++ b/native_locator/src/homebrew.rs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; + +use crate::{known::Environment, messaging::MessageDispatcher}; +use regex::Regex; + +fn is_symlinked_python_executable(path: Result) -> Option { + let path = path.ok()?.path(); + let name = path.file_name()?.to_string_lossy(); + if !name.starts_with("python") || name.ends_with("-config") { + 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()?) +} + +pub fn find_and_report( + dispatcher: &mut impl MessageDispatcher, + environment: &impl Environment, +) -> Option<()> { + // https://docs.brew.sh/Homebrew-and-Python#brewed-python-modules + // Executable Python scripts will be in $(brew --prefix)/bin. + // They are always symlinks, hence we will only look for symlinks. + + let homebrew_prefix = environment.get_env_var("HOMEBREW_PREFIX".to_string())?; + let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); + let mut reported: HashSet = HashSet::new(); + for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { + if let Some(exe) = is_symlinked_python_executable(file) { + let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); + let python_version = exe.to_string_lossy().to_string(); + let version = match python_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(&exe.to_string_lossy().to_string()) { + continue; + } + reported.insert(exe.to_string_lossy().to_string()); + + let env = crate::messaging::PythonEnvironment::new( + "Python".to_string(), + vec![exe.to_string_lossy().to_string()], + crate::messaging::PythonEnvironmentCategory::Homebrew, + version, + None, + None, + None, + ); + dispatcher.report_environment(env); + } + } + + None +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 1c533a7fefec..ff2806e89342 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -8,12 +8,13 @@ use messaging::{create_dispatcher, MessageDispatcher}; mod common_python; mod conda; +mod homebrew; mod known; mod logging; mod messaging; +mod pyenv; mod utils; mod windows_python; -mod pyenv; fn main() { let mut dispatcher = create_dispatcher(); @@ -34,6 +35,8 @@ fn main() { pyenv::find_and_report(&mut dispatcher, &environment); + homebrew::find_and_report(&mut dispatcher, &environment); + match now.elapsed() { Ok(elapsed) => { dispatcher.log_info(&format!( diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 7fe24fd8b4c8..f39cdd4c5418 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -52,6 +52,7 @@ impl EnvManagerMessage { #[serde(rename_all = "camelCase")] pub enum PythonEnvironmentCategory { System, + Homebrew, Conda, Pyenv, WindowsStore, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index a77b45494c3d..275bad04e603 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -39,6 +39,7 @@ function categoryToKind(category: string): PythonEnvKind { case 'conda': return PythonEnvKind.Conda; case 'system': + case 'homebrew': return PythonEnvKind.System; case 'pyenv': return PythonEnvKind.Pyenv; From 05875b3d3b873c26ec2806cb94361a020e9c135b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 7 May 2024 16:48:39 +1000 Subject: [PATCH 015/106] Homebrew only on unix and not windows (#23364) --- native_locator/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index ff2806e89342..f8305b685296 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -35,6 +35,7 @@ fn main() { pyenv::find_and_report(&mut dispatcher, &environment); + #[cfg(unix)] homebrew::find_and_report(&mut dispatcher, &environment); match now.elapsed() { From 463cdbc5366942671a39c8df7645321ddce68333 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 09:54:19 +1000 Subject: [PATCH 016/106] Better py env version extraction (#23368) --- .github/workflows/pr-check.yml | 2 +- native_locator/src/lib.rs | 1 + native_locator/src/pyenv.rs | 58 ++++++++++-- native_locator/tests/common.rs | 1 + native_locator/tests/pyenv_test.rs | 92 +++++++++++++++++++ .../pyenv/.pyenv/versions/3.12.1/bin/python | 0 .../pyenv/.pyenv/versions/3.12.1a3/bin/python | 0 .../pyenv/.pyenv/versions/3.13-dev/bin/python | 0 .../pyenv/.pyenv/versions/3.9.9/bin/python | 0 .../.pyenv/versions/anaconda-4.0.0/bin/python | 0 .../mambaforge-4.10.1-4/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../versions/anaconda3-2021.04/bin/python | 0 .../mambaforge-4.10.1-4/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../versions/mambaforge-4.10.1-4/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../.pyenv/versions/mambaforge/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../mambaforge/miniforge3-4.11.0-1/bin/python | 0 .../versions/miniconda-latest/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../versions/miniconda3-3.10.1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../versions/miniconda3-4.0.5/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../versions/miniforge3-4.11.0-1/bin/python | 0 .../.pyenv/versions/nogil-3.9.10/bin/python | 0 .../versions/pypy3.10-7.3.14/bin/python | 0 .../.pyenv/versions/pyston-2.3.5/bin/python | 0 .../versions/stacklets-3.7.5/bin/python | 0 .../tests/unix/pyenv/opt/homebrew/bin/pyenv | 0 .../pyenv_without_envs/opt/homebrew/bin/pyenv | 0 48 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 native_locator/tests/pyenv_test.rs create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv create mode 100644 native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 19556a38e30c..ed9b10e29e2b 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -332,7 +332,7 @@ jobs: path: ${{ env.special-working-directory-relative }} - name: Native Locator tests - run: cargo test + run: cargo test -- --nocapture working-directory: ${{ env.special-working-directory }}/native_locator smoke-tests: diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index d95a4300d253..17ce17253f77 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -7,3 +7,4 @@ pub mod common_python; pub mod logging; pub mod conda; pub mod known; +pub mod pyenv; diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 6d66b906c82c..8e7a734a297d 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use regex::Regex; use std::fs; use std::path::PathBuf; @@ -66,6 +67,38 @@ fn get_pyenv_binary(environment: &impl known::Environment) -> Option { } } +fn get_pyenv_version(folder_name: String) -> Option { + // 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 Versions = like 3.10.0a3 + let python_regex = Regex::new(r"^(\d+\.\d+.\d+a\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 => None, + } + } + } + } + } +} + pub fn find_and_report( dispatcher: &mut impl messaging::MessageDispatcher, environment: &impl known::Environment, @@ -91,18 +124,29 @@ pub fn find_and_report( let path = path.path(); if path.is_dir() { if let Some(executable) = find_python_binary_path(&path) { - let version = path.file_name().unwrap().to_string_lossy().to_string(); + let version = + get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string()); + + // If we cannot extract version, this isn't a valid pyenv environment. + // Or its one that we're not interested in. + if version.is_none() { + continue; + } let env_path = path.to_string_lossy().to_string(); + let activated_run = match version.clone() { + Some(version) => Some(vec![ + pyenv_binary_for_activation.clone(), + "local".to_string(), + version.clone(), + ]), + None => None, + }; dispatcher.report_environment(messaging::PythonEnvironment::new( "Python".to_string(), vec![executable.into_os_string().into_string().unwrap()], messaging::PythonEnvironmentCategory::Pyenv, - Some(version.clone()), - Some(vec![ - pyenv_binary_for_activation.clone(), - "shell".to_string(), - version, - ]), + version, + activated_run, Some(env_path.clone()), Some(env_path), )); diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 107e649e3ae5..e75352691b6b 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -16,6 +16,7 @@ pub fn test_file_path(paths: &[&str]) -> String { root.to_string_lossy().to_string() } + #[allow(dead_code)] pub fn join_test_paths(paths: &[&str]) -> String { let path: PathBuf = paths.iter().map(|p| p.to_string()).collect(); diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs new file mode 100644 index 000000000000..896fbe62a035 --- /dev/null +++ b/native_locator/tests/pyenv_test.rs @@ -0,0 +1,92 @@ +// 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_dispatcher, create_test_environment}; + use python_finder::pyenv; + use std::collections::HashMap; + + let mut dispatcher = create_test_dispatcher(); + let known = create_test_environment( + HashMap::new(), + Some("SOME_BOGUS_HOME_DIR".to_string()), + Vec::new(), + ); + + pyenv::find_and_report(&mut dispatcher, &known); + + assert_eq!(dispatcher.messages.len(), 0); +} + +#[test] +#[cfg(unix)] +fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { + use crate::common::{ + assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + test_file_path, + }; + use python_finder::pyenv; + use serde_json::json; + use std::{collections::HashMap, path::PathBuf}; + + let mut dispatcher = create_test_dispatcher(); + let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]); + let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]); + let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]); + let known = create_test_environment( + HashMap::new(), + Some(home.clone()), + vec![PathBuf::from(homebrew_bin)], + ); + + pyenv::find_and_report(&mut dispatcher, &known); + + assert_eq!(dispatcher.messages.len(), 1); + let expected_json = json!({"executablePath":[pyenv_exe],"version":null}); + assert_messages(&[expected_json], &dispatcher) +} + +#[test] +#[cfg(unix)] +fn find_pyenv_envs() { + use crate::common::{ + assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + test_file_path, + }; + use python_finder::pyenv; + use serde_json::json; + use std::{collections::HashMap, path::PathBuf}; + + let mut dispatcher = create_test_dispatcher(); + let home = test_file_path(&["tests", "unix", "pyenv"]); + let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]); + let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]); + let known = create_test_environment( + HashMap::new(), + Some(home.clone()), + vec![PathBuf::from(homebrew_bin)], + ); + + pyenv::find_and_report(&mut dispatcher, &known); + + assert_eq!(dispatcher.messages.len(), 5); + let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null }); + let expected_3_9_9 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "local", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])}); + let expected_3_12_1 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "local", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])}); + let expected_3_13_dev = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "local", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])}); + let expected_3_12_1a3 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "local", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])}); + assert_messages( + &[ + expected_manager, + expected_3_9_9, + expected_3_12_1, + expected_3_13_dev, + expected_3_12_1a3, + ], + &dispatcher, + ) +} diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1 From 74d6ce66027c5006968d946defd023e3890a72c5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 10:14:24 +1000 Subject: [PATCH 017/106] Remove unwanted test feature (#23371) --- native_locator/Cargo.toml | 3 --- native_locator/src/utils.rs | 20 +------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml index 29fdba160ce9..4f9c8ca81c78 100644 --- a/native_locator/Cargo.toml +++ b/native_locator/Cargo.toml @@ -12,8 +12,5 @@ serde_json = "1.0.93" serde_repr = "0.1.10" regex = "1.10.4" -[features] -test = [] - [lib] doctest = false diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 7a235a251942..93afcd5ac554 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -6,7 +6,7 @@ use std::{ process::Command, }; -fn get_version_impl(path: &str) -> Option { +pub fn get_version(path: &str) -> Option { let output = Command::new(path) .arg("-c") .arg("import sys; print(sys.version)") @@ -18,24 +18,6 @@ fn get_version_impl(path: &str) -> Option { Some(output.to_string()) } -#[cfg(not(feature = "test"))] -pub fn get_version(path: &str) -> Option { - get_version_impl(path) -} - -// Tests - -#[cfg(feature = "test")] -pub fn get_version(path: &str) -> Option { - use std::path::PathBuf; - let version_file = PathBuf::from(path.to_owned() + ".version"); - if version_file.exists() { - let version = std::fs::read_to_string(version_file).ok()?; - return Some(version.trim().to_string()); - } - get_version_impl(path) -} - pub fn find_python_binary_path(env_path: &Path) -> Option { let python_bin_name = if cfg!(windows) { "python.exe" From 0ba3ba5f59ce8549cf0d89390b6fae7f27b27a72 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 11:39:48 +1000 Subject: [PATCH 018/106] Support for pyenv virtual-env in native locator (#23372) --- native_locator/src/messaging.rs | 1 + native_locator/src/pyenv.rs | 128 ++++++++++++++---- native_locator/tests/common.rs | 74 +++++++++- native_locator/tests/conda_test.rs | 4 +- native_locator/tests/pyenv_test.rs | 12 +- .../.pyenv/versions/my-virtual-env/bin/python | 0 .../.pyenv/versions/my-virtual-env/pyvenv.cfg | 3 + .../base/locators/lowLevel/nativeLocator.ts | 2 + 8 files changed, 183 insertions(+), 41 deletions(-) create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python create mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index f39cdd4c5418..0ae0b90b02c6 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -55,6 +55,7 @@ pub enum PythonEnvironmentCategory { Homebrew, Conda, Pyenv, + PyenvVirtualEnv, WindowsStore, } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 8e7a734a297d..fbad817108c4 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -99,6 +99,86 @@ fn get_pyenv_version(folder_name: String) -> Option { } } +fn report_if_pure_python_environment( + executable: PathBuf, + path: &PathBuf, + pyenv_binary_for_activation: String, + dispatcher: &mut impl messaging::MessageDispatcher, +) -> Option<()> { + let version = get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string())?; + + let env_path = path.to_string_lossy().to_string(); + let activated_run = Some(vec![ + pyenv_binary_for_activation, + "shell".to_string(), + version.clone(), + ]); + dispatcher.report_environment(messaging::PythonEnvironment::new( + version.clone(), + vec![executable.into_os_string().into_string().unwrap()], + messaging::PythonEnvironmentCategory::Pyenv, + Some(version), + activated_run, + Some(env_path.clone()), + Some(env_path), + )); + + Some(()) +} + +#[derive(Debug)] +struct PyEnvCfg { + version: String, +} + +fn parse_pyenv_cfg(path: &PathBuf) -> Option { + let cfg = path.join("pyvenv.cfg"); + 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(); + for line in contents.lines() { + if let Some(captures) = version_regex.captures(line) { + if let Some(value) = captures.get(1) { + return Some(PyEnvCfg { + version: value.as_str().to_string(), + }); + } + } + } + None +} + +fn report_if_virtual_env_environment( + executable: PathBuf, + path: &PathBuf, + pyenv_binary_for_activation: String, + dispatcher: &mut impl messaging::MessageDispatcher, +) -> Option<()> { + let pyenv_cfg = parse_pyenv_cfg(path)?; + let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); + + let env_path = path.to_string_lossy().to_string(); + let activated_run = Some(vec![ + pyenv_binary_for_activation, + "activate".to_string(), + folder_name.clone(), + ]); + dispatcher.report_environment(messaging::PythonEnvironment::new( + folder_name, + vec![executable.into_os_string().into_string().unwrap()], + messaging::PythonEnvironmentCategory::PyenvVirtualEnv, + Some(pyenv_cfg.version), + activated_run, + Some(env_path.clone()), + Some(env_path), + )); + + Some(()) +} + pub fn find_and_report( dispatcher: &mut impl messaging::MessageDispatcher, environment: &impl known::Environment, @@ -122,35 +202,27 @@ pub fn find_and_report( for entry in fs::read_dir(&versions_dir).ok()? { if let Ok(path) = entry { let path = path.path(); - if path.is_dir() { - if let Some(executable) = find_python_binary_path(&path) { - let version = - get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string()); - - // If we cannot extract version, this isn't a valid pyenv environment. - // Or its one that we're not interested in. - if version.is_none() { - continue; - } - let env_path = path.to_string_lossy().to_string(); - let activated_run = match version.clone() { - Some(version) => Some(vec![ - pyenv_binary_for_activation.clone(), - "local".to_string(), - version.clone(), - ]), - None => None, - }; - dispatcher.report_environment(messaging::PythonEnvironment::new( - "Python".to_string(), - vec![executable.into_os_string().into_string().unwrap()], - messaging::PythonEnvironmentCategory::Pyenv, - version, - activated_run, - Some(env_path.clone()), - Some(env_path), - )); + if !path.is_dir() { + continue; + } + if let Some(executable) = find_python_binary_path(&path) { + if report_if_pure_python_environment( + executable.clone(), + &path, + pyenv_binary_for_activation.clone(), + dispatcher, + ) + .is_some() + { + continue; } + + report_if_virtual_env_environment( + executable.clone(), + &path, + pyenv_binary_for_activation.clone(), + dispatcher, + ); } } } diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index e75352691b6b..7e87eee758ab 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{collections::HashMap, path::PathBuf}; - -use python_finder::{known::Environment, messaging::{EnvManager, MessageDispatcher, PythonEnvironment}}; +use python_finder::{ + known::Environment, + messaging::{EnvManager, MessageDispatcher, PythonEnvironment}, +}; use serde_json::Value; +use std::{collections::HashMap, path::PathBuf}; #[allow(dead_code)] pub fn test_file_path(paths: &[&str]) -> String { @@ -16,7 +18,6 @@ pub fn test_file_path(paths: &[&str]) -> String { root.to_string_lossy().to_string() } - #[allow(dead_code)] pub fn join_test_paths(paths: &[&str]) -> String { let path: PathBuf = paths.iter().map(|p| p.to_string()).collect(); @@ -85,8 +86,49 @@ pub fn create_test_environment( } } +fn compare_json(expected: &Value, actual: &Value) -> bool { + if expected == actual { + return true; + } + + if expected.is_object() { + 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], dispatcher: &TestDispatcher) { + let mut expected_json = expected_json.to_vec(); assert_eq!( expected_json.len(), dispatcher.messages.len(), @@ -97,6 +139,26 @@ pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { return; } - let actual: serde_json::Value = serde_json::from_str(dispatcher.messages[0].as_str()).unwrap(); - assert_eq!(expected_json[0], actual); + // Ignore the order of the json items when comparing. + for actual in dispatcher.messages.iter() { + let actual: serde_json::Value = serde_json::from_str(actual.as_str()).unwrap(); + + let mut valid_index: Option = 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); + } + } } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index c049f2be98b2..3adf8e334948 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -81,8 +81,8 @@ fn finds_two_conda_envs_from_txt() { let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); let expected_conda_env = json!({"executablePath":[conda_exe.clone()],"version":null}); - let expected_conda_1 = json!({"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone()}); - let expected_conda_2 = json!({"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone()}); + let expected_conda_1 = json!({"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone(), "sysPrefixPath":conda_1.clone()}); + let expected_conda_2 = json!({"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone(), "sysPrefixPath":conda_2.clone()}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 896fbe62a035..a901a1c13ca6 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -73,16 +73,18 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); - assert_eq!(dispatcher.messages.len(), 5); + assert_eq!(dispatcher.messages.len(), 6); let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null }); - let expected_3_9_9 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "local", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])}); - let expected_3_12_1 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "local", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])}); - let expected_3_13_dev = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "local", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])}); - let expected_3_12_1a3 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "local", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])}); + let expected_3_9_9 = json!({"name": "3.9.9","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "shell", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"])}); + let expected_virtual_env = json!( {"name": "my-virtual-env", "version": "3.10.13", "activatedRun": [pyenv_exe.clone(), "activate", "my-virtual-env"], "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"])}); + let expected_3_12_1 = json!({"name": "3.12.1","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"])}); + let expected_3_13_dev = json!({"name": "3.13-dev","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "shell", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"])}); + let expected_3_12_1a3 = json!({"name": "3.12.1a3","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"])}); assert_messages( &[ expected_manager, expected_3_9_9, + expected_virtual_env, expected_3_12_1, expected_3_13_dev, expected_3_12_1a3, diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg b/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg new file mode 100644 index 000000000000..6190a656901f --- /dev/null +++ b/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /Users/donjayamanne/.pyenv/versions/3.10.13/bin +include-system-site-packages = false +version = 3.10.13 diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 275bad04e603..07453c704f14 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -43,6 +43,8 @@ function categoryToKind(category: string): PythonEnvKind { return PythonEnvKind.System; case 'pyenv': return PythonEnvKind.Pyenv; + case 'pyenvvirtualenv': + return PythonEnvKind.VirtualEnv; case 'windowsstore': return PythonEnvKind.MicrosoftStore; default: { From 5168747d94ffa5920470ecb0cac385d5b4ba9b29 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 12:41:58 +1000 Subject: [PATCH 019/106] Extract conda version where possible in native locator (#23374) --- native_locator/src/conda.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 8e64bacf13b1..a058a7d13ef1 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -216,6 +216,11 @@ pub fn find_conda_binary(environment: &impl known::Environment) -> Option Option { + let conda_python_json_path = get_conda_package_json_path(&conda_binary.parent()?, "conda")?; + get_version_from_meta_json(&conda_python_json_path) +} + fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec { let mut envs = vec![]; let home = environment.get_user_home(); @@ -369,8 +374,10 @@ pub fn find_and_report( let conda_binary = find_conda_binary(environment); match conda_binary { Some(conda_binary) => { - let params = - messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None); + let params = messaging::EnvManager::new( + vec![conda_binary.to_string_lossy().to_string()], + get_conda_version(&conda_binary), + ); dispatcher.report_environment_manager(params); let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment); From dd0766b4e96451836fe7a014a85b5cc7be668602 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 13:43:04 +1000 Subject: [PATCH 020/106] Compute regex once in native locator (#23375) --- native_locator/src/homebrew.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index e78043619bc9..3e5d2c92f66c 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -30,9 +30,9 @@ pub fn find_and_report( let homebrew_prefix = environment.get_env_var("HOMEBREW_PREFIX".to_string())?; let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); let mut reported: HashSet = HashSet::new(); + let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { if let Some(exe) = is_symlinked_python_executable(file) { - let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); let python_version = exe.to_string_lossy().to_string(); let version = match python_regex.captures(&python_version) { Some(captures) => match captures.get(1) { From 6a615fce29ad01bdf496f97bfef02ea25e235bc1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 14:01:35 +1000 Subject: [PATCH 021/106] Strip 'envs' prefix from conda env name (#23377) --- native_locator/src/conda.rs | 2 +- native_locator/tests/conda_test.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index a058a7d13ef1..35dee6dc5cac 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -338,7 +338,7 @@ fn get_distinct_conda_envs( let mut named = false; let mut name = "".to_string(); for location in &locations { - let location = Path::new(location); + let location = Path::new(location).join("envs"); match env.strip_prefix(location) { Ok(prefix) => { named = true; diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 3adf8e334948..54f16eef4f5b 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -81,8 +81,8 @@ fn finds_two_conda_envs_from_txt() { let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); let expected_conda_env = json!({"executablePath":[conda_exe.clone()],"version":null}); - let expected_conda_1 = json!({"name":"envs/one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","envs/one","python"],"envPath":conda_1.clone(), "sysPrefixPath":conda_1.clone()}); - let expected_conda_2 = json!({"name":"envs/two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","envs/two","python"],"envPath":conda_2.clone(), "sysPrefixPath":conda_2.clone()}); + let expected_conda_1 = json!({"name":"one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","one","python"],"envPath":conda_1.clone(), "sysPrefixPath":conda_1.clone()}); + let expected_conda_2 = json!({"name":"two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","two","python"],"envPath":conda_2.clone(), "sysPrefixPath":conda_2.clone()}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, From c4e4ee64354259ce3d4ab3a95dd9fb12b04f36b8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 14:44:36 +1000 Subject: [PATCH 022/106] Update python env properties sent back to TS layer (#23378) --- native_locator/src/common_python.rs | 7 +-- native_locator/src/conda.rs | 13 +++--- native_locator/src/homebrew.rs | 7 +-- native_locator/src/messaging.rs | 30 +++++++++---- native_locator/src/pyenv.rs | 52 ++++++++++------------ native_locator/src/windows_python.rs | 5 ++- native_locator/tests/common_python_test.rs | 2 +- native_locator/tests/conda_test.rs | 8 ++-- native_locator/tests/pyenv_test.rs | 14 +++--- 9 files changed, 74 insertions(+), 64 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 7317b9c52c7f..5144c4ade2fa 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -25,13 +25,14 @@ fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: let version = utils::get_version(path); let env_path = get_env_path(path); dispatcher.report_environment(messaging::PythonEnvironment::new( - "Python".to_string(), - vec![path.to_string()], + None, + Some(path.to_string()), messaging::PythonEnvironmentCategory::System, version, - None, env_path.clone(), env_path, + None, + Some(vec![path.to_string()]), )); } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 35dee6dc5cac..2610f53778c7 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -375,7 +375,7 @@ pub fn find_and_report( match conda_binary { Some(conda_binary) => { let params = messaging::EnvManager::new( - vec![conda_binary.to_string_lossy().to_string()], + conda_binary.to_string_lossy().to_string(), get_conda_version(&conda_binary), ); dispatcher.report_environment_manager(params); @@ -385,13 +385,16 @@ pub fn find_and_report( let executable = find_python_binary_path(Path::new(&env.path)); let env_path = env.path.to_string_lossy().to_string(); let params = messaging::PythonEnvironment::new( - env.name.to_string(), + Some(env.name.to_string()), match executable { - Some(executable) => vec![executable.to_string_lossy().to_string()], - None => vec![], + Some(executable) => Some(executable.to_string_lossy().to_string()), + None => None, }, messaging::PythonEnvironmentCategory::Conda, get_conda_python_version(&env.path), + Some(env_path.clone()), + Some(env_path), + None, if env.named { Some(vec![ conda_binary.to_string_lossy().to_string(), @@ -409,8 +412,6 @@ pub fn find_and_report( "python".to_string(), ]) }, - Some(env_path.clone()), - Some(env_path), ); dispatcher.report_environment(params); } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 3e5d2c92f66c..37b52720aed6 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -45,15 +45,16 @@ pub fn find_and_report( continue; } reported.insert(exe.to_string_lossy().to_string()); - + let executable = exe.to_string_lossy().to_string(); let env = crate::messaging::PythonEnvironment::new( - "Python".to_string(), - vec![exe.to_string_lossy().to_string()], + None, + Some(executable.clone()), crate::messaging::PythonEnvironmentCategory::Homebrew, version, None, None, None, + Some(vec![executable]), ); dispatcher.report_environment(env); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 0ae0b90b02c6..858f74c59e8a 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -17,12 +17,12 @@ pub trait MessageDispatcher { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { - pub executable_path: Vec, + pub executable_path: String, pub version: Option, } impl EnvManager { - pub fn new(executable_path: Vec, version: Option) -> Self { + pub fn new(executable_path: String, version: Option) -> Self { Self { executable_path, version, @@ -30,6 +30,15 @@ impl EnvManager { } } +impl Clone for EnvManager { + fn clone(&self) -> Self { + Self { + executable_path: self.executable_path.clone(), + version: self.version.clone(), + } + } +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManagerMessage { @@ -62,33 +71,36 @@ pub enum PythonEnvironmentCategory { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { - pub name: String, - pub python_executable_path: Vec, + pub name: Option, + pub python_executable_path: Option, pub category: PythonEnvironmentCategory, pub version: Option, - pub activated_run: Option>, pub env_path: Option, pub sys_prefix_path: Option, + pub env_manager: Option, + pub python_run_command: Option>, } impl PythonEnvironment { pub fn new( - name: String, - python_executable_path: Vec, + name: Option, + python_executable_path: Option, category: PythonEnvironmentCategory, version: Option, - activated_run: Option>, env_path: Option, sys_prefix_path: Option, + env_manager: Option, + python_run_command: Option>, ) -> Self { Self { name, python_executable_path, category, version, - activated_run, env_path, sys_prefix_path, + env_manager, + python_run_command, } } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index fbad817108c4..5365818dd318 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use crate::known; use crate::messaging; +use crate::messaging::EnvManager; use crate::utils::find_python_binary_path; #[cfg(windows)] @@ -102,25 +103,21 @@ fn get_pyenv_version(folder_name: String) -> Option { fn report_if_pure_python_environment( executable: PathBuf, path: &PathBuf, - pyenv_binary_for_activation: String, + manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { let version = get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string())?; - + let executable = executable.into_os_string().into_string().unwrap(); let env_path = path.to_string_lossy().to_string(); - let activated_run = Some(vec![ - pyenv_binary_for_activation, - "shell".to_string(), - version.clone(), - ]); dispatcher.report_environment(messaging::PythonEnvironment::new( - version.clone(), - vec![executable.into_os_string().into_string().unwrap()], + None, + Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, Some(version), - activated_run, Some(env_path.clone()), Some(env_path), + manager, + Some(vec![executable]), )); Some(()) @@ -154,26 +151,22 @@ fn parse_pyenv_cfg(path: &PathBuf) -> Option { fn report_if_virtual_env_environment( executable: PathBuf, path: &PathBuf, - pyenv_binary_for_activation: String, + manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { let pyenv_cfg = parse_pyenv_cfg(path)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); - + let executable = executable.into_os_string().into_string().unwrap(); let env_path = path.to_string_lossy().to_string(); - let activated_run = Some(vec![ - pyenv_binary_for_activation, - "activate".to_string(), - folder_name.clone(), - ]); dispatcher.report_environment(messaging::PythonEnvironment::new( - folder_name, - vec![executable.into_os_string().into_string().unwrap()], + Some(folder_name), + Some(executable.clone()), messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), - activated_run, Some(env_path.clone()), Some(env_path), + manager, + Some(vec![executable]), )); Some(()) @@ -185,9 +178,14 @@ pub fn find_and_report( ) -> Option<()> { let pyenv_dir = get_pyenv_dir(environment)?; - if let Some(pyenv_binary) = get_pyenv_binary(environment) { - dispatcher.report_environment_manager(messaging::EnvManager::new(vec![pyenv_binary], None)); - } + let manager = match get_pyenv_binary(environment) { + Some(pyenv_binary) => { + let manager = messaging::EnvManager::new(pyenv_binary, None); + dispatcher.report_environment_manager(manager.clone()); + Some(manager) + } + None => None, + }; let versions_dir = PathBuf::from(&pyenv_dir) .join("versions") @@ -195,10 +193,6 @@ pub fn find_and_report( .into_string() .ok()?; - let pyenv_binary_for_activation = match get_pyenv_binary(environment) { - Some(binary) => binary, - None => "pyenv".to_string(), - }; for entry in fs::read_dir(&versions_dir).ok()? { if let Ok(path) = entry { let path = path.path(); @@ -209,7 +203,7 @@ pub fn find_and_report( if report_if_pure_python_environment( executable.clone(), &path, - pyenv_binary_for_activation.clone(), + manager.clone(), dispatcher, ) .is_some() @@ -220,7 +214,7 @@ pub fn find_and_report( report_if_virtual_env_environment( executable.clone(), &path, - pyenv_binary_for_activation.clone(), + manager.clone(), dispatcher, ); } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index c4c9ae8f388c..0494066eef0c 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -9,13 +9,14 @@ use std::path::Path; fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { let version = utils::get_version(path); dispatcher.report_environment(messaging::PythonEnvironment::new( - "Python".to_string(), - vec![path.to_string()], + None, + Some(path.to_string()), messaging::PythonEnvironmentCategory::WindowsStore, version, None, None, None, + None, )); } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 31d8b8c52f63..f8841ee9b6fa 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -27,6 +27,6 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"system","version":null,"activatedRun":null,"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); + let expected_json = json!({"envManager":null,"name":null,"pythonExecutablePath":unix_python_exe.clone(),"category":"system","version":null,"pythonRunCommand":[unix_python_exe.clone()],"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); assert_messages(&[expected_json], &dispatcher); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 54f16eef4f5b..94274c8f7db7 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -44,7 +44,7 @@ fn find_conda_exe_and_empty_envs() { conda::find_and_report(&mut dispatcher, &known); let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); - let expected_json = json!({"executablePath":[conda_exe.clone()],"version":null}); + let expected_json = json!({"executablePath":conda_exe.clone(),"version":null}); assert_messages(&[expected_json], &dispatcher) } #[test] @@ -80,9 +80,9 @@ fn finds_two_conda_envs_from_txt() { let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); - let expected_conda_env = json!({"executablePath":[conda_exe.clone()],"version":null}); - let expected_conda_1 = json!({"name":"one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","one","python"],"envPath":conda_1.clone(), "sysPrefixPath":conda_1.clone()}); - let expected_conda_2 = json!({"name":"two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","two","python"],"envPath":conda_2.clone(), "sysPrefixPath":conda_2.clone()}); + let expected_conda_env = json!({ "executablePath": conda_exe.clone(), "version": null}); + let expected_conda_1 = json!({ "name": "one", "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); + let expected_conda_2 = json!({ "name": "two", "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index a901a1c13ca6..d5327b9bd275 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -46,7 +46,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"executablePath":[pyenv_exe],"version":null}); + let expected_json = json!({"executablePath":pyenv_exe,"version":null}); assert_messages(&[expected_json], &dispatcher) } @@ -74,12 +74,12 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 6); - let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null }); - let expected_3_9_9 = json!({"name": "3.9.9","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "shell", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"])}); - let expected_virtual_env = json!( {"name": "my-virtual-env", "version": "3.10.13", "activatedRun": [pyenv_exe.clone(), "activate", "my-virtual-env"], "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"])}); - let expected_3_12_1 = json!({"name": "3.12.1","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"])}); - let expected_3_13_dev = json!({"name": "3.13-dev","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "shell", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"])}); - let expected_3_12_1a3 = json!({"name": "3.12.1a3","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"])}); + let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null }); + let expected_3_9_9 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); + let expected_virtual_env = json!({"name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); + let expected_3_12_1 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); + let expected_3_13_dev = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); + let expected_3_12_1a3 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); assert_messages( &[ expected_manager, From 1e7f925371a5f890dd60427b1517213d5dcbb999 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 17:14:32 +1000 Subject: [PATCH 023/106] Add support for pipenv environments (#23379) --- native_locator/src/global_virtualenvs.rs | 75 +++++++++++++++++++ native_locator/src/lib.rs | 1 + native_locator/src/main.rs | 4 + native_locator/src/messaging.rs | 29 +++++++ native_locator/src/pipenv.rs | 56 ++++++++++++++ native_locator/src/pyenv.rs | 26 +------ native_locator/src/utils.rs | 48 ++++++++++++ native_locator/tests/common_python_test.rs | 2 +- native_locator/tests/conda_test.rs | 4 +- native_locator/tests/pyenv_test.rs | 10 +-- .../base/locators/lowLevel/nativeLocator.ts | 19 +++-- 11 files changed, 235 insertions(+), 39 deletions(-) create mode 100644 native_locator/src/global_virtualenvs.rs create mode 100644 native_locator/src/pipenv.rs diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs new file mode 100644 index 000000000000..cd8c2e392054 --- /dev/null +++ b/native_locator/src/global_virtualenvs.rs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + known, + utils::{find_python_binary_path, get_version}, +}; +use std::{fs, path::PathBuf}; + +fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { + let mut venv_dirs: Vec = 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 struct PythonEnv { + pub path: PathBuf, + pub executable: PathBuf, + pub version: Option, +} + +pub fn list_global_virtualenvs(environment: &impl known::Environment) -> Vec { + let mut python_envs: Vec = 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 { + path: venv_dir, + executable: executable.clone(), + version: get_version(executable.to_str().unwrap()), + }); + } + } + } + } + } + + python_envs +} diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index 17ce17253f77..41cb3d97bd10 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -8,3 +8,4 @@ pub mod logging; pub mod conda; pub mod known; pub mod pyenv; +pub mod global_virtualenvs; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index f8305b685296..dad488f2186c 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -8,10 +8,12 @@ use messaging::{create_dispatcher, MessageDispatcher}; mod common_python; mod conda; +mod global_virtualenvs; mod homebrew; mod known; mod logging; mod messaging; +mod pipenv; mod pyenv; mod utils; mod windows_python; @@ -35,6 +37,8 @@ fn main() { pyenv::find_and_report(&mut dispatcher, &environment); + pipenv::find_and_report(&mut dispatcher, &environment); + #[cfg(unix)] homebrew::find_and_report(&mut dispatcher, &environment); diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 858f74c59e8a..a996ec3964e1 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -66,6 +66,7 @@ pub enum PythonEnvironmentCategory { Pyenv, PyenvVirtualEnv, WindowsStore, + Pipenv, } #[derive(Serialize, Deserialize)] @@ -79,6 +80,10 @@ pub struct PythonEnvironment { pub sys_prefix_path: Option, pub env_manager: Option, pub python_run_command: Option>, + /** + * The project path for the Pipenv environment. + */ + pub project_path: Option, } impl PythonEnvironment { @@ -101,6 +106,30 @@ impl PythonEnvironment { sys_prefix_path, env_manager, python_run_command, + project_path: None, + } + } + pub fn new_pipenv( + python_executable_path: Option, + version: Option, + env_path: Option, + sys_prefix_path: Option, + env_manager: Option, + project_path: String, + ) -> Self { + Self { + name: None, + python_executable_path: python_executable_path.clone(), + category: PythonEnvironmentCategory::Pipenv, + version, + env_path, + sys_prefix_path, + env_manager, + python_run_command: match python_executable_path { + Some(exe) => Some(vec![exe]), + None => None, + }, + project_path: Some(project_path), } } } diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs new file mode 100644 index 000000000000..53c0b0e248d6 --- /dev/null +++ b/native_locator/src/pipenv.rs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::global_virtualenvs::{list_global_virtualenvs, PythonEnv}; +use crate::known; +use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use std::fs; +use std::path::PathBuf; + +fn get_project_folder(env: &PythonEnv) -> Option { + let project_file = env.path.join(".project"); + if project_file.exists() { + 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.to_string_lossy().to_string()); + } + } + } + + None +} + +pub fn find_and_report( + dispatcher: &mut impl MessageDispatcher, + environment: &impl known::Environment, +) -> Option<()> { + for env in list_global_virtualenvs(environment).iter() { + if let Some(project_path) = get_project_folder(&env) { + let env_path = env + .path + .clone() + .into_os_string() + .to_string_lossy() + .to_string(); + let executable = env + .executable + .clone() + .into_os_string() + .to_string_lossy() + .to_string(); + let env = PythonEnvironment::new_pipenv( + Some(executable), + env.version.clone(), + Some(env_path.clone()), + Some(env_path), + None, + project_path, + ); + + dispatcher.report_environment(env); + } + } + + None +} diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 5365818dd318..3b083d30d8ce 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -9,6 +9,7 @@ use crate::known; use crate::messaging; use crate::messaging::EnvManager; use crate::utils::find_python_binary_path; +use crate::utils::parse_pyenv_cfg; #[cfg(windows)] fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { @@ -123,31 +124,6 @@ fn report_if_pure_python_environment( Some(()) } -#[derive(Debug)] -struct PyEnvCfg { - version: String, -} - -fn parse_pyenv_cfg(path: &PathBuf) -> Option { - let cfg = path.join("pyvenv.cfg"); - 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(); - for line in contents.lines() { - if let Some(captures) = version_regex.captures(line) { - if let Some(value) = captures.get(1) { - return Some(PyEnvCfg { - version: value.as_str().to_string(), - }); - } - } - } - None -} - fn report_if_virtual_env_environment( executable: PathBuf, path: &PathBuf, diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 93afcd5ac554..cdf7348368f3 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -1,12 +1,60 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use regex::Regex; use std::{ + fs, path::{Path, PathBuf}, process::Command, }; +#[derive(Debug)] +pub struct PyEnvCfg { + pub version: String, +} + +pub fn parse_pyenv_cfg(path: &PathBuf) -> Option { + let cfg = path.join("pyvenv.cfg"); + 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(path: &str) -> Option { + if let Some(parent_folder) = PathBuf::from(path).parent() { + if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) { + return Some(pyenv_cfg.version); + } + if let Some(parent_folder) = parent_folder.parent() { + if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) { + return Some(pyenv_cfg.version); + } + } + } let output = Command::new(path) .arg("-c") .arg("import sys; print(sys.version)") diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index f8841ee9b6fa..7ef6b8eb3f53 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -27,6 +27,6 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"envManager":null,"name":null,"pythonExecutablePath":unix_python_exe.clone(),"category":"system","version":null,"pythonRunCommand":[unix_python_exe.clone()],"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); + let expected_json = json!({"envManager":null,"projectPath": null, "name":null,"pythonExecutablePath":unix_python_exe.clone(),"category":"system","version":null,"pythonRunCommand":[unix_python_exe.clone()],"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); assert_messages(&[expected_json], &dispatcher); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 94274c8f7db7..ad3a41181023 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -81,8 +81,8 @@ fn finds_two_conda_envs_from_txt() { let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); let expected_conda_env = json!({ "executablePath": conda_exe.clone(), "version": null}); - let expected_conda_1 = json!({ "name": "one", "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); - let expected_conda_2 = json!({ "name": "two", "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); + let expected_conda_1 = json!({ "name": "one","projectPath": null, "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); + let expected_conda_2 = json!({ "name": "two", "projectPath": null, "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index d5327b9bd275..ecf1ba548abf 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -75,11 +75,11 @@ fn find_pyenv_envs() { assert_eq!(dispatcher.messages.len(), 6); let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null }); - let expected_3_9_9 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); - let expected_virtual_env = json!({"name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); - let expected_3_12_1 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); - let expected_3_13_dev = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); - let expected_3_12_1a3 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); + let expected_3_9_9 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); + let expected_virtual_env = json!({"projectPath": null, "name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); + let expected_3_12_1 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); + let expected_3_13_dev = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); + let expected_3_12_1a3 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); assert_messages( &[ expected_manager, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 07453c704f14..ec809ee798d5 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -20,17 +20,21 @@ const NATIVE_LOCATOR = isWindows() interface NativeEnvInfo { name: string; - pythonExecutablePath: string[]; + pythonExecutablePath?: string; category: string; version?: string; - activatedRun?: string[]; + pythonRunCommand?: string[]; envPath?: string; sysPrefixPath?: string; + /** + * Path to the project directory when dealing with pipenv virtual environments. + */ + projectPath?: string; } interface EnvManager { tool: string; - executablePath: string[]; + executablePath: string; version?: string; } @@ -43,6 +47,8 @@ function categoryToKind(category: string): PythonEnvKind { return PythonEnvKind.System; case 'pyenv': return PythonEnvKind.Pyenv; + case 'pipenv': + return PythonEnvKind.Pipenv; case 'pyenvvirtualenv': return PythonEnvKind.VirtualEnv; case 'windowsstore': @@ -115,7 +121,8 @@ export class NativeLocator implements ILocator, IDisposable { connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { envs.push({ kind: categoryToKind(data.category), - executablePath: data.pythonExecutablePath[0], + // TODO: What if executable is undefined? + executablePath: data.pythonExecutablePath!, envPath: data.envPath, version: parseVersion(data.version), name: data.name === '' ? undefined : data.name, @@ -124,11 +131,11 @@ export class NativeLocator implements ILocator, IDisposable { connection.onNotification('envManager', (data: EnvManager) => { switch (toolToKnownEnvironmentTool(data.tool)) { case 'Conda': { - Conda.setConda(data.executablePath[0]); + Conda.setConda(data.executablePath); break; } case 'Pyenv': { - setPyEnvBinary(data.executablePath[0]); + setPyEnvBinary(data.executablePath); break; } default: { From a7c3798d18761b09bcf0ad88c6e4bffdc4140408 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 18:20:20 +1000 Subject: [PATCH 024/106] Fixes to detection of conda version on windows (#23380) --- native_locator/src/conda.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 2610f53778c7..be6f586ba5b4 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -217,7 +217,17 @@ pub fn find_conda_binary(environment: &impl known::Environment) -> Option Option { - let conda_python_json_path = get_conda_package_json_path(&conda_binary.parent()?, "conda")?; + let mut parent = conda_binary.parent()?; + if parent.ends_with("bin"){ + parent = parent.parent()?; + } + if parent.ends_with("Library"){ + parent = parent.parent()?; + } + let conda_python_json_path = match get_conda_package_json_path(&parent, "conda") { + Some(exe) => Some(exe), + None => get_conda_package_json_path(&parent.parent()?, "conda") + }?; get_version_from_meta_json(&conda_python_json_path) } From 347a15c9bf177fd3502c36c2a737ec26bca6cda6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 8 May 2024 22:01:50 +1000 Subject: [PATCH 025/106] Tweek pyenv version detection on windows (#23383) --- native_locator/src/pyenv.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 3b083d30d8ce..15969b321464 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -86,8 +86,8 @@ fn get_pyenv_version(folder_name: String) -> Option { None => None, }, None => { - // Alpha Versions = like 3.10.0a3 - let python_regex = Regex::new(r"^(\d+\.\d+.\d+a\d+)").unwrap(); + // 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()), From bbe380f2a0b0fa3b50e4dda6fab06f5cc90a7b9c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 May 2024 09:38:17 +1000 Subject: [PATCH 026/106] Support virtualenvwrapper in nativelocator (#23388) --- native_locator/src/conda.rs | 8 +- native_locator/src/global_virtualenvs.rs | 27 +++++-- native_locator/src/lib.rs | 3 + native_locator/src/main.rs | 6 +- native_locator/src/messaging.rs | 13 +++- native_locator/src/pipenv.rs | 57 +++++++------- native_locator/src/pyenv.rs | 3 +- native_locator/src/utils.rs | 7 ++ native_locator/src/virtualenv.rs | 43 +++++++++++ native_locator/src/virtualenvwrapper.rs | 96 ++++++++++++++++++++++++ native_locator/tests/conda_test.rs | 6 +- native_locator/tests/pyenv_test.rs | 4 +- 12 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 native_locator/src/virtualenv.rs create mode 100644 native_locator/src/virtualenvwrapper.rs diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index be6f586ba5b4..9377185e8eed 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -3,6 +3,7 @@ use crate::known; use crate::messaging; +use crate::messaging::EnvManagerType; use crate::utils::find_python_binary_path; use regex::Regex; use std::env; @@ -218,15 +219,15 @@ pub fn find_conda_binary(environment: &impl known::Environment) -> Option Option { let mut parent = conda_binary.parent()?; - if parent.ends_with("bin"){ + if parent.ends_with("bin") { parent = parent.parent()?; } - if parent.ends_with("Library"){ + if parent.ends_with("Library") { parent = parent.parent()?; } let conda_python_json_path = match get_conda_package_json_path(&parent, "conda") { Some(exe) => Some(exe), - None => get_conda_package_json_path(&parent.parent()?, "conda") + None => get_conda_package_json_path(&parent.parent()?, "conda"), }?; get_version_from_meta_json(&conda_python_json_path) } @@ -387,6 +388,7 @@ pub fn find_and_report( let params = messaging::EnvManager::new( conda_binary.to_string_lossy().to_string(), get_conda_version(&conda_binary), + EnvManagerType::Conda, ); dispatcher.report_environment_manager(params); diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index cd8c2e392054..f86a493ecee1 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -1,9 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::pipenv; +use crate::virtualenvwrapper; use crate::{ known, - utils::{find_python_binary_path, get_version}, + messaging::MessageDispatcher, + utils::{find_python_binary_path, get_version, PythonEnv}, }; use std::{fs, path::PathBuf}; @@ -43,12 +46,6 @@ fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec, -} - pub fn list_global_virtualenvs(environment: &impl known::Environment) -> Vec { let mut python_envs: Vec = vec![]; for root_dir in get_global_virtualenv_dirs(environment).iter() { @@ -73,3 +70,19 @@ pub fn list_global_virtualenvs(environment: &impl known::Environment) -> Vec Option<()> { + for env in list_global_virtualenvs(environment).iter() { + if pipenv::find_and_report(&env, dispatcher).is_some() { + continue; + } + if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { + continue; + } + } + + None +} diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index 41cb3d97bd10..1d9439bce7d7 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -9,3 +9,6 @@ pub mod conda; pub mod known; pub mod pyenv; pub mod global_virtualenvs; +pub mod virtualenvwrapper; +pub mod pipenv; +pub mod virtualenv; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index dad488f2186c..129a0973464e 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -16,6 +16,8 @@ mod messaging; mod pipenv; mod pyenv; mod utils; +mod virtualenv; +mod virtualenvwrapper; mod windows_python; fn main() { @@ -25,6 +27,8 @@ fn main() { dispatcher.log_info("Starting Native Locator"); let now = SystemTime::now(); + global_virtualenvs::find_and_report(&mut dispatcher, &environment); + // Finds python on PATH common_python::find_and_report(&mut dispatcher, &environment); @@ -37,8 +41,6 @@ fn main() { pyenv::find_and_report(&mut dispatcher, &environment); - pipenv::find_and_report(&mut dispatcher, &environment); - #[cfg(unix)] homebrew::find_and_report(&mut dispatcher, &environment); diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index a996ec3964e1..103c3b77fe4c 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -14,18 +14,27 @@ pub trait MessageDispatcher { fn log_error(&mut self, message: &str) -> (); } +#[derive(Serialize, Deserialize, Copy, Clone)] +#[serde(rename_all = "camelCase")] +pub enum EnvManagerType { + Conda, + Pyenv, +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { pub executable_path: String, pub version: Option, + pub tool: EnvManagerType, } impl EnvManager { - pub fn new(executable_path: String, version: Option) -> Self { + pub fn new(executable_path: String, version: Option, tool: EnvManagerType) -> Self { Self { executable_path, version, + tool, } } } @@ -35,6 +44,7 @@ impl Clone for EnvManager { Self { executable_path: self.executable_path.clone(), version: self.version.clone(), + tool: self.tool, } } } @@ -67,6 +77,7 @@ pub enum PythonEnvironmentCategory { PyenvVirtualEnv, WindowsStore, Pipenv, + VirtualEnvWrapper, } #[derive(Serialize, Deserialize)] diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 53c0b0e248d6..607ddd591113 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::global_virtualenvs::{list_global_virtualenvs, PythonEnv}; -use crate::known; use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use crate::utils::PythonEnv; use std::fs; use std::path::PathBuf; -fn get_project_folder(env: &PythonEnv) -> Option { +fn get_pipenv_project(env: &PythonEnv) -> Option { let project_file = env.path.join(".project"); if project_file.exists() { if let Ok(contents) = fs::read_to_string(project_file) { @@ -21,35 +20,31 @@ fn get_project_folder(env: &PythonEnv) -> Option { None } -pub fn find_and_report( - dispatcher: &mut impl MessageDispatcher, - environment: &impl known::Environment, -) -> Option<()> { - for env in list_global_virtualenvs(environment).iter() { - if let Some(project_path) = get_project_folder(&env) { - let env_path = env - .path - .clone() - .into_os_string() - .to_string_lossy() - .to_string(); - let executable = env - .executable - .clone() - .into_os_string() - .to_string_lossy() - .to_string(); - let env = PythonEnvironment::new_pipenv( - Some(executable), - env.version.clone(), - Some(env_path.clone()), - Some(env_path), - None, - project_path, - ); +pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { + if let Some(project_path) = get_pipenv_project(env) { + let env_path = env + .path + .clone() + .into_os_string() + .to_string_lossy() + .to_string(); + let executable = env + .executable + .clone() + .into_os_string() + .to_string_lossy() + .to_string(); + let env = PythonEnvironment::new_pipenv( + Some(executable), + env.version.clone(), + Some(env_path.clone()), + Some(env_path), + None, + project_path, + ); - dispatcher.report_environment(env); - } + dispatcher.report_environment(env); + return Some(()); } None diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 15969b321464..00b39e3c3ddd 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use crate::known; use crate::messaging; use crate::messaging::EnvManager; +use crate::messaging::EnvManagerType; use crate::utils::find_python_binary_path; use crate::utils::parse_pyenv_cfg; @@ -156,7 +157,7 @@ pub fn find_and_report( let manager = match get_pyenv_binary(environment) { Some(pyenv_binary) => { - let manager = messaging::EnvManager::new(pyenv_binary, None); + let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); dispatcher.report_environment_manager(manager.clone()); Some(manager) } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index cdf7348368f3..6e9684faeb33 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -8,6 +8,13 @@ use std::{ process::Command, }; +#[derive(Debug)] +pub struct PythonEnv { + pub path: PathBuf, + pub executable: PathBuf, + pub version: Option, +} + #[derive(Debug)] pub struct PyEnvCfg { pub version: String, diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs new file mode 100644 index 000000000000..b655d707798c --- /dev/null +++ b/native_locator/src/virtualenv.rs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::path::PathBuf; + +use crate::utils::PythonEnv; + +pub fn is_virtualenv(env: &PythonEnv) -> bool { + if let Some(file_path) = PathBuf::from(env.executable.clone()).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 +} diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs new file mode 100644 index 000000000000..d234111e4a15 --- /dev/null +++ b/native_locator/src/virtualenvwrapper.rs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory}; +use crate::virtualenv; +use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; +use std::path::PathBuf; + +#[cfg(windows)] +fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { + // 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.to_string_lossy().to_string()); + } + let home = PathBuf::from(home).join("virtualenvs"); + if home.exists() { + return Some(home.to_string_lossy().to_string()); + } + } + None +} + +#[cfg(unix)] +fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { + if let Some(home) = environment.get_user_home() { + let home = PathBuf::from(home).join("virtualenvs"); + if home.exists() { + return Some(home.to_string_lossy().to_string()); + } + } + None +} + +fn get_work_on_home_path(environment: &impl Environment) -> Option { + // 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.to_string_lossy().to_string()); + } + } + } + get_default_virtualenvwrapper_path(environment) +} + +fn create_virtualenvwrapper_env(env: &PythonEnv) -> PythonEnvironment { + let executable = env.executable.clone().into_os_string().into_string().ok(); + let env_path = env.path.clone().into_os_string().into_string().ok(); + PythonEnvironment { + name: match env.path.file_name().to_owned() { + Some(name) => Some(name.to_string_lossy().to_owned().to_string()), + None => None, + }, + python_executable_path: executable.clone(), + category: PythonEnvironmentCategory::VirtualEnvWrapper, + version: env.version.clone(), + env_path: env_path.clone(), + sys_prefix_path: env_path, + env_manager: None, + python_run_command: match executable { + Some(exe) => Some(vec![exe]), + None => None, + }, + project_path: None, + } +} + +pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> bool { + // 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 +} + +pub fn find_and_report( + env: &PythonEnv, + dispatcher: &mut impl MessageDispatcher, + environment: &impl Environment, +) -> Option<()> { + if is_virtualenvwrapper(env, environment) { + dispatcher.report_environment(create_virtualenvwrapper_env(env)); + return Some(()); + } + None +} diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index ad3a41181023..e7318f378845 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -44,7 +44,7 @@ fn find_conda_exe_and_empty_envs() { conda::find_and_report(&mut dispatcher, &known); let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); - let expected_json = json!({"executablePath":conda_exe.clone(),"version":null}); + let expected_json = json!({"executablePath":conda_exe.clone(),"version":null, "tool": "conda"}); assert_messages(&[expected_json], &dispatcher) } #[test] @@ -80,11 +80,11 @@ fn finds_two_conda_envs_from_txt() { let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); - let expected_conda_env = json!({ "executablePath": conda_exe.clone(), "version": null}); + let expected_conda_env = json!({ "executablePath": conda_exe.clone(), "version": null, "tool": "conda"}); let expected_conda_1 = json!({ "name": "one","projectPath": null, "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); let expected_conda_2 = json!({ "name": "two", "projectPath": null, "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); assert_messages( - &[expected_conda_env, expected_conda_1, expected_conda_2], + &[expected_conda_1, expected_conda_env, expected_conda_2], &dispatcher, ) } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index ecf1ba548abf..a73481291992 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -46,7 +46,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"executablePath":pyenv_exe,"version":null}); + let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); assert_messages(&[expected_json], &dispatcher) } @@ -74,7 +74,7 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 6); - let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null }); + let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null, "tool": "pyenv" }); let expected_3_9_9 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); let expected_virtual_env = json!({"projectPath": null, "name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); let expected_3_12_1 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); From 4114225fde0cdf0092a89882ee549c4f7f07b499 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 May 2024 09:48:29 +1000 Subject: [PATCH 027/106] virtualenvwrapper support in ts layer of native locator (#23390) --- native_locator/src/messaging.rs | 2 +- .../pythonEnvironments/base/locators/lowLevel/nativeLocator.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 103c3b77fe4c..2196aa758970 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -44,7 +44,7 @@ impl Clone for EnvManager { Self { executable_path: self.executable_path.clone(), version: self.version.clone(), - tool: self.tool, + tool: self.tool.clone(), } } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index ec809ee798d5..98f007bd0d70 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -51,6 +51,8 @@ function categoryToKind(category: string): PythonEnvKind { return PythonEnvKind.Pipenv; case 'pyenvvirtualenv': return PythonEnvKind.VirtualEnv; + case 'virtualenvwrapper': + return PythonEnvKind.VirtualEnvWrapper; case 'windowsstore': return PythonEnvKind.MicrosoftStore; default: { From e5a4c75f7d9e044e416740ab051273fb813d4c37 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 8 May 2024 16:56:28 -0700 Subject: [PATCH 028/106] Tweaks to how native finder is spawned and logging (#23387) --- package.json | 4 +- package.nls.json | 2 +- .../locators/common/nativePythonFinder.ts | 138 ++++++++++++++++++ .../base/locators/lowLevel/nativeLocator.ts | 109 ++++++-------- src/client/pythonEnvironments/index.ts | 2 +- 5 files changed, 184 insertions(+), 71 deletions(-) create mode 100644 src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts diff --git a/package.json b/package.json index b471ec654bdc..45e4b5ee3e5c 100644 --- a/package.json +++ b/package.json @@ -550,9 +550,9 @@ "experimental" ] }, - "python.nativeLocator": { + "python.locator": { "default": "js", - "description": "%python.nativeLocator.description%", + "description": "%python.locator.description%", "enum": [ "js", "native" diff --git a/package.nls.json b/package.nls.json index 536eab3e800d..e7c82f86b243 100644 --- a/package.nls.json +++ b/package.nls.json @@ -57,7 +57,7 @@ "python.logging.level.description": "The logging level the extension logs at, defaults to 'error'", "python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.", "python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml", - "python.nativeLocator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.", + "python.locator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.", "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts new file mode 100644 index 000000000000..63c99cec9398 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; +import * as ch from 'child_process'; +import * as path from 'path'; +import * as rpc from 'vscode-jsonrpc/node'; +import { isWindows } from '../../../../common/platform/platformService'; +import { EXTENSION_ROOT_DIR } from '../../../../constants'; +import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; +import { createDeferred } from '../../../../common/utils/async'; + +const NATIVE_LOCATOR = isWindows() + ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe') + : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder'); + +export interface NativeEnvInfo { + name: string; + pythonExecutablePath?: string; + category: string; + version?: string; + pythonRunCommand?: string[]; + envPath?: string; + sysPrefixPath?: string; + /** + * Path to the project directory when dealing with pipenv virtual environments. + */ + projectPath?: string; +} + +export interface NativeEnvManagerInfo { + tool: string; + executablePath: string; + version?: string; +} + +export interface NativeGlobalPythonFinder extends Disposable { + startSearch(token?: CancellationToken): Promise; + onDidFindPythonEnvironment: Event; + onDidFindEnvironmentManager: Event; +} + +interface NativeLog { + level: string; + message: string; +} + +class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { + private readonly _onDidFindPythonEnvironment = new EventEmitter(); + + private readonly _onDidFindEnvironmentManager = new EventEmitter(); + + public readonly onDidFindPythonEnvironment = this._onDidFindPythonEnvironment.event; + + public readonly onDidFindEnvironmentManager = this._onDidFindEnvironmentManager.event; + + public startSearch(token?: CancellationToken): Promise { + const deferred = createDeferred(); + const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' }); + const disposables: Disposable[] = []; + + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(proc.stdout), + new rpc.StreamMessageWriter(proc.stdin), + ); + + disposables.push( + connection, + connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { + this._onDidFindPythonEnvironment.fire(data); + }), + connection.onNotification('envManager', (data: NativeEnvManagerInfo) => { + this._onDidFindEnvironmentManager.fire(data); + }), + connection.onNotification('exit', () => { + traceVerbose('Native Python Finder exited'); + }), + connection.onNotification('log', (data: NativeLog) => { + switch (data.level) { + case 'info': + traceInfo(`Native Python Finder: ${data.message}`); + break; + case 'warning': + traceWarn(`Native Python Finder: ${data.message}`); + break; + case 'error': + traceError(`Native Python Finder: ${data.message}`); + break; + case 'debug': + traceVerbose(`Native Python Finder: ${data.message}`); + break; + default: + traceLog(`Native Python Finder: ${data.message}`); + } + }), + connection.onClose(() => { + deferred.resolve(); + disposables.forEach((d) => d.dispose()); + }), + { + dispose: () => { + try { + if (proc.exitCode === null) { + proc.kill(); + } + } catch (err) { + traceVerbose('Error while disposing Native Python Finder', err); + } + }, + }, + ); + + if (token) { + disposables.push( + token.onCancellationRequested(() => { + deferred.resolve(); + try { + proc.kill(); + } catch (err) { + traceVerbose('Error while handling cancellation request for Native Python Finder', err); + } + }), + ); + } + + connection.listen(); + return deferred.promise; + } + + public dispose() { + this._onDidFindPythonEnvironment.dispose(); + this._onDidFindEnvironmentManager.dispose(); + } +} + +export function createNativeGlobalPythonFinder(): NativeGlobalPythonFinder { + return new NativeGlobalPythonFinderImpl(); +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 98f007bd0d70..9ba186f95b35 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -1,42 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + import { Event, EventEmitter } from 'vscode'; -import * as ch from 'child_process'; -import * as path from 'path'; -import * as rpc from 'vscode-jsonrpc/node'; -import { EXTENSION_ROOT_DIR } from '../../../../constants'; -import { isWindows } from '../../../../common/platform/platformService'; import { IDisposable } from '../../../../common/types'; import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; -import { createDeferred } from '../../../../common/utils/async'; import { PythonEnvKind, PythonVersion } from '../../info'; import { Conda } from '../../../common/environmentManagers/conda'; import { traceError } from '../../../../logging'; import type { KnownEnvironmentTools } from '../../../../api/types'; import { setPyEnvBinary } from '../../../common/environmentManagers/pyenv'; - -const NATIVE_LOCATOR = isWindows() - ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe') - : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder'); - -interface NativeEnvInfo { - name: string; - pythonExecutablePath?: string; - category: string; - version?: string; - pythonRunCommand?: string[]; - envPath?: string; - sysPrefixPath?: string; - /** - * Path to the project directory when dealing with pipenv virtual environments. - */ - projectPath?: string; -} - -interface EnvManager { - tool: string; - executablePath: string; - version?: string; -} +import { + NativeEnvInfo, + NativeEnvManagerInfo, + NativeGlobalPythonFinder, + createNativeGlobalPythonFinder, +} from '../common/nativePythonFinder'; function categoryToKind(category: string): PythonEnvKind { switch (category.toLowerCase()) { @@ -61,6 +40,7 @@ function categoryToKind(category: string): PythonEnvKind { } } } + function toolToKnownEnvironmentTool(tool: string): KnownEnvironmentTools { switch (tool.toLowerCase()) { case 'conda': @@ -99,9 +79,12 @@ export class NativeLocator implements ILocator, IDisposable { private readonly disposables: IDisposable[] = []; + private readonly finder: NativeGlobalPythonFinder; + constructor() { this.onChanged = this.onChangedEmitter.event; - this.disposables.push(this.onChangedEmitter); + this.finder = createNativeGlobalPythonFinder(); + this.disposables.push(this.onChangedEmitter, this.finder); } public readonly onChanged: Event; @@ -112,46 +95,38 @@ export class NativeLocator implements ILocator, IDisposable { } public iterEnvs(): IPythonEnvsIterator { - const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' }); + const promise = this.finder.startSearch(); const envs: BasicEnvInfo[] = []; - const deferred = createDeferred(); - const connection = rpc.createMessageConnection( - new rpc.StreamMessageReader(proc.stdout), - new rpc.StreamMessageWriter(proc.stdin), - ); - this.disposables.push(connection); - connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { - envs.push({ - kind: categoryToKind(data.category), - // TODO: What if executable is undefined? - executablePath: data.pythonExecutablePath!, - envPath: data.envPath, - version: parseVersion(data.version), - name: data.name === '' ? undefined : data.name, - }); - }); - connection.onNotification('envManager', (data: EnvManager) => { - switch (toolToKnownEnvironmentTool(data.tool)) { - case 'Conda': { - Conda.setConda(data.executablePath); - break; + this.disposables.push( + this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { + envs.push({ + kind: categoryToKind(data.category), + // TODO: What if executable is undefined? + executablePath: data.pythonExecutablePath!, + envPath: data.envPath, + version: parseVersion(data.version), + name: data.name === '' ? undefined : data.name, + }); + }), + this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => { + switch (toolToKnownEnvironmentTool(data.tool)) { + case 'Conda': { + Conda.setConda(data.executablePath); + break; + } + case 'Pyenv': { + setPyEnvBinary(data.executablePath); + break; + } + default: { + break; + } } - case 'Pyenv': { - setPyEnvBinary(data.executablePath); - break; - } - default: { - break; - } - } - }); - connection.onNotification('exit', () => { - deferred.resolve(); - }); - connection.listen(); + }), + ); const iterator = async function* (): IPythonEnvsIterator { - await deferred.promise; + await promise; yield* envs; }; return iterator(); diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 2b341dab0e9c..9406f890c6da 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -140,7 +140,7 @@ async function createLocator( function useNativeLocator(): boolean { const config = getConfiguration('python'); - return config.get('nativeLocator', 'js') === 'native'; + return config.get('locator', 'js') === 'native'; } function createNonWorkspaceLocators(ext: ExtensionState): ILocator[] { From 054242193d6905c3ab61dbd85b98ae29f0e65157 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 May 2024 11:38:30 +1000 Subject: [PATCH 029/106] Drop jsonrpc connection after all messages are handled (#23391) --- .../locators/common/nativePythonFinder.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 63c99cec9398..1c9c0c480ec9 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -5,6 +5,7 @@ import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; +import { PassThrough } from 'stream'; import { isWindows } from '../../../../common/platform/platformService'; import { EXTENSION_ROOT_DIR } from '../../../../constants'; import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; @@ -56,16 +57,34 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { public startSearch(token?: CancellationToken): Promise { const deferred = createDeferred(); - const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' }); + const proc = ch.spawn(NATIVE_LOCATOR, [], { env: process.env }); const disposables: Disposable[] = []; + // jsonrpc package cannot handle messages coming through too quicly. + // Lets handle the messages and close the stream only when + // we have got the exit event. + const readable = new PassThrough(); + proc.stdout.pipe(readable, { end: false }); + const writable = new PassThrough(); + writable.pipe(proc.stdin, { end: false }); + const disposeStreams = new Disposable(() => { + readable.end(); + readable.destroy(); + writable.end(); + writable.destroy(); + }); const connection = rpc.createMessageConnection( - new rpc.StreamMessageReader(proc.stdout), - new rpc.StreamMessageWriter(proc.stdin), + new rpc.StreamMessageReader(readable), + new rpc.StreamMessageWriter(writable), ); disposables.push( connection, + disposeStreams, + connection.onError((ex) => { + disposeStreams.dispose(); + traceError('Error in Native Python Finder', ex); + }), connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { this._onDidFindPythonEnvironment.fire(data); }), @@ -73,7 +92,8 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { this._onDidFindEnvironmentManager.fire(data); }), connection.onNotification('exit', () => { - traceVerbose('Native Python Finder exited'); + traceInfo('Native Python Finder exited'); + disposeStreams.dispose(); }), connection.onNotification('log', (data: NativeLog) => { switch (data.level) { From 0479bc99ea95884e0b4002e81e51100d82c3ab83 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 May 2024 14:56:04 +1000 Subject: [PATCH 030/106] Use PathBufs to avoid regular conversions (#23393) --- native_locator/src/common_python.rs | 9 +-- native_locator/src/conda.rs | 23 +++---- native_locator/src/homebrew.rs | 5 +- native_locator/src/known.rs | 10 +-- native_locator/src/main.rs | 2 +- native_locator/src/messaging.rs | 30 ++++----- native_locator/src/pipenv.rs | 22 ++----- native_locator/src/pyenv.rs | 72 ++++++++++------------ native_locator/src/virtualenvwrapper.rs | 27 ++++---- native_locator/src/windows_python.rs | 3 +- native_locator/tests/common.rs | 14 ++--- native_locator/tests/common_python_test.rs | 4 +- native_locator/tests/conda_test.rs | 39 +++++++----- native_locator/tests/pyenv_test.rs | 25 ++++---- 14 files changed, 134 insertions(+), 151 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 5144c4ade2fa..0237d5f22700 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -6,15 +6,16 @@ use crate::messaging; use crate::utils; use std::env; use std::path::Path; +use std::path::PathBuf; -fn get_env_path(path: &str) -> Option { +fn get_env_path(path: &str) -> Option { let path = Path::new(path); match path.parent() { Some(parent) => { if parent.file_name()? == "Scripts" { - return Some(parent.parent()?.to_string_lossy().to_string()); + return Some(parent.parent()?.to_path_buf()); } else { - return Some(parent.to_string_lossy().to_string()); + return Some(parent.to_path_buf()); } } None => None, @@ -26,7 +27,7 @@ fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: let env_path = get_env_path(path); dispatcher.report_environment(messaging::PythonEnvironment::new( None, - Some(path.to_string()), + Some(PathBuf::from(path)), messaging::PythonEnvironmentCategory::System, version, env_path.clone(), diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 9377185e8eed..c4f43caace2a 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -254,7 +254,7 @@ fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> } fn get_known_env_locations( - conda_bin: PathBuf, + conda_bin: &PathBuf, environment: &impl known::Environment, ) -> Vec { let mut paths = vec![]; @@ -289,7 +289,7 @@ fn get_known_env_locations( } fn get_conda_envs_from_known_env_locations( - conda_bin: PathBuf, + conda_bin: &PathBuf, environment: &impl known::Environment, ) -> Vec { let mut envs = vec![]; @@ -332,12 +332,11 @@ struct CondaEnv { } fn get_distinct_conda_envs( - conda_bin: PathBuf, + conda_bin: &PathBuf, environment: &impl known::Environment, ) -> Vec { let mut envs = get_conda_envs_from_environment_txt(environment); - let mut known_envs = - get_conda_envs_from_known_env_locations(conda_bin.to_path_buf(), environment); + let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); envs.append(&mut known_envs); envs.sort(); envs.dedup(); @@ -386,26 +385,22 @@ pub fn find_and_report( match conda_binary { Some(conda_binary) => { let params = messaging::EnvManager::new( - conda_binary.to_string_lossy().to_string(), + conda_binary.clone(), get_conda_version(&conda_binary), EnvManagerType::Conda, ); dispatcher.report_environment_manager(params); - let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment); + let envs = get_distinct_conda_envs(&conda_binary, environment); for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); - let env_path = env.path.to_string_lossy().to_string(); let params = messaging::PythonEnvironment::new( Some(env.name.to_string()), - match executable { - Some(executable) => Some(executable.to_string_lossy().to_string()), - None => None, - }, + executable, messaging::PythonEnvironmentCategory::Conda, get_conda_python_version(&env.path), - Some(env_path.clone()), - Some(env_path), + Some(env.path.clone()), + Some(env.path.clone()), None, if env.named { Some(vec![ diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 37b52720aed6..f70c68fb6411 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -45,16 +45,15 @@ pub fn find_and_report( continue; } reported.insert(exe.to_string_lossy().to_string()); - let executable = exe.to_string_lossy().to_string(); let env = crate::messaging::PythonEnvironment::new( None, - Some(executable.clone()), + Some(exe.clone()), crate::messaging::PythonEnvironmentCategory::Homebrew, version, None, None, None, - Some(vec![executable]), + Some(vec![exe.to_string_lossy().to_string()]), ); dispatcher.report_environment(env); } diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index 8c2fdb4386e1..52392343eee9 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -3,7 +3,7 @@ use std::{env, path::PathBuf}; pub trait Environment { - fn get_user_home(&self) -> Option; + fn get_user_home(&self) -> Option; fn get_env_var(&self, key: String) -> Option; fn get_know_global_search_locations(&self) -> Vec; } @@ -12,7 +12,7 @@ pub struct EnvironmentApi {} #[cfg(windows)] impl Environment for EnvironmentApi { - fn get_user_home(&self) -> Option { + fn get_user_home(&self) -> Option { get_user_home() } fn get_env_var(&self, key: String) -> Option { @@ -25,7 +25,7 @@ impl Environment for EnvironmentApi { #[cfg(unix)] impl Environment for EnvironmentApi { - fn get_user_home(&self) -> Option { + fn get_user_home(&self) -> Option { get_user_home() } fn get_env_var(&self, key: String) -> Option { @@ -49,10 +49,10 @@ impl Environment for EnvironmentApi { } } -fn get_user_home() -> Option { +fn get_user_home() -> Option { let home = env::var("HOME").or_else(|_| env::var("USERPROFILE")); match home { - Ok(home) => Some(home), + Ok(home) => Some(PathBuf::from(home)), Err(_) => None, } } diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 129a0973464e..042dc132f3a9 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -55,6 +55,6 @@ fn main() { dispatcher.log_error(&format!("Error getting elapsed time: {:?}", e)); } } - + dispatcher.exit(); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 2196aa758970..5936abb01427 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use std::path::PathBuf; + use crate::logging::{LogLevel, LogMessage}; use serde::{Deserialize, Serialize}; @@ -24,13 +26,13 @@ pub enum EnvManagerType { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { - pub executable_path: String, + pub executable_path: PathBuf, pub version: Option, pub tool: EnvManagerType, } impl EnvManager { - pub fn new(executable_path: String, version: Option, tool: EnvManagerType) -> Self { + pub fn new(executable_path: PathBuf, version: Option, tool: EnvManagerType) -> Self { Self { executable_path, version, @@ -84,27 +86,27 @@ pub enum PythonEnvironmentCategory { #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { pub name: Option, - pub python_executable_path: Option, + pub python_executable_path: Option, pub category: PythonEnvironmentCategory, pub version: Option, - pub env_path: Option, - pub sys_prefix_path: Option, + pub env_path: Option, + pub sys_prefix_path: Option, pub env_manager: Option, pub python_run_command: Option>, /** * The project path for the Pipenv environment. */ - pub project_path: Option, + pub project_path: Option, } impl PythonEnvironment { pub fn new( name: Option, - python_executable_path: Option, + python_executable_path: Option, category: PythonEnvironmentCategory, version: Option, - env_path: Option, - sys_prefix_path: Option, + env_path: Option, + sys_prefix_path: Option, env_manager: Option, python_run_command: Option>, ) -> Self { @@ -121,12 +123,12 @@ impl PythonEnvironment { } } pub fn new_pipenv( - python_executable_path: Option, + python_executable_path: Option, version: Option, - env_path: Option, - sys_prefix_path: Option, + env_path: Option, + sys_prefix_path: Option, env_manager: Option, - project_path: String, + project_path: PathBuf, ) -> Self { Self { name: None, @@ -137,7 +139,7 @@ impl PythonEnvironment { sys_prefix_path, env_manager, python_run_command: match python_executable_path { - Some(exe) => Some(vec![exe]), + Some(exe) => Some(vec![exe.to_string_lossy().to_string()]), None => None, }, project_path: Some(project_path), diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 607ddd591113..737c7c776c18 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -6,13 +6,13 @@ use crate::utils::PythonEnv; use std::fs; use std::path::PathBuf; -fn get_pipenv_project(env: &PythonEnv) -> Option { +fn get_pipenv_project(env: &PythonEnv) -> Option { let project_file = env.path.join(".project"); if project_file.exists() { 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.to_string_lossy().to_string()); + return Some(project_folder); } } } @@ -22,23 +22,11 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { if let Some(project_path) = get_pipenv_project(env) { - let env_path = env - .path - .clone() - .into_os_string() - .to_string_lossy() - .to_string(); - let executable = env - .executable - .clone() - .into_os_string() - .to_string_lossy() - .to_string(); let env = PythonEnvironment::new_pipenv( - Some(executable), + Some(env.executable.clone()), env.version.clone(), - Some(env_path.clone()), - Some(env_path), + Some(env.path.clone()), + Some(env.path.clone()), None, project_path, ); diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 00b39e3c3ddd..89fda7803055 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -13,37 +13,28 @@ use crate::utils::find_python_binary_path; use crate::utils::parse_pyenv_cfg; #[cfg(windows)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { let home = environment.get_user_home()?; - PathBuf::from(home) - .join(".pyenv") - .join("pyenv-win") - .into_os_string() - .into_string() - .ok() + Some(PathBuf::from(home).join(".pyenv").join("pyenv-win")) } #[cfg(unix)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { let home = environment.get_user_home()?; - PathBuf::from(home) - .join(".pyenv") - .into_os_string() - .into_string() - .ok() + Some(PathBuf::from(home).join(".pyenv")) } -fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { +fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { for known_path in environment.get_know_global_search_locations() { let bin = known_path.join("pyenv"); if bin.exists() { - return bin.into_os_string().into_string().ok(); + return Some(bin); } } None } -fn get_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_pyenv_dir(environment: &impl known::Environment) -> Option { // 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. @@ -52,25 +43,25 @@ fn get_pyenv_dir(environment: &impl known::Environment) -> Option { // And https://github.com/pyenv-win/pyenv-win for Windows specifics. match environment.get_env_var("PYENV_ROOT".to_string()) { - Some(dir) => Some(dir), + Some(dir) => Some(PathBuf::from(dir)), None => match environment.get_env_var("PYENV".to_string()) { - Some(dir) => Some(dir), + Some(dir) => Some(PathBuf::from(dir)), None => get_home_pyenv_dir(environment), }, } } -fn get_pyenv_binary(environment: &impl known::Environment) -> Option { +fn get_pyenv_binary(environment: &impl known::Environment) -> Option { let dir = get_pyenv_dir(environment)?; let exe = PathBuf::from(dir).join("bin").join("pyenv"); if fs::metadata(&exe).is_ok() { - exe.into_os_string().into_string().ok() + Some(exe) } else { get_binary_from_known_paths(environment) } } -fn get_pyenv_version(folder_name: String) -> Option { +fn get_pyenv_version(folder_name: &String) -> Option { // Stable Versions = like 3.10.10 let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap(); match python_regex.captures(&folder_name) { @@ -103,47 +94,51 @@ fn get_pyenv_version(folder_name: String) -> Option { } fn report_if_pure_python_environment( - executable: PathBuf, + executable: &PathBuf, path: &PathBuf, manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { - let version = get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string())?; - let executable = executable.into_os_string().into_string().unwrap(); - let env_path = path.to_string_lossy().to_string(); + let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?; dispatcher.report_environment(messaging::PythonEnvironment::new( None, Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, Some(version), - Some(env_path.clone()), - Some(env_path), + Some(path.clone()), + Some(path.clone()), manager, - Some(vec![executable]), + Some(vec![executable + .clone() + .into_os_string() + .into_string() + .unwrap()]), )); Some(()) } fn report_if_virtual_env_environment( - executable: PathBuf, + executable: &PathBuf, path: &PathBuf, manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { let pyenv_cfg = parse_pyenv_cfg(path)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); - let executable = executable.into_os_string().into_string().unwrap(); - let env_path = path.to_string_lossy().to_string(); dispatcher.report_environment(messaging::PythonEnvironment::new( Some(folder_name), Some(executable.clone()), messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), - Some(env_path.clone()), - Some(env_path), + Some(path.clone()), + Some(path.clone()), manager, - Some(vec![executable]), + Some(vec![executable + .clone() + .into_os_string() + .into_string() + .unwrap()]), )); Some(()) @@ -178,7 +173,7 @@ pub fn find_and_report( } if let Some(executable) = find_python_binary_path(&path) { if report_if_pure_python_environment( - executable.clone(), + &executable, &path, manager.clone(), dispatcher, @@ -188,12 +183,7 @@ pub fn find_and_report( continue; } - report_if_virtual_env_environment( - executable.clone(), - &path, - manager.clone(), - dispatcher, - ); + report_if_virtual_env_environment(&executable, &path, manager.clone(), dispatcher); } } } diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index d234111e4a15..0d20d5d6d1d7 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -7,41 +7,41 @@ use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; use std::path::PathBuf; #[cfg(windows)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { // 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.to_string_lossy().to_string()); + return Some(home); } let home = PathBuf::from(home).join("virtualenvs"); if home.exists() { - return Some(home.to_string_lossy().to_string()); + return Some(home); } } None } #[cfg(unix)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { if let Some(home) = environment.get_user_home() { let home = PathBuf::from(home).join("virtualenvs"); if home.exists() { - return Some(home.to_string_lossy().to_string()); + return Some(home); } } None } -fn get_work_on_home_path(environment: &impl Environment) -> Option { +fn get_work_on_home_path(environment: &impl Environment) -> Option { // 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.to_string_lossy().to_string()); + return Some(work_on_home); } } } @@ -49,23 +49,18 @@ fn get_work_on_home_path(environment: &impl Environment) -> Option { } fn create_virtualenvwrapper_env(env: &PythonEnv) -> PythonEnvironment { - let executable = env.executable.clone().into_os_string().into_string().ok(); - let env_path = env.path.clone().into_os_string().into_string().ok(); PythonEnvironment { name: match env.path.file_name().to_owned() { Some(name) => Some(name.to_string_lossy().to_owned().to_string()), None => None, }, - python_executable_path: executable.clone(), + python_executable_path: Some(env.executable.clone()), category: PythonEnvironmentCategory::VirtualEnvWrapper, version: env.version.clone(), - env_path: env_path.clone(), - sys_prefix_path: env_path, + env_path: Some(env.path.clone()), + sys_prefix_path: Some(env.path.clone()), env_manager: None, - python_run_command: match executable { - Some(exe) => Some(vec![exe]), - None => None, - }, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: None, } } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 0494066eef0c..8199a503f924 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -5,12 +5,13 @@ use crate::known; use crate::messaging; use crate::utils; use std::path::Path; +use std::path::PathBuf; fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { let version = utils::get_version(path); dispatcher.report_environment(messaging::PythonEnvironment::new( None, - Some(path.to_string()), + Some(PathBuf::from(path)), messaging::PythonEnvironmentCategory::WindowsStore, version, None, diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 7e87eee758ab..7579c44eec41 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -9,19 +9,19 @@ use serde_json::Value; use std::{collections::HashMap, path::PathBuf}; #[allow(dead_code)] -pub fn test_file_path(paths: &[&str]) -> String { +pub fn test_file_path(paths: &[&str]) -> PathBuf { // let parts: Vec = paths.iter().map(|p| p.to_string()).collect(); let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); paths.iter().for_each(|p| root.push(p)); - root.to_string_lossy().to_string() + root } #[allow(dead_code)] -pub fn join_test_paths(paths: &[&str]) -> String { +pub fn join_test_paths(paths: &[&str]) -> PathBuf { let path: PathBuf = paths.iter().map(|p| p.to_string()).collect(); - path.to_string_lossy().to_string() + path } pub struct TestDispatcher { @@ -59,20 +59,20 @@ pub fn create_test_dispatcher() -> TestDispatcher { } pub struct TestEnvironment { vars: HashMap, - home: Option, + home: Option, globals_locations: Vec, } #[allow(dead_code)] pub fn create_test_environment( vars: HashMap, - home: Option, + home: Option, globals_locations: Vec, ) -> TestEnvironment { impl Environment for TestEnvironment { fn get_env_var(&self, key: String) -> Option { self.vars.get(&key).cloned() } - fn get_user_home(&self) -> Option { + fn get_user_home(&self) -> Option { self.home.clone() } fn get_know_global_search_locations(&self) -> Vec { diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 7ef6b8eb3f53..6499b88730e3 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -15,11 +15,11 @@ fn find_python_in_path_this() { use std::collections::HashMap; let unix_python = test_file_path(&["tests/unix/known"]); - let unix_python_exe = join_test_paths(&[unix_python.as_str(), "python"]); + let unix_python_exe = join_test_paths(&[unix_python.clone().to_str().unwrap(), "python"]); let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( - HashMap::from([("PATH".to_string(), unix_python.clone())]), + HashMap::from([("PATH".to_string(), unix_python.clone().to_str().unwrap().to_string())]), Some(unix_python.clone()), Vec::new(), ); diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index e7318f378845..d0abfdf9d9ca 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -8,12 +8,12 @@ mod common; fn does_not_find_any_conda_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; use python_finder::conda; - use std::collections::HashMap; + use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([("PATH".to_string(), "".to_string())]), - Some("SOME_BOGUS_HOME_DIR".to_string()), + Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); @@ -31,19 +31,22 @@ fn find_conda_exe_and_empty_envs() { }; use python_finder::conda; use serde_json::json; - use std::collections::HashMap; + use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( - HashMap::from([("PATH".to_string(), conda_dir.clone())]), - Some("SOME_BOGUS_HOME_DIR".to_string()), + HashMap::from([( + "PATH".to_string(), + conda_dir.clone().to_str().unwrap().to_string(), + )]), + Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); conda::find_and_report(&mut dispatcher, &known); - let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); + let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let expected_json = json!({"executablePath":conda_exe.clone(),"version":null, "tool": "conda"}); assert_messages(&[expected_json], &dispatcher) } @@ -60,27 +63,35 @@ fn finds_two_conda_envs_from_txt() { use std::fs; let conda_dir = test_file_path(&["tests/unix/conda"]); - let conda_1 = join_test_paths(&[conda_dir.clone().as_str(), "envs/one"]); - let conda_2 = join_test_paths(&[conda_dir.clone().as_str(), "envs/two"]); + 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 _ = fs::write( "tests/unix/conda/.conda/environments.txt", - format!("{}\n{}", conda_1.clone(), conda_2.clone()), + format!( + "{}\n{}", + conda_1.clone().to_str().unwrap().to_string(), + conda_2.clone().to_str().unwrap().to_string() + ), ); let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( - HashMap::from([("PATH".to_string(), conda_dir.clone())]), + HashMap::from([( + "PATH".to_string(), + conda_dir.clone().to_str().unwrap().to_string(), + )]), Some(conda_dir.clone()), Vec::new(), ); conda::find_and_report(&mut dispatcher, &known); - let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); - let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); - let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); + let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "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_env = json!({ "executablePath": conda_exe.clone(), "version": null, "tool": "conda"}); + let expected_conda_env = + json!({ "executablePath": conda_exe.clone(), "version": null, "tool": "conda"}); let expected_conda_1 = json!({ "name": "one","projectPath": null, "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); let expected_conda_2 = json!({ "name": "two", "projectPath": null, "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); assert_messages( diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index a73481291992..375d44444916 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -8,12 +8,12 @@ mod common; fn does_not_find_any_pyenv_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; use python_finder::pyenv; - use std::collections::HashMap; + use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::new(), - Some("SOME_BOGUS_HOME_DIR".to_string()), + Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); @@ -35,8 +35,8 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { let mut dispatcher = create_test_dispatcher(); let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]); - let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]); - let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]); + let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "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()), @@ -63,8 +63,8 @@ fn find_pyenv_envs() { let mut dispatcher = create_test_dispatcher(); let home = test_file_path(&["tests", "unix", "pyenv"]); - let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]); - let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]); + let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "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()), @@ -74,12 +74,13 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 6); - let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null, "tool": "pyenv" }); - let expected_3_9_9 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); - let expected_virtual_env = json!({"projectPath": null, "name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); - let expected_3_12_1 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); - let expected_3_13_dev = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); - let expected_3_12_1a3 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); + let expected_manager = + json!({ "executablePath": pyenv_exe.clone(), "version": null, "tool": "pyenv" }); + let expected_3_9_9 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); + let expected_virtual_env = json!({"projectPath": null, "name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); + let expected_3_12_1 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); + let expected_3_13_dev = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); + let expected_3_12_1a3 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); assert_messages( &[ expected_manager, From 58311231d5a85453752eb591f89d0f1f7accab48 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 May 2024 15:58:27 +1000 Subject: [PATCH 031/106] Use structs instead of loose json in native test (#23394) --- native_locator/src/conda.rs | 6 +- native_locator/tests/common.rs | 14 +- native_locator/tests/common_python_test.rs | 21 ++- native_locator/tests/conda_test.rs | 63 ++++++-- native_locator/tests/pyenv_test.rs | 159 +++++++++++++++++++-- 5 files changed, 233 insertions(+), 30 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index c4f43caace2a..68557dbd46ce 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -384,12 +384,12 @@ pub fn find_and_report( let conda_binary = find_conda_binary(environment); match conda_binary { Some(conda_binary) => { - let params = messaging::EnvManager::new( + let env_manager = messaging::EnvManager::new( conda_binary.clone(), get_conda_version(&conda_binary), EnvManagerType::Conda, ); - dispatcher.report_environment_manager(params); + dispatcher.report_environment_manager(env_manager.clone()); let envs = get_distinct_conda_envs(&conda_binary, environment); for env in envs { @@ -401,7 +401,7 @@ pub fn find_and_report( get_conda_python_version(&env.path), Some(env.path.clone()), Some(env.path.clone()), - None, + Some(env_manager.clone()), if env.named { Some(vec![ conda_binary.to_string_lossy().to_string(), diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 7579c44eec41..073c5c533124 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -92,6 +92,17 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { } 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(); @@ -99,7 +110,8 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { if !actual.contains_key(key) { return false; } - + println!("\nCompare Key {:?}", key); + println!("\nCompare Key {:?}", actual.get(key).is_none()); if !compare_json(value, actual.get(key).unwrap()) { return false; } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 6499b88730e3..9ee1c03c201e 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -10,7 +10,7 @@ fn find_python_in_path_this() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::common_python; + use python_finder::{common_python, messaging::PythonEnvironment}; use serde_json::json; use std::collections::HashMap; @@ -19,7 +19,10 @@ fn find_python_in_path_this() { let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( - HashMap::from([("PATH".to_string(), unix_python.clone().to_str().unwrap().to_string())]), + HashMap::from([( + "PATH".to_string(), + unix_python.clone().to_str().unwrap().to_string(), + )]), Some(unix_python.clone()), Vec::new(), ); @@ -27,6 +30,16 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"envManager":null,"projectPath": null, "name":null,"pythonExecutablePath":unix_python_exe.clone(),"category":"system","version":null,"pythonRunCommand":[unix_python_exe.clone()],"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); - assert_messages(&[expected_json], &dispatcher); + let env = PythonEnvironment { + 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(unix_python.clone()), + sys_prefix_path: Some(unix_python.clone()), + }; + assert_messages(&[json!(env)], &dispatcher); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index d0abfdf9d9ca..2673ea99cf7b 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -29,7 +29,10 @@ fn find_conda_exe_and_empty_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::conda; + use python_finder::{ + conda, + messaging::{EnvManager, EnvManagerType}, + }; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); @@ -47,8 +50,12 @@ fn find_conda_exe_and_empty_envs() { conda::find_and_report(&mut dispatcher, &known); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); - let expected_json = json!({"executablePath":conda_exe.clone(),"version":null, "tool": "conda"}); - assert_messages(&[expected_json], &dispatcher) + let expected_conda_manager = EnvManager { + executable_path: conda_exe.clone(), + version: None, + tool: EnvManagerType::Conda, + }; + assert_messages(&[json!(expected_conda_manager)], &dispatcher) } #[test] #[cfg(unix)] @@ -58,6 +65,7 @@ fn finds_two_conda_envs_from_txt() { test_file_path, }; use python_finder::conda; + use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use serde_json::json; use std::collections::HashMap; use std::fs; @@ -90,12 +98,51 @@ fn finds_two_conda_envs_from_txt() { 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_env = - json!({ "executablePath": conda_exe.clone(), "version": null, "tool": "conda"}); - let expected_conda_1 = json!({ "name": "one","projectPath": null, "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); - let expected_conda_2 = json!({ "name": "two", "projectPath": null, "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); + let expected_conda_manager = EnvManager { + executable_path: conda_exe.clone(), + version: None, + tool: EnvManagerType::Conda, + }; + let expected_conda_1 = PythonEnvironment { + 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()), + sys_prefix_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(), + ]), + }; + let expected_conda_2 = PythonEnvironment { + 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()), + sys_prefix_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(), + ]), + }; assert_messages( - &[expected_conda_1, expected_conda_env, expected_conda_2], + &[ + json!(expected_conda_1), + json!(expected_conda_manager), + json!(expected_conda_2), + ], &dispatcher, ) } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 375d44444916..1b9e53c4ebbf 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -57,7 +57,10 @@ fn find_pyenv_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::pyenv; + use python_finder::{ + messaging::{EnvManager, EnvManagerType, PythonEnvironment}, + pyenv, + }; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; @@ -74,21 +77,149 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 6); - let expected_manager = - json!({ "executablePath": pyenv_exe.clone(), "version": null, "tool": "pyenv" }); - let expected_3_9_9 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); - let expected_virtual_env = json!({"projectPath": null, "name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); - let expected_3_12_1 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); - let expected_3_13_dev = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); - let expected_3_12_1a3 = json!({"projectPath": null, "name": null,"pythonExecutablePath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.to_str().unwrap(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); + let expected_manager = EnvManager { + executable_path: pyenv_exe.clone(), + version: None, + tool: EnvManagerType::Pyenv, + }; + let expected_3_9_9 = json!(PythonEnvironment { + 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" + ])), + sys_prefix_path: Some(join_test_paths(&[ + home.to_str().unwrap(), + ".pyenv/versions/3.9.9" + ])), + env_manager: Some(expected_manager.clone()) + }); + let expected_virtual_env = PythonEnvironment { + 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", + ])), + sys_prefix_path: Some(join_test_paths(&[ + home.to_str().unwrap(), + ".pyenv/versions/my-virtual-env", + ])), + env_manager: Some(expected_manager.clone()), + }; + let expected_3_12_1 = PythonEnvironment { + 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", + ])), + sys_prefix_path: Some(join_test_paths(&[ + home.to_str().unwrap(), + ".pyenv/versions/3.12.1", + ])), + env_manager: Some(expected_manager.clone()), + }; + let expected_3_13_dev = PythonEnvironment { + 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", + ])), + sys_prefix_path: Some(join_test_paths(&[ + home.to_str().unwrap(), + ".pyenv/versions/3.13-dev", + ])), + env_manager: Some(expected_manager.clone()), + }; + let expected_3_12_1a3 = PythonEnvironment { + 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", + ])), + sys_prefix_path: Some(join_test_paths(&[ + home.to_str().unwrap(), + ".pyenv/versions/3.12.1a3", + ])), + env_manager: Some(expected_manager.clone()), + }; assert_messages( &[ - expected_manager, - expected_3_9_9, - expected_virtual_env, - expected_3_12_1, - expected_3_13_dev, - expected_3_12_1a3, + json!(expected_manager), + json!(expected_3_9_9), + json!(expected_virtual_env), + json!(expected_3_12_1), + json!(expected_3_13_dev), + json!(expected_3_12_1a3), ], &dispatcher, ) From 795bba9695f035a37939bd52e0445c04e408f68a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 06:22:04 +1000 Subject: [PATCH 032/106] Support venv, virtualEnv in global folders (#23395) --- native_locator/src/global_virtualenvs.rs | 8 ++++ native_locator/src/lib.rs | 1 + native_locator/src/main.rs | 3 +- native_locator/src/messaging.rs | 2 + native_locator/src/pyenv.rs | 5 +- native_locator/src/utils.rs | 48 ++++++++++++++----- native_locator/src/venv.rs | 35 ++++++++++++++ native_locator/src/virtualenv.rs | 28 ++++++++++- native_locator/src/virtualenvwrapper.rs | 34 +++++++------ .../mambaforge-4.10.1-4/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../mambaforge-4.10.1-4/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../mambaforge/miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 .../miniforge3-4.11.0-1/bin/python | 0 33 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 native_locator/src/venv.rs delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index f86a493ecee1..67b8cc2c6ea8 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -2,6 +2,8 @@ // Licensed under the MIT License. use crate::pipenv; +use crate::venv; +use crate::virtualenv; use crate::virtualenvwrapper; use crate::{ known, @@ -82,6 +84,12 @@ pub fn find_and_report( if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { continue; } + if venv::find_and_report(&env, dispatcher).is_some() { + continue; + } + if virtualenv::find_and_report(&env, dispatcher).is_some() { + continue; + } } None diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index 1d9439bce7d7..b0cccef843c5 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -12,3 +12,4 @@ pub mod global_virtualenvs; pub mod virtualenvwrapper; pub mod pipenv; pub mod virtualenv; +pub mod venv; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 042dc132f3a9..0332bbaf2f63 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -19,6 +19,7 @@ mod utils; mod virtualenv; mod virtualenvwrapper; mod windows_python; +mod venv; fn main() { let mut dispatcher = create_dispatcher(); @@ -55,6 +56,6 @@ fn main() { dispatcher.log_error(&format!("Error getting elapsed time: {:?}", e)); } } - + dispatcher.exit(); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 5936abb01427..01f13c34984f 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -80,6 +80,8 @@ pub enum PythonEnvironmentCategory { WindowsStore, Pipenv, VirtualEnvWrapper, + Venv, + VirtualEnv, } #[derive(Serialize, Deserialize)] diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 89fda7803055..814be30af441 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -9,8 +9,8 @@ use crate::known; use crate::messaging; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; +use crate::utils::find_and_parse_pyvenv_cfg; use crate::utils::find_python_binary_path; -use crate::utils::parse_pyenv_cfg; #[cfg(windows)] fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { @@ -124,7 +124,7 @@ fn report_if_virtual_env_environment( manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { - let pyenv_cfg = parse_pyenv_cfg(path)?; + let pyenv_cfg = find_and_parse_pyvenv_cfg(executable)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); dispatcher.report_environment(messaging::PythonEnvironment::new( Some(folder_name), @@ -182,7 +182,6 @@ pub fn find_and_report( { continue; } - report_if_virtual_env_environment(&executable, &path, manager.clone(), dispatcher); } } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 6e9684faeb33..88122085a9d8 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -20,13 +20,41 @@ pub struct PyEnvCfg { pub version: String, } -pub fn parse_pyenv_cfg(path: &PathBuf) -> Option { - let cfg = path.join("pyvenv.cfg"); +const PYVENV_CONFIG_FILE: &str = "pyvenv.cfg"; + +pub fn find_pyvenv_config_path(python_executable: &PathBuf) -> Option { + // 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 { + 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 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() { @@ -51,18 +79,14 @@ pub fn parse_pyenv_cfg(path: &PathBuf) -> Option { None } -pub fn get_version(path: &str) -> Option { - if let Some(parent_folder) = PathBuf::from(path).parent() { - if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) { +pub fn get_version(python_executable: &str) -> Option { + if let Some(parent_folder) = PathBuf::from(python_executable).parent() { + if let Some(pyenv_cfg) = find_and_parse_pyvenv_cfg(&parent_folder.to_path_buf()) { return Some(pyenv_cfg.version); } - if let Some(parent_folder) = parent_folder.parent() { - if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) { - return Some(pyenv_cfg.version); - } - } } - let output = Command::new(path) + + let output = Command::new(python_executable) .arg("-c") .arg("import sys; print(sys.version)") .output() diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs new file mode 100644 index 000000000000..9fa16031af8a --- /dev/null +++ b/native_locator/src/venv.rs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}, + utils::{self, PythonEnv}, +}; + +pub fn is_venv(env: &PythonEnv) -> bool { + return utils::find_pyvenv_config_path(&env.executable).is_some(); +} + +pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { + if is_venv(env) { + let env = PythonEnvironment { + name: match env.path.file_name().to_owned() { + Some(name) => Some(name.to_string_lossy().to_owned().to_string()), + None => None, + }, + python_executable_path: Some(env.executable.clone()), + category: PythonEnvironmentCategory::Venv, + version: env.version.clone(), + env_path: Some(env.path.clone()), + sys_prefix_path: Some(env.path.clone()), + env_manager: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + project_path: None, + }; + + dispatcher.report_environment(env); + + return Some(()); + } + None +} diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index b655d707798c..65da331abaa2 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::path::PathBuf; - +use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}; use crate::utils::PythonEnv; +use std::path::PathBuf; pub fn is_virtualenv(env: &PythonEnv) -> bool { if let Some(file_path) = PathBuf::from(env.executable.clone()).parent() { @@ -41,3 +41,27 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { false } + +pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { + if is_virtualenv(env) { + let env = PythonEnvironment { + name: match env.path.file_name().to_owned() { + Some(name) => Some(name.to_string_lossy().to_owned().to_string()), + None => None, + }, + python_executable_path: Some(env.executable.clone()), + category: PythonEnvironmentCategory::VirtualEnv, + version: env.version.clone(), + env_path: Some(env.path.clone()), + sys_prefix_path: Some(env.path.clone()), + env_manager: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + project_path: None, + }; + + dispatcher.report_environment(env); + + return Some(()); + } + None +} diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 0d20d5d6d1d7..d0cc5d4551e1 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -48,23 +48,6 @@ fn get_work_on_home_path(environment: &impl Environment) -> Option { get_default_virtualenvwrapper_path(environment) } -fn create_virtualenvwrapper_env(env: &PythonEnv) -> PythonEnvironment { - PythonEnvironment { - name: match env.path.file_name().to_owned() { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::VirtualEnvWrapper, - version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - } -} - pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> bool { // 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 @@ -84,7 +67,22 @@ pub fn find_and_report( environment: &impl Environment, ) -> Option<()> { if is_virtualenvwrapper(env, environment) { - dispatcher.report_environment(create_virtualenvwrapper_env(env)); + let env = PythonEnvironment { + name: match env.path.file_name().to_owned() { + Some(name) => Some(name.to_string_lossy().to_owned().to_string()), + None => None, + }, + python_executable_path: Some(env.executable.clone()), + category: PythonEnvironmentCategory::VirtualEnvWrapper, + version: env.version.clone(), + env_path: Some(env.path.clone()), + sys_prefix_path: Some(env.path.clone()), + env_manager: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + project_path: None, + }; + + dispatcher.report_environment(env); return Some(()); } None diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 From 3119849fc3d28d2e10837731c938336a00cbf9eb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 07:25:58 +1000 Subject: [PATCH 033/106] End the passthrough stream instead of ending (#23399) --- .../base/locators/common/nativePythonFinder.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 1c9c0c480ec9..dbc5914e4715 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -69,9 +69,7 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { writable.pipe(proc.stdin, { end: false }); const disposeStreams = new Disposable(() => { readable.end(); - readable.destroy(); writable.end(); - writable.destroy(); }); const connection = rpc.createMessageConnection( new rpc.StreamMessageReader(readable), From dbb813adc06b7f859a5a8a04160886fcf74e5a5f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 12:03:56 +1000 Subject: [PATCH 034/106] Refactor locators to implement a specific trait (#23404) --- native_locator/src/common_python.rs | 110 +++++++++------ native_locator/src/conda.rs | 152 ++++++++++++++------- native_locator/src/global_virtualenvs.rs | 41 +----- native_locator/src/homebrew.rs | 115 ++++++++++------ native_locator/src/lib.rs | 1 + native_locator/src/locator.rs | 26 ++++ native_locator/src/main.rs | 85 ++++++++++-- native_locator/src/messaging.rs | 7 +- native_locator/src/pipenv.rs | 68 +++++++-- native_locator/src/pyenv.rs | 145 +++++++++++++------- native_locator/src/utils.rs | 44 +++++- native_locator/src/venv.rs | 86 +++++++++--- native_locator/src/virtualenv.rs | 83 ++++++++--- native_locator/src/virtualenvwrapper.rs | 101 ++++++++++---- native_locator/src/windows_python.rs | 149 ++++++++++++-------- native_locator/tests/common_python_test.rs | 8 +- native_locator/tests/conda_test.rs | 22 +-- native_locator/tests/pyenv_test.rs | 16 ++- 18 files changed, 866 insertions(+), 393 deletions(-) create mode 100644 native_locator/src/locator.rs diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 0237d5f22700..dc796c164070 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -1,47 +1,72 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::known; -use crate::messaging; -use crate::utils; +use crate::known::Environment; +use crate::locator::Locator; +use crate::messaging::MessageDispatcher; +use crate::messaging::PythonEnvironment; +use crate::utils::{self, PythonEnv}; +use std::collections::HashMap; use std::env; -use std::path::Path; use std::path::PathBuf; -fn get_env_path(path: &str) -> Option { - let path = Path::new(path); - match path.parent() { - Some(parent) => { - if parent.file_name()? == "Scripts" { - return Some(parent.parent()?.to_path_buf()); - } else { - return Some(parent.to_path_buf()); - } - } - None => None, +fn get_env_path(python_executable_path: &PathBuf) -> Option { + 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()); } } -fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) { - let version = utils::get_version(path); - let env_path = get_env_path(path); - dispatcher.report_environment(messaging::PythonEnvironment::new( - None, - Some(PathBuf::from(path)), - messaging::PythonEnvironmentCategory::System, - version, - env_path.clone(), - env_path, - None, - Some(vec![path.to_string()]), - )); +pub struct PythonOnPath<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} + +impl PythonOnPath<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath { + PythonOnPath { + environments: HashMap::new(), + environment, + } + } } -fn report_python_on_path( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - if let Some(paths) = environment.get_env_var("PATH".to_string()) { +impl Locator for PythonOnPath<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + let bin = if cfg!(windows) { + "python.exe" + } else { + "python" + }; + if env.executable.file_name().unwrap().to_ascii_lowercase() != bin { + return false; + } + self.environments.insert( + env.executable.to_str().unwrap().to_string(), + PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::System, + sys_prefix_path: None, + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }, + ); + true + } + + fn gather(&mut self) -> Option<()> { + let paths = self.environment.get_env_var("PATH".to_string())?; let bin = if cfg!(windows) { "python.exe" } else { @@ -50,13 +75,18 @@ fn report_python_on_path( env::split_paths(&paths) .map(|p| p.join(bin)) .filter(|p| p.exists()) - .for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap())); + .for_each(|full_path| { + let version = utils::get_version(&full_path); + let env_path = get_env_path(&full_path); + self.track_if_compatible(&PythonEnv::new(full_path, env_path, version)); + }); + + Some(()) } -} -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - report_python_on_path(dispatcher, environment); + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 68557dbd46ce..23f86f811ba0 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -2,10 +2,17 @@ // Licensed under the MIT License. use crate::known; +use crate::known::Environment; +use crate::locator::Locator; use crate::messaging; +use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; +use crate::messaging::MessageDispatcher; +use crate::messaging::PythonEnvironment; use crate::utils::find_python_binary_path; +use crate::utils::PythonEnv; use regex::Regex; +use std::collections::HashMap; use std::env; use std::path::{Path, PathBuf}; @@ -129,7 +136,7 @@ fn get_conda_bin_names() -> Vec<&'static str> { } /// Find the conda binary on the PATH environment variable -fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option { +fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { let paths = environment.get_env_var("PATH".to_string())?; for path in env::split_paths(&paths) { for bin in get_conda_bin_names() { @@ -148,7 +155,7 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option Vec { +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); let all_user_profile = environment @@ -170,7 +177,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec Vec { +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { let mut known_paths = vec![ PathBuf::from("/opt/anaconda3/bin"), PathBuf::from("/opt/miniconda3/bin"), @@ -192,7 +199,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec Option { +fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option { let conda_bin_names = get_conda_bin_names(); let known_locations = get_known_conda_locations(environment); for location in known_locations { @@ -209,7 +216,7 @@ fn find_conda_binary_in_known_locations(environment: &impl known::Environment) - } /// Find the conda binary on the system -pub fn find_conda_binary(environment: &impl known::Environment) -> Option { +pub fn find_conda_binary(environment: &dyn known::Environment) -> Option { let conda_binary_on_path = find_conda_binary_on_path(environment); match conda_binary_on_path { Some(conda_binary_on_path) => Some(conda_binary_on_path), @@ -232,7 +239,7 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { get_version_from_meta_json(&conda_python_json_path) } -fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec { +fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { let mut envs = vec![]; let home = environment.get_user_home(); match home { @@ -255,7 +262,7 @@ fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> fn get_known_env_locations( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut paths = vec![]; let home = environment.get_user_home(); @@ -290,7 +297,7 @@ fn get_known_env_locations( fn get_conda_envs_from_known_env_locations( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut envs = vec![]; for location in get_known_env_locations(conda_bin, environment) { @@ -333,7 +340,7 @@ struct CondaEnv { fn get_distinct_conda_envs( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut envs = get_conda_envs_from_environment_txt(environment); let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); @@ -377,52 +384,91 @@ fn get_distinct_conda_envs( conda_envs } -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - let conda_binary = find_conda_binary(environment); - match conda_binary { - Some(conda_binary) => { - let env_manager = messaging::EnvManager::new( - conda_binary.clone(), - get_conda_version(&conda_binary), - EnvManagerType::Conda, +pub struct Conda<'a> { + pub environments: HashMap, + pub manager: Option, + pub environment: &'a dyn Environment, +} + +impl Conda<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> Conda { + Conda { + environments: HashMap::new(), + environment, + manager: None, + } + } +} + +impl Locator for Conda<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + // We will find everything in gather + false + } + + fn gather(&mut self) -> Option<()> { + let conda_binary = find_conda_binary(self.environment)?; + let manager = EnvManager::new( + conda_binary.clone(), + get_conda_version(&conda_binary), + EnvManagerType::Conda, + ); + self.manager = Some(manager.clone()); + + let envs = get_distinct_conda_envs(&conda_binary, self.environment); + for env in envs { + let executable = find_python_binary_path(Path::new(&env.path)); + let env = messaging::PythonEnvironment::new( + Some(env.name.to_string()), + executable.clone(), + messaging::PythonEnvironmentCategory::Conda, + get_conda_python_version(&env.path), + Some(env.path.clone()), + Some(env.path.clone()), + Some(manager.clone()), + if env.named { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-n".to_string(), + env.name.to_string(), + "python".to_string(), + ]) + } else { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-p".to_string(), + env.path.to_string_lossy().to_string(), + "python".to_string(), + ]) + }, ); - dispatcher.report_environment_manager(env_manager.clone()); - - let envs = get_distinct_conda_envs(&conda_binary, environment); - for env in envs { - let executable = find_python_binary_path(Path::new(&env.path)); - let params = messaging::PythonEnvironment::new( - Some(env.name.to_string()), - executable, - messaging::PythonEnvironmentCategory::Conda, - get_conda_python_version(&env.path), - Some(env.path.clone()), - Some(env.path.clone()), - Some(env_manager.clone()), - if env.named { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-n".to_string(), - env.name.to_string(), - "python".to_string(), - ]) - } else { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-p".to_string(), - env.path.to_string_lossy().to_string(), - "python".to_string(), - ]) - }, - ); - dispatcher.report_environment(params); + + if let Some(exe) = executable { + self.environments + .insert(exe.to_str().unwrap_or_default().to_string(), env); + } else if let Some(env_path) = env.env_path.clone() { + self.environments + .insert(env_path.to_str().unwrap().to_string(), env); } } - None => (), + + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + if let Some(manager) = &self.manager { + reporter.report_environment_manager(manager.clone()); + } + + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } } } diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index 67b8cc2c6ea8..8004775e3ee2 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -1,18 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::pipenv; -use crate::venv; -use crate::virtualenv; -use crate::virtualenvwrapper; use crate::{ known, - messaging::MessageDispatcher, utils::{find_python_binary_path, get_version, PythonEnv}, }; use std::{fs, path::PathBuf}; -fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { +pub fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { let mut venv_dirs: Vec = vec![]; if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) { @@ -48,7 +43,7 @@ fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec Vec { +pub fn list_global_virtual_envs(environment: &impl known::Environment) -> Vec { let mut python_envs: Vec = vec![]; for root_dir in get_global_virtualenv_dirs(environment).iter() { if let Ok(dirs) = fs::read_dir(root_dir) { @@ -59,11 +54,11 @@ pub fn list_global_virtualenvs(environment: &impl known::Environment) -> Vec Vec Option<()> { - for env in list_global_virtualenvs(environment).iter() { - if pipenv::find_and_report(&env, dispatcher).is_some() { - continue; - } - if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { - continue; - } - if venv::find_and_report(&env, dispatcher).is_some() { - continue; - } - if virtualenv::find_and_report(&env, dispatcher).is_some() { - continue; - } - } - - None -} diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index f70c68fb6411..b565dbaf27ba 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -1,15 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + fs::DirEntry, + io::Error, + path::PathBuf, +}; -use crate::{known::Environment, messaging::MessageDispatcher}; +use crate::{ + known::Environment, + locator::Locator, + messaging::{MessageDispatcher, PythonEnvironment}, + utils::PythonEnv, +}; use regex::Regex; fn is_symlinked_python_executable(path: Result) -> Option { let path = path.ok()?.path(); let name = path.file_name()?.to_string_lossy(); - if !name.starts_with("python") || name.ends_with("-config") { + if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") { return None; } let metadata = std::fs::symlink_metadata(&path).ok()?; @@ -19,45 +29,72 @@ fn is_symlinked_python_executable(path: Result) -> Option Option<()> { - // https://docs.brew.sh/Homebrew-and-Python#brewed-python-modules - // Executable Python scripts will be in $(brew --prefix)/bin. - // They are always symlinks, hence we will only look for symlinks. - - let homebrew_prefix = environment.get_env_var("HOMEBREW_PREFIX".to_string())?; - let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); - let mut reported: HashSet = HashSet::new(); - let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); - for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { - if let Some(exe) = is_symlinked_python_executable(file) { - let python_version = exe.to_string_lossy().to_string(); - let version = match python_regex.captures(&python_version) { - Some(captures) => match captures.get(1) { - Some(version) => Some(version.as_str().to_string()), +pub struct Homebrew<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} + +impl Homebrew<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> Homebrew { + Homebrew { + environments: HashMap::new(), + environment, + } + } +} + +impl Locator for Homebrew<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + // We will find everything in gather + false + } + + fn gather(&mut self) -> Option<()> { + let homebrew_prefix = self + .environment + .get_env_var("HOMEBREW_PREFIX".to_string())?; + let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); + let mut reported: HashSet = HashSet::new(); + let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); + for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { + if let Some(exe) = is_symlinked_python_executable(file) { + let python_version = exe.to_string_lossy().to_string(); + let version = match python_regex.captures(&python_version) { + Some(captures) => match captures.get(1) { + Some(version) => Some(version.as_str().to_string()), + None => None, + }, None => None, - }, - None => None, - }; - if reported.contains(&exe.to_string_lossy().to_string()) { - continue; + }; + if reported.contains(&exe.to_string_lossy().to_string()) { + continue; + } + reported.insert(exe.to_string_lossy().to_string()); + let env = crate::messaging::PythonEnvironment::new( + None, + Some(exe.clone()), + crate::messaging::PythonEnvironmentCategory::Homebrew, + version, + None, + None, + None, + Some(vec![exe.to_string_lossy().to_string()]), + ); + self.environments + .insert(exe.to_string_lossy().to_string(), env); } - reported.insert(exe.to_string_lossy().to_string()); - let env = crate::messaging::PythonEnvironment::new( - None, - Some(exe.clone()), - crate::messaging::PythonEnvironmentCategory::Homebrew, - version, - None, - None, - None, - Some(vec![exe.to_string_lossy().to_string()]), - ); - dispatcher.report_environment(env); } + Some(()) } - None + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index b0cccef843c5..251e977af00c 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -13,3 +13,4 @@ pub mod virtualenvwrapper; pub mod pipenv; pub mod virtualenv; pub mod venv; +pub mod locator; diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs new file mode 100644 index 000000000000..9354207fa14a --- /dev/null +++ b/native_locator/src/locator.rs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{messaging::MessageDispatcher, utils::PythonEnv}; +use std::path::PathBuf; + +pub trait Locator { + /** + * Whether the given Python executable is known to this locator. + */ + fn is_known(&self, python_executable: &PathBuf) -> bool; + /** + * Track the given Python executable if it is compatible with the environments supported by this locator. + * This way, when report is called, the environment passed here will be reported as a known environment by this locator. + * Returns true if the environment was tracked, false otherwise. + */ + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool; + /** + * Finds all environments managed by this locator. + */ + fn gather(&mut self) -> Option<()>; + /** + * Report all of the tracked environments and managers. + */ + fn report(&self, reporter: &mut dyn MessageDispatcher); +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 0332bbaf2f63..a01bd8bd57fd 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,25 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::time::SystemTime; - +use global_virtualenvs::list_global_virtual_envs; use known::EnvironmentApi; +use locator::Locator; use messaging::{create_dispatcher, MessageDispatcher}; +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_python; -mod venv; fn main() { let mut dispatcher = create_dispatcher(); @@ -28,22 +30,79 @@ fn main() { dispatcher.log_info("Starting Native Locator"); let now = SystemTime::now(); - global_virtualenvs::find_and_report(&mut dispatcher, &environment); - - // Finds python on PATH - common_python::find_and_report(&mut dispatcher, &environment); + let mut virtualenv_locator = virtualenv::VirtualEnv::new(); + let mut venv_locator = venv::Venv::new(); + let mut virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); + let mut pipenv_locator = pipenv::PipEnv::new(); + let mut path_locator = common_python::PythonOnPath::with(&environment); + let mut pyenv_locator = pyenv::PyEnv::with(&environment); + #[cfg(unix)] + let mut homebrew_locator = homebrew::Homebrew::with(&environment); + #[cfg(windows)] + let mut windows_locator = windows_python::WindowsPython::with(&environment); + let mut conda_locator = conda::Conda::with(&environment); - // Finds conda binary and conda environments - conda::find_and_report(&mut dispatcher, &environment); + // These environments take predence over all others. + // As they are very specific and guaranteed to be specific type. + pyenv_locator.gather(); + #[cfg(unix)] + homebrew_locator.gather(); + conda_locator.gather(); // Finds Windows Store, Known Path, and Registry pythons #[cfg(windows)] - windows_python::find_and_report(&mut dispatcher, &environment); + windows_locator.gather(); - pyenv::find_and_report(&mut dispatcher, &environment); + for env in list_global_virtual_envs(&environment).iter() { + if pyenv_locator.is_known(&env.executable) { + continue; + } + #[cfg(windows)] + if windows_locator.is_known(&env.executable) { + continue; + } + if conda_locator.is_known(&env.executable) { + continue; + } + #[cfg(unix)] + if homebrew_locator.is_known(&env.executable) { + continue; + } - #[cfg(unix)] - homebrew::find_and_report(&mut dispatcher, &environment); + if pipenv_locator.track_if_compatible(&env) { + continue; + } + if virtualenvwrapper_locator.track_if_compatible(&env) { + continue; + } + if venv_locator.track_if_compatible(&env) { + continue; + } + if virtualenv_locator.track_if_compatible(&env) { + continue; + } + } + + // Finds python on PATH + // This is the last place to look for unknown python environments. + path_locator.gather(); + + let all_locators: [&dyn Locator; 8] = [ + &virtualenv_locator, + &venv_locator, + &virtualenvwrapper_locator, + &pipenv_locator, + &path_locator, + &pyenv_locator, + #[cfg(unix)] + &homebrew_locator, + #[cfg(windows)] + &windows_locator, + &conda_locator, + ]; + all_locators + .iter() + .for_each(|locator| locator.report(&mut dispatcher)); match now.elapsed() { Ok(elapsed) => { diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 01f13c34984f..044c1af82b5c 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::path::PathBuf; - use crate::logging::{LogLevel, LogMessage}; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; pub trait MessageDispatcher { fn report_environment_manager(&mut self, env: EnvManager) -> (); @@ -69,7 +68,7 @@ impl EnvManagerMessage { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub enum PythonEnvironmentCategory { System, @@ -84,7 +83,7 @@ pub enum PythonEnvironmentCategory { VirtualEnv, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { pub name: Option, diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 737c7c776c18..1e266732536c 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,13 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::locator::Locator; use crate::messaging::{MessageDispatcher, PythonEnvironment}; use crate::utils::PythonEnv; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; fn get_pipenv_project(env: &PythonEnv) -> Option { - let project_file = env.path.join(".project"); + let project_file = env.path.clone()?.join(".project"); if project_file.exists() { if let Ok(contents) = fs::read_to_string(project_file) { let project_folder = PathBuf::from(contents.trim().to_string()); @@ -20,20 +22,56 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { None } -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if let Some(project_path) = get_pipenv_project(env) { - let env = PythonEnvironment::new_pipenv( - Some(env.executable.clone()), - env.version.clone(), - Some(env.path.clone()), - Some(env.path.clone()), - None, - project_path, - ); - - dispatcher.report_environment(env); - return Some(()); +pub struct PipEnv { + pub environments: HashMap, +} + +impl PipEnv { + pub fn new() -> PipEnv { + PipEnv { + environments: HashMap::new(), + } + } +} + +impl Locator for PipEnv { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) } - None + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if let Some(project_path) = get_pipenv_project(env) { + let env = PythonEnvironment::new_pipenv( + Some(env.executable.clone()), + env.version.clone(), + env.path.clone(), + env.path.clone(), + None, + project_path, + ); + + self.environments.insert( + env.python_executable_path + .clone() + .unwrap() + .to_str() + .unwrap() + .to_string(), + env, + ); + return true; + } + false + } + + fn gather(&mut self) -> Option<()> { + None + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 814be30af441..a5ead7d44ad2 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -2,29 +2,35 @@ // Licensed under the MIT License. use regex::Regex; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; use crate::known; +use crate::known::Environment; +use crate::locator::Locator; use crate::messaging; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; +use crate::messaging::MessageDispatcher; +use crate::messaging::PythonEnvironment; use crate::utils::find_and_parse_pyvenv_cfg; use crate::utils::find_python_binary_path; +use crate::utils::PythonEnv; #[cfg(windows)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option { let home = environment.get_user_home()?; Some(PathBuf::from(home).join(".pyenv").join("pyenv-win")) } #[cfg(unix)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option { let home = environment.get_user_home()?; Some(PathBuf::from(home).join(".pyenv")) } -fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { +fn get_binary_from_known_paths(environment: &dyn known::Environment) -> Option { for known_path in environment.get_know_global_search_locations() { let bin = known_path.join("pyenv"); if bin.exists() { @@ -34,7 +40,7 @@ fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option< None } -fn get_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_pyenv_dir(environment: &dyn known::Environment) -> Option { // 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. @@ -51,7 +57,7 @@ fn get_pyenv_dir(environment: &impl known::Environment) -> Option { } } -fn get_pyenv_binary(environment: &impl known::Environment) -> Option { +fn get_pyenv_binary(environment: &dyn known::Environment) -> Option { let dir = get_pyenv_dir(environment)?; let exe = PathBuf::from(dir).join("bin").join("pyenv"); if fs::metadata(&exe).is_ok() { @@ -93,72 +99,57 @@ fn get_pyenv_version(folder_name: &String) -> Option { } } -fn report_if_pure_python_environment( +fn get_pure_python_environment( executable: &PathBuf, path: &PathBuf, - manager: Option, - dispatcher: &mut impl messaging::MessageDispatcher, -) -> Option<()> { + manager: &Option, +) -> Option { let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?; - dispatcher.report_environment(messaging::PythonEnvironment::new( + Some(messaging::PythonEnvironment::new( None, Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, Some(version), Some(path.clone()), Some(path.clone()), - manager, + manager.clone(), Some(vec![executable .clone() .into_os_string() .into_string() .unwrap()]), - )); - - Some(()) + )) } -fn report_if_virtual_env_environment( +fn get_virtual_env_environment( executable: &PathBuf, path: &PathBuf, - manager: Option, - dispatcher: &mut impl messaging::MessageDispatcher, -) -> Option<()> { + manager: &Option, +) -> Option { let pyenv_cfg = find_and_parse_pyvenv_cfg(executable)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); - dispatcher.report_environment(messaging::PythonEnvironment::new( + Some(messaging::PythonEnvironment::new( Some(folder_name), Some(executable.clone()), messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), Some(path.clone()), Some(path.clone()), - manager, + manager.clone(), Some(vec![executable .clone() .into_os_string() .into_string() .unwrap()]), - )); - - Some(()) + )) } -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) -> Option<()> { +pub fn list_pyenv_environments( + manager: &Option, + environment: &dyn known::Environment, +) -> Option> { let pyenv_dir = get_pyenv_dir(environment)?; - - let manager = match get_pyenv_binary(environment) { - Some(pyenv_binary) => { - let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); - dispatcher.report_environment_manager(manager.clone()); - Some(manager) - } - None => None, - }; - + let mut envs: Vec = vec![]; let versions_dir = PathBuf::from(&pyenv_dir) .join("versions") .into_os_string() @@ -172,20 +163,78 @@ pub fn find_and_report( continue; } if let Some(executable) = find_python_binary_path(&path) { - if report_if_pure_python_environment( - &executable, - &path, - manager.clone(), - dispatcher, - ) - .is_some() - { - continue; + match get_pure_python_environment(&executable, &path, manager) { + Some(env) => envs.push(env), + None => match get_virtual_env_environment(&executable, &path, manager) { + Some(env) => envs.push(env), + None => (), + }, } - report_if_virtual_env_environment(&executable, &path, manager.clone(), dispatcher); } } } - None + Some(envs) +} + +pub struct PyEnv<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, + pub manager: Option, +} + +impl PyEnv<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> PyEnv { + PyEnv { + environments: HashMap::new(), + environment, + manager: None, + } + } +} + +impl Locator for PyEnv<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + // We will find everything in gather + false + } + + fn gather(&mut self) -> Option<()> { + let manager = match get_pyenv_binary(self.environment) { + Some(pyenv_binary) => Some(messaging::EnvManager::new( + pyenv_binary, + None, + EnvManagerType::Pyenv, + )), + None => None, + }; + self.manager = manager.clone(); + + for env in list_pyenv_environments(&manager, self.environment)? { + self.environments.insert( + env.python_executable_path + .as_ref() + .unwrap() + .to_str() + .unwrap() + .to_string(), + env, + ); + } + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + if let Some(manager) = &self.manager { + reporter.report_environment_manager(manager.clone()); + } + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 88122085a9d8..26aa68566019 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -10,11 +10,28 @@ use std::{ #[derive(Debug)] pub struct PythonEnv { - pub path: PathBuf, pub executable: PathBuf, + pub path: Option, pub version: Option, } +impl PythonEnv { + pub fn new(executable: PathBuf, path: Option, version: Option) -> Self { + Self { + executable, + path, + version, + } + } + pub fn from(executable: PathBuf) -> Self { + Self { + executable, + path: None, + version: None, + } + } +} + #[derive(Debug)] pub struct PyEnvCfg { pub version: String, @@ -79,8 +96,8 @@ pub fn find_and_parse_pyvenv_cfg(python_executable: &PathBuf) -> Option Option { - if let Some(parent_folder) = PathBuf::from(python_executable).parent() { +pub fn get_version(python_executable: &PathBuf) -> Option { + 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); } @@ -109,3 +126,24 @@ pub fn find_python_binary_path(env_path: &Path) -> Option { let paths = vec![path_1, path_2, path_3]; paths.into_iter().find(|path| path.exists()) } + +pub fn list_python_environments(path: &PathBuf) -> Option> { + let mut python_envs: Vec = 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) +} diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 9fa16031af8a..2b76c7dcf751 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -1,35 +1,77 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use std::{collections::HashMap, path::PathBuf}; + use crate::{ - messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}, + locator::Locator, + messaging::{MessageDispatcher, 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 { + pub environments: HashMap, +} + +impl Venv { + pub fn new() -> Venv { + Venv { + environments: HashMap::new(), + } + } +} + +impl Locator for Venv { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if is_venv(&env) { + self.environments.insert( + env.executable.to_str().unwrap().to_string(), + 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, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }, + ); + return true; + } + false + } + + fn gather(&mut self) -> Option<()> { + // There are no common global locations for virtual environments. + // We expect the user of this class to call `is_compatible` + None + } -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if is_venv(env) { - let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::Venv, - version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; - - dispatcher.report_environment(env); - - return Some(()); + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } } - None } diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 65da331abaa2..40972b80a14d 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -1,12 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}; +use crate::locator::Locator; +use crate::messaging::{MessageDispatcher, PythonEnvironment}; use crate::utils::PythonEnv; +use std::collections::HashMap; use std::path::PathBuf; pub fn is_virtualenv(env: &PythonEnv) -> bool { - if let Some(file_path) = PathBuf::from(env.executable.clone()).parent() { + 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 @@ -42,26 +47,62 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { false } -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if is_virtualenv(env) { - let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::VirtualEnv, - version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; +pub struct VirtualEnv { + pub environments: HashMap, +} + +impl VirtualEnv { + pub fn new() -> VirtualEnv { + VirtualEnv { + environments: HashMap::new(), + } + } +} - dispatcher.report_environment(env); +impl Locator for VirtualEnv { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } - return Some(()); + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if is_virtualenv(env) { + self.environments.insert( + env.executable.to_str().unwrap().to_string(), + 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, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }, + ); + return true; + } + false + } + + fn gather(&mut self) -> Option<()> { + // There are no common global locations for virtual environments. + // We expect the user of this class to call `is_compatible` + None + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } } - None } diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index d0cc5d4551e1..e70cfc82af75 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory}; +use crate::locator::Locator; +use crate::messaging::PythonEnvironment; +use crate::utils::list_python_environments; use crate::virtualenv; use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; +use std::collections::HashMap; use std::path::PathBuf; #[cfg(windows)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option { // 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. @@ -25,7 +28,7 @@ fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option< } #[cfg(unix)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option { if let Some(home) = environment.get_user_home() { let home = PathBuf::from(home).join("virtualenvs"); if home.exists() { @@ -35,7 +38,7 @@ fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option< None } -fn get_work_on_home_path(environment: &impl Environment) -> Option { +fn get_work_on_home_path(environment: &dyn Environment) -> Option { // 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()) { @@ -48,7 +51,10 @@ fn get_work_on_home_path(environment: &impl Environment) -> Option { get_default_virtualenvwrapper_path(environment) } -pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> bool { +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 @@ -61,29 +67,68 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> false } -pub fn find_and_report( - env: &PythonEnv, - dispatcher: &mut impl MessageDispatcher, - environment: &impl Environment, -) -> Option<()> { - if is_virtualenvwrapper(env, environment) { - let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::VirtualEnvWrapper, - version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; +pub struct VirtualEnvWrapper<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} - dispatcher.report_environment(env); - return Some(()); +impl VirtualEnvWrapper<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> VirtualEnvWrapper { + VirtualEnvWrapper { + environments: HashMap::new(), + environment, + } + } +} + +impl Locator for VirtualEnvWrapper<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if is_virtualenvwrapper(env, self.environment) { + self.environments.insert( + env.executable.to_str().unwrap().to_string(), + 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::Venv, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }, + ); + return true; + } + false + } + + fn gather(&mut self) -> Option<()> { + let work_on_home = get_work_on_home_path(self.environment)?; + let envs = list_python_environments(&work_on_home)?; + envs.iter().for_each(|env| { + self.track_if_compatible(env); + }); + + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } } - None } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 8199a503f924..5e34ba127947 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -2,72 +2,107 @@ // Licensed under the MIT License. use crate::known; -use crate::messaging; -use crate::utils; +use crate::known::Environment; +use crate::locator::Locator; +use crate::messaging::MessageDispatcher; +use crate::messaging::PythonEnvironment; +use crate::utils::PythonEnv; +use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { - let version = utils::get_version(path); - dispatcher.report_environment(messaging::PythonEnvironment::new( - None, - Some(PathBuf::from(path)), - messaging::PythonEnvironmentCategory::WindowsStore, - version, - None, - None, - None, - None, - )); +fn is_windows_python_executable(path: &PathBuf) -> bool { + let name = path.file_name().unwrap().to_string_lossy().to_lowercase(); + name.starts_with("python3.") && name.ends_with(".exe") } - -fn report_windows_store_python( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - let home = environment.get_user_home(); - match home { - Some(home) => { - let apps_path = Path::new(&home) - .join("AppData") - .join("Local") - .join("Microsoft") - .join("WindowsApps"); - let files = std::fs::read_dir(apps_path); - match files { - Ok(files) => { - for file in files { - match file { - Ok(file) => { - let path = file.path(); - match path.file_name() { - Some(name) => { - let name = name.to_string_lossy().to_lowercase(); - if name.starts_with("python3.") && name.ends_with(".exe") { - report_path_python(&path.to_string_lossy(), dispatcher); - } - } - None => {} - } - } - Err(_) => {} - } - } +fn list_windows_store_python_executables( + environment: &dyn known::Environment, +) -> Option> { + let mut python_envs: Vec = vec![]; + let home = environment.get_user_home()?; + let apps_path = Path::new(&home) + .join("AppData") + .join("Local") + .join("Microsoft") + .join("WindowsApps"); + for file in std::fs::read_dir(apps_path).ok()? { + match file { + Ok(file) => { + let path = file.path(); + if path.is_file() && is_windows_python_executable(&path) { + python_envs.push(path); } - Err(_) => {} } + Err(_) => {} + } + } + + Some(python_envs) +} + +fn list_registry_pythons() -> Option> { + None +} + +pub struct WindowsPython<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} + +impl WindowsPython<'_> { + #[allow(dead_code)] + pub fn with<'a>(environment: &'a impl Environment) -> WindowsPython { + WindowsPython { + environments: HashMap::new(), + environment, } - None => {} } } -fn report_registry_pythons() {} +impl Locator for WindowsPython<'_> { + fn is_known(&self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if is_windows_python_executable(&env.executable) { + self.environments.insert( + env.executable.to_str().unwrap().to_string(), + PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: None, + category: crate::messaging::PythonEnvironmentCategory::WindowsStore, + sys_prefix_path: None, + env_path: None, + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }, + ); + return true; + } + false + } + + fn gather(&mut self) -> Option<()> { + if let Some(envs) = list_windows_store_python_executables(self.environment) { + envs.iter().for_each(|env| { + self.track_if_compatible(&&PythonEnv::from(env.clone())); + }); + } + if let Some(envs) = list_registry_pythons() { + envs.iter().for_each(|env| { + self.track_if_compatible(&&PythonEnv::from(env.clone())); + }); + } + Some(()) + } -#[allow(dead_code)] -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - report_windows_store_python(dispatcher, environment); - report_registry_pythons(); + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 9ee1c03c201e..00050fec96f4 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -10,7 +10,7 @@ fn find_python_in_path_this() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::{common_python, messaging::PythonEnvironment}; + use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment}; use serde_json::json; use std::collections::HashMap; @@ -27,7 +27,9 @@ fn find_python_in_path_this() { Vec::new(), ); - common_python::find_and_report(&mut dispatcher, &known); + let mut locator = common_python::PythonOnPath::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 1); let env = PythonEnvironment { @@ -39,7 +41,7 @@ fn find_python_in_path_this() { version: None, python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]), env_path: Some(unix_python.clone()), - sys_prefix_path: Some(unix_python.clone()), + sys_prefix_path: None, }; assert_messages(&[json!(env)], &dispatcher); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 2673ea99cf7b..80798fdbfe91 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn does_not_find_any_conda_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; - use python_finder::conda; + use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); @@ -17,7 +17,9 @@ fn does_not_find_any_conda_envs() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 0); } @@ -29,10 +31,8 @@ fn find_conda_exe_and_empty_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::{ - conda, - messaging::{EnvManager, EnvManagerType}, - }; + use python_finder::messaging::{EnvManager, EnvManagerType}; + use python_finder::{conda, locator::Locator}; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); @@ -47,7 +47,9 @@ fn find_conda_exe_and_empty_envs() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let expected_conda_manager = EnvManager { @@ -64,8 +66,8 @@ fn finds_two_conda_envs_from_txt() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::conda; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; + use python_finder::{conda, locator::Locator}; use serde_json::json; use std::collections::HashMap; use std::fs; @@ -92,7 +94,9 @@ fn finds_two_conda_envs_from_txt() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let conda_1_exe = join_test_paths(&[conda_1.clone().to_str().unwrap(), "python"]); diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 1b9e53c4ebbf..6702d258d958 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn does_not_find_any_pyenv_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; - use python_finder::pyenv; + use python_finder::{locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); @@ -17,7 +17,9 @@ fn does_not_find_any_pyenv_envs() { Vec::new(), ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 0); } @@ -29,6 +31,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; + use python_finder::locator::Locator; use python_finder::pyenv; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; @@ -43,7 +46,9 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { vec![PathBuf::from(homebrew_bin)], ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); @@ -57,6 +62,7 @@ fn find_pyenv_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; + use python_finder::locator::Locator; use python_finder::{ messaging::{EnvManager, EnvManagerType, PythonEnvironment}, pyenv, @@ -74,7 +80,9 @@ fn find_pyenv_envs() { vec![PathBuf::from(homebrew_bin)], ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 6); let expected_manager = EnvManager { From 97ff7d52647dc75fd5f83cab0a71233b562e0ea1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 12:39:01 +1000 Subject: [PATCH 035/106] Use logger formatter to send log msgs via json rpc (#23405) --- native_locator/Cargo.toml | 2 ++ native_locator/src/main.rs | 13 ++++++----- native_locator/src/messaging.rs | 40 +++++++++++++++++++-------------- native_locator/tests/common.rs | 4 ---- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml index 4f9c8ca81c78..4814adf1044c 100644 --- a/native_locator/Cargo.toml +++ b/native_locator/Cargo.toml @@ -11,6 +11,8 @@ 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.11.3" [lib] doctest = false diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index a01bd8bd57fd..faa1108f8b05 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -4,9 +4,12 @@ use global_virtualenvs::list_global_virtual_envs; use known::EnvironmentApi; use locator::Locator; +use log::LevelFilter; use messaging::{create_dispatcher, MessageDispatcher}; use std::time::SystemTime; +use crate::messaging::initialize_logger; + mod common_python; mod conda; mod global_virtualenvs; @@ -26,8 +29,9 @@ mod windows_python; fn main() { let mut dispatcher = create_dispatcher(); let environment = EnvironmentApi {}; + initialize_logger(LevelFilter::Debug); - dispatcher.log_info("Starting Native Locator"); + log::info!("Starting Native Locator"); let now = SystemTime::now(); let mut virtualenv_locator = virtualenv::VirtualEnv::new(); @@ -106,13 +110,10 @@ fn main() { match now.elapsed() { Ok(elapsed) => { - dispatcher.log_info(&format!( - "Native Locator took {} milliseconds.", - elapsed.as_millis() - )); + log::info!("Native Locator took {} milliseconds.", elapsed.as_millis()); } Err(e) => { - dispatcher.log_error(&format!("Error getting elapsed time: {:?}", e)); + log::error!("Error getting elapsed time: {:?}", e); } } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 044c1af82b5c..7b782bca1b11 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -2,6 +2,8 @@ // Licensed under the MIT License. use crate::logging::{LogLevel, LogMessage}; +use env_logger::Builder; +use log::LevelFilter; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -9,10 +11,6 @@ pub trait MessageDispatcher { fn report_environment_manager(&mut self, env: EnvManager) -> (); fn report_environment(&mut self, env: PythonEnvironment) -> (); fn exit(&mut self) -> (); - fn log_debug(&mut self, message: &str) -> (); - fn log_info(&mut self, message: &str) -> (); - fn log_warning(&mut self, message: &str) -> (); - fn log_error(&mut self, message: &str) -> (); } #[derive(Serialize, Deserialize, Copy, Clone)] @@ -185,7 +183,7 @@ impl ExitMessage { } pub struct JsonRpcDispatcher {} -fn send_message(message: T) -> () { +pub fn send_message(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{}", @@ -193,6 +191,26 @@ fn send_message(message: T) -> () { 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 MessageDispatcher for JsonRpcDispatcher { fn report_environment_manager(&mut self, env: EnvManager) -> () { send_message(EnvManagerMessage::new(env)); @@ -203,18 +221,6 @@ impl MessageDispatcher for JsonRpcDispatcher { fn exit(&mut self) -> () { send_message(ExitMessage::new()); } - fn log_debug(&mut self, message: &str) -> () { - send_message(LogMessage::new(message.to_string(), LogLevel::Debug)); - } - fn log_error(&mut self, message: &str) -> () { - send_message(LogMessage::new(message.to_string(), LogLevel::Error)); - } - fn log_info(&mut self, message: &str) -> () { - send_message(LogMessage::new(message.to_string(), LogLevel::Info)); - } - fn log_warning(&mut self, message: &str) -> () { - send_message(LogMessage::new(message.to_string(), LogLevel::Warning)); - } } pub fn create_dispatcher() -> JsonRpcDispatcher { diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 073c5c533124..7752bc4eb468 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -43,10 +43,6 @@ pub fn create_test_dispatcher() -> TestDispatcher { fn exit(&mut self) -> () { // } - fn log_debug(&mut self, _message: &str) -> () {} - fn log_error(&mut self, _message: &str) -> () {} - fn log_info(&mut self, _message: &str) -> () {} - fn log_warning(&mut self, _message: &str) -> () {} } impl TestMessages for TestDispatcher { fn get_messages(&self) -> Vec { From 424ca3ff0c14ec53b41eca31e40869083dfd67d9 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 16:53:16 +1000 Subject: [PATCH 036/106] Remove duplicate environment finder event handlers (#23406) --- .../base/locators/lowLevel/nativeLocator.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 9ba186f95b35..95bf67262f53 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Event, EventEmitter } from 'vscode'; +import { Disposable, Event, EventEmitter } from 'vscode'; import { IDisposable } from '../../../../common/types'; import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; @@ -16,6 +16,7 @@ import { NativeGlobalPythonFinder, createNativeGlobalPythonFinder, } from '../common/nativePythonFinder'; +import { disposeAll } from '../../../../common/utils/resourceLifecycle'; function categoryToKind(category: string): PythonEnvKind { switch (category.toLowerCase()) { @@ -97,7 +98,11 @@ export class NativeLocator implements ILocator, IDisposable { public iterEnvs(): IPythonEnvsIterator { const promise = this.finder.startSearch(); const envs: BasicEnvInfo[] = []; - this.disposables.push( + const disposables: IDisposable[] = []; + const disposable = new Disposable(() => disposeAll(disposables)); + this.disposables.push(disposable); + promise.finally(() => disposable.dispose()); + disposables.push( this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { envs.push({ kind: categoryToKind(data.category), From 3e7bf500138211970c6cdc13351113978c065c35 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 16:55:05 +1000 Subject: [PATCH 037/106] Remove duplicates from reported envs & refactor locators (#23407) --- native_locator/src/common_python.rs | 62 +++++------ native_locator/src/conda.rs | 40 ++----- native_locator/src/homebrew.rs | 44 +++----- native_locator/src/locator.rs | 33 +++--- native_locator/src/main.rs | 115 +++++++++------------ native_locator/src/messaging.rs | 65 ++++++++++-- native_locator/src/pipenv.rs | 59 +++-------- native_locator/src/pyenv.rs | 69 ++++--------- native_locator/src/venv.rs | 73 +++++-------- native_locator/src/virtualenv.rs | 73 +++++-------- native_locator/src/virtualenvwrapper.rs | 79 ++++++-------- native_locator/src/windows_python.rs | 65 +++++------- native_locator/src/windows_registry.rs | 59 +++++++++++ native_locator/tests/common.rs | 76 +++++++------- native_locator/tests/common_python_test.rs | 18 ++-- native_locator/tests/conda_test.rs | 55 +++++----- native_locator/tests/pyenv_test.rs | 56 ++++++---- 17 files changed, 495 insertions(+), 546 deletions(-) create mode 100644 native_locator/src/windows_registry.rs diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index dc796c164070..ddb3d3bbe1ab 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -2,11 +2,9 @@ // Licensed under the MIT License. use crate::known::Environment; -use crate::locator::Locator; -use crate::messaging::MessageDispatcher; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::{self, PythonEnv}; -use std::collections::HashMap; use std::env; use std::path::PathBuf; @@ -20,73 +18,61 @@ fn get_env_path(python_executable_path: &PathBuf) -> Option { } pub struct PythonOnPath<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl PythonOnPath<'_> { pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath { - PythonOnPath { - environments: HashMap::new(), - environment, - } + PythonOnPath { environment } } } impl Locator for PythonOnPath<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { let bin = if cfg!(windows) { "python.exe" } else { "python" }; if env.executable.file_name().unwrap().to_ascii_lowercase() != bin { - return false; + return None; } - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: None, - python_executable_path: Some(env.executable.clone()), - version: env.version.clone(), - category: crate::messaging::PythonEnvironmentCategory::System, - sys_prefix_path: None, - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - true + Some(PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::System, + sys_prefix_path: None, + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }) } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let paths = self.environment.get_env_var("PATH".to_string())?; let bin = if cfg!(windows) { "python.exe" } else { "python" }; + let mut environments: Vec = vec![]; env::split_paths(&paths) .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); - self.track_if_compatible(&PythonEnv::new(full_path, env_path, version)); + if let Some(env) = self.resolve(&PythonEnv::new(full_path, env_path, version)) { + environments.push(env); + } }); - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 23f86f811ba0..a56c655c06ee 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -4,15 +4,14 @@ 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::MessageDispatcher; use crate::messaging::PythonEnvironment; use crate::utils::find_python_binary_path; use crate::utils::PythonEnv; use regex::Regex; -use std::collections::HashMap; use std::env; use std::path::{Path, PathBuf}; @@ -385,7 +384,6 @@ fn get_distinct_conda_envs( } pub struct Conda<'a> { - pub environments: HashMap, pub manager: Option, pub environment: &'a dyn Environment, } @@ -393,7 +391,6 @@ pub struct Conda<'a> { impl Conda<'_> { pub fn with<'a>(environment: &'a impl Environment) -> Conda { Conda { - environments: HashMap::new(), environment, manager: None, } @@ -401,26 +398,21 @@ impl Conda<'_> { } impl Locator for Conda<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + fn resolve(&self, _env: &PythonEnv) -> Option { // We will find everything in gather - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let conda_binary = find_conda_binary(self.environment)?; let manager = EnvManager::new( conda_binary.clone(), get_conda_version(&conda_binary), EnvManagerType::Conda, ); - self.manager = Some(manager.clone()); let envs = get_distinct_conda_envs(&conda_binary, self.environment); + let mut environments: Vec = vec![]; for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); let env = messaging::PythonEnvironment::new( @@ -450,25 +442,13 @@ impl Locator for Conda<'_> { }, ); - if let Some(exe) = executable { - self.environments - .insert(exe.to_str().unwrap_or_default().to_string(), env); - } else if let Some(env_path) = env.env_path.clone() { - self.environments - .insert(env_path.to_str().unwrap().to_string(), env); - } - } - - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - if let Some(manager) = &self.manager { - reporter.report_environment_manager(manager.clone()); + environments.push(env) } - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + Some(LocatorResult::Managers(vec![manager])) + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index b565dbaf27ba..0830c4f7d532 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -1,20 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{ - collections::{HashMap, HashSet}, - fs::DirEntry, - io::Error, - path::PathBuf, -}; - use crate::{ known::Environment, - locator::Locator, - messaging::{MessageDispatcher, PythonEnvironment}, + locator::{Locator, LocatorResult}, + messaging::PythonEnvironment, utils::PythonEnv, }; use regex::Regex; +use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; fn is_symlinked_python_executable(path: Result) -> Option { let path = path.ok()?.path(); @@ -30,37 +24,28 @@ fn is_symlinked_python_executable(path: Result) -> Option { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl Homebrew<'_> { pub fn with<'a>(environment: &'a impl Environment) -> Homebrew { - Homebrew { - environments: HashMap::new(), - environment, - } + Homebrew { environment } } } impl Locator for Homebrew<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) + fn resolve(&self, _env: &PythonEnv) -> Option { + None } - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { - // We will find everything in gather - false - } - - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let homebrew_prefix = self .environment .get_env_var("HOMEBREW_PREFIX".to_string())?; let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); let mut reported: HashSet = HashSet::new(); let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); + let mut environments: Vec = vec![]; for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { if let Some(exe) = is_symlinked_python_executable(file) { let python_version = exe.to_string_lossy().to_string(); @@ -85,16 +70,13 @@ impl Locator for Homebrew<'_> { None, Some(vec![exe.to_string_lossy().to_string()]), ); - self.environments - .insert(exe.to_string_lossy().to_string(), env); + environments.push(env); } } - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index 9354207fa14a..aa812c11bd37 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -1,26 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{messaging::MessageDispatcher, utils::PythonEnv}; -use std::path::PathBuf; +use crate::{ + messaging::{EnvManager, PythonEnvironment}, + utils::PythonEnv, +}; + +#[derive(Debug)] +pub enum LocatorResult { + Managers(Vec), + Environments(Vec), +} pub trait Locator { /** - * Whether the given Python executable is known to this locator. - */ - fn is_known(&self, python_executable: &PathBuf) -> bool; - /** - * Track the given Python executable if it is compatible with the environments supported by this locator. - * This way, when report is called, the environment passed here will be reported as a known environment by this locator. - * Returns true if the environment was tracked, false otherwise. - */ - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool; - /** - * Finds all environments managed by this 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 gather(&mut self) -> Option<()>; + fn resolve(&self, env: &PythonEnv) -> Option; /** - * Report all of the tracked environments and managers. + * Finds all environments specific to this locator. */ - fn report(&self, reporter: &mut dyn MessageDispatcher); + fn find(&self) -> Option; } diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index faa1108f8b05..9a3ffc27d6df 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::messaging::initialize_logger; use global_virtualenvs::list_global_virtual_envs; use known::EnvironmentApi; use locator::Locator; use log::LevelFilter; -use messaging::{create_dispatcher, MessageDispatcher}; +use messaging::{create_dispatcher, JsonRpcDispatcher, MessageDispatcher}; use std::time::SystemTime; - -use crate::messaging::initialize_logger; +use utils::PythonEnv; mod common_python; mod conda; @@ -27,86 +27,49 @@ mod virtualenvwrapper; mod windows_python; fn main() { - let mut dispatcher = create_dispatcher(); let environment = EnvironmentApi {}; initialize_logger(LevelFilter::Debug); log::info!("Starting Native Locator"); let now = SystemTime::now(); + let mut dispatcher = create_dispatcher(); + + let virtualenv_locator = virtualenv::VirtualEnv::new(); + let venv_locator = venv::Venv::new(); + let virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); + let pipenv_locator = pipenv::PipEnv::new(); + let path_locator = common_python::PythonOnPath::with(&environment); + let pyenv_locator = pyenv::PyEnv::with(&environment); - let mut virtualenv_locator = virtualenv::VirtualEnv::new(); - let mut venv_locator = venv::Venv::new(); - let mut virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); - let mut pipenv_locator = pipenv::PipEnv::new(); - let mut path_locator = common_python::PythonOnPath::with(&environment); - let mut pyenv_locator = pyenv::PyEnv::with(&environment); #[cfg(unix)] - let mut homebrew_locator = homebrew::Homebrew::with(&environment); + let homebrew_locator = homebrew::Homebrew::with(&environment); #[cfg(windows)] - let mut windows_locator = windows_python::WindowsPython::with(&environment); - let mut conda_locator = conda::Conda::with(&environment); + let windows_locator = windows_python::WindowsPython::with(&environment); + let conda_locator = conda::Conda::with(&environment); - // These environments take predence over all others. + // Step 1: These environments take precedence over all others. // As they are very specific and guaranteed to be specific type. - pyenv_locator.gather(); + find_environments(&pyenv_locator, &mut dispatcher); #[cfg(unix)] - homebrew_locator.gather(); - conda_locator.gather(); - - // Finds Windows Store, Known Path, and Registry pythons + find_environments(&homebrew_locator, &mut dispatcher); + find_environments(&conda_locator, &mut dispatcher); #[cfg(windows)] - windows_locator.gather(); + find_environments(&windows_locator, &mut dispatcher); + // Step 2: Search in some global locations. for env in list_global_virtual_envs(&environment).iter() { - if pyenv_locator.is_known(&env.executable) { - continue; - } - #[cfg(windows)] - if windows_locator.is_known(&env.executable) { - continue; - } - if conda_locator.is_known(&env.executable) { - continue; - } - #[cfg(unix)] - if homebrew_locator.is_known(&env.executable) { + if dispatcher.was_environment_reported(&env) { continue; } - if pipenv_locator.track_if_compatible(&env) { - continue; - } - if virtualenvwrapper_locator.track_if_compatible(&env) { - continue; - } - if venv_locator.track_if_compatible(&env) { - continue; - } - if virtualenv_locator.track_if_compatible(&env) { - continue; - } + let _ = resolve_environment(&pipenv_locator, env, &mut dispatcher) + || resolve_environment(&virtualenvwrapper_locator, env, &mut dispatcher) + || resolve_environment(&venv_locator, env, &mut dispatcher) + || resolve_environment(&virtualenv_locator, env, &mut dispatcher); } - // Finds python on PATH - // This is the last place to look for unknown python environments. - path_locator.gather(); - - let all_locators: [&dyn Locator; 8] = [ - &virtualenv_locator, - &venv_locator, - &virtualenvwrapper_locator, - &pipenv_locator, - &path_locator, - &pyenv_locator, - #[cfg(unix)] - &homebrew_locator, - #[cfg(windows)] - &windows_locator, - &conda_locator, - ]; - all_locators - .iter() - .for_each(|locator| locator.report(&mut dispatcher)); + // Step 3: Finally find in the current PATH variable + find_environments(&path_locator, &mut dispatcher); match now.elapsed() { Ok(elapsed) => { @@ -119,3 +82,27 @@ fn main() { dispatcher.exit(); } + +fn resolve_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 find_environments(locator: &dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { + match locator.find()? { + locator::LocatorResult::Environments(envs) => envs + .iter() + .for_each(|e| dispatcher.report_environment(e.clone())), + locator::LocatorResult::Managers(items) => items + .iter() + .for_each(|m| dispatcher.report_environment_manager(m.clone())), + } + Some(()) +} diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 7b782bca1b11..959ec8d36f31 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,13 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::logging::{LogLevel, LogMessage}; +use crate::{ + logging::{LogLevel, LogMessage}, + utils::PythonEnv, +}; use env_logger::Builder; use log::LevelFilter; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::{collections::HashSet, path::PathBuf}; 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) -> (); @@ -15,6 +19,7 @@ pub trait MessageDispatcher { #[derive(Serialize, Deserialize, Copy, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub enum EnvManagerType { Conda, Pyenv, @@ -22,6 +27,7 @@ pub enum EnvManagerType { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct EnvManager { pub executable_path: PathBuf, pub version: Option, @@ -50,6 +56,7 @@ impl Clone for EnvManager { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct EnvManagerMessage { pub jsonrpc: String, pub method: String, @@ -68,6 +75,7 @@ impl EnvManagerMessage { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub enum PythonEnvironmentCategory { System, Homebrew, @@ -83,6 +91,7 @@ pub enum PythonEnvironmentCategory { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct PythonEnvironment { pub name: Option, pub python_executable_path: Option, @@ -148,6 +157,7 @@ impl PythonEnvironment { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct PythonEnvironmentMessage { pub jsonrpc: String, pub method: String, @@ -166,6 +176,7 @@ impl PythonEnvironmentMessage { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct ExitMessage { pub jsonrpc: String, pub method: String, @@ -182,7 +193,10 @@ impl ExitMessage { } } -pub struct JsonRpcDispatcher {} +pub struct JsonRpcDispatcher { + pub reported_managers: HashSet, + pub reported_environments: HashSet, +} pub fn send_message(message: T) -> () { let message = serde_json::to_string(&message).unwrap(); print!( @@ -211,12 +225,34 @@ pub fn initialize_logger(log_level: LevelFilter) { .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) -> () { - send_message(EnvManagerMessage::new(env)); + if let Some(key) = get_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) -> () { - send_message(PythonEnvironmentMessage::new(env)); + if let Some(key) = get_environment_key(&env) { + if !self.reported_environments.contains(&key) { + self.reported_environments.insert(key); + send_message(PythonEnvironmentMessage::new(env.clone())); + } + if let Some(manager) = env.env_manager { + self.report_environment_manager(manager); + } + } } fn exit(&mut self) -> () { send_message(ExitMessage::new()); @@ -224,5 +260,22 @@ impl MessageDispatcher for JsonRpcDispatcher { } pub fn create_dispatcher() -> JsonRpcDispatcher { - JsonRpcDispatcher {} + JsonRpcDispatcher { + reported_managers: HashSet::new(), + reported_environments: HashSet::new(), + } +} + +fn get_environment_key(env: &PythonEnvironment) -> Option { + match env.python_executable_path.clone() { + Some(key) => Some(key.as_os_str().to_str()?.to_string()), + None => match env.env_path.clone() { + Some(key) => Some(key.as_os_str().to_str().unwrap().to_string()), + None => None, + }, + } +} + +fn get_manager_key(manager: &EnvManager) -> Option { + Some(manager.executable_path.to_str()?.to_string()) } diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 1e266732536c..3e9de48ff13a 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; -use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use crate::locator::{Locator, LocatorResult}; +use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; use std::fs; use std::path::PathBuf; @@ -22,56 +21,28 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { None } -pub struct PipEnv { - pub environments: HashMap, -} +pub struct PipEnv {} impl PipEnv { pub fn new() -> PipEnv { - PipEnv { - environments: HashMap::new(), - } + PipEnv {} } } impl Locator for PipEnv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) + fn resolve(&self, env: &PythonEnv) -> Option { + let project_path = get_pipenv_project(env)?; + Some(PythonEnvironment::new_pipenv( + Some(env.executable.clone()), + env.version.clone(), + env.path.clone(), + env.path.clone(), + None, + project_path, + )) } - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { - if let Some(project_path) = get_pipenv_project(env) { - let env = PythonEnvironment::new_pipenv( - Some(env.executable.clone()), - env.version.clone(), - env.path.clone(), - env.path.clone(), - None, - project_path, - ); - - self.environments.insert( - env.python_executable_path - .clone() - .unwrap() - .to_str() - .unwrap() - .to_string(), - env, - ); - return true; - } - false - } - - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index a5ead7d44ad2..1d1d73b94e64 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -1,22 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use regex::Regex; -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; - 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::MessageDispatcher; 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 { @@ -178,63 +176,34 @@ pub fn list_pyenv_environments( } pub struct PyEnv<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, - pub manager: Option, } impl PyEnv<'_> { pub fn with<'a>(environment: &'a impl Environment) -> PyEnv { - PyEnv { - environments: HashMap::new(), - environment, - manager: None, - } + PyEnv { environment } } } impl Locator for PyEnv<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + fn resolve(&self, _env: &PythonEnv) -> Option { // We will find everything in gather - false + None } - fn gather(&mut self) -> Option<()> { - let manager = match get_pyenv_binary(self.environment) { - Some(pyenv_binary) => Some(messaging::EnvManager::new( - pyenv_binary, - None, - EnvManagerType::Pyenv, - )), - None => None, - }; - self.manager = manager.clone(); - - for env in list_pyenv_environments(&manager, self.environment)? { - self.environments.insert( - env.python_executable_path - .as_ref() - .unwrap() - .to_str() - .unwrap() - .to_string(), - env, - ); - } - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - if let Some(manager) = &self.manager { - reporter.report_environment_manager(manager.clone()); + fn find(&self) -> Option { + let pyenv_binary = get_pyenv_binary(self.environment)?; + let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); + let mut environments: Vec = vec![]; + if let Some(envs) = list_pyenv_environments(&Some(manager.clone()), self.environment) { + for env in envs { + environments.push(env); + } } - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + Some(LocatorResult::Managers(vec![manager])) + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 2b76c7dcf751..24c490acafad 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{collections::HashMap, path::PathBuf}; - use crate::{ - locator::Locator, - messaging::{MessageDispatcher, PythonEnvironment}, + locator::{Locator, LocatorResult}, + messaging::PythonEnvironment, utils::{self, PythonEnv}, }; @@ -16,62 +14,43 @@ pub fn is_venv(env: &PythonEnv) -> bool { } return utils::find_pyvenv_config_path(&env.executable).is_some(); } -pub struct Venv { - pub environments: HashMap, -} +pub struct Venv {} impl Venv { pub fn new() -> Venv { - Venv { - environments: HashMap::new(), - } + Venv {} } } impl Locator for Venv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_venv(&env) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - 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, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + 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, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 40972b80a14d..49926f14c0ad 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; -use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use crate::locator::{Locator, LocatorResult}; +use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; -use std::path::PathBuf; pub fn is_virtualenv(env: &PythonEnv) -> bool { if env.path.is_none() { @@ -47,62 +45,43 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { false } -pub struct VirtualEnv { - pub environments: HashMap, -} +pub struct VirtualEnv {} impl VirtualEnv { pub fn new() -> VirtualEnv { - VirtualEnv { - environments: HashMap::new(), - } + VirtualEnv {} } } impl Locator for VirtualEnv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenv(env) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - 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, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + 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, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index e70cfc82af75..65b5b3da0003 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::list_python_environments; use crate::virtualenv; -use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; -use std::collections::HashMap; +use crate::{known::Environment, utils::PythonEnv}; use std::path::PathBuf; #[cfg(windows)] @@ -68,67 +67,55 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b } pub struct VirtualEnvWrapper<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl VirtualEnvWrapper<'_> { pub fn with<'a>(environment: &'a impl Environment) -> VirtualEnvWrapper { - VirtualEnvWrapper { - environments: HashMap::new(), - environment, - } + VirtualEnvWrapper { environment } } } impl Locator for VirtualEnvWrapper<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenvwrapper(env, self.environment) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - 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::Venv, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return 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::Venv, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let work_on_home = get_work_on_home_path(self.environment)?; let envs = list_python_environments(&work_on_home)?; + let mut environments: Vec = vec![]; envs.iter().for_each(|env| { - self.track_if_compatible(env); + if let Some(env) = self.resolve(env) { + environments.push(env); + } }); - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 5e34ba127947..cb1d384869a7 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -3,11 +3,9 @@ use crate::known; use crate::known::Environment; -use crate::locator::Locator; -use crate::messaging::MessageDispatcher; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -45,64 +43,55 @@ fn list_registry_pythons() -> Option> { } pub struct WindowsPython<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl WindowsPython<'_> { #[allow(dead_code)] pub fn with<'a>(environment: &'a impl Environment) -> WindowsPython { - WindowsPython { - environments: HashMap::new(), - environment, - } + WindowsPython { environment } } } impl Locator for WindowsPython<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: None, - python_executable_path: Some(env.executable.clone()), - version: None, - category: crate::messaging::PythonEnvironmentCategory::WindowsStore, - sys_prefix_path: None, - env_path: None, - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return Some(PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: None, + category: crate::messaging::PythonEnvironmentCategory::WindowsStore, + sys_prefix_path: None, + env_path: None, + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { + let mut environments: Vec = vec![]; if let Some(envs) = list_windows_store_python_executables(self.environment) { envs.iter().for_each(|env| { - self.track_if_compatible(&&PythonEnv::from(env.clone())); + if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { + environments.push(env); + } }); } if let Some(envs) = list_registry_pythons() { envs.iter().for_each(|env| { - self.track_if_compatible(&&PythonEnv::from(env.clone())); + if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { + environments.push(env); + } }); } - Some(()) - } - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs new file mode 100644 index 000000000000..beb503a4a541 --- /dev/null +++ b/native_locator/src/windows_registry.rs @@ -0,0 +1,59 @@ +// // Copyright (c) Microsoft Corporation. All rights reserved. +// // Licensed under the MIT License. +// use crate::messaging; +// use std::path::PathBuf; +// use winreg::RegKey; + +// fn get_registry_pythons_from_key( +// dispatcher: &mut impl messaging::MessageDispatcher, +// hk: &RegKey, +// company: &str, +// ) -> Option> { +// let python_key = hk.open_subkey("Software\\Python").ok()?; +// let company_key = python_key.open_subkey(company).ok()?; + +// let mut pythons = vec![]; +// for key in company_key.enum_keys().filter_map(Result::ok) { +// let version_key = company_key.open_subkey(key).ok()?; +// let install_path_key = version_key.open_subkey("InstallPath").ok()?; +// let executable: String = install_path_key.get_value("ExecutablePath").ok()?; +// let version = version_key.get_value("Version").ok()?; + +// dispatcher.report_environment(messaging::PythonEnvironment::new( +// None, +// Some(PathBuf::from(executable.clone())), +// messaging::PythonEnvironmentCategory::WindowsRegistry, +// Some(version), +// None, +// None, +// None, +// None, +// )); + +// pythons.push(PathBuf::from(executable)); +// } + +// Some(pythons) +// } + +// #[cfg(windows)] +// pub fn report_and_get_registry_pythons( +// dispatcher: &mut impl messaging::MessageDispatcher, +// company: &str, +// ) -> Option> { +// let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); +// let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + +// let mut pythons = vec![]; +// if let Some(hklm_pythons) = get_registry_pythons_from_key(dispatcher, &hklm, company) { +// pythons.extend(hklm_pythons); +// } +// if let Some(hkcu_pythons) = get_registry_pythons_from_key(dispatcher, &hkcu, company) { +// pythons.extend(hkcu_pythons); +// } + +// Some(pythons) +// } + +// // PythonCore +// // ContinuumAnalytics diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 7752bc4eb468..a26f44cafc15 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -3,7 +3,8 @@ use python_finder::{ known::Environment, - messaging::{EnvManager, MessageDispatcher, PythonEnvironment}, + locator::LocatorResult, + messaging::{EnvManager, PythonEnvironment}, }; use serde_json::Value; use std::{collections::HashMap, path::PathBuf}; @@ -24,35 +25,10 @@ pub fn join_test_paths(paths: &[&str]) -> PathBuf { path } -pub struct TestDispatcher { - pub messages: Vec, -} pub trait TestMessages { fn get_messages(&self) -> Vec; } -#[allow(dead_code)] -pub fn create_test_dispatcher() -> TestDispatcher { - impl MessageDispatcher for TestDispatcher { - fn report_environment_manager(&mut self, env: EnvManager) -> () { - self.messages.push(serde_json::to_string(&env).unwrap()); - } - fn report_environment(&mut self, env: PythonEnvironment) -> () { - self.messages.push(serde_json::to_string(&env).unwrap()); - } - fn exit(&mut self) -> () { - // - } - } - impl TestMessages for TestDispatcher { - fn get_messages(&self) -> Vec { - self.messages.clone() - } - } - TestDispatcher { - messages: Vec::new(), - } -} pub struct TestEnvironment { vars: HashMap, home: Option, @@ -106,8 +82,6 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { if !actual.contains_key(key) { return false; } - println!("\nCompare Key {:?}", key); - println!("\nCompare Key {:?}", actual.get(key).is_none()); if !compare_json(value, actual.get(key).unwrap()) { return false; } @@ -135,11 +109,11 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { } #[allow(dead_code)] -pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { +pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) { let mut expected_json = expected_json.to_vec(); assert_eq!( expected_json.len(), - dispatcher.messages.len(), + actual_json.len(), "Incorrect number of messages" ); @@ -148,9 +122,7 @@ pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { } // Ignore the order of the json items when comparing. - for actual in dispatcher.messages.iter() { - let actual: serde_json::Value = serde_json::from_str(actual.as_str()).unwrap(); - + for actual in actual_json.iter() { let mut valid_index: Option = None; for (i, expected) in expected_json.iter().enumerate() { if !compare_json(expected, &actual) { @@ -159,14 +131,48 @@ pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { // Ensure we verify using standard assert_eq!, just in case the code is faulty.. valid_index = Some(i); - assert_eq!(expected, &actual); + 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); + assert_eq!(&expected_json[0], actual); } } } + +#[allow(dead_code)] +pub fn get_environments_from_result(result: &Option) -> Vec { + match result { + Some(environments) => match environments { + python_finder::locator::LocatorResult::Environments(envs) => envs.clone(), + _ => vec![], + }, + None => vec![], + } +} + +#[allow(dead_code)] +pub fn get_managers_from_result(result: &Option) -> Vec { + match result { + Some(environments) => match environments { + python_finder::locator::LocatorResult::Managers(managers) => managers.clone(), + python_finder::locator::LocatorResult::Environments(envs) => { + let mut managers: HashMap = HashMap::new(); + envs.iter().for_each(|env| { + if let Some(manager) = env.env_manager.clone() { + let key = manager.executable_path.to_str().unwrap().to_string(); + managers.insert(key, manager); + } + }); + managers + .values() + .map(|m| m.clone()) + .collect::>() + } + }, + None => vec![], + } +} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 00050fec96f4..01e765d23c0d 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn find_python_in_path_this() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + assert_messages, create_test_environment, get_environments_from_result, join_test_paths, test_file_path, }; use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment}; @@ -17,7 +17,6 @@ fn find_python_in_path_this() { let unix_python = test_file_path(&["tests/unix/known"]); let unix_python_exe = join_test_paths(&[unix_python.clone().to_str().unwrap(), "python"]); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -27,11 +26,13 @@ fn find_python_in_path_this() { Vec::new(), ); - let mut locator = common_python::PythonOnPath::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = common_python::PythonOnPath::with(&known); + locator.find(); + let result = locator.find(); + + let environments = get_environments_from_result(&result); + assert_eq!(environments.len(), 1); - assert_eq!(dispatcher.messages.len(), 1); let env = PythonEnvironment { env_manager: None, project_path: None, @@ -43,5 +44,8 @@ fn find_python_in_path_this() { env_path: Some(unix_python.clone()), sys_prefix_path: None, }; - assert_messages(&[json!(env)], &dispatcher); + assert_messages( + &[json!(env)], + &environments.iter().map(|e| json!(e)).collect::>(), + ); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 80798fdbfe91..3d3a296e52b4 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -6,29 +6,28 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_conda_envs() { - use crate::common::{create_test_dispatcher, create_test_environment}; + use crate::common::{create_test_environment, get_environments_from_result}; use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([("PATH".to_string(), "".to_string())]), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); - assert_eq!(dispatcher.messages.len(), 0); + let environments = get_environments_from_result(&result); + assert_eq!(environments.len(), 0); } #[test] #[cfg(unix)] fn find_conda_exe_and_empty_envs() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + assert_messages, create_test_environment, get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::messaging::{EnvManager, EnvManagerType}; @@ -37,7 +36,6 @@ fn find_conda_exe_and_empty_envs() { use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -47,9 +45,11 @@ fn find_conda_exe_and_empty_envs() { Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let expected_conda_manager = EnvManager { @@ -57,14 +57,17 @@ fn find_conda_exe_and_empty_envs() { version: None, tool: EnvManagerType::Conda, }; - assert_messages(&[json!(expected_conda_manager)], &dispatcher) + assert_messages( + &[json!(expected_conda_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ) } #[test] #[cfg(unix)] fn finds_two_conda_envs_from_txt() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, - test_file_path, + assert_messages, create_test_environment, get_environments_from_result, + get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use python_finder::{conda, locator::Locator}; @@ -84,7 +87,6 @@ fn finds_two_conda_envs_from_txt() { ), ); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -94,9 +96,12 @@ fn finds_two_conda_envs_from_txt() { Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + let environments = get_environments_from_result(&result); + assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let conda_1_exe = join_test_paths(&[conda_1.clone().to_str().unwrap(), "python"]); @@ -142,11 +147,11 @@ fn finds_two_conda_envs_from_txt() { ]), }; assert_messages( - &[ - json!(expected_conda_1), - json!(expected_conda_manager), - json!(expected_conda_2), - ], - &dispatcher, - ) + &[json!(expected_conda_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ); + assert_messages( + &[json!(expected_conda_1), json!(expected_conda_2)], + &environments.iter().map(|e| json!(e)).collect::>(), + ); } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 6702d258d958..a63615a68ef1 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -6,29 +6,31 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs() { - use crate::common::{create_test_dispatcher, create_test_environment}; + use crate::common::{ + create_test_environment, get_environments_from_result, get_managers_from_result, + }; use python_finder::{locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::new(), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + locator.find(); + let result = locator.find(); - assert_eq!(dispatcher.messages.len(), 0); + assert_eq!(get_managers_from_result(&result).len(), 0); + assert_eq!(get_environments_from_result(&result).len(), 0); } #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + assert_messages, create_test_environment, get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::locator::Locator; @@ -36,7 +38,6 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { use serde_json::json; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]); let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); @@ -46,21 +47,25 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { vec![PathBuf::from(homebrew_bin)], ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); - assert_eq!(dispatcher.messages.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); - assert_messages(&[expected_json], &dispatcher) + assert_messages( + &[expected_json], + &managers.iter().map(|e| json!(e)).collect::>(), + ) } #[test] #[cfg(unix)] fn find_pyenv_envs() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, - test_file_path, + assert_messages, create_test_environment, get_environments_from_result, + get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::locator::Locator; use python_finder::{ @@ -70,7 +75,6 @@ fn find_pyenv_envs() { use serde_json::json; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let home = test_file_path(&["tests", "unix", "pyenv"]); let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); @@ -80,16 +84,23 @@ fn find_pyenv_envs() { vec![PathBuf::from(homebrew_bin)], ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); - assert_eq!(dispatcher.messages.len(), 6); let expected_manager = EnvManager { executable_path: pyenv_exe.clone(), version: None, tool: EnvManagerType::Pyenv, }; + + assert_messages( + &[json!(expected_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ); + let expected_3_9_9 = json!(PythonEnvironment { project_path: None, name: None, @@ -220,15 +231,16 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), }; + let environments = get_environments_from_result(&result); + assert_messages( &[ - json!(expected_manager), json!(expected_3_9_9), json!(expected_virtual_env), json!(expected_3_12_1), json!(expected_3_13_dev), json!(expected_3_12_1a3), ], - &dispatcher, + &environments.iter().map(|e| json!(e)).collect::>(), ) } From d6c2fb65d6ef2b944a895117c0134107f8736cdd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 May 2024 11:00:57 +1000 Subject: [PATCH 038/106] Replace locator enum with struct (#23412) --- native_locator/src/common_python.rs | 5 ++++- native_locator/src/conda.rs | 9 ++++----- native_locator/src/homebrew.rs | 5 ++++- native_locator/src/locator.rs | 6 +++--- native_locator/src/main.rs | 12 +++++++----- native_locator/src/pyenv.rs | 10 +++++----- native_locator/src/virtualenvwrapper.rs | 5 ++++- native_locator/src/windows_python.rs | 5 ++++- native_locator/tests/common.rs | 22 ++-------------------- 9 files changed, 37 insertions(+), 42 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index ddb3d3bbe1ab..7e2b532fb91c 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -72,7 +72,10 @@ impl Locator for PythonOnPath<'_> { if environments.is_empty() { None } else { - Some(LocatorResult::Environments(environments)) + Some(LocatorResult { + environments, + managers: vec![], + }) } } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index a56c655c06ee..20818aa3c849 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -445,10 +445,9 @@ impl Locator for Conda<'_> { environments.push(env) } - if environments.is_empty() { - Some(LocatorResult::Managers(vec![manager])) - } else { - Some(LocatorResult::Environments(environments)) - } + Some(LocatorResult { + managers: vec![manager], + environments, + }) } } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 0830c4f7d532..9111f5d9699d 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -76,7 +76,10 @@ impl Locator for Homebrew<'_> { if environments.is_empty() { None } else { - Some(LocatorResult::Environments(environments)) + Some(LocatorResult { + managers: vec![], + environments, + }) } } } diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index aa812c11bd37..ee72ef0591a7 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -7,9 +7,9 @@ use crate::{ }; #[derive(Debug)] -pub enum LocatorResult { - Managers(Vec), - Environments(Vec), +pub struct LocatorResult { + pub managers: Vec, + pub environments: Vec, } pub trait Locator { diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 9a3ffc27d6df..6f511ad3dddd 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -96,13 +96,15 @@ fn resolve_environment( } fn find_environments(locator: &dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { - match locator.find()? { - locator::LocatorResult::Environments(envs) => envs + if let Some(result) = locator.find() { + result + .environments .iter() - .for_each(|e| dispatcher.report_environment(e.clone())), - locator::LocatorResult::Managers(items) => items + .for_each(|e| dispatcher.report_environment(e.clone())); + result + .managers .iter() - .for_each(|m| dispatcher.report_environment_manager(m.clone())), + .for_each(|m| dispatcher.report_environment_manager(m.clone())); } Some(()) } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 1d1d73b94e64..fb48615b7464 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -200,10 +200,10 @@ impl Locator for PyEnv<'_> { environments.push(env); } } - if environments.is_empty() { - Some(LocatorResult::Managers(vec![manager])) - } else { - Some(LocatorResult::Environments(environments)) - } + + Some(LocatorResult { + managers: vec![manager], + environments, + }) } } diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 65b5b3da0003..1c679171c903 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -115,7 +115,10 @@ impl Locator for VirtualEnvWrapper<'_> { if environments.is_empty() { None } else { - Some(LocatorResult::Environments(environments)) + Some(LocatorResult { + managers: vec![], + environments, + }) } } } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index cb1d384869a7..071455509be3 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -91,7 +91,10 @@ impl Locator for WindowsPython<'_> { if environments.is_empty() { None } else { - Some(LocatorResult::Environments(environments)) + Some(LocatorResult { + managers: vec![], + environments, + }) } } } diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index a26f44cafc15..407e93707b7e 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -146,10 +146,7 @@ pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) { #[allow(dead_code)] pub fn get_environments_from_result(result: &Option) -> Vec { match result { - Some(environments) => match environments { - python_finder::locator::LocatorResult::Environments(envs) => envs.clone(), - _ => vec![], - }, + Some(result) => result.environments.clone(), None => vec![], } } @@ -157,22 +154,7 @@ pub fn get_environments_from_result(result: &Option) -> Vec) -> Vec { match result { - Some(environments) => match environments { - python_finder::locator::LocatorResult::Managers(managers) => managers.clone(), - python_finder::locator::LocatorResult::Environments(envs) => { - let mut managers: HashMap = HashMap::new(); - envs.iter().for_each(|env| { - if let Some(manager) = env.env_manager.clone() { - let key = manager.executable_path.to_str().unwrap().to_string(); - managers.insert(key, manager); - } - }); - managers - .values() - .map(|m| m.clone()) - .collect::>() - } - }, + Some(result) => result.managers.clone(), None => vec![], } } From 094041ff8f2f5c44eb880c3b0a8fa1fe45a5363e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 May 2024 12:32:45 +1000 Subject: [PATCH 039/106] Look for Python (PythonCore) in windows registry (#23414) --- native_locator/src/lib.rs | 2 + native_locator/src/main.rs | 11 +- native_locator/src/messaging.rs | 1 + native_locator/src/windows_registry.rs | 150 +++++++++++------- .../{windows_python.rs => windows_store.rs} | 25 +-- 5 files changed, 110 insertions(+), 79 deletions(-) rename native_locator/src/{windows_python.rs => windows_store.rs} (82%) diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index 251e977af00c..ba353c71ce12 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -14,3 +14,5 @@ pub mod pipenv; pub mod virtualenv; pub mod venv; pub mod locator; +pub mod windows_registry; +pub mod windows_store; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 6f511ad3dddd..56b6dc65e237 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -24,7 +24,8 @@ mod utils; mod venv; mod virtualenv; mod virtualenvwrapper; -mod windows_python; +mod windows_store; +mod windows_registry; fn main() { let environment = EnvironmentApi {}; @@ -44,7 +45,9 @@ fn main() { #[cfg(unix)] let homebrew_locator = homebrew::Homebrew::with(&environment); #[cfg(windows)] - let windows_locator = windows_python::WindowsPython::with(&environment); + let windows_store = windows_store::WindowsStore::with(&environment); + #[cfg(windows)] + let windows_registry = windows_registry::WindowsRegistry::new(); let conda_locator = conda::Conda::with(&environment); // Step 1: These environments take precedence over all others. @@ -54,7 +57,9 @@ fn main() { find_environments(&homebrew_locator, &mut dispatcher); find_environments(&conda_locator, &mut dispatcher); #[cfg(windows)] - find_environments(&windows_locator, &mut dispatcher); + find_environments(&windows_registry, &mut dispatcher); + #[cfg(windows)] + find_environments(&windows_store, &mut dispatcher); // Step 2: Search in some global locations. for env in list_global_virtual_envs(&environment).iter() { diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 959ec8d36f31..b39cd19bb5b6 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -83,6 +83,7 @@ pub enum PythonEnvironmentCategory { Pyenv, PyenvVirtualEnv, WindowsStore, + WindowsRegistry, Pipenv, VirtualEnvWrapper, Venv, diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index beb503a4a541..a26362a6f9e7 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -1,59 +1,91 @@ -// // Copyright (c) Microsoft Corporation. All rights reserved. -// // Licensed under the MIT License. -// use crate::messaging; -// use std::path::PathBuf; -// use winreg::RegKey; - -// fn get_registry_pythons_from_key( -// dispatcher: &mut impl messaging::MessageDispatcher, -// hk: &RegKey, -// company: &str, -// ) -> Option> { -// let python_key = hk.open_subkey("Software\\Python").ok()?; -// let company_key = python_key.open_subkey(company).ok()?; - -// let mut pythons = vec![]; -// for key in company_key.enum_keys().filter_map(Result::ok) { -// let version_key = company_key.open_subkey(key).ok()?; -// let install_path_key = version_key.open_subkey("InstallPath").ok()?; -// let executable: String = install_path_key.get_value("ExecutablePath").ok()?; -// let version = version_key.get_value("Version").ok()?; - -// dispatcher.report_environment(messaging::PythonEnvironment::new( -// None, -// Some(PathBuf::from(executable.clone())), -// messaging::PythonEnvironmentCategory::WindowsRegistry, -// Some(version), -// None, -// None, -// None, -// None, -// )); - -// pythons.push(PathBuf::from(executable)); -// } - -// Some(pythons) -// } - -// #[cfg(windows)] -// pub fn report_and_get_registry_pythons( -// dispatcher: &mut impl messaging::MessageDispatcher, -// company: &str, -// ) -> Option> { -// let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); -// let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); - -// let mut pythons = vec![]; -// if let Some(hklm_pythons) = get_registry_pythons_from_key(dispatcher, &hklm, company) { -// pythons.extend(hklm_pythons); -// } -// if let Some(hkcu_pythons) = get_registry_pythons_from_key(dispatcher, &hkcu, company) { -// pythons.extend(hkcu_pythons); -// } - -// Some(pythons) -// } - -// // PythonCore -// // ContinuumAnalytics +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#[cfg(windows)] +use crate::locator::{Locator, LocatorResult}; +#[cfg(windows)] +use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory}; +#[cfg(windows)] +use crate::utils::PythonEnv; +#[cfg(windows)] +use winreg::RegKey; +#[cfg(windows)] +use std::path::PathBuf; + +#[cfg(windows)] +fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option> { + let python_key = hk.open_subkey("Software\\Python").ok()?; + let company_key = python_key.open_subkey(company).ok()?; + + let mut pythons = vec![]; + for key in company_key.enum_keys().filter_map(Result::ok) { + let version_key = company_key.open_subkey(key).ok()?; + let install_path_key = version_key.open_subkey("InstallPath").ok()?; + let executable: String = install_path_key.get_value("ExecutablePath").ok()?; + let version = version_key.get_value("Version").ok()?; + + let env = PythonEnvironment::new( + None, + Some(PathBuf::from(executable.clone())), + PythonEnvironmentCategory::WindowsRegistry, + Some(version), + None, + None, + None, + Some(vec![executable.clone()]), + ); + + pythons.push(env); + } + + Some(pythons) +} + +#[cfg(windows)] +pub fn get_registry_pythons(company: &str) -> Option> { + let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + + let mut pythons = vec![]; + if let Some(hklm_pythons) = get_registry_pythons_from_key(&hklm, company) { + pythons.extend(hklm_pythons); + } + if let Some(hkcu_pythons) = get_registry_pythons_from_key(&hkcu, company) { + pythons.extend(hkcu_pythons); + } + + Some(pythons) +} + +#[cfg(windows)] +pub struct WindowsRegistry {} + +#[cfg(windows)] +impl WindowsRegistry { + #[allow(dead_code)] + pub fn new() -> WindowsRegistry { + WindowsRegistry {} + } +} + +#[cfg(windows)] +impl Locator for WindowsRegistry { + fn resolve(&self, env: &PythonEnv) -> Option { + None + } + + fn find(&self) -> Option { + let environments = get_registry_pythons("PythonCore")?; + if environments.is_empty() { + None + } else { + Some(LocatorResult { + managers: vec![], + environments, + }) + } + } +} + +// PythonCore +// ContinuumAnalytics diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_store.rs similarity index 82% rename from native_locator/src/windows_python.rs rename to native_locator/src/windows_store.rs index 071455509be3..3c31673a1193 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_store.rs @@ -9,10 +9,12 @@ use crate::utils::PythonEnv; use std::path::Path; use std::path::PathBuf; -fn is_windows_python_executable(path: &PathBuf) -> bool { +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") } + fn list_windows_store_python_executables( environment: &dyn known::Environment, ) -> Option> { @@ -38,22 +40,18 @@ fn list_windows_store_python_executables( Some(python_envs) } -fn list_registry_pythons() -> Option> { - None -} - -pub struct WindowsPython<'a> { +pub struct WindowsStore<'a> { pub environment: &'a dyn Environment, } -impl WindowsPython<'_> { +impl WindowsStore<'_> { #[allow(dead_code)] - pub fn with<'a>(environment: &'a impl Environment) -> WindowsPython { - WindowsPython { environment } + pub fn with<'a>(environment: &'a impl Environment) -> WindowsStore { + WindowsStore { environment } } } -impl Locator for WindowsPython<'_> { +impl Locator for WindowsStore<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { return Some(PythonEnvironment { @@ -80,13 +78,6 @@ impl Locator for WindowsPython<'_> { } }); } - if let Some(envs) = list_registry_pythons() { - envs.iter().for_each(|env| { - if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { - environments.push(env); - } - }); - } if environments.is_empty() { None From 268c2eb00eb61726fa36f7d9d0e355792f2ae90b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 May 2024 12:47:18 +1000 Subject: [PATCH 040/106] Fix extraction of Python version for Conda environments (#23415) Also added a test file to ensure we test this correctly. --- native_locator/src/conda.rs | 45 +++++++++---------- .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 2 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 native_locator/tests/unix/conda/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 20818aa3c849..4236a7329c7c 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -73,30 +73,27 @@ pub fn is_conda_environment(any_path: &Path) -> bool { } } -/// Get the version of a package in a conda environment. This will find the version -/// from the 'conda-meta' directory in a platform agnostic way. -fn get_version_from_meta_json(json_file: &Path) -> Option { - let file_name = json_file.file_name()?.to_string_lossy(); - - match Regex::new(r"(?m)([\d\w\-]*)-([\d\.]*)-.*\.json") - .ok()? - .captures(&file_name)? - .get(2) - { - Some(version) => Some(version.as_str().to_string()), - None => None, - } +struct CondaPackage { + path: PathBuf, + version: String, } -/// Get the path to the json file of a package in the conda environment from the 'conda-meta' directory. -fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { +/// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. +fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { let package_name = format!("{}-", package); let conda_meta_path = get_conda_meta_path(any_path)?; + let regex = Regex::new(format!("^{}-((\\d+\\.*)*)-.*.json$", package).as_str()); std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { let path = entry.ok()?.path(); let file_name = path.file_name()?.to_string_lossy(); if file_name.starts_with(&package_name) && file_name.ends_with(".json") { - Some(path) + match regex.clone().ok()?.captures(&file_name)?.get(1) { + Some(version) => Some(CondaPackage { + path: path.clone(), + version: version.as_str().to_string(), + }), + None => None, + } } else { None } @@ -108,7 +105,7 @@ fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option bool { let conda_python_json_path = get_conda_package_json_path(any_path, "python"); match conda_python_json_path { - Some(path) => path.exists(), + Some(result) => result.path.exists(), None => false, } } @@ -117,7 +114,7 @@ pub fn is_python_conda_env(any_path: &Path) -> bool { pub fn get_conda_python_version(any_path: &Path) -> Option { let conda_python_json_path = get_conda_package_json_path(any_path, "python"); match conda_python_json_path { - Some(path) => get_version_from_meta_json(&path), + Some(result) => Some(result.version.clone()), None => None, } } @@ -231,11 +228,13 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { if parent.ends_with("Library") { parent = parent.parent()?; } - let conda_python_json_path = match get_conda_package_json_path(&parent, "conda") { - Some(exe) => Some(exe), - None => get_conda_package_json_path(&parent.parent()?, "conda"), - }?; - get_version_from_meta_json(&conda_python_json_path) + match get_conda_package_json_path(&parent, "conda") { + Some(result) => Some(result.version), + None => match get_conda_package_json_path(&parent.parent()?, "conda") { + Some(result) => Some(result.version), + None => None, + }, + } } fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { diff --git a/native_locator/tests/unix/conda/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json new file mode 100644 index 000000000000..e69de29bb2d1 From 50fd0081d984d82d4032136576000d34a34cae9c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 May 2024 22:51:01 +1000 Subject: [PATCH 041/106] Fixes to comparing of Python environments (#23417) --- src/client/pythonEnvironments/base/info/env.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index b77acde5333d..930a522ef1e9 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -276,14 +276,20 @@ export function areSameEnv( if (leftInfo === undefined || rightInfo === undefined) { return undefined; } - const leftFilename = leftInfo.executable!.filename; - const rightFilename = rightInfo.executable!.filename; - + if ( + (leftInfo.executable?.filename && !rightInfo.executable?.filename) || + (!leftInfo.executable?.filename && rightInfo.executable?.filename) + ) { + return false; + } if (leftInfo.id && leftInfo.id === rightInfo.id) { // In case IDs are available, use it. return true; } + const leftFilename = leftInfo.executable!.filename; + const rightFilename = rightInfo.executable!.filename; + if (getEnvID(leftFilename, leftInfo.location) === getEnvID(rightFilename, rightInfo.location)) { // Otherwise use ID function to get the ID. Note ID returned by function may itself change if executable of // an environment changes, for eg. when conda installs python into the env. So only use it as a fallback if From bdedb0a374c33c0776cd44c7041b12f33c1c36d0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 May 2024 23:00:29 +1000 Subject: [PATCH 042/106] Revised native conda locator (#23416) **Fixes** * https://github.com/microsoft/vscode-python/issues/23413 * & the fact that we always use the same conda exe to manage all conda envs, even if multiple are installed. * Step 1: * Look for conda install folders in known locations such as `//miniconda3`, `/Anaconda3` * Step 2: * For each install location identified, inspect that folder and extract the * Conda executable * All environments belonging to that conda installation * Step 3: * Old approach, * 1. find any conda in sys path or other locations and find conda installation via that mechanism * 2. Get all envs from environments.txt file (any remaining conda envs not discovered in 1 & 2 will be discoverred here and use some the global conda exe) Once we have step 1 and 2, I do not expect anything new to show up in step 3, Even if users install conda into some custom locations (the solution would be to run step 1 with the custom location provided by user in settings.json file) **How to find environments?** * Look in the `envs` folder of the conda installation * Look at the entries in the `environments.txt` file * Look at the `env_dirs` in the `.condarc` file With these two, we should be able to eliminate the need to ever spawn conda to get the env directories. **How do we know whether a conda environment belongs to a specific conda installation** * If it is in the `envs` sub directory of the conda installation * Else, have a look at `/conda-meta/history` file to look at the conda installation that was used to create this environment --- native_locator/src/common_python.rs | 2 +- native_locator/src/conda.rs | 882 +++++++++++++----- native_locator/src/conda_old.rs | 452 +++++++++ native_locator/src/homebrew.rs | 2 +- native_locator/src/locator.rs | 2 +- native_locator/src/main.rs | 28 +- native_locator/src/messaging.rs | 25 +- native_locator/src/pipenv.rs | 2 +- native_locator/src/pyenv.rs | 45 +- native_locator/src/utils.rs | 16 + native_locator/src/venv.rs | 2 +- native_locator/src/virtualenv.rs | 2 +- native_locator/src/virtualenvwrapper.rs | 2 +- native_locator/src/windows_registry.rs | 2 +- native_locator/src/windows_store.rs | 2 +- native_locator/tests/common_python_test.rs | 3 +- native_locator/tests/conda_test.rs | 30 +- native_locator/tests/pyenv_test.rs | 15 +- .../tests/unix/conda/.conda/environments.txt | 4 +- .../unix/conda/{ => anaconda3/bin}/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 + .../python-slugify-5.0.2-pyhd3eb1b0_0.json} | 0 .../conda => conda/anaconda3/envs/one/python} | 0 .../unix/conda/anaconda3/envs/two/python | 0 .../conda_without_envs/anaconda3/bin/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 + .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../base/locators/lowLevel/nativeLocator.ts | 18 +- 32 files changed, 1237 insertions(+), 301 deletions(-) create mode 100644 native_locator/src/conda_old.rs rename native_locator/tests/unix/conda/{ => anaconda3/bin}/conda (100%) rename native_locator/tests/unix/conda/{envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json => anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/conda/{envs/one => anaconda3}/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/conda/{envs/one/python => anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json} (100%) create mode 100644 native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename native_locator/tests/unix/conda/{envs/two/python => anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/{conda_without_envs/conda => conda/anaconda3/envs/one/python} (100%) create mode 100644 native_locator/tests/unix/conda/anaconda3/envs/two/python create mode 100644 native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda create mode 100644 native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json create mode 100644 native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json create mode 100644 native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 7e2b532fb91c..039965bae502 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -50,7 +50,7 @@ impl Locator for PythonOnPath<'_> { }) } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let paths = self.environment.get_env_var("PATH".to_string())?; let bin = if cfg!(windows) { "python.exe" diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 4236a7329c7c..65e0fd073292 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -9,81 +9,61 @@ use crate::messaging; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; use crate::messaging::PythonEnvironment; -use crate::utils::find_python_binary_path; use crate::utils::PythonEnv; +use crate::utils::{find_python_binary_path, get_environment_key, get_environment_manager_key}; +use log::warn; use regex::Regex; +use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; -/// relative to the interpreter. This layout is common on linux/Mac. -/// -/// ``` -/// env // <--- Input can be this path -/// |-- conda-meta // <--- Returns this directory -/// |-- bin // <--- Input can be this path -/// |-- python // <--- Input can be this path -/// ``` +/// Specifically returns the file names that are valid for 'conda' on windows +/// Path is relative to the installation folder of conda. +#[cfg(windows)] +fn get_relative_paths_to_conda_executable() -> Vec { + vec![ + PathBuf::from("Scripts").join("conda.exe"), + PathBuf::from("Scripts").join("conda.bat"), + ] +} + +/// Specifically returns the file names that are valid for 'conda' on linux/Mac +/// Path is relative to the installation folder of conda. #[cfg(unix)] -fn get_conda_meta_path(any_path: &Path) -> Option { - if any_path.ends_with("bin/python") { - match any_path.parent() { - Some(parent) => match parent.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - }, - None => None, - } - } else if any_path.ends_with("bin") { - match any_path.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - } - } else { - Some(any_path.to_path_buf().join("conda-meta")) - } +fn get_relative_paths_to_conda_executable() -> Vec { + vec![PathBuf::from("bin").join("conda")] } -/// Get the conda-meta directory. For windows 'conda-meta' is in the same directory as the interpreter. -/// This layout is common in Windows. -/// -/// ``` -/// env // <--- Input can be this path -/// |-- conda-meta // <--- Returns this directory -/// |-- python.exe // <--- Input can be this path -/// ``` +/// Returns the relative path to the python executable for the conda installation. +/// Path is relative to the installation folder of conda. +/// In windows the python.exe for the conda installation is in the root folder. #[cfg(windows)] -fn get_conda_meta_path(any_path: &Path) -> Option { - if any_path.ends_with("python.exe") { - match any_path.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - } - } else { - Some(any_path.to_path_buf().join("conda-meta")) - } +fn get_relative_paths_to_main_python_executable() -> PathBuf { + PathBuf::from("python.exe") } -/// Check if a given path is a conda environment. A conda environment is a directory that contains -/// a 'conda-meta' directory as child. This will find 'conda-meta' in a platform agnostic way. -pub fn is_conda_environment(any_path: &Path) -> bool { - let conda_meta_path = get_conda_meta_path(any_path); - match conda_meta_path { - Some(path) => path.exists(), - None => false, - } +/// Returns the relative path to the python executable for the conda installation. +/// Path is relative to the installation folder of conda. +/// In windows the python.exe for the conda installation is in the bin folder. +#[cfg(unix)] +fn get_relative_paths_to_main_python_executable() -> PathBuf { + PathBuf::from("bin").join("python") } +#[derive(Debug)] struct CondaPackage { + #[allow(dead_code)] path: PathBuf, version: String, } /// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. -fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { +fn get_conda_package_json_path(path: &Path, package: &str) -> Option { + // conda-meta is in the root of the conda installation folder + let path = path.join("conda-meta"); let package_name = format!("{}-", package); - let conda_meta_path = get_conda_meta_path(any_path)?; let regex = Regex::new(format!("^{}-((\\d+\\.*)*)-.*.json$", package).as_str()); - std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { + std::fs::read_dir(path).ok()?.find_map(|entry| { let path = entry.ok()?.path(); let file_name = path.file_name()?.to_string_lossy(); if file_name.starts_with(&package_name) && file_name.ends_with(".json") { @@ -100,23 +80,15 @@ fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option bool { - let conda_python_json_path = get_conda_package_json_path(any_path, "python"); - match conda_python_json_path { - Some(result) => result.path.exists(), - None => false, +fn get_conda_executable(path: &PathBuf) -> Option { + for relative_path in get_relative_paths_to_conda_executable() { + let exe = path.join(&relative_path); + if exe.exists() { + return Some(exe); + } } -} -/// Get the version of the `python` package in the conda environment -pub fn get_conda_python_version(any_path: &Path) -> Option { - let conda_python_json_path = get_conda_package_json_path(any_path, "python"); - match conda_python_json_path { - Some(result) => Some(result.version.clone()), - None => None, - } + None } /// Specifically returns the file names that are valid for 'conda' on windows @@ -220,21 +192,84 @@ pub fn find_conda_binary(environment: &dyn known::Environment) -> Option Option { - let mut parent = conda_binary.parent()?; - if parent.ends_with("bin") { - parent = parent.parent()?; - } - if parent.ends_with("Library") { - parent = parent.parent()?; +fn get_conda_manager(path: &PathBuf) -> Option { + let conda_exe = get_conda_executable(path)?; + let conda_pkg = get_conda_package_json_path(path, "conda")?; + + Some(EnvManager { + executable_path: conda_exe, + version: Some(conda_pkg.version), + tool: EnvManagerType::Conda, + }) +} + +#[derive(Debug, Clone)] +struct CondaEnvironment { + name: String, + named: bool, + path: PathBuf, + python_executable_path: Option, + version: Option, +} +fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { + let metadata = env_path.metadata(); + match metadata { + Ok(metadata) => { + if metadata.is_dir() { + let path = env_path.clone(); + if let Some(python_binary) = find_python_binary_path(&path) { + if let Some(package_info) = get_conda_package_json_path(&path, "python") { + return Some(CondaEnvironment { + name: path.file_name()?.to_string_lossy().to_string(), + path, + named, + python_executable_path: Some(python_binary), + version: Some(package_info.version), + }); + } else { + return Some(CondaEnvironment { + name: path.file_name()?.to_string_lossy().to_string(), + path, + named, + python_executable_path: Some(python_binary), + version: None, + }); + } + } else { + return Some(CondaEnvironment { + name: path.file_name()?.to_string_lossy().to_string(), + path, + named, + python_executable_path: None, + version: None, + }); + } + } + } + Err(_) => (), } - match get_conda_package_json_path(&parent, "conda") { - Some(result) => Some(result.version), - None => match get_conda_package_json_path(&parent.parent()?, "conda") { - Some(result) => Some(result.version), - None => None, - }, + + None +} +fn get_environments_from_envs_folder_in_conda_directory( + path: &Path, +) -> Option> { + let mut envs: Vec = vec![]; + // iterate through all sub directories in the env folder + // for each sub directory, check if it has a python executable + // if it does, create a PythonEnvironment object and add it to the list + for entry in std::fs::read_dir(path.join("envs")).ok()? { + match entry { + Ok(entry) => { + if let Some(env) = get_conda_environment_info(&entry.path(), true) { + envs.push(env); + } + } + Err(_) => (), + } } + + Some(envs) } fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { @@ -258,133 +293,485 @@ fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> envs } -fn get_known_env_locations( - conda_bin: &PathBuf, +#[derive(Debug)] +struct Condarc { + env_dirs: Vec, +} + +/** + * The .condarc file contains a list of directories where conda environments are created. + * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs + * + * TODO: Search for the .condarc file in the following locations: + * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc + */ +fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { + if let Some(home) = environment.get_user_home() { + let conda_rc = Path::new(&home).join(".condarc"); + let mut start_consuming_values = false; + match std::fs::read_to_string(conda_rc) { + Ok(reader) => { + let mut env_dirs = vec![]; + for line in reader.lines() { + if line.starts_with("envs_dirs:") && !start_consuming_values { + start_consuming_values = true; + continue; + } + if start_consuming_values { + if line.trim().starts_with("-") { + if let Some(env_dir) = line.splitn(2, '-').nth(1) { + let env_dir = PathBuf::from(env_dir.trim()); + if env_dir.exists() { + env_dirs.push(env_dir); + } + } + continue; + } else { + break; + } + } + } + return Some(Condarc { env_dirs }); + } + Err(_) => (), + } + } + None +} + +fn get_conda_envs_from_conda_rc( + root_conda_path: &PathBuf, environment: &dyn known::Environment, -) -> Vec { - let mut paths = vec![]; - let home = environment.get_user_home(); - match home { - Some(home) => { - let home = Path::new(&home); - let conda_envs = home.join(".conda").join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); +) -> Option> { + let mut envs: Vec = vec![]; + for env in get_conda_conda_rc(environment)?.env_dirs { + if let Ok(reader) = std::fs::read_dir(env) { + for entry in reader { + match entry { + Ok(entry) => { + if entry.path().is_dir() + && was_conda_environment_created_by_specific_conda( + &entry.path(), + root_conda_path, + ) + { + if let Some(env) = get_conda_environment_info(&entry.path(), false) { + envs.push(env); + } + } + } + Err(_) => (), + } + } } - None => (), } - match conda_bin.parent() { - Some(parent) => { - paths.push(parent.to_string_lossy().to_string()); - let conda_envs = parent.join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); - match parent.parent() { - Some(parent) => { - paths.push(parent.to_string_lossy().to_string()); - let conda_envs = parent.join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); + Some(envs) +} + +/** + * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. + * If the user has multiple conda installations, any one of those could have created that specific environment. + * Fortunately the conda-meta/history file contains the path to the conda executable (script) that was used to create the environment. + * The format of the file is as follows: + * # cmd: C:\Users\user\miniconda3\Scripts\conda-script.py create --name myenv + * + * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. + */ +fn was_conda_environment_created_by_specific_conda( + env_path: &PathBuf, + root_conda_path: &PathBuf, +) -> bool { + let conda_meta_history = env_path.join("conda-meta").join("history"); + match std::fs::read_to_string(conda_meta_history.clone()) { + Ok(reader) => { + for line in reader.lines() { + let line = line.to_lowercase(); + if line.starts_with("# cmd:") && line.contains(" create ") { + if line.contains(&root_conda_path.to_str().unwrap().to_lowercase()) { + return true; + } else { + return false; + } } - None => (), } } - None => (), + Err(_) => warn!( + "Error reading conda-meta/history file {:?}", + conda_meta_history + ), } - paths + false } -fn get_conda_envs_from_known_env_locations( - conda_bin: &PathBuf, +/** + * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. + * If the user has multiple conda installations, any one of those could have created that specific environment. + * Fortunately the conda-meta/history file contains the path to the conda executable (script) that was used to create the environment. + * The format of the file is as follows: + * # cmd: C:\Users\user\miniconda3\Scripts\conda-script.py create --name myenv + * + * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. + */ +fn get_environments_from_environments_txt_belonging_to_conda_directory( + path: &PathBuf, environment: &dyn known::Environment, -) -> Vec { - let mut envs = vec![]; - for location in get_known_env_locations(conda_bin, environment) { - if is_conda_environment(&Path::new(&location)) { - envs.push(location.to_string()); +) -> Option> { + let mut envs: Vec = vec![]; + for env in get_conda_envs_from_environment_txt(environment) { + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + if env.contains(path.to_str().unwrap()) { + continue; } - match std::fs::read_dir(location) { - Ok(reader) => { - for entry in reader { - match entry { - Ok(entry) => { - let metadata = entry.metadata(); - match metadata { - Ok(metadata) => { - if metadata.is_dir() { - let path = entry.path(); - if is_conda_environment(&path) { - envs.push(path.to_string_lossy().to_string()); - } - } - } - Err(_) => (), - } - } - Err(_) => (), + + let env_path = PathBuf::from(env); + if !env_path.is_dir() { + continue; + } + if was_conda_environment_created_by_specific_conda(&env_path, path) { + if let Some(env) = get_conda_environment_info(&env_path, false) { + envs.push(env); + } + } + } + + Some(envs) +} + +fn get_conda_environments_from_conda_directory( + path: &PathBuf, + environment: &dyn known::Environment, +) -> Option> { + let mut all_envs: Vec = vec![]; + if let Some(envs) = get_environments_from_envs_folder_in_conda_directory(path) { + envs.iter().for_each(|env| all_envs.push(env.clone())); + } + + if let Some(envs) = + get_environments_from_environments_txt_belonging_to_conda_directory(path, environment) + { + envs.iter().for_each(|env| all_envs.push(env.clone())); + } + + if let Some(envs) = get_conda_envs_from_conda_rc(path, environment) { + envs.iter().for_each(|env| all_envs.push(env.clone())); + } + + Some(all_envs) +} + +#[cfg(windows)] +fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { + let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); + let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); + let all_user_profile = environment + .get_env_var("ALLUSERSPROFILE".to_string()) + .unwrap(); + let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); + let mut known_paths = vec![ + Path::new(&user_profile).join("Anaconda3"), + Path::new(&program_data).join("Anaconda3"), + Path::new(&all_user_profile).join("Anaconda3"), + Path::new(&home_drive).join("Anaconda3"), + Path::new(&user_profile).join("Miniconda3"), + Path::new(&program_data).join("Miniconda3"), + Path::new(&all_user_profile).join("Miniconda3"), + Path::new(&home_drive).join("Miniconda3"), + Path::new(&all_user_profile).join("miniforge3"), + Path::new(&home_drive).join("miniforge3"), + ]; + if let Some(home) = environment.get_user_home() { + known_paths.push(PathBuf::from(home.clone()).join("anaconda3")); + known_paths.push(PathBuf::from(home.clone()).join("miniconda3")); + known_paths.push(PathBuf::from(home.clone()).join("miniforge3")); + known_paths.push(PathBuf::from(home).join(".conda")); + } + known_paths +} + +#[cfg(unix)] +fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { + let mut known_paths = vec![ + PathBuf::from("/opt/anaconda3"), + PathBuf::from("/opt/miniconda3"), + PathBuf::from("/usr/local/anaconda3"), + PathBuf::from("/usr/local/miniconda3"), + PathBuf::from("/usr/anaconda3"), + PathBuf::from("/usr/miniconda3"), + PathBuf::from("/home/anaconda3"), + PathBuf::from("/home/miniconda3"), + PathBuf::from("/anaconda3"), + PathBuf::from("/miniconda3"), + PathBuf::from("/miniforge3"), + PathBuf::from("/miniforge3"), + ]; + if let Some(home) = environment.get_user_home() { + known_paths.push(PathBuf::from(home.clone()).join("anaconda3")); + known_paths.push(PathBuf::from(home.clone()).join("miniconda3")); + known_paths.push(PathBuf::from(home.clone()).join("miniforge3")); + known_paths.push(PathBuf::from(home).join(".conda")); + } + known_paths +} + +fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Option> { + if env.python_executable_path.is_none() { + return None; + } + let conda_exe = manager.executable_path.to_str().unwrap().to_string(); + if env.named { + Some(vec![ + conda_exe, + "run".to_string(), + "-n".to_string(), + env.name.clone(), + "python".to_string(), + ]) + } else { + Some(vec![ + conda_exe, + "run".to_string(), + "-p".to_string(), + env.path.to_str().unwrap().to_string(), + "python".to_string(), + ]) + } +} + +fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option { + let python_exe = path.join(get_relative_paths_to_main_python_executable()); + if !python_exe.exists() { + return None; + } + if let Some(package_info) = get_conda_package_json_path(&path, "python") { + let conda_exe = manager.executable_path.to_str().unwrap().to_string(); + return Some(PythonEnvironment { + name: None, + category: messaging::PythonEnvironmentCategory::Conda, + python_executable_path: Some(python_exe), + version: Some(package_info.version), + env_path: Some(path.clone()), + sys_prefix_path: Some(path.clone()), + env_manager: Some(manager.clone()), + python_run_command: Some(vec![ + conda_exe, + "run".to_string(), + "-p".to_string(), + path.to_str().unwrap().to_string(), + "python".to_string(), + ]), + project_path: None, + }); + } + None +} + +pub fn get_conda_environments_in_specified_path( + possible_conda_folder: &PathBuf, + environment: &dyn known::Environment, +) -> Option { + let mut managers: Vec = vec![]; + let mut environments: Vec = vec![]; + let mut detected_envs: HashSet = HashSet::new(); + let mut detected_managers: HashSet = HashSet::new(); + if possible_conda_folder.is_dir() && possible_conda_folder.exists() { + if let Some(manager) = get_conda_manager(&possible_conda_folder) { + let envs = + get_conda_environments_from_conda_directory(&possible_conda_folder, environment); + + if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { + if let Some(key) = get_environment_key(&env) { + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(env); } } } - Err(_) => (), + + envs.unwrap_or_default().iter().for_each(|env| { + let exe = env.python_executable_path.clone(); + let env = PythonEnvironment::new( + Some(env.name.clone()), + exe.clone(), + messaging::PythonEnvironmentCategory::Conda, + env.version.clone(), + Some(env.path.clone()), + Some(env.path.clone()), + Some(manager.clone()), + get_activation_command(env, &manager), + ); + if let Some(key) = get_environment_key(&env) { + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(env); + } + } + }); + + let key = get_environment_manager_key(&manager); + if !detected_managers.contains(&key) { + detected_managers.insert(key); + managers.push(manager); + } } } - envs + + if managers.is_empty() && environments.is_empty() { + return None; + } + + Some(LocatorResult { + managers, + environments, + }) } -struct CondaEnv { - named: bool, - name: String, - path: PathBuf, +fn find_conda_environments_from_known_conda_install_locations( + environment: &dyn known::Environment, +) -> Option { + let mut managers: Vec = vec![]; + let mut environments: Vec = vec![]; + let mut detected_envs: HashSet = HashSet::new(); + let mut detected_managers: HashSet = HashSet::new(); + + for possible_conda_folder in get_known_conda_install_locations(environment) { + if let Some(result) = + get_conda_environments_in_specified_path(&possible_conda_folder, environment) + { + result.managers.iter().for_each(|m| { + let key = get_environment_manager_key(m); + if !detected_managers.contains(&key) { + detected_managers.insert(key); + managers.push(m.clone()); + } + }); + + result.environments.iter().for_each(|e| { + if let Some(key) = get_environment_key(e) { + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(e.clone()); + } + } + }); + } + } + + if managers.is_empty() && environments.is_empty() { + return None; + } + + Some(LocatorResult { + managers, + environments, + }) +} + +pub fn get_conda_version(conda_binary: &PathBuf) -> Option { + let mut parent = conda_binary.parent()?; + if parent.ends_with("bin") { + parent = parent.parent()?; + } + if parent.ends_with("Library") { + parent = parent.parent()?; + } + match get_conda_package_json_path(&parent, "conda") { + Some(result) => Some(result.version), + None => match get_conda_package_json_path(&parent.parent()?, "conda") { + Some(result) => Some(result.version), + None => None, + }, + } } -fn get_distinct_conda_envs( - conda_bin: &PathBuf, +fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( + known_environment_keys: &HashSet, + known_environment: &Vec, environment: &dyn known::Environment, -) -> Vec { - let mut envs = get_conda_envs_from_environment_txt(environment); - let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); - envs.append(&mut known_envs); - envs.sort(); - envs.dedup(); - - let locations = get_known_env_locations(conda_bin, environment); - let mut conda_envs = vec![]; - for env in envs { - let env = Path::new(&env); - let mut named = false; - let mut name = "".to_string(); - for location in &locations { - let location = Path::new(location).join("envs"); - match env.strip_prefix(location) { - Ok(prefix) => { - named = true; - name = match prefix.to_str() { - Some(name) => { - let name = name.to_string(); - if name == "" { - "base".to_string() - } else { - name.to_string() - } - } - None => "base".to_string(), - }; - break; +) -> Option { + let binding = get_conda_envs_from_environment_txt(environment); + let undiscovered_environments_in_txt = binding + .iter() + .filter(|env| { + for known in known_environment_keys.iter() { + if known.contains(*env) { + return false; } - Err(_) => (), + } + true + }) + .collect::>(); + + if undiscovered_environments_in_txt.len() == 0 { + return None; + } + + // Ok, weird, we have an environment in environments.txt file that was not discovered. + // Let's try to discover it. + warn!( + "Found environments in environments.txt that were not discovered: {:?}", + undiscovered_environments_in_txt + ); + + let manager = match known_environment + .iter() + .find_map(|env| env.env_manager.as_ref()) + { + Some(manager) => Some(manager.clone()), + None => { + // Old approach of finding the conda executable. + let conda_binary = find_conda_binary(environment)?; + Some(EnvManager::new( + conda_binary.clone(), + get_conda_version(&conda_binary), + EnvManagerType::Conda, + )) + } + }; + + if let Some(manager) = manager { + let mut environments: Vec = vec![]; + for env in undiscovered_environments_in_txt { + if let Some(env) = get_conda_environment_info(&PathBuf::from(env), false) { + let exe = env.python_executable_path.clone(); + let env = PythonEnvironment::new( + Some(env.name.clone()), + exe.clone(), + messaging::PythonEnvironmentCategory::Conda, + env.version.clone(), + Some(env.path.clone()), + Some(env.path.clone()), + Some(manager.clone()), + get_activation_command(&env, &manager), + ); + environments.push(env); } } - conda_envs.push(CondaEnv { - named, - name, - path: PathBuf::from(env), - }); + if environments.len() > 0 { + return Some(LocatorResult { + managers: vec![manager], + environments, + }); + } + } else { + warn!("Could not find conda executable to discover environments in environments.txt"); } - conda_envs + + None } pub struct Conda<'a> { pub manager: Option, pub environment: &'a dyn Environment, + pub discovered_environments: HashSet, + pub discovered_managers: HashSet, +} + +pub trait CondaLocator { + fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option; } impl Conda<'_> { @@ -392,60 +779,119 @@ impl Conda<'_> { Conda { environment, manager: None, + discovered_environments: HashSet::new(), + discovered_managers: HashSet::new(), + } + } + fn filter_result(&mut self, result: Option) -> Option { + if let Some(result) = result { + let envs: Vec = result + .environments + .iter() + .filter(|e| { + if let Some(key) = get_environment_key(e) { + if self.discovered_environments.contains(&key) { + return false; + } + self.discovered_environments.insert(key); + return true; + } + false + }) + .cloned() + .collect(); + + let managers: Vec = result + .managers + .iter() + .filter(|e| { + let key = get_environment_manager_key(e); + if self.discovered_managers.contains(&key) { + return false; + } + self.discovered_managers.insert(key); + return true; + }) + .cloned() + .collect(); + + if envs.len() > 0 || managers.len() > 0 { + return Some(LocatorResult { + managers: managers, + environments: envs, + }); + } } + None + } +} + +impl CondaLocator for Conda<'_> { + fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { + self.filter_result(get_conda_environments_in_specified_path( + possible_conda_folder, + self.environment, + )) } } impl Locator for Conda<'_> { fn resolve(&self, _env: &PythonEnv) -> Option { - // We will find everything in gather + // We will find everything in find None } - fn find(&self) -> Option { - let conda_binary = find_conda_binary(self.environment)?; - let manager = EnvManager::new( - conda_binary.clone(), - get_conda_version(&conda_binary), - EnvManagerType::Conda, - ); - - let envs = get_distinct_conda_envs(&conda_binary, self.environment); + fn find(&mut self) -> Option { + let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; - for env in envs { - let executable = find_python_binary_path(Path::new(&env.path)); - let env = messaging::PythonEnvironment::new( - Some(env.name.to_string()), - executable.clone(), - messaging::PythonEnvironmentCategory::Conda, - get_conda_python_version(&env.path), - Some(env.path.clone()), - Some(env.path.clone()), - Some(manager.clone()), - if env.named { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-n".to_string(), - env.name.to_string(), - "python".to_string(), - ]) - } else { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-p".to_string(), - env.path.to_string_lossy().to_string(), - "python".to_string(), - ]) - }, - ); - - environments.push(env) + let mut detected_managers: HashSet = HashSet::new(); + + if let Some(result) = self.filter_result( + find_conda_environments_from_known_conda_install_locations(self.environment), + ) { + result.managers.iter().for_each(|m| { + let key = get_environment_manager_key(m); + detected_managers.insert(key); + managers.push(m.clone()); + }); + + result + .environments + .iter() + .for_each(|e| environments.push(e.clone())); + } + + if let Some(result) = self.filter_result( + get_conda_environments_from_environments_txt_that_have_not_been_discovered( + &self.discovered_environments, + &environments, + self.environment, + ), + ) { + result.managers.iter().for_each(|m| { + let key = get_environment_manager_key(m); + if !detected_managers.contains(&key) { + warn!("Found a new manager using the fallback mechanism: {:?}", m); + detected_managers.insert(key); + managers.push(m.clone()); + } + }); + + result.environments.iter().for_each(|e| { + warn!( + "Found a new conda environment using the fallback mechanism: {:?}", + e + ); + environments.push(e.clone()); + }); + } + + if managers.is_empty() && environments.is_empty() { + return None; } Some(LocatorResult { - managers: vec![manager], + managers, environments, }) } diff --git a/native_locator/src/conda_old.rs b/native_locator/src/conda_old.rs new file mode 100644 index 000000000000..9861c7b7a8e2 --- /dev/null +++ b/native_locator/src/conda_old.rs @@ -0,0 +1,452 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +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_python_binary_path; +use crate::utils::PythonEnv; +use regex::Regex; +use std::env; +use std::path::{Path, PathBuf}; + +/// relative to the interpreter. This layout is common on linux/Mac. +/// +/// ``` +/// env // <--- Input can be this path +/// |-- conda-meta // <--- Returns this directory +/// |-- bin // <--- Input can be this path +/// |-- python // <--- Input can be this path +/// ``` +#[cfg(unix)] +fn get_conda_meta_path(any_path: &Path) -> Option { + if any_path.ends_with("bin/python") { + match any_path.parent() { + Some(parent) => match parent.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + }, + None => None, + } + } else if any_path.ends_with("bin") { + match any_path.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + } + } else { + Some(any_path.to_path_buf().join("conda-meta")) + } +} + +/// Get the conda-meta directory. For windows 'conda-meta' is in the same directory as the interpreter. +/// This layout is common in Windows. +/// +/// ``` +/// env // <--- Input can be this path +/// |-- conda-meta // <--- Returns this directory +/// |-- python.exe // <--- Input can be this path +/// ``` +#[cfg(windows)] +fn get_conda_meta_path(any_path: &Path) -> Option { + if any_path.ends_with("python.exe") { + match any_path.parent() { + Some(parent) => Some(parent.to_path_buf().join("conda-meta")), + None => None, + } + } else { + Some(any_path.to_path_buf().join("conda-meta")) + } +} + +/// Check if a given path is a conda environment. A conda environment is a directory that contains +/// a 'conda-meta' directory as child. This will find 'conda-meta' in a platform agnostic way. +pub fn is_conda_environment(any_path: &Path) -> bool { + let conda_meta_path = get_conda_meta_path(any_path); + match conda_meta_path { + Some(path) => path.exists(), + None => false, + } +} + +struct CondaPackage { + path: PathBuf, + version: String, +} + +/// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. +fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { + let package_name = format!("{}-", package); + let conda_meta_path = get_conda_meta_path(any_path)?; + let regex = Regex::new(format!("^{}-((\\d+\\.*)*)-.*.json$", package).as_str()); + std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { + let path = entry.ok()?.path(); + let file_name = path.file_name()?.to_string_lossy(); + if file_name.starts_with(&package_name) && file_name.ends_with(".json") { + match regex.clone().ok()?.captures(&file_name)?.get(1) { + Some(version) => Some(CondaPackage { + path: path.clone(), + version: version.as_str().to_string(), + }), + None => None, + } + } else { + None + } + }) +} + +/// Checks if the `python` package is installed in the conda environment +#[allow(dead_code)] +pub fn is_python_conda_env(any_path: &Path) -> bool { + let conda_python_json_path = get_conda_package_json_path(any_path, "python"); + match conda_python_json_path { + Some(result) => result.path.exists(), + None => false, + } +} + +/// Get the version of the `python` package in the conda environment +pub fn get_conda_python_version(any_path: &Path) -> Option { + let conda_python_json_path = get_conda_package_json_path(any_path, "python"); + match conda_python_json_path { + Some(result) => Some(result.version.clone()), + None => None, + } +} + +/// Specifically returns the file names that are valid for 'conda' on windows +#[cfg(windows)] +fn get_conda_bin_names() -> Vec<&'static str> { + vec!["conda.exe", "conda.bat"] +} + +/// Specifically returns the file names that are valid for 'conda' on linux/Mac +#[cfg(unix)] +fn get_conda_bin_names() -> Vec<&'static str> { + vec!["conda"] +} + +/// Find the conda binary on the PATH environment variable +fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { + let paths = environment.get_env_var("PATH".to_string())?; + for path in env::split_paths(&paths) { + for bin in get_conda_bin_names() { + let conda_path = path.join(bin); + match std::fs::metadata(&conda_path) { + Ok(metadata) => { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); + } + } + Err(_) => (), + } + } + } + None +} + +#[cfg(windows)] +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { + let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); + let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); + let all_user_profile = environment + .get_env_var("ALLUSERSPROFILE".to_string()) + .unwrap(); + let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); + let mut known_paths = vec![ + Path::new(&user_profile).join("Anaconda3\\Scripts"), + Path::new(&program_data).join("Anaconda3\\Scripts"), + Path::new(&all_user_profile).join("Anaconda3\\Scripts"), + Path::new(&home_drive).join("Anaconda3\\Scripts"), + Path::new(&user_profile).join("Miniconda3\\Scripts"), + Path::new(&program_data).join("Miniconda3\\Scripts"), + Path::new(&all_user_profile).join("Miniconda3\\Scripts"), + Path::new(&home_drive).join("Miniconda3\\Scripts"), + ]; + known_paths.append(&mut environment.get_know_global_search_locations()); + known_paths +} + +#[cfg(unix)] +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { + let mut known_paths = vec![ + PathBuf::from("/opt/anaconda3/bin"), + PathBuf::from("/opt/miniconda3/bin"), + PathBuf::from("/usr/local/anaconda3/bin"), + PathBuf::from("/usr/local/miniconda3/bin"), + PathBuf::from("/usr/anaconda3/bin"), + PathBuf::from("/usr/miniconda3/bin"), + PathBuf::from("/home/anaconda3/bin"), + PathBuf::from("/home/miniconda3/bin"), + PathBuf::from("/anaconda3/bin"), + PathBuf::from("/miniconda3/bin"), + ]; + if let Some(home) = environment.get_user_home() { + known_paths.push(PathBuf::from(home.clone()).join("anaconda3/bin")); + known_paths.push(PathBuf::from(home).join("miniconda3/bin")); + } + known_paths.append(&mut environment.get_know_global_search_locations()); + known_paths +} + +/// Find conda binary in known locations +fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option { + let conda_bin_names = get_conda_bin_names(); + let known_locations = get_known_conda_locations(environment); + for location in known_locations { + for bin in &conda_bin_names { + let conda_path = location.join(bin); + if let Some(metadata) = std::fs::metadata(&conda_path).ok() { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); + } + } + } + } + None +} + +/// Find the conda binary on the system +pub fn find_conda_binary(environment: &dyn known::Environment) -> Option { + let conda_binary_on_path = find_conda_binary_on_path(environment); + match conda_binary_on_path { + Some(conda_binary_on_path) => Some(conda_binary_on_path), + None => find_conda_binary_in_known_locations(environment), + } +} + +pub fn get_conda_version(conda_binary: &PathBuf) -> Option { + let mut parent = conda_binary.parent()?; + if parent.ends_with("bin") { + parent = parent.parent()?; + } + if parent.ends_with("Library") { + parent = parent.parent()?; + } + match get_conda_package_json_path(&parent, "conda") { + Some(result) => Some(result.version), + None => match get_conda_package_json_path(&parent.parent()?, "conda") { + Some(result) => Some(result.version), + None => None, + }, + } +} + +fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { + let mut envs = vec![]; + let home = environment.get_user_home(); + match home { + Some(home) => { + let home = Path::new(&home); + let environment_txt = home.join(".conda").join("environments.txt"); + match std::fs::read_to_string(environment_txt) { + Ok(reader) => { + for line in reader.lines() { + envs.push(line.to_string()); + } + } + Err(_) => (), + } + } + None => (), + } + envs +} + +fn get_known_env_locations( + conda_bin: &PathBuf, + environment: &dyn known::Environment, +) -> Vec { + let mut paths = vec![]; + let home = environment.get_user_home(); + match home { + Some(home) => { + let home = Path::new(&home); + let conda_envs = home.join(".conda").join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + } + None => (), + } + + match conda_bin.parent() { + Some(parent) => { + paths.push(parent.to_string_lossy().to_string()); + let conda_envs = parent.join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + match parent.parent() { + Some(parent) => { + paths.push(parent.to_string_lossy().to_string()); + let conda_envs = parent.join("envs"); + paths.push(conda_envs.to_string_lossy().to_string()); + } + None => (), + } + } + None => (), + } + + paths +} + +fn get_conda_envs_from_known_env_locations( + conda_bin: &PathBuf, + environment: &dyn known::Environment, +) -> Vec { + let mut envs = vec![]; + for location in get_known_env_locations(conda_bin, environment) { + if is_conda_environment(&Path::new(&location)) { + envs.push(location.to_string()); + } + match std::fs::read_dir(location) { + Ok(reader) => { + for entry in reader { + match entry { + Ok(entry) => { + let metadata = entry.metadata(); + match metadata { + Ok(metadata) => { + if metadata.is_dir() { + let path = entry.path(); + if is_conda_environment(&path) { + envs.push(path.to_string_lossy().to_string()); + } + } + } + Err(_) => (), + } + } + Err(_) => (), + } + } + } + Err(_) => (), + } + } + envs +} + +struct CondaEnv { + named: bool, + name: String, + path: PathBuf, +} + +fn get_distinct_conda_envs( + conda_bin: &PathBuf, + environment: &dyn known::Environment, +) -> Vec { + let mut envs = get_conda_envs_from_environment_txt(environment); + let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); + envs.append(&mut known_envs); + envs.sort(); + envs.dedup(); + + let locations = get_known_env_locations(conda_bin, environment); + let mut conda_envs = vec![]; + for env in envs { + let env = Path::new(&env); + let mut named = false; + let mut name = "".to_string(); + for location in &locations { + let location = Path::new(location).join("envs"); + match env.strip_prefix(location) { + Ok(prefix) => { + named = true; + name = match prefix.to_str() { + Some(name) => { + let name = name.to_string(); + if name == "" { + "base".to_string() + } else { + name.to_string() + } + } + None => "base".to_string(), + }; + break; + } + Err(_) => (), + } + } + conda_envs.push(CondaEnv { + named, + name, + path: PathBuf::from(env), + }); + } + conda_envs +} + +pub struct Conda<'a> { + pub manager: Option, + pub environment: &'a dyn Environment, +} + +impl Conda<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> Conda { + Conda { + environment, + manager: None, + } + } +} + +impl Locator for Conda<'_> { + fn resolve(&self, _env: &PythonEnv) -> Option { + // We will find everything in gather + None + } + + fn find(&mut self) -> Option { + let conda_binary = find_conda_binary(self.environment)?; + let manager = EnvManager::new( + conda_binary.clone(), + get_conda_version(&conda_binary), + EnvManagerType::Conda, + ); + + let envs = get_distinct_conda_envs(&conda_binary, self.environment); + let mut environments: Vec = vec![]; + for env in envs { + let executable = find_python_binary_path(Path::new(&env.path)); + let env = messaging::PythonEnvironment::new( + Some(env.name.to_string()), + executable.clone(), + messaging::PythonEnvironmentCategory::Conda, + get_conda_python_version(&env.path), + Some(env.path.clone()), + Some(env.path.clone()), + Some(manager.clone()), + if env.named { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-n".to_string(), + env.name.to_string(), + "python".to_string(), + ]) + } else { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-p".to_string(), + env.path.to_string_lossy().to_string(), + "python".to_string(), + ]) + }, + ); + + environments.push(env) + } + + Some(LocatorResult { + managers: vec![manager], + environments, + }) + } +} diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 9111f5d9699d..36a8cfb8f46d 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -38,7 +38,7 @@ impl Locator for Homebrew<'_> { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let homebrew_prefix = self .environment .get_env_var("HOMEBREW_PREFIX".to_string())?; diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index ee72ef0591a7..18d529a80564 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -23,5 +23,5 @@ pub trait Locator { /** * Finds all environments specific to this locator. */ - fn find(&self) -> Option; + fn find(&mut self) -> Option; } diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 56b6dc65e237..3620fe284bda 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -24,8 +24,8 @@ mod utils; mod venv; mod virtualenv; mod virtualenvwrapper; -mod windows_store; mod windows_registry; +mod windows_store; fn main() { let environment = EnvironmentApi {}; @@ -39,27 +39,27 @@ fn main() { let venv_locator = venv::Venv::new(); let virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); let pipenv_locator = pipenv::PipEnv::new(); - let path_locator = common_python::PythonOnPath::with(&environment); - let pyenv_locator = pyenv::PyEnv::with(&environment); + let mut path_locator = common_python::PythonOnPath::with(&environment); + let mut conda_locator = conda::Conda::with(&environment); + let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); #[cfg(unix)] - let homebrew_locator = homebrew::Homebrew::with(&environment); + let mut homebrew_locator = homebrew::Homebrew::with(&environment); #[cfg(windows)] - let windows_store = windows_store::WindowsStore::with(&environment); + let mut windows_store = windows_store::WindowsStore::with(&environment); #[cfg(windows)] - let windows_registry = windows_registry::WindowsRegistry::new(); - let conda_locator = conda::Conda::with(&environment); + let mut windows_registry = windows_registry::WindowsRegistry::new(); // Step 1: These environments take precedence over all others. // As they are very specific and guaranteed to be specific type. - find_environments(&pyenv_locator, &mut dispatcher); + find_environments(&mut pyenv_locator, &mut dispatcher); #[cfg(unix)] - find_environments(&homebrew_locator, &mut dispatcher); - find_environments(&conda_locator, &mut dispatcher); + find_environments(&mut homebrew_locator, &mut dispatcher); + find_environments(&mut conda_locator, &mut dispatcher); #[cfg(windows)] - find_environments(&windows_registry, &mut dispatcher); + find_environments(&mut windows_registry, &mut dispatcher); #[cfg(windows)] - find_environments(&windows_store, &mut dispatcher); + find_environments(&mut windows_store, &mut dispatcher); // Step 2: Search in some global locations. for env in list_global_virtual_envs(&environment).iter() { @@ -74,7 +74,7 @@ fn main() { } // Step 3: Finally find in the current PATH variable - find_environments(&path_locator, &mut dispatcher); + find_environments(&mut path_locator, &mut dispatcher); match now.elapsed() { Ok(elapsed) => { @@ -100,7 +100,7 @@ fn resolve_environment( false } -fn find_environments(locator: &dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { +fn find_environments(locator: &mut dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { if let Some(result) = locator.find() { result .environments diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index b39cd19bb5b6..dddcb6e1b595 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -3,7 +3,7 @@ use crate::{ logging::{LogLevel, LogMessage}, - utils::PythonEnv, + utils::{get_environment_key, get_environment_manager_key, PythonEnv}, }; use env_logger::Builder; use log::LevelFilter; @@ -237,11 +237,10 @@ impl MessageDispatcher for JsonRpcDispatcher { } fn report_environment_manager(&mut self, env: EnvManager) -> () { - if let Some(key) = get_manager_key(&env) { - if !self.reported_managers.contains(&key) { - self.reported_managers.insert(key); - send_message(EnvManagerMessage::new(env)); - } + 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) -> () { @@ -266,17 +265,3 @@ pub fn create_dispatcher() -> JsonRpcDispatcher { reported_environments: HashSet::new(), } } - -fn get_environment_key(env: &PythonEnvironment) -> Option { - match env.python_executable_path.clone() { - Some(key) => Some(key.as_os_str().to_str()?.to_string()), - None => match env.env_path.clone() { - Some(key) => Some(key.as_os_str().to_str().unwrap().to_string()), - None => None, - }, - } -} - -fn get_manager_key(manager: &EnvManager) -> Option { - Some(manager.executable_path.to_str()?.to_string()) -} diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 3e9de48ff13a..0acd00893310 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -42,7 +42,7 @@ impl Locator for PipEnv { )) } - fn find(&self) -> Option { + fn find(&mut self) -> Option { None } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index fb48615b7464..5832718b4c44 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::conda::CondaLocator; use crate::known; use crate::known::Environment; use crate::locator::Locator; @@ -119,6 +120,16 @@ fn get_pure_python_environment( )) } +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, @@ -145,6 +156,7 @@ fn get_virtual_env_environment( pub fn list_pyenv_environments( manager: &Option, environment: &dyn known::Environment, + conda_locator: &mut dyn CondaLocator, ) -> Option> { let pyenv_dir = get_pyenv_dir(environment)?; let mut envs: Vec = vec![]; @@ -161,12 +173,16 @@ pub fn list_pyenv_environments( continue; } if let Some(executable) = find_python_binary_path(&path) { - match get_pure_python_environment(&executable, &path, manager) { - Some(env) => envs.push(env), - None => match get_virtual_env_environment(&executable, &path, manager) { - Some(env) => envs.push(env), - None => (), - }, + 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()); + }); + } } } } @@ -177,11 +193,18 @@ pub fn list_pyenv_environments( 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) -> PyEnv { - PyEnv { environment } + pub fn with<'a>( + environment: &'a impl Environment, + conda_locator: &'a mut impl CondaLocator, + ) -> PyEnv<'a> { + PyEnv { + environment, + conda_locator, + } } } @@ -191,11 +214,13 @@ impl Locator for PyEnv<'_> { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let pyenv_binary = get_pyenv_binary(self.environment)?; let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); let mut environments: Vec = vec![]; - if let Some(envs) = list_pyenv_environments(&Some(manager.clone()), self.environment) { + if let Some(envs) = + list_pyenv_environments(&Some(manager.clone()), self.environment, self.conda_locator) + { for env in envs { environments.push(env); } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 26aa68566019..b0cfb0e6e412 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::messaging::{EnvManager, PythonEnvironment}; use regex::Regex; use std::{ fs, @@ -147,3 +148,18 @@ pub fn list_python_environments(path: &PathBuf) -> Option> { Some(python_envs) } + +pub fn get_environment_key(env: &PythonEnvironment) -> Option { + 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(); +} diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 24c490acafad..05ddaf7f7522 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -48,7 +48,7 @@ impl Locator for Venv { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 49926f14c0ad..2268a8253b06 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -79,7 +79,7 @@ impl Locator for VirtualEnv { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 1c679171c903..9e5d28e9f445 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -102,7 +102,7 @@ impl Locator for VirtualEnvWrapper<'_> { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let work_on_home = get_work_on_home_path(self.environment)?; let envs = list_python_environments(&work_on_home)?; let mut environments: Vec = vec![]; diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index a26362a6f9e7..ef886b28dbdd 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -74,7 +74,7 @@ impl Locator for WindowsRegistry { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let environments = get_registry_pythons("PythonCore")?; if environments.is_empty() { None diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index 3c31673a1193..2f2a0c2f81ce 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -69,7 +69,7 @@ impl Locator for WindowsStore<'_> { None } - fn find(&self) -> Option { + fn find(&mut self) -> Option { let mut environments: Vec = vec![]; if let Some(envs) = list_windows_store_python_executables(self.environment) { envs.iter().for_each(|env| { diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 01e765d23c0d..a21f3349f38c 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -26,8 +26,7 @@ fn find_python_in_path_this() { Vec::new(), ); - let locator = common_python::PythonOnPath::with(&known); - locator.find(); + let mut locator = common_python::PythonOnPath::with(&known); let result = locator.find(); let environments = get_environments_from_result(&result); diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 3d3a296e52b4..77fed4a2393d 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -16,7 +16,7 @@ fn does_not_find_any_conda_envs() { Vec::new(), ); - let locator = conda::Conda::with(&known); + let mut locator = conda::Conda::with(&known); let result = locator.find(); let environments = get_environments_from_result(&result); @@ -33,7 +33,8 @@ fn find_conda_exe_and_empty_envs() { use python_finder::messaging::{EnvManager, EnvManagerType}; use python_finder::{conda, locator::Locator}; use serde_json::json; - use std::{collections::HashMap, path::PathBuf}; + use std::collections::HashMap; + let user_home = test_file_path(&["tests/unix/conda_without_envs"]); let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); let known = create_test_environment( @@ -41,20 +42,24 @@ fn find_conda_exe_and_empty_envs() { "PATH".to_string(), conda_dir.clone().to_str().unwrap().to_string(), )]), - Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), + Some(user_home), Vec::new(), ); - let locator = conda::Conda::with(&known); + let mut locator = conda::Conda::with(&known); let result = locator.find(); - let managers = get_managers_from_result(&result); assert_eq!(managers.len(), 1); - let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); + 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: None, + version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; assert_messages( @@ -75,7 +80,8 @@ fn finds_two_conda_envs_from_txt() { use std::collections::HashMap; use std::fs; - let conda_dir = test_file_path(&["tests/unix/conda"]); + let home = test_file_path(&["tests/unix/conda"]); + let conda_dir = test_file_path(&["tests/unix/conda/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 _ = fs::write( @@ -92,24 +98,24 @@ fn finds_two_conda_envs_from_txt() { "PATH".to_string(), conda_dir.clone().to_str().unwrap().to_string(), )]), - Some(conda_dir.clone()), + Some(home), Vec::new(), ); - let locator = conda::Conda::with(&known); + let mut locator = conda::Conda::with(&known); let result = locator.find(); let managers = get_managers_from_result(&result); let environments = get_environments_from_result(&result); assert_eq!(managers.len(), 1); - let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); + 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: None, + version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; let expected_conda_1 = PythonEnvironment { diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index a63615a68ef1..45df29031fb7 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -9,7 +9,7 @@ fn does_not_find_any_pyenv_envs() { use crate::common::{ create_test_environment, get_environments_from_result, get_managers_from_result, }; - use python_finder::{locator::Locator, pyenv}; + use python_finder::{conda::Conda, locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; let known = create_test_environment( @@ -18,8 +18,8 @@ fn does_not_find_any_pyenv_envs() { Vec::new(), ); - let locator = pyenv::PyEnv::with(&known); - locator.find(); + let mut conda = Conda::with(&known); + let mut locator = pyenv::PyEnv::with(&known, &mut conda); let result = locator.find(); assert_eq!(get_managers_from_result(&result).len(), 0); @@ -33,8 +33,8 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { assert_messages, create_test_environment, get_managers_from_result, join_test_paths, test_file_path, }; - use python_finder::locator::Locator; use python_finder::pyenv; + use python_finder::{conda::Conda, locator::Locator}; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; @@ -47,7 +47,8 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { vec![PathBuf::from(homebrew_bin)], ); - let locator = pyenv::PyEnv::with(&known); + let mut conda = Conda::with(&known); + let mut locator = pyenv::PyEnv::with(&known, &mut conda); let result = locator.find(); let managers = get_managers_from_result(&result); @@ -67,6 +68,7 @@ fn find_pyenv_envs() { assert_messages, create_test_environment, get_environments_from_result, get_managers_from_result, join_test_paths, test_file_path, }; + use python_finder::conda::Conda; use python_finder::locator::Locator; use python_finder::{ messaging::{EnvManager, EnvManagerType, PythonEnvironment}, @@ -84,7 +86,8 @@ fn find_pyenv_envs() { vec![PathBuf::from(homebrew_bin)], ); - let locator = pyenv::PyEnv::with(&known); + let mut conda = Conda::with(&known); + let mut locator = pyenv::PyEnv::with(&known, &mut conda); let result = locator.find(); let managers = get_managers_from_result(&result); diff --git a/native_locator/tests/unix/conda/.conda/environments.txt b/native_locator/tests/unix/conda/.conda/environments.txt index 908019719b55..3a9e625c3050 100644 --- a/native_locator/tests/unix/conda/.conda/environments.txt +++ b/native_locator/tests/unix/conda/.conda/environments.txt @@ -1,2 +1,2 @@ -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/envs/one -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/envs/two \ No newline at end of file +/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/one +/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/two \ No newline at end of file diff --git a/native_locator/tests/unix/conda/conda b/native_locator/tests/unix/conda/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/conda/conda rename to native_locator/tests/unix/conda/anaconda3/bin/conda diff --git a/native_locator/tests/unix/conda/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda/envs/one/python b/native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/envs/one/python rename to native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json new file mode 100644 index 000000000000..23127993ac05 --- /dev/null +++ b/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json @@ -0,0 +1 @@ +10.1.1 diff --git a/native_locator/tests/unix/conda/envs/two/python b/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/envs/two/python rename to native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda_without_envs/conda b/native_locator/tests/unix/conda/anaconda3/envs/one/python similarity index 100% rename from native_locator/tests/unix/conda_without_envs/conda rename to native_locator/tests/unix/conda/anaconda3/envs/one/python diff --git a/native_locator/tests/unix/conda/anaconda3/envs/two/python b/native_locator/tests/unix/conda/anaconda3/envs/two/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda b/native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json new file mode 100644 index 000000000000..23127993ac05 --- /dev/null +++ b/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json @@ -0,0 +1 @@ +10.1.1 diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 95bf67262f53..4ec6723705f8 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -104,14 +104,16 @@ export class NativeLocator implements ILocator, IDisposable { promise.finally(() => disposable.dispose()); disposables.push( this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { - envs.push({ - kind: categoryToKind(data.category), - // TODO: What if executable is undefined? - executablePath: data.pythonExecutablePath!, - envPath: data.envPath, - version: parseVersion(data.version), - name: data.name === '' ? undefined : data.name, - }); + // TODO: What if executable is undefined? + if (data.pythonExecutablePath) { + envs.push({ + kind: categoryToKind(data.category), + executablePath: data.pythonExecutablePath, + envPath: data.envPath, + version: parseVersion(data.version), + name: data.name === '' ? undefined : data.name, + }); + } }), this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => { switch (toolToKnownEnvironmentTool(data.tool)) { From a6214e29df266d3bef699852e61069155e945138 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 14 May 2024 13:04:37 +1000 Subject: [PATCH 043/106] Fix winreg locator & add conda registry locator on windows (#23422) --- native_locator/src/common_python.rs | 1 + native_locator/src/conda.rs | 3 + native_locator/src/conda_old.rs | 1 + native_locator/src/homebrew.rs | 1 + native_locator/src/main.rs | 8 +- native_locator/src/messaging.rs | 4 + native_locator/src/pyenv.rs | 2 + native_locator/src/venv.rs | 1 + native_locator/src/virtualenv.rs | 1 + native_locator/src/virtualenvwrapper.rs | 1 + native_locator/src/windows_registry.rs | 130 ++++++++++++++++----- native_locator/src/windows_store.rs | 1 + native_locator/tests/common_python_test.rs | 1 + native_locator/tests/conda_test.rs | 8 +- native_locator/tests/pyenv_test.rs | 25 ++-- 15 files changed, 139 insertions(+), 49 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 039965bae502..76ce116ea995 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -38,6 +38,7 @@ impl Locator for PythonOnPath<'_> { return None; } Some(PythonEnvironment { + display_name: None, name: None, python_executable_path: Some(env.executable.clone()), version: env.version.clone(), diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 65e0fd073292..8f6ae6f8b507 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -549,6 +549,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

{ for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); let env = messaging::PythonEnvironment::new( + None, Some(env.name.to_string()), executable.clone(), messaging::PythonEnvironmentCategory::Conda, diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 36a8cfb8f46d..34a02fe9845f 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -61,6 +61,7 @@ impl Locator for Homebrew<'_> { } reported.insert(exe.to_string_lossy().to_string()); let env = crate::messaging::PythonEnvironment::new( + None, None, Some(exe.clone()), crate::messaging::PythonEnvironmentCategory::Homebrew, diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 3620fe284bda..ee976bf756d2 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -41,24 +41,24 @@ fn main() { let pipenv_locator = pipenv::PipEnv::new(); let mut path_locator = common_python::PythonOnPath::with(&environment); let mut conda_locator = conda::Conda::with(&environment); - let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); #[cfg(unix)] let mut homebrew_locator = homebrew::Homebrew::with(&environment); #[cfg(windows)] let mut windows_store = windows_store::WindowsStore::with(&environment); #[cfg(windows)] - let mut windows_registry = windows_registry::WindowsRegistry::new(); + let mut windows_registry = windows_registry::WindowsRegistry::with(&mut conda_locator); // Step 1: These environments take precedence over all others. // As they are very specific and guaranteed to be specific type. + #[cfg(windows)] + find_environments(&mut windows_registry, &mut dispatcher); + let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); find_environments(&mut pyenv_locator, &mut dispatcher); #[cfg(unix)] find_environments(&mut homebrew_locator, &mut dispatcher); find_environments(&mut conda_locator, &mut dispatcher); #[cfg(windows)] - find_environments(&mut windows_registry, &mut dispatcher); - #[cfg(windows)] find_environments(&mut windows_store, &mut dispatcher); // Step 2: Search in some global locations. diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index dddcb6e1b595..b79c5fa172ca 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -94,6 +94,7 @@ pub enum PythonEnvironmentCategory { #[serde(rename_all = "camelCase")] #[derive(Debug)] pub struct PythonEnvironment { + pub display_name: Option, pub name: Option, pub python_executable_path: Option, pub category: PythonEnvironmentCategory, @@ -110,6 +111,7 @@ pub struct PythonEnvironment { impl PythonEnvironment { pub fn new( + display_name: Option, name: Option, python_executable_path: Option, category: PythonEnvironmentCategory, @@ -120,6 +122,7 @@ impl PythonEnvironment { python_run_command: Option>, ) -> Self { Self { + display_name, name, python_executable_path, category, @@ -140,6 +143,7 @@ impl PythonEnvironment { project_path: PathBuf, ) -> Self { Self { + display_name: None, name: None, python_executable_path: python_executable_path.clone(), category: PythonEnvironmentCategory::Pipenv, diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 5832718b4c44..a94d63bc4751 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -105,6 +105,7 @@ fn get_pure_python_environment( ) -> Option { let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?; Some(messaging::PythonEnvironment::new( + None, None, Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, @@ -138,6 +139,7 @@ fn get_virtual_env_environment( 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, diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 05ddaf7f7522..9e0161a6df7d 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -26,6 +26,7 @@ impl Locator for Venv { fn resolve(&self, env: &PythonEnv) -> Option { if is_venv(&env) { return Some(PythonEnvironment { + display_name: None, name: Some( env.path .clone() diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 2268a8253b06..04c10e0e0d25 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -57,6 +57,7 @@ impl Locator for VirtualEnv { fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenv(env) { return Some(PythonEnvironment { + display_name: None, name: Some( env.path .clone() diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 9e5d28e9f445..856f6a78d537 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -80,6 +80,7 @@ impl Locator for VirtualEnvWrapper<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenvwrapper(env, self.environment) { return Some(PythonEnvironment { + display_name: None, name: Some( env.path .clone() diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index ef886b28dbdd..279ef46d173e 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -1,16 +1,20 @@ // 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 winreg::RegKey; -#[cfg(windows)] use std::path::PathBuf; +#[cfg(windows)] +use winreg::RegKey; #[cfg(windows)] fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option> { @@ -19,23 +23,47 @@ fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option 0 { + Some(version) + } else { + None + }, + env_path, + None, + None, + Some(vec![executable.to_string_lossy().to_string()]), + ); + pythons.push(env); + } + } } Some(pythons) @@ -53,39 +81,77 @@ pub fn get_registry_pythons(company: &str) -> Option> { if let Some(hkcu_pythons) = get_registry_pythons_from_key(&hkcu, company) { pythons.extend(hkcu_pythons); } - Some(pythons) } #[cfg(windows)] -pub struct WindowsRegistry {} +pub fn get_registry_pythons_anaconda(conda_locator: &mut dyn CondaLocator) -> LocatorResult { + let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + + let mut pythons = vec![]; + if let Some(hklm_pythons) = get_registry_pythons_from_key(&hklm, "ContinuumAnalytics") { + pythons.extend(hklm_pythons); + } + if let Some(hkcu_pythons) = get_registry_pythons_from_key(&hkcu, "ContinuumAnalytics") { + pythons.extend(hkcu_pythons); + } + + let mut environments: Vec = vec![]; + let mut managers: Vec = vec![]; + + for env in pythons.iter() { + if let Some(env_path) = env.clone().env_path { + if let Some(mut result) = conda_locator.find_in(&env_path) { + environments.append(&mut result.environments); + managers.append(&mut result.managers); + } + } + } + + LocatorResult { + managers, + environments, + } +} #[cfg(windows)] -impl WindowsRegistry { +pub struct WindowsRegistry<'a> { + pub conda_locator: &'a mut dyn CondaLocator, +} + +#[cfg(windows)] +impl WindowsRegistry<'_> { #[allow(dead_code)] - pub fn new() -> WindowsRegistry { - WindowsRegistry {} + 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 { +impl Locator for WindowsRegistry<'_> { + fn resolve(&self, _env: &PythonEnv) -> Option { None } fn find(&mut self) -> Option { - let environments = get_registry_pythons("PythonCore")?; - if environments.is_empty() { + let mut environments: Vec = vec![]; + let mut managers: Vec = vec![]; + + let mut result = get_registry_pythons("PythonCore").unwrap_or_default(); + environments.append(&mut result); + + let mut result = get_registry_pythons_anaconda(self.conda_locator) ; + environments.append(&mut result.environments); + managers.append(&mut result.managers); + + if environments.is_empty() && managers.is_empty() { None } else { Some(LocatorResult { - managers: vec![], + managers, environments, }) } } } - -// PythonCore -// ContinuumAnalytics diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index 2f2a0c2f81ce..5a0fd7b6ebd0 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -55,6 +55,7 @@ impl Locator for WindowsStore<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { return Some(PythonEnvironment { + display_name: None, name: None, python_executable_path: Some(env.executable.clone()), version: None, diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index a21f3349f38c..06ed5ff7811d 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -33,6 +33,7 @@ fn find_python_in_path_this() { assert_eq!(environments.len(), 1); let env = PythonEnvironment { + display_name: None, env_manager: None, project_path: None, name: None, diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 77fed4a2393d..fb3d59e59b5d 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -119,6 +119,7 @@ fn finds_two_conda_envs_from_txt() { tool: EnvManagerType::Conda, }; let expected_conda_1 = PythonEnvironment { + display_name: None, name: Some("one".to_string()), project_path: None, python_executable_path: Some(conda_1_exe.clone()), @@ -133,9 +134,10 @@ fn finds_two_conda_envs_from_txt() { "-n".to_string(), "one".to_string(), "python".to_string(), - ]), - }; - let expected_conda_2 = PythonEnvironment { + ]), + }; + let expected_conda_2 = PythonEnvironment { + display_name: None, name: Some("two".to_string()), project_path: None, python_executable_path: Some(conda_2_exe.clone()), diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 45df29031fb7..7ad6904867fa 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -105,6 +105,7 @@ fn find_pyenv_envs() { ); let expected_3_9_9 = json!(PythonEnvironment { + display_name: None, project_path: None, name: None, python_executable_path: Some(join_test_paths(&[ @@ -123,20 +124,21 @@ fn find_pyenv_envs() { env_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/3.9.9" - ])), - sys_prefix_path: Some(join_test_paths(&[ + ])), + sys_prefix_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/3.9.9" ])), env_manager: Some(expected_manager.clone()) }); 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", @@ -153,16 +155,17 @@ fn find_pyenv_envs() { sys_prefix_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/my-virtual-env", - ])), - env_manager: Some(expected_manager.clone()), - }; - let expected_3_12_1 = PythonEnvironment { + ])), + env_manager: Some(expected_manager.clone()), + }; + 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", @@ -179,10 +182,11 @@ fn find_pyenv_envs() { sys_prefix_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/3.12.1", - ])), + ])), env_manager: Some(expected_manager.clone()), }; let expected_3_13_dev = PythonEnvironment { + display_name: None, project_path: None, name: None, python_executable_path: Some(join_test_paths(&[ @@ -205,10 +209,11 @@ fn find_pyenv_envs() { sys_prefix_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/3.13-dev", - ])), + ])), env_manager: Some(expected_manager.clone()), }; let expected_3_12_1a3 = PythonEnvironment { + display_name: None, project_path: None, name: None, python_executable_path: Some(join_test_paths(&[ From 22496cc17344974440ad467ef83d9af44368dbb2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 14 May 2024 14:30:53 +1000 Subject: [PATCH 044/106] Updates to the native windows store locator (#23426) --- native_locator/src/common_python.rs | 12 +++- native_locator/src/windows_store.rs | 101 +++++++++++++++++++++++----- 2 files changed, 95 insertions(+), 18 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 76ce116ea995..f3b7fbbe20c4 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -6,7 +6,7 @@ use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::{self, PythonEnv}; use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; fn get_env_path(python_executable_path: &PathBuf) -> Option { let parent = python_executable_path.parent()?; @@ -58,8 +58,18 @@ impl Locator for PythonOnPath<'_> { } 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 = 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| { diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index 5a0fd7b6ebd0..85f67ffe503e 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -1,49 +1,122 @@ // 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 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)] fn list_windows_store_python_executables( environment: &dyn known::Environment, -) -> Option> { - let mut python_envs: Vec = vec![]; +) -> Option> { + let mut python_envs: Vec = vec![]; let home = environment.get_user_home()?; let apps_path = Path::new(&home) .join("AppData") .join("Local") .join("Microsoft") .join("WindowsApps"); - for file in std::fs::read_dir(apps_path).ok()? { - match file { - Ok(file) => { - let path = file.path(); - if path.is_file() && is_windows_python_executable(&path) { - python_envs.push(path); + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + for file in std::fs::read_dir(apps_path).ok()?.filter_map(Result::ok) { + let path = file.path(); + if let Some(name) = path.file_name() { + let exe = path.join("python.exe"); + if name + .to_str() + .unwrap_or_default() + .starts_with("PythonSoftwareFoundation.Python.") + && exe.is_file() + && exe.exists() + { + if let Some(result) = + get_package_display_name_and_location(name.to_string_lossy().to_string(), &hkcu) + { + let env = PythonEnvironment { + display_name: Some(result.display_name), + name: None, + python_executable_path: Some(exe.clone()), + version: result.version, + category: crate::messaging::PythonEnvironmentCategory::WindowsStore, + sys_prefix_path: Some(PathBuf::from(result.env_path.clone())), + env_path: Some(PathBuf::from(result.env_path.clone())), + env_manager: None, + project_path: None, + python_run_command: Some(vec![exe.to_string_lossy().to_string()]), + }; + python_envs.push(env); } } - Err(_) => {} } } Some(python_envs) } +#[cfg(windows)] +fn get_package_full_name_from_registry(name: String, hkcu: &RegKey) -> Option { + let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\SystemAppData\\{}\\Schemas", name); + let package_key = hkcu.open_subkey(key).ok()?; + let value = package_key.get_value("PackageFullName").unwrap_or_default(); + Some(value) +} + +#[derive(Debug)] +#[cfg(windows)] +struct StorePythonInfo { + display_name: String, + version: Option, + env_path: String, +} + +#[cfg(windows)] +fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option { + 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); + 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()?; + + let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?; + let version = match regex.captures(&name)?.get(1) { + Some(version) => Some(version.as_str().to_string()), + None => None, + }; + + return Some(StorePythonInfo { + display_name, + version, + env_path, + }); + } + 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 { @@ -51,6 +124,7 @@ impl WindowsStore<'_> { } } +#[cfg(windows)] impl Locator for WindowsStore<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { @@ -71,14 +145,7 @@ impl Locator for WindowsStore<'_> { } fn find(&mut self) -> Option { - let mut environments: Vec = vec![]; - if let Some(envs) = list_windows_store_python_executables(self.environment) { - envs.iter().for_each(|env| { - if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { - environments.push(env); - } - }); - } + let environments = list_windows_store_python_executables(self.environment)?; if environments.is_empty() { None From 79e1423b6c911097fd74756a868273197c5bc046 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 13 May 2024 22:49:28 -0700 Subject: [PATCH 045/106] Add more logging and some tweaks (#23425) --- native_locator/Cargo.toml | 3 +- src/client/common/utils/async.ts | 1 + .../base/info/environmentInfoService.ts | 23 ++++++++++++ .../composite/envsCollectionService.ts | 17 +++++++-- .../locators/lowLevel/activeStateLocator.ts | 8 ++-- .../base/locators/lowLevel/condaLocator.ts | 7 +++- .../lowLevel/customVirtualEnvLocator.ts | 7 +++- .../globalVirtualEnvronmentLocator.ts | 7 +++- .../lowLevel/microsoftStoreLocator.ts | 8 ++-- .../base/locators/lowLevel/nativeLocator.ts | 13 ++++++- .../lowLevel/posixKnownPathsLocator.ts | 10 +++-- .../base/locators/lowLevel/pyenvLocator.ts | 37 +++++++++++-------- .../lowLevel/windowsKnownPathsLocator.ts | 8 ++-- .../lowLevel/windowsRegistryLocator.ts | 13 ++++--- 14 files changed, 118 insertions(+), 44 deletions(-) diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml index 4814adf1044c..c6ae0f27c36f 100644 --- a/native_locator/Cargo.toml +++ b/native_locator/Cargo.toml @@ -12,7 +12,8 @@ serde_json = "1.0.93" serde_repr = "0.1.10" regex = "1.10.4" log = "0.4.21" -env_logger = "0.11.3" +env_logger = "0.10.2" + [lib] doctest = false diff --git a/src/client/common/utils/async.ts b/src/client/common/utils/async.ts index c119d8f19b06..a99db8e94562 100644 --- a/src/client/common/utils/async.ts +++ b/src/client/common/utils/async.ts @@ -155,6 +155,7 @@ export async function* chain( ): IAsyncIterableIterator { const promises = iterators.map(getNext); let numRunning = iterators.length; + while (numRunning > 0) { // Promise.race will not fail, because each promise calls getNext, // Which handles failures by wrapping each iterator in a try/catch block. diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index 6a981d21b6df..251834b29683 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -106,6 +106,13 @@ class EnvironmentInfoService implements IEnvironmentInfoService { } const deferred = createDeferred(); + const info = EnvironmentInfoService.getInterpreterInfo(env); + if (info !== undefined) { + this.cache.set(normCasePath(interpreterPath), deferred); + deferred.resolve(info); + return info; + } + this.cache.set(normCasePath(interpreterPath), deferred); this._getEnvironmentInfo(env, priority) .then((r) => { @@ -205,6 +212,22 @@ class EnvironmentInfoService implements IEnvironmentInfoService { } }); } + + private static getInterpreterInfo(env: PythonEnvInfo): InterpreterInformation | undefined { + if (env.version.major > -1 && env.version.minor > -1 && env.version.micro > -1 && env.location) { + return { + arch: env.arch, + executable: { + filename: env.executable.filename, + ctime: -1, + mtime: -1, + sysPrefix: env.location, + }, + version: env.version, + }; + } + return undefined; + } } function addToQueue( diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index a54489e34633..30da89c87628 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -5,7 +5,7 @@ import { Event, EventEmitter } from 'vscode'; import '../../../../common/extensions'; import { createDeferred, Deferred } from '../../../../common/utils/async'; import { StopWatch } from '../../../../common/utils/stopWatch'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { normalizePath } from '../../../common/externalDependencies'; @@ -107,14 +107,18 @@ export class EnvsCollectionService extends PythonEnvsWatcher { - const stopWatch = new StopWatch(); let refreshPromise = this.getRefreshPromiseForQuery(query); if (!refreshPromise) { if (options?.ifNotTriggerredAlready && this.hasRefreshFinished(query)) { // Do not trigger another refresh if a refresh has previously finished. return Promise.resolve(); } - refreshPromise = this.startRefresh(query).then(() => this.sendTelemetry(query, stopWatch)); + const stopWatch = new StopWatch(); + traceInfo(`Starting Environment refresh`); + refreshPromise = this.startRefresh(query).then(() => { + this.sendTelemetry(query, stopWatch); + traceInfo(`Environment refresh took ${stopWatch.elapsedTime} milliseconds`); + }); } return refreshPromise; } @@ -139,7 +143,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher(); - + const stopWatch = new StopWatch(); if (iterator.onUpdated !== undefined) { const listener = iterator.onUpdated(async (event) => { if (isProgressEvent(event)) { @@ -147,9 +151,13 @@ export class EnvsCollectionService extends PythonEnvsWatcher { + const stopWatch = new StopWatch(); const state = await ActiveState.getState(); if (state === undefined) { traceVerbose(`Couldn't locate the state binary.`); return; } - traceVerbose(`Searching for active state environments`); + traceInfo(`Searching for active state environments`); const projects = await state.getProjects(); if (projects === undefined) { traceVerbose(`Couldn't fetch State Tool projects.`); @@ -41,6 +43,6 @@ export class ActiveStateLocator extends LazyResourceBasedLocator { } } } - traceVerbose(`Finished searching for active state environments`); + traceInfo(`Finished searching for active state environments: ${stopWatch.elapsedTime} milliseconds`); } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts index a58bfdd65b2c..bb48ba75b9dd 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts @@ -4,8 +4,9 @@ import '../../../../common/extensions'; import { PythonEnvKind } from '../../info'; import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManagers/conda'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; import { FSWatchingLocator } from './fsWatchingLocator'; +import { StopWatch } from '../../../../common/utils/stopWatch'; export class CondaEnvironmentLocator extends FSWatchingLocator { public readonly providerId: string = 'conda-envs'; @@ -20,6 +21,8 @@ export class CondaEnvironmentLocator extends FSWatchingLocator { // eslint-disable-next-line class-methods-use-this public async *doIterEnvs(_: unknown): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for conda environments'); const conda = await Conda.getConda(); if (conda === undefined) { traceVerbose(`Couldn't locate the conda binary.`); @@ -38,6 +41,6 @@ export class CondaEnvironmentLocator extends FSWatchingLocator { traceError(`Failed to process conda env: ${JSON.stringify(env)}`, ex); } } - traceVerbose(`Finished searching for conda environments`); + traceInfo(`Finished searching for conda environments: ${stopWatch.elapsedTime} milliseconds`); } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts index e4daeee640c9..ae74d2f3e189 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -23,7 +23,8 @@ import { } from '../../../common/environmentManagers/simplevirtualenvs'; import '../../../../common/extensions'; import { asyncFilter } from '../../../../common/utils/arrayUtils'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; /** * Default number of levels of sub-directories to recurse when looking for interpreters. */ @@ -99,6 +100,8 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { // eslint-disable-next-line class-methods-use-this protected doIterEnvs(): IPythonEnvsIterator { async function* iterator() { + const stopWatch = new StopWatch(); + traceInfo('Searching for custom virtual environments'); const envRootDirs = await getCustomVirtualEnvDirs(); const envGenerators = envRootDirs.map((envRootDir) => { async function* generator() { @@ -132,7 +135,7 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { }); yield* iterable(chain(envGenerators)); - traceVerbose(`Finished searching for custom virtual envs`); + traceInfo(`Finished searching for custom virtual envs: ${stopWatch.elapsedTime} milliseconds`); } return iterator(); diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts index cc623be8392d..3964a6ceb893 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts @@ -19,7 +19,8 @@ import { } from '../../../common/environmentManagers/simplevirtualenvs'; import '../../../../common/extensions'; import { asyncFilter } from '../../../../common/utils/arrayUtils'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; const DEFAULT_SEARCH_DEPTH = 2; /** @@ -118,6 +119,8 @@ export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { const searchDepth = this.searchDepth ?? DEFAULT_SEARCH_DEPTH; async function* iterator() { + const stopWatch = new StopWatch(); + traceInfo('Searching for global virtual environments'); const envRootDirs = await getGlobalVirtualEnvDirs(); const envGenerators = envRootDirs.map((envRootDir) => { async function* generator() { @@ -152,7 +155,7 @@ export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { }); yield* iterable(chain(envGenerators)); - traceVerbose(`Finished searching for global virtual envs`); + traceInfo(`Finished searching for global virtual envs: ${stopWatch.elapsedTime} milliseconds`); } return iterator(); diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts index 7adeeae89858..60528bd939aa 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts @@ -12,7 +12,8 @@ import { isStorePythonInstalled, getMicrosoftStoreAppsRoot, } from '../../../common/environmentManagers/microsoftStoreEnv'; -import { traceVerbose } from '../../../../logging'; +import { traceInfo } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; /** * This is a glob pattern which matches following file names: @@ -87,13 +88,14 @@ export class MicrosoftStoreLocator extends FSWatchingLocator { protected doIterEnvs(): IPythonEnvsIterator { const iterator = async function* (kind: PythonEnvKind) { - traceVerbose('Searching for windows store envs'); + const stopWatch = new StopWatch(); + traceInfo('Searching for windows store envs'); const exes = await getMicrosoftStorePythonExes(); yield* exes.map(async (executablePath: string) => ({ kind, executablePath, })); - traceVerbose(`Finished searching for windows store envs`); + traceInfo(`Finished searching for windows store envs: ${stopWatch.elapsedTime} milliseconds`); }; return iterator(this.kind); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 4ec6723705f8..d39a0af1a61f 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -7,7 +7,7 @@ import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; import { PythonEnvKind, PythonVersion } from '../../info'; import { Conda } from '../../../common/environmentManagers/conda'; -import { traceError } from '../../../../logging'; +import { traceError, traceInfo } from '../../../../logging'; import type { KnownEnvironmentTools } from '../../../../api/types'; import { setPyEnvBinary } from '../../../common/environmentManagers/pyenv'; import { @@ -17,6 +17,7 @@ import { createNativeGlobalPythonFinder, } from '../common/nativePythonFinder'; import { disposeAll } from '../../../../common/utils/resourceLifecycle'; +import { StopWatch } from '../../../../common/utils/stopWatch'; function categoryToKind(category: string): PythonEnvKind { switch (category.toLowerCase()) { @@ -96,6 +97,8 @@ export class NativeLocator implements ILocator, IDisposable { } public iterEnvs(): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for Python environments using Native Locator'); const promise = this.finder.startSearch(); const envs: BasicEnvInfo[] = []; const disposables: IDisposable[] = []; @@ -133,9 +136,17 @@ export class NativeLocator implements ILocator, IDisposable { ); const iterator = async function* (): IPythonEnvsIterator { + // When this promise is complete, we know that the search is complete. await promise; + traceInfo( + `Finished searching for Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, + ); yield* envs; + traceInfo( + `Finished yielding Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, + ); }; + return iterator(); } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts index 2d7ebc2af111..daca4b860907 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts @@ -9,7 +9,8 @@ import { commonPosixBinPaths, getPythonBinFromPosixPaths } from '../../../common import { isPyenvShimDir } from '../../../common/environmentManagers/pyenv'; import { getOSType, OSType } from '../../../../common/utils/platform'; import { isMacDefaultPythonPath } from '../../../common/environmentManagers/macDefault'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; export class PosixKnownPathsLocator extends Locator { public readonly providerId = 'posixKnownPaths'; @@ -26,7 +27,8 @@ export class PosixKnownPathsLocator extends Locator { } const iterator = async function* (kind: PythonEnvKind) { - traceVerbose('Searching for interpreters in posix paths locator'); + const stopWatch = new StopWatch(); + traceInfo('Searching for interpreters in posix paths locator'); try { // Filter out pyenv shims. They are not actual python binaries, they are used to launch // the binaries specified in .python-version file in the cwd. We should not be reporting @@ -50,7 +52,9 @@ export class PosixKnownPathsLocator extends Locator { } catch (ex) { traceError('Failed to process posix paths', ex); } - traceVerbose('Finished searching for interpreters in posix paths locator'); + traceInfo( + `Finished searching for interpreters in posix paths locator: ${stopWatch.elapsedTime} milliseconds`, + ); }; return iterator(this.kind); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts index 4fd1891a179c..e97b69c6b882 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts @@ -7,7 +7,8 @@ import { FSWatchingLocator } from './fsWatchingLocator'; import { getInterpreterPathFromDir } from '../../../common/commonUtils'; import { getSubDirs } from '../../../common/externalDependencies'; import { getPyenvVersionsDir } from '../../../common/environmentManagers/pyenv'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; /** * Gets all the pyenv environments. @@ -16,25 +17,31 @@ import { traceError, traceVerbose } from '../../../../logging'; * all the environments (global or virtual) in that directory. */ async function* getPyenvEnvironments(): AsyncIterableIterator { - traceVerbose('Searching for pyenv environments'); - const pyenvVersionDir = getPyenvVersionsDir(); + const stopWatch = new StopWatch(); + traceInfo('Searching for pyenv environments'); + try { + const pyenvVersionDir = getPyenvVersionsDir(); - const subDirs = getSubDirs(pyenvVersionDir, { resolveSymlinks: true }); - for await (const subDirPath of subDirs) { - const interpreterPath = await getInterpreterPathFromDir(subDirPath); + const subDirs = getSubDirs(pyenvVersionDir, { resolveSymlinks: true }); + for await (const subDirPath of subDirs) { + const interpreterPath = await getInterpreterPathFromDir(subDirPath); - if (interpreterPath) { - try { - yield { - kind: PythonEnvKind.Pyenv, - executablePath: interpreterPath, - }; - } catch (ex) { - traceError(`Failed to process environment: ${interpreterPath}`, ex); + if (interpreterPath) { + try { + yield { + kind: PythonEnvKind.Pyenv, + executablePath: interpreterPath, + }; + } catch (ex) { + traceError(`Failed to process environment: ${interpreterPath}`, ex); + } } } + } catch (ex) { + // This is expected when pyenv is not installed + traceInfo(`pyenv is not installed`); } - traceVerbose('Finished searching for pyenv environments'); + traceInfo(`Finished searching for pyenv environments: ${stopWatch.elapsedTime} milliseconds`); } export class PyenvLocator extends FSWatchingLocator { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts index d158d1da156c..440d075b4071 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts @@ -16,10 +16,11 @@ import { Locators } from '../../locators'; import { getEnvs } from '../../locatorUtils'; import { PythonEnvsChangedEvent } from '../../watcher'; import { DirFilesLocator } from './filesLocator'; -import { traceVerbose } from '../../../../logging'; +import { traceInfo } from '../../../../logging'; import { inExperiment, pathExists } from '../../../common/externalDependencies'; import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups'; import { iterPythonExecutablesInDir, looksLikeBasicGlobalPython } from '../../../common/commonUtils'; +import { StopWatch } from '../../../../common/utils/stopWatch'; /** * A locator for Windows locators found under the $PATH env var. @@ -68,11 +69,12 @@ export class WindowsPathEnvVarLocator implements ILocator, IDispos // are valid executables. That is left to callers (e.g. composite // locators). async function* iterator(it: IPythonEnvsIterator) { - traceVerbose(`Searching windows known paths locator`); + const stopWatch = new StopWatch(); + traceInfo(`Searching windows known paths locator`); for await (const env of it) { yield env; } - traceVerbose(`Finished searching windows known paths locator`); + traceInfo(`Finished searching windows known paths locator: ${stopWatch.elapsedTime} milliseconds`); } return iterator(this.locators.iterEnvs(query)); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts index a574116f1854..1447c2a90767 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts @@ -6,11 +6,12 @@ import { PythonEnvKind, PythonEnvSource } from '../../info'; import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery, IEmitter } from '../../locator'; import { getRegistryInterpreters } from '../../../common/windowsUtils'; -import { traceError, traceVerbose } from '../../../../logging'; +import { traceError, traceInfo } from '../../../../logging'; import { isMicrosoftStoreDir } from '../../../common/environmentManagers/microsoftStoreEnv'; import { PythonEnvsChangedEvent } from '../../watcher'; import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups'; import { inExperiment } from '../../../common/externalDependencies'; +import { StopWatch } from '../../../../common/utils/stopWatch'; export const WINDOWS_REG_PROVIDER_ID = 'windows-registry'; @@ -42,13 +43,15 @@ async function* iterateEnvsLazily(changed: IEmitter): IP } async function loadAllEnvs(changed: IEmitter) { - traceVerbose('Searching for windows registry interpreters'); - await getRegistryInterpreters(); + const stopWatch = new StopWatch(); + traceInfo('Searching for windows registry interpreters'); changed.fire({ providerId: WINDOWS_REG_PROVIDER_ID }); - traceVerbose('Finished searching for windows registry interpreters'); + traceInfo(`Finished searching for windows registry interpreters: ${stopWatch.elapsedTime} milliseconds`); } async function* iterateEnvs(): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for windows registry interpreters'); const interpreters = await getRegistryInterpreters(); // Value should already be loaded at this point, so this returns immediately. for (const interpreter of interpreters) { try { @@ -68,5 +71,5 @@ async function* iterateEnvs(): IPythonEnvsIterator { traceError(`Failed to process environment: ${interpreter}`, ex); } } - traceVerbose('Finished searching for windows registry interpreters'); + traceInfo(`Finished searching for windows registry interpreters: ${stopWatch.elapsedTime} milliseconds`); } From fea01499414a299cf060145df3eb01b955fa0c54 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 14 May 2024 16:59:07 +1000 Subject: [PATCH 046/106] Remove sysPrefixPath from native locator (#23427) --- native_locator/src/common_python.rs | 1 - native_locator/src/conda.rs | 203 ++++---- native_locator/src/conda_old.rs | 453 ------------------ native_locator/src/homebrew.rs | 12 +- native_locator/src/messaging.rs | 27 -- native_locator/src/pipenv.rs | 30 +- native_locator/src/pyenv.rs | 34 +- native_locator/src/venv.rs | 1 - native_locator/src/virtualenv.rs | 1 - native_locator/src/virtualenvwrapper.rs | 1 - native_locator/src/windows_registry.rs | 1 - native_locator/src/windows_store.rs | 10 +- native_locator/tests/common_python_test.rs | 1 - native_locator/tests/conda_test.rs | 2 - native_locator/tests/pyenv_test.rs | 30 +- .../locators/common/nativePythonFinder.ts | 2 +- .../base/locators/lowLevel/nativeLocator.ts | 1 + 17 files changed, 132 insertions(+), 678 deletions(-) delete mode 100644 native_locator/src/conda_old.rs diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index f3b7fbbe20c4..3d206529035c 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -43,7 +43,6 @@ impl Locator for PythonOnPath<'_> { python_executable_path: Some(env.executable.clone()), version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::System, - sys_prefix_path: None, env_path: env.path.clone(), env_manager: None, project_path: None, diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 8f6ae6f8b507..4233461fc264 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -63,21 +63,24 @@ fn get_conda_package_json_path(path: &Path, package: &str) -> Option Some(CondaPackage { - path: path.clone(), - version: version.as_str().to_string(), - }), - None => None, + std::fs::read_dir(path) + .ok()? + .filter_map(Result::ok) + .find_map(|entry| { + let path = entry.path(); + let file_name = path.file_name()?.to_string_lossy(); + if file_name.starts_with(&package_name) && file_name.ends_with(".json") { + match regex.clone().ok().unwrap().captures(&file_name)?.get(1) { + Some(version) => Some(CondaPackage { + path: path.clone(), + version: version.as_str().to_string(), + }), + None => None, + } + } else { + None } - } else { - None - } - }) + }) } fn get_conda_executable(path: &PathBuf) -> Option { @@ -109,13 +112,10 @@ fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } + if let Ok(metadata) = std::fs::metadata(&conda_path) { + if metadata.is_file() || metadata.is_symlink() { + return Some(conda_path); } - Err(_) => (), } } } @@ -213,40 +213,37 @@ struct CondaEnvironment { } fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { let metadata = env_path.metadata(); - match metadata { - Ok(metadata) => { - if metadata.is_dir() { - let path = env_path.clone(); - if let Some(python_binary) = find_python_binary_path(&path) { - if let Some(package_info) = get_conda_package_json_path(&path, "python") { - return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, - named, - python_executable_path: Some(python_binary), - version: Some(package_info.version), - }); - } else { - return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, - named, - python_executable_path: Some(python_binary), - version: None, - }); - } + if let Ok(metadata) = metadata { + if metadata.is_dir() { + let path = env_path.clone(); + if let Some(python_binary) = find_python_binary_path(&path) { + if let Some(package_info) = get_conda_package_json_path(&path, "python") { + return Some(CondaEnvironment { + name: path.file_name()?.to_string_lossy().to_string(), + path, + named, + python_executable_path: Some(python_binary), + version: Some(package_info.version), + }); } else { return Some(CondaEnvironment { name: path.file_name()?.to_string_lossy().to_string(), path, named, - python_executable_path: None, + python_executable_path: Some(python_binary), version: None, }); } + } else { + return Some(CondaEnvironment { + name: path.file_name()?.to_string_lossy().to_string(), + path, + named, + python_executable_path: None, + version: None, + }); } } - Err(_) => (), } None @@ -258,14 +255,12 @@ fn get_environments_from_envs_folder_in_conda_directory( // iterate through all sub directories in the env folder // for each sub directory, check if it has a python executable // if it does, create a PythonEnvironment object and add it to the list - for entry in std::fs::read_dir(path.join("envs")).ok()? { - match entry { - Ok(entry) => { - if let Some(env) = get_conda_environment_info(&entry.path(), true) { - envs.push(env); - } - } - Err(_) => (), + for entry in std::fs::read_dir(path.join("envs")) + .ok()? + .filter_map(Result::ok) + { + if let Some(env) = get_conda_environment_info(&entry.path(), true) { + envs.push(env); } } @@ -274,21 +269,14 @@ fn get_environments_from_envs_folder_in_conda_directory( fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { let mut envs = vec![]; - let home = environment.get_user_home(); - match home { - Some(home) => { - let home = Path::new(&home); - let environment_txt = home.join(".conda").join("environments.txt"); - match std::fs::read_to_string(environment_txt) { - Ok(reader) => { - for line in reader.lines() { - envs.push(line.to_string()); - } - } - Err(_) => (), + if let Some(home) = environment.get_user_home() { + let home = Path::new(&home); + let environment_txt = home.join(".conda").join("environments.txt"); + if let Ok(reader) = std::fs::read_to_string(environment_txt) { + for line in reader.lines() { + envs.push(line.to_string()); } } - None => (), } envs } @@ -309,31 +297,28 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { if let Some(home) = environment.get_user_home() { let conda_rc = Path::new(&home).join(".condarc"); let mut start_consuming_values = false; - match std::fs::read_to_string(conda_rc) { - Ok(reader) => { - let mut env_dirs = vec![]; - for line in reader.lines() { - if line.starts_with("envs_dirs:") && !start_consuming_values { - start_consuming_values = true; - continue; - } - if start_consuming_values { - if line.trim().starts_with("-") { - if let Some(env_dir) = line.splitn(2, '-').nth(1) { - let env_dir = PathBuf::from(env_dir.trim()); - if env_dir.exists() { - env_dirs.push(env_dir); - } + if let Ok(reader) = std::fs::read_to_string(conda_rc) { + let mut env_dirs = vec![]; + for line in reader.lines() { + if line.starts_with("envs_dirs:") && !start_consuming_values { + start_consuming_values = true; + continue; + } + if start_consuming_values { + if line.trim().starts_with("-") { + if let Some(env_dir) = line.splitn(2, '-').nth(1) { + let env_dir = PathBuf::from(env_dir.trim()); + if env_dir.exists() { + env_dirs.push(env_dir); } - continue; - } else { - break; } + continue; + } else { + break; } } - return Some(Condarc { env_dirs }); } - Err(_) => (), + return Some(Condarc { env_dirs }); } } None @@ -346,21 +331,16 @@ fn get_conda_envs_from_conda_rc( let mut envs: Vec = vec![]; for env in get_conda_conda_rc(environment)?.env_dirs { if let Ok(reader) = std::fs::read_dir(env) { - for entry in reader { - match entry { - Ok(entry) => { - if entry.path().is_dir() - && was_conda_environment_created_by_specific_conda( - &entry.path(), - root_conda_path, - ) - { - if let Some(env) = get_conda_environment_info(&entry.path(), false) { - envs.push(env); - } - } + for entry in reader.filter_map(Result::ok) { + if entry.path().is_dir() + && was_conda_environment_created_by_specific_conda( + &entry.path(), + root_conda_path, + ) + { + if let Some(env) = get_conda_environment_info(&entry.path(), false) { + envs.push(env); } - Err(_) => (), } } } @@ -383,23 +363,17 @@ fn was_conda_environment_created_by_specific_conda( root_conda_path: &PathBuf, ) -> bool { let conda_meta_history = env_path.join("conda-meta").join("history"); - match std::fs::read_to_string(conda_meta_history.clone()) { - Ok(reader) => { - for line in reader.lines() { - let line = line.to_lowercase(); - if line.starts_with("# cmd:") && line.contains(" create ") { - if line.contains(&root_conda_path.to_str().unwrap().to_lowercase()) { - return true; - } else { - return false; - } + if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { + for line in reader.lines() { + let line = line.to_lowercase(); + if line.starts_with("# cmd:") && line.contains(" create ") { + if line.contains(&root_conda_path.to_str().unwrap().to_lowercase()) { + return true; + } else { + return false; } } } - Err(_) => warn!( - "Error reading conda-meta/history file {:?}", - conda_meta_history - ), } false @@ -555,7 +529,6 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

Option { - if any_path.ends_with("bin/python") { - match any_path.parent() { - Some(parent) => match parent.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - }, - None => None, - } - } else if any_path.ends_with("bin") { - match any_path.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - } - } else { - Some(any_path.to_path_buf().join("conda-meta")) - } -} - -/// Get the conda-meta directory. For windows 'conda-meta' is in the same directory as the interpreter. -/// This layout is common in Windows. -/// -/// ``` -/// env // <--- Input can be this path -/// |-- conda-meta // <--- Returns this directory -/// |-- python.exe // <--- Input can be this path -/// ``` -#[cfg(windows)] -fn get_conda_meta_path(any_path: &Path) -> Option { - if any_path.ends_with("python.exe") { - match any_path.parent() { - Some(parent) => Some(parent.to_path_buf().join("conda-meta")), - None => None, - } - } else { - Some(any_path.to_path_buf().join("conda-meta")) - } -} - -/// Check if a given path is a conda environment. A conda environment is a directory that contains -/// a 'conda-meta' directory as child. This will find 'conda-meta' in a platform agnostic way. -pub fn is_conda_environment(any_path: &Path) -> bool { - let conda_meta_path = get_conda_meta_path(any_path); - match conda_meta_path { - Some(path) => path.exists(), - None => false, - } -} - -struct CondaPackage { - path: PathBuf, - version: String, -} - -/// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. -fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option { - let package_name = format!("{}-", package); - let conda_meta_path = get_conda_meta_path(any_path)?; - let regex = Regex::new(format!("^{}-((\\d+\\.*)*)-.*.json$", package).as_str()); - std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| { - let path = entry.ok()?.path(); - let file_name = path.file_name()?.to_string_lossy(); - if file_name.starts_with(&package_name) && file_name.ends_with(".json") { - match regex.clone().ok()?.captures(&file_name)?.get(1) { - Some(version) => Some(CondaPackage { - path: path.clone(), - version: version.as_str().to_string(), - }), - None => None, - } - } else { - None - } - }) -} - -/// Checks if the `python` package is installed in the conda environment -#[allow(dead_code)] -pub fn is_python_conda_env(any_path: &Path) -> bool { - let conda_python_json_path = get_conda_package_json_path(any_path, "python"); - match conda_python_json_path { - Some(result) => result.path.exists(), - None => false, - } -} - -/// Get the version of the `python` package in the conda environment -pub fn get_conda_python_version(any_path: &Path) -> Option { - let conda_python_json_path = get_conda_package_json_path(any_path, "python"); - match conda_python_json_path { - Some(result) => Some(result.version.clone()), - None => None, - } -} - -/// Specifically returns the file names that are valid for 'conda' on windows -#[cfg(windows)] -fn get_conda_bin_names() -> Vec<&'static str> { - vec!["conda.exe", "conda.bat"] -} - -/// Specifically returns the file names that are valid for 'conda' on linux/Mac -#[cfg(unix)] -fn get_conda_bin_names() -> Vec<&'static str> { - vec!["conda"] -} - -/// Find the conda binary on the PATH environment variable -fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { - let paths = environment.get_env_var("PATH".to_string())?; - for path in env::split_paths(&paths) { - for bin in get_conda_bin_names() { - let conda_path = path.join(bin); - match std::fs::metadata(&conda_path) { - Ok(metadata) => { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - Err(_) => (), - } - } - } - None -} - -#[cfg(windows)] -fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { - let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); - let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); - let all_user_profile = environment - .get_env_var("ALLUSERSPROFILE".to_string()) - .unwrap(); - let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); - let mut known_paths = vec![ - Path::new(&user_profile).join("Anaconda3\\Scripts"), - Path::new(&program_data).join("Anaconda3\\Scripts"), - Path::new(&all_user_profile).join("Anaconda3\\Scripts"), - Path::new(&home_drive).join("Anaconda3\\Scripts"), - Path::new(&user_profile).join("Miniconda3\\Scripts"), - Path::new(&program_data).join("Miniconda3\\Scripts"), - Path::new(&all_user_profile).join("Miniconda3\\Scripts"), - Path::new(&home_drive).join("Miniconda3\\Scripts"), - ]; - known_paths.append(&mut environment.get_know_global_search_locations()); - known_paths -} - -#[cfg(unix)] -fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { - let mut known_paths = vec![ - PathBuf::from("/opt/anaconda3/bin"), - PathBuf::from("/opt/miniconda3/bin"), - PathBuf::from("/usr/local/anaconda3/bin"), - PathBuf::from("/usr/local/miniconda3/bin"), - PathBuf::from("/usr/anaconda3/bin"), - PathBuf::from("/usr/miniconda3/bin"), - PathBuf::from("/home/anaconda3/bin"), - PathBuf::from("/home/miniconda3/bin"), - PathBuf::from("/anaconda3/bin"), - PathBuf::from("/miniconda3/bin"), - ]; - if let Some(home) = environment.get_user_home() { - known_paths.push(PathBuf::from(home.clone()).join("anaconda3/bin")); - known_paths.push(PathBuf::from(home).join("miniconda3/bin")); - } - known_paths.append(&mut environment.get_know_global_search_locations()); - known_paths -} - -/// Find conda binary in known locations -fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option { - let conda_bin_names = get_conda_bin_names(); - let known_locations = get_known_conda_locations(environment); - for location in known_locations { - for bin in &conda_bin_names { - let conda_path = location.join(bin); - if let Some(metadata) = std::fs::metadata(&conda_path).ok() { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - } - } - None -} - -/// Find the conda binary on the system -pub fn find_conda_binary(environment: &dyn known::Environment) -> Option { - let conda_binary_on_path = find_conda_binary_on_path(environment); - match conda_binary_on_path { - Some(conda_binary_on_path) => Some(conda_binary_on_path), - None => find_conda_binary_in_known_locations(environment), - } -} - -pub fn get_conda_version(conda_binary: &PathBuf) -> Option { - let mut parent = conda_binary.parent()?; - if parent.ends_with("bin") { - parent = parent.parent()?; - } - if parent.ends_with("Library") { - parent = parent.parent()?; - } - match get_conda_package_json_path(&parent, "conda") { - Some(result) => Some(result.version), - None => match get_conda_package_json_path(&parent.parent()?, "conda") { - Some(result) => Some(result.version), - None => None, - }, - } -} - -fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { - let mut envs = vec![]; - let home = environment.get_user_home(); - match home { - Some(home) => { - let home = Path::new(&home); - let environment_txt = home.join(".conda").join("environments.txt"); - match std::fs::read_to_string(environment_txt) { - Ok(reader) => { - for line in reader.lines() { - envs.push(line.to_string()); - } - } - Err(_) => (), - } - } - None => (), - } - envs -} - -fn get_known_env_locations( - conda_bin: &PathBuf, - environment: &dyn known::Environment, -) -> Vec { - let mut paths = vec![]; - let home = environment.get_user_home(); - match home { - Some(home) => { - let home = Path::new(&home); - let conda_envs = home.join(".conda").join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); - } - None => (), - } - - match conda_bin.parent() { - Some(parent) => { - paths.push(parent.to_string_lossy().to_string()); - let conda_envs = parent.join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); - match parent.parent() { - Some(parent) => { - paths.push(parent.to_string_lossy().to_string()); - let conda_envs = parent.join("envs"); - paths.push(conda_envs.to_string_lossy().to_string()); - } - None => (), - } - } - None => (), - } - - paths -} - -fn get_conda_envs_from_known_env_locations( - conda_bin: &PathBuf, - environment: &dyn known::Environment, -) -> Vec { - let mut envs = vec![]; - for location in get_known_env_locations(conda_bin, environment) { - if is_conda_environment(&Path::new(&location)) { - envs.push(location.to_string()); - } - match std::fs::read_dir(location) { - Ok(reader) => { - for entry in reader { - match entry { - Ok(entry) => { - let metadata = entry.metadata(); - match metadata { - Ok(metadata) => { - if metadata.is_dir() { - let path = entry.path(); - if is_conda_environment(&path) { - envs.push(path.to_string_lossy().to_string()); - } - } - } - Err(_) => (), - } - } - Err(_) => (), - } - } - } - Err(_) => (), - } - } - envs -} - -struct CondaEnv { - named: bool, - name: String, - path: PathBuf, -} - -fn get_distinct_conda_envs( - conda_bin: &PathBuf, - environment: &dyn known::Environment, -) -> Vec { - let mut envs = get_conda_envs_from_environment_txt(environment); - let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); - envs.append(&mut known_envs); - envs.sort(); - envs.dedup(); - - let locations = get_known_env_locations(conda_bin, environment); - let mut conda_envs = vec![]; - for env in envs { - let env = Path::new(&env); - let mut named = false; - let mut name = "".to_string(); - for location in &locations { - let location = Path::new(location).join("envs"); - match env.strip_prefix(location) { - Ok(prefix) => { - named = true; - name = match prefix.to_str() { - Some(name) => { - let name = name.to_string(); - if name == "" { - "base".to_string() - } else { - name.to_string() - } - } - None => "base".to_string(), - }; - break; - } - Err(_) => (), - } - } - conda_envs.push(CondaEnv { - named, - name, - path: PathBuf::from(env), - }); - } - conda_envs -} - -pub struct Conda<'a> { - pub manager: Option, - pub environment: &'a dyn Environment, -} - -impl Conda<'_> { - pub fn with<'a>(environment: &'a impl Environment) -> Conda { - Conda { - environment, - manager: None, - } - } -} - -impl Locator for Conda<'_> { - fn resolve(&self, _env: &PythonEnv) -> Option { - // We will find everything in gather - None - } - - fn find(&mut self) -> Option { - let conda_binary = find_conda_binary(self.environment)?; - let manager = EnvManager::new( - conda_binary.clone(), - get_conda_version(&conda_binary), - EnvManagerType::Conda, - ); - - let envs = get_distinct_conda_envs(&conda_binary, self.environment); - let mut environments: Vec = vec![]; - for env in envs { - let executable = find_python_binary_path(Path::new(&env.path)); - let env = messaging::PythonEnvironment::new( - None, - Some(env.name.to_string()), - executable.clone(), - messaging::PythonEnvironmentCategory::Conda, - get_conda_python_version(&env.path), - Some(env.path.clone()), - Some(env.path.clone()), - Some(manager.clone()), - if env.named { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-n".to_string(), - env.name.to_string(), - "python".to_string(), - ]) - } else { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-p".to_string(), - env.path.to_string_lossy().to_string(), - "python".to_string(), - ]) - }, - ); - - environments.push(env) - } - - Some(LocatorResult { - managers: vec![manager], - environments, - }) - } -} diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 34a02fe9845f..f51e783e12c8 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -8,10 +8,10 @@ use crate::{ utils::PythonEnv, }; use regex::Regex; -use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; +use std::{collections::HashSet, fs::DirEntry, path::PathBuf}; -fn is_symlinked_python_executable(path: Result) -> Option { - let path = path.ok()?.path(); +fn is_symlinked_python_executable(path: DirEntry) -> Option { + let path = path.path(); let name = path.file_name()?.to_string_lossy(); if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") { return None; @@ -46,7 +46,10 @@ impl Locator for Homebrew<'_> { let mut reported: HashSet = HashSet::new(); let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); let mut environments: Vec = vec![]; - for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { + for file in std::fs::read_dir(homebrew_prefix_bin) + .ok()? + .filter_map(Result::ok) + { if let Some(exe) = is_symlinked_python_executable(file) { let python_version = exe.to_string_lossy().to_string(); let version = match python_regex.captures(&python_version) { @@ -68,7 +71,6 @@ impl Locator for Homebrew<'_> { version, None, None, - None, Some(vec![exe.to_string_lossy().to_string()]), ); environments.push(env); diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index b79c5fa172ca..34a2ca1d8c73 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -100,7 +100,6 @@ pub struct PythonEnvironment { pub category: PythonEnvironmentCategory, pub version: Option, pub env_path: Option, - pub sys_prefix_path: Option, pub env_manager: Option, pub python_run_command: Option>, /** @@ -117,7 +116,6 @@ impl PythonEnvironment { category: PythonEnvironmentCategory, version: Option, env_path: Option, - sys_prefix_path: Option, env_manager: Option, python_run_command: Option>, ) -> Self { @@ -128,36 +126,11 @@ impl PythonEnvironment { category, version, env_path, - sys_prefix_path, env_manager, python_run_command, project_path: None, } } - pub fn new_pipenv( - python_executable_path: Option, - version: Option, - env_path: Option, - sys_prefix_path: Option, - env_manager: Option, - project_path: PathBuf, - ) -> Self { - Self { - display_name: None, - name: None, - python_executable_path: python_executable_path.clone(), - category: PythonEnvironmentCategory::Pipenv, - version, - env_path, - sys_prefix_path, - env_manager, - python_run_command: match python_executable_path { - Some(exe) => Some(vec![exe.to_string_lossy().to_string()]), - None => None, - }, - project_path: Some(project_path), - } - } } #[derive(Serialize, Deserialize)] diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 0acd00893310..bb5eab5776fe 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -9,15 +9,12 @@ use std::path::PathBuf; fn get_pipenv_project(env: &PythonEnv) -> Option { let project_file = env.path.clone()?.join(".project"); - if project_file.exists() { - 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); - } + 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 } @@ -32,14 +29,17 @@ impl PipEnv { impl Locator for PipEnv { fn resolve(&self, env: &PythonEnv) -> Option { let project_path = get_pipenv_project(env)?; - Some(PythonEnvironment::new_pipenv( - Some(env.executable.clone()), - env.version.clone(), - env.path.clone(), - env.path.clone(), - None, - project_path, - )) + Some(PythonEnvironment { + display_name: None, + name: None, + python_executable_path: Some(env.executable.clone()), + category: crate::messaging::PythonEnvironmentCategory::Pipenv, + version: env.version.clone(), + env_path: env.path.clone(), + env_manager: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + project_path: Some(project_path), + }) } fn find(&mut self) -> Option { diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index a94d63bc4751..9137a80df1f7 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -111,7 +111,6 @@ fn get_pure_python_environment( messaging::PythonEnvironmentCategory::Pyenv, Some(version), Some(path.clone()), - Some(path.clone()), manager.clone(), Some(vec![executable .clone() @@ -145,7 +144,6 @@ fn get_virtual_env_environment( messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), Some(path.clone()), - Some(path.clone()), manager.clone(), Some(vec![executable .clone() @@ -168,23 +166,21 @@ pub fn list_pyenv_environments( .into_string() .ok()?; - for entry in fs::read_dir(&versions_dir).ok()? { - if let Ok(path) = entry { - let path = path.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()); - }); - } + 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()); + }); } } } diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 9e0161a6df7d..94040a536989 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -39,7 +39,6 @@ impl Locator for Venv { python_executable_path: Some(env.executable.clone()), version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::Venv, - sys_prefix_path: env.path.clone(), env_path: env.path.clone(), env_manager: None, project_path: None, diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 04c10e0e0d25..2a6909e63fa2 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -70,7 +70,6 @@ impl Locator for VirtualEnv { python_executable_path: Some(env.executable.clone()), version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::VirtualEnv, - sys_prefix_path: env.path.clone(), env_path: env.path.clone(), env_manager: None, project_path: None, diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 856f6a78d537..d55a89e09dca 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -93,7 +93,6 @@ impl Locator for VirtualEnvWrapper<'_> { python_executable_path: Some(env.executable.clone()), version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::Venv, - sys_prefix_path: env.path.clone(), env_path: env.path.clone(), env_manager: None, project_path: None, diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index 279ef46d173e..4f8e97710fc2 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -58,7 +58,6 @@ fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option Option, env_path: String, } @@ -97,14 +95,9 @@ fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option< let env_path = package_key.get_value("PackageRootFolder").ok()?; let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?; - let version = match regex.captures(&name)?.get(1) { - Some(version) => Some(version.as_str().to_string()), - None => None, - }; return Some(StorePythonInfo { display_name, - version, env_path, }); } @@ -134,7 +127,6 @@ impl Locator for WindowsStore<'_> { python_executable_path: Some(env.executable.clone()), version: None, category: crate::messaging::PythonEnvironmentCategory::WindowsStore, - sys_prefix_path: None, env_path: None, env_manager: None, project_path: None, diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 06ed5ff7811d..7db280c9496b 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -42,7 +42,6 @@ fn find_python_in_path_this() { version: None, python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]), env_path: Some(unix_python.clone()), - sys_prefix_path: None, }; assert_messages( &[json!(env)], diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index fb3d59e59b5d..1705dc72bf47 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -126,7 +126,6 @@ fn finds_two_conda_envs_from_txt() { category: python_finder::messaging::PythonEnvironmentCategory::Conda, version: Some("10.0.1".to_string()), env_path: Some(conda_1.clone()), - sys_prefix_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(), @@ -144,7 +143,6 @@ fn finds_two_conda_envs_from_txt() { category: python_finder::messaging::PythonEnvironmentCategory::Conda, version: None, env_path: Some(conda_2.clone()), - sys_prefix_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(), diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 7ad6904867fa..42249586864d 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -124,10 +124,6 @@ fn find_pyenv_envs() { env_path: Some(join_test_paths(&[ home.to_str().unwrap(), ".pyenv/versions/3.9.9" - ])), - sys_prefix_path: Some(join_test_paths(&[ - home.to_str().unwrap(), - ".pyenv/versions/3.9.9" ])), env_manager: Some(expected_manager.clone()) }); @@ -138,7 +134,7 @@ fn find_pyenv_envs() { 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", @@ -152,20 +148,16 @@ fn find_pyenv_envs() { home.to_str().unwrap(), ".pyenv/versions/my-virtual-env", ])), - sys_prefix_path: Some(join_test_paths(&[ - home.to_str().unwrap(), - ".pyenv/versions/my-virtual-env", - ])), - env_manager: Some(expected_manager.clone()), - }; - let expected_3_12_1 = PythonEnvironment { + env_manager: Some(expected_manager.clone()), + }; + 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", @@ -179,10 +171,6 @@ fn find_pyenv_envs() { home.to_str().unwrap(), ".pyenv/versions/3.12.1", ])), - sys_prefix_path: Some(join_test_paths(&[ - home.to_str().unwrap(), - ".pyenv/versions/3.12.1", - ])), env_manager: Some(expected_manager.clone()), }; let expected_3_13_dev = PythonEnvironment { @@ -206,10 +194,6 @@ fn find_pyenv_envs() { home.to_str().unwrap(), ".pyenv/versions/3.13-dev", ])), - sys_prefix_path: Some(join_test_paths(&[ - home.to_str().unwrap(), - ".pyenv/versions/3.13-dev", - ])), env_manager: Some(expected_manager.clone()), }; let expected_3_12_1a3 = PythonEnvironment { @@ -233,10 +217,6 @@ fn find_pyenv_envs() { home.to_str().unwrap(), ".pyenv/versions/3.12.1a3", ])), - sys_prefix_path: Some(join_test_paths(&[ - home.to_str().unwrap(), - ".pyenv/versions/3.12.1a3", - ])), env_manager: Some(expected_manager.clone()), }; let environments = get_environments_from_result(&result); diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index dbc5914e4715..7d005dd7484a 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -16,13 +16,13 @@ const NATIVE_LOCATOR = isWindows() : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder'); export interface NativeEnvInfo { + displayName?: string; name: string; pythonExecutablePath?: string; category: string; version?: string; pythonRunCommand?: string[]; envPath?: string; - sysPrefixPath?: string; /** * Path to the project directory when dealing with pipenv virtual environments. */ diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index d39a0af1a61f..4e9d372520f6 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -25,6 +25,7 @@ function categoryToKind(category: string): PythonEnvKind { return PythonEnvKind.Conda; case 'system': case 'homebrew': + case 'windowsregistry': return PythonEnvKind.System; case 'pyenv': return PythonEnvKind.Pyenv; From 6a2565710edf6405e39348f3e801c7645d8649ac Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 12:04:54 +1000 Subject: [PATCH 047/106] Search for conda envs in known locations (#23428) --- native_locator/src/conda.rs | 572 ++++++++++++------ native_locator/src/known.rs | 10 + native_locator/src/locator.rs | 2 +- native_locator/src/messaging.rs | 12 +- native_locator/src/utils.rs | 7 - native_locator/tests/common.rs | 29 +- native_locator/tests/common_python_test.rs | 25 +- native_locator/tests/conda_test.rs | 203 +++++-- native_locator/tests/pyenv_test.rs | 56 +- .../tests/unix/conda/.conda/environments.txt | 2 - .../user_home}/.conda/environments.txt | 0 .../conda/{ => user_home}/anaconda3/bin/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../anaconda3/envs/.conda_envs_dir_test} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../{ => user_home}/anaconda3/envs/one/python | 0 .../{ => user_home}/anaconda3/envs/two/python | 0 .../user_home/.conda/environments.txt} | 0 .../some_location/anaconda3/bin/conda} | 0 .../some_location/anaconda3/bin}/python | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json} | 0 .../anaconda3/envs/.conda_envs_dir_test} | 0 .../user_home/.conda/environments.txt} | 0 .../user_home/anaconda3/bin/conda} | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 + .../python-slugify-5.0.2-pyhd3eb1b0_0.json} | 0 .../anaconda3/envs/.conda_envs_dir_test} | 0 .../bin => known/user_home}/python | 0 .../unix/known/{ => user_home}/python.version | 0 .../pyenv/{ => home}/opt/homebrew/bin/pyenv | 0 .../.pyenv/versions/3.12.1}/bin/python | 0 .../.pyenv/versions/3.12.1a3}/bin/python | 0 .../.pyenv/versions/3.13-dev}/bin/python | 0 .../.pyenv/versions/3.9.9}/bin/python | 0 .../bin/envs/.conda_envs_dir_test} | 0 .../versions/anaconda-4.0.0}/bin/python | 0 .../bin/envs/.conda_envs_dir_test} | 0 .../versions/anaconda3-2021.04}/bin/python | 0 .../bin/envs/.conda_envs_dir_test} | 0 .../versions/mambaforge-4.10.1-4/bin/python} | 0 .../mambaforge/bin/envs/.conda_envs_dir_test | 0 .../.pyenv/versions/mambaforge/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda-latest/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda3-3.10.1/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda3-4.0.5/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniforge3-4.11.0-1/bin/python | 0 .../.pyenv/versions/my-virtual-env/bin/python | 0 .../.pyenv/versions/my-virtual-env/pyvenv.cfg | 0 .../.pyenv/versions/nogil-3.9.10/bin/python | 0 .../versions/pypy3.10-7.3.14/bin/python | 0 .../.pyenv/versions/pyston-2.3.5/bin/python | 0 .../versions/stacklets-3.7.5/bin/python | 0 .../home/opt/homebrew/bin/pyenv | 0 65 files changed, 603 insertions(+), 316 deletions(-) delete mode 100644 native_locator/tests/unix/conda/.conda/environments.txt rename native_locator/tests/unix/{conda_without_envs => conda/user_home}/.conda/environments.txt (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/bin/conda (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json => user_home/anaconda3/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/{conda_without_envs/anaconda3 => conda/user_home/anaconda3/envs/one}/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/one/python (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/two/python (100%) rename native_locator/tests/unix/{conda_without_envs/anaconda3/bin/conda => conda_custom_install_path/user_home/.conda/environments.txt} (100%) rename native_locator/tests/unix/{conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json => conda_custom_install_path/user_home/some_location/anaconda3/bin/conda} (100%) rename native_locator/tests/unix/{known => conda_custom_install_path/user_home/some_location/anaconda3/bin}/python (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.12.1/bin/python => conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home/some_location}/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.12.1a3/bin/python => conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.13-dev/bin/python => conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.9.9/bin/python => conda_without_envs/user_home/.conda/environments.txt} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/anaconda-4.0.0/bin/python => conda_without_envs/user_home/anaconda3/bin/conda} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/anaconda3-2021.04/bin/python => conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} (100%) create mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename native_locator/tests/unix/{pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python => conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/mambaforge/bin/python => conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/miniconda-latest/bin => known/user_home}/python (100%) rename native_locator/tests/unix/known/{ => user_home}/python.version (100%) rename native_locator/tests/unix/pyenv/{ => home}/opt/homebrew/bin/pyenv (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-3.10-22.11.1-1 => user_home/.pyenv/versions/3.12.1}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-3.10.1 => user_home/.pyenv/versions/3.12.1a3}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-4.0.5 => user_home/.pyenv/versions/3.13-dev}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniforge3-4.11.0-1 => user_home/.pyenv/versions/3.9.9}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/my-virtual-env/bin/python => user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/nogil-3.9.10 => user_home/.pyenv/versions/anaconda-4.0.0}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/pypy3.10-7.3.14/bin/python => user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/pyston-2.3.5 => user_home/.pyenv/versions/anaconda3-2021.04}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/stacklets-3.7.5/bin/python => user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test} (100%) rename native_locator/tests/unix/{pyenv_without_envs/opt/homebrew/bin/pyenv => pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python} (100%) create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python rename native_locator/tests/unix/pyenv/{ => user_home}/.pyenv/versions/my-virtual-env/pyvenv.cfg (100%) create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python create mode 100644 native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 4233461fc264..929a991689cc 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -11,8 +11,10 @@ use crate::messaging::EnvManagerType; use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; use crate::utils::{find_python_binary_path, get_environment_key, get_environment_manager_key}; +use log::trace; use log::warn; use regex::Regex; +use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; @@ -207,40 +209,45 @@ fn get_conda_manager(path: &PathBuf) -> Option { struct CondaEnvironment { name: String, named: bool, - path: PathBuf, + env_path: PathBuf, python_executable_path: Option, version: Option, + conda_install_folder: Option, } fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { let metadata = env_path.metadata(); if let Ok(metadata) = metadata { if metadata.is_dir() { - let path = env_path.clone(); - if let Some(python_binary) = find_python_binary_path(&path) { - if let Some(package_info) = get_conda_package_json_path(&path, "python") { + let conda_install_folder = get_conda_installation_used_to_create_conda_env(env_path); + let env_path = env_path.clone(); + if let Some(python_binary) = find_python_binary_path(&env_path) { + if let Some(package_info) = get_conda_package_json_path(&env_path, "python") { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: Some(python_binary), version: Some(package_info.version), + conda_install_folder, }); } else { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: Some(python_binary), version: None, + conda_install_folder, }); } } else { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: None, version: None, + conda_install_folder, }); } } @@ -248,6 +255,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option Option> { @@ -272,7 +280,8 @@ fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> if let Some(home) = environment.get_user_home() { let home = Path::new(&home); let environment_txt = home.join(".conda").join("environments.txt"); - if let Ok(reader) = std::fs::read_to_string(environment_txt) { + if let Ok(reader) = std::fs::read_to_string(environment_txt.clone()) { + trace!("Found environments.txt file {:?}", environment_txt); for line in reader.lines() { envs.push(line.to_string()); } @@ -287,66 +296,188 @@ struct Condarc { } /** - * The .condarc file contains a list of directories where conda environments are created. - * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs - * - * TODO: Search for the .condarc file in the following locations: - * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc + * Get the list of conda environments found in other locations such as + * /.conda/envs + * /AppData/Local/conda/conda/envs */ -fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { +pub fn get_conda_environment_paths_from_conda_rc( + environment: &dyn known::Environment, +) -> Vec { + if let Some(paths) = get_conda_conda_rc(environment) { + paths.env_dirs + } else { + vec![] + } +} + +fn get_conda_environment_paths_from_known_paths( + environment: &dyn known::Environment, +) -> Vec { if let Some(home) = environment.get_user_home() { - let conda_rc = Path::new(&home).join(".condarc"); - let mut start_consuming_values = false; - if let Ok(reader) = std::fs::read_to_string(conda_rc) { - let mut env_dirs = vec![]; - for line in reader.lines() { - if line.starts_with("envs_dirs:") && !start_consuming_values { - start_consuming_values = true; - continue; - } - if start_consuming_values { - if line.trim().starts_with("-") { - if let Some(env_dir) = line.splitn(2, '-').nth(1) { - let env_dir = PathBuf::from(env_dir.trim()); - if env_dir.exists() { - env_dirs.push(env_dir); - } - } - continue; - } else { - break; - } + let mut env_paths: Vec = vec![]; + let _ = [ + PathBuf::from(".conda").join("envs"), + PathBuf::from("AppData") + .join("Local") + .join("conda") + .join("conda") + .join("envs"), + ] + .iter() + .map(|path| { + let full_path = home.join(path); + for entry in std::fs::read_dir(full_path).ok()?.filter_map(Result::ok) { + if entry.path().is_dir() { + trace!("Search for conda envs in location {:?}", entry.path()); + env_paths.push(entry.path()); } } - return Some(Condarc { env_dirs }); + None::<()> + }); + return env_paths; + } + vec![] +} + +#[cfg(windows)] +fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { + let mut search_paths: Vec = vec![ + "C:\\ProgramData\\conda\\.condarc", + "C:\\ProgramData\\conda\\condarc", + "C:\\ProgramData\\conda\\condarc.d", + ] + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + + if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_root.clone()).join(".condarc"), + PathBuf::from(conda_root.clone()).join("condarc"), + PathBuf::from(conda_root.clone()).join(".condarc.d"), + ]); + } + if let Some(home) = environment.get_user_home() { + search_paths.append(&mut vec![ + home.join(".config").join("conda").join(".condarc"), + home.join(".config").join("conda").join("condarc"), + home.join(".config").join("conda").join("condarc.d"), + home.join(".conda").join(".condarc"), + home.join(".conda").join("condarc"), + home.join(".conda").join("condarc.d"), + home.join(".condarc"), + ]); + } + if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_prefix.clone()).join(".condarc"), + PathBuf::from(conda_prefix.clone()).join("condarc"), + PathBuf::from(conda_prefix.clone()).join(".condarc.d"), + ]); + } + if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { + search_paths.append(&mut vec![PathBuf::from(condarc)]); + } + + search_paths +} +#[cfg(unix)] +fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { + let mut search_paths: Vec = vec![ + "/etc/conda/.condarc", + "/etc/conda/condarc", + "/etc/conda/condarc.d/", + "/var/lib/conda/.condarc", + "/var/lib/conda/condarc", + "/var/lib/conda/condarc.d/", + ] + .iter() + .map(|p| PathBuf::from(p)) + .map(|p| { + // This only applies in tests. + // We need this, as the root folder cannot be mocked. + if let Some(root) = environment.get_root() { + root.join(p.to_string_lossy()[1..].to_string()) + } else { + p } + }) + .collect(); + + if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_root.clone()).join(".condarc"), + PathBuf::from(conda_root.clone()).join("condarc"), + PathBuf::from(conda_root.clone()).join(".condarc.d"), + ]); } - None + if let Some(xdg_config_home) = environment.get_env_var("XDG_CONFIG_HOME".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(xdg_config_home.clone()).join(".condarc"), + PathBuf::from(xdg_config_home.clone()).join("condarc"), + PathBuf::from(xdg_config_home.clone()).join(".condarc.d"), + ]); + } + if let Some(home) = environment.get_user_home() { + search_paths.append(&mut vec![ + home.join(".config").join("conda").join(".condarc"), + home.join(".config").join("conda").join("condarc"), + home.join(".config").join("conda").join("condarc.d"), + home.join(".conda").join(".condarc"), + home.join(".conda").join("condarc"), + home.join(".conda").join("condarc.d"), + home.join(".condarc"), + ]); + } + if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_prefix.clone()).join(".condarc"), + PathBuf::from(conda_prefix.clone()).join("condarc"), + PathBuf::from(conda_prefix.clone()).join(".condarc.d"), + ]); + } + if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { + search_paths.append(&mut vec![PathBuf::from(condarc)]); + } + + search_paths } -fn get_conda_envs_from_conda_rc( - root_conda_path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut envs: Vec = vec![]; - for env in get_conda_conda_rc(environment)?.env_dirs { - if let Ok(reader) = std::fs::read_dir(env) { - for entry in reader.filter_map(Result::ok) { - if entry.path().is_dir() - && was_conda_environment_created_by_specific_conda( - &entry.path(), - root_conda_path, - ) - { - if let Some(env) = get_conda_environment_info(&entry.path(), false) { - envs.push(env); +/** + * The .condarc file contains a list of directories where conda environments are created. + * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs + * + * TODO: Search for the .condarc file in the following locations: + * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc + */ +fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { + let conda_rc = get_conda_rc_search_paths(environment) + .into_iter() + .find(|p| p.exists())?; + let mut start_consuming_values = false; + trace!("conda_rc: {:?}", conda_rc); + let reader = std::fs::read_to_string(conda_rc).ok()?; + let mut env_dirs = vec![]; + for line in reader.lines() { + if line.starts_with("envs_dirs:") && !start_consuming_values { + start_consuming_values = true; + continue; + } + if start_consuming_values { + if line.trim().starts_with("-") { + if let Some(env_dir) = line.splitn(2, '-').nth(1) { + let env_dir = PathBuf::from(env_dir.trim()).join("envs"); + if env_dir.exists() { + env_dirs.push(env_dir); } } + continue; + } else { + break; } } } - - Some(envs) + return Some(Condarc { env_dirs }); } /** @@ -359,20 +490,17 @@ fn get_conda_envs_from_conda_rc( * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. */ fn was_conda_environment_created_by_specific_conda( - env_path: &PathBuf, + env: &CondaEnvironment, root_conda_path: &PathBuf, ) -> bool { - let conda_meta_history = env_path.join("conda-meta").join("history"); - if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { - for line in reader.lines() { - let line = line.to_lowercase(); - if line.starts_with("# cmd:") && line.contains(" create ") { - if line.contains(&root_conda_path.to_str().unwrap().to_lowercase()) { - return true; - } else { - return false; - } - } + if let Some(cmd_line) = env.conda_install_folder.clone() { + if cmd_line + .to_lowercase() + .contains(&root_conda_path.to_string_lossy().to_lowercase()) + { + return true; + } else { + return false; } } @@ -380,61 +508,41 @@ fn was_conda_environment_created_by_specific_conda( } /** - * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. - * If the user has multiple conda installations, any one of those could have created that specific environment. - * Fortunately the conda-meta/history file contains the path to the conda executable (script) that was used to create the environment. - * The format of the file is as follows: - * # cmd: C:\Users\user\miniconda3\Scripts\conda-script.py create --name myenv + * The conda-meta/history file in conda environments contain the command used to create the conda environment. + * And example is `# cmd: \Scripts\conda-script.py create -n sample`` + * And example is `# cmd: conda create -n sample`` * - * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. + * Sometimes the cmd line contains the fully qualified path to the conda install folder. + * This function returns the path to the conda installation that was used to create the environment. */ -fn get_environments_from_environments_txt_belonging_to_conda_directory( - path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut envs: Vec = vec![]; - for env in get_conda_envs_from_environment_txt(environment) { - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - if env.contains(path.to_str().unwrap()) { - continue; - } - - let env_path = PathBuf::from(env); - if !env_path.is_dir() { - continue; - } - if was_conda_environment_created_by_specific_conda(&env_path, path) { - if let Some(env) = get_conda_environment_info(&env_path, false) { - envs.push(env); +fn get_conda_installation_used_to_create_conda_env(env_path: &PathBuf) -> Option { + let conda_meta_history = env_path.join("conda-meta").join("history"); + if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { + if let Some(line) = reader.lines().map(|l| l.trim()).find(|l| { + l.to_lowercase().starts_with("# cmd:") && l.to_lowercase().contains(" create -") + }) { + // Sample lines + // # cmd: \Scripts\conda-script.py create -n samlpe1 + // # cmd: \Scripts\conda-script.py create -p + // # cmd: /Users/donjayamanne/miniconda3/bin/conda create -n conda1 + let start_index = line.to_lowercase().find("# cmd:")? + "# cmd:".len(); + let end_index = line.to_lowercase().find(" create -")?; + let cmd_line = PathBuf::from(line[start_index..end_index].trim().to_string()); + if let Some(cmd_line) = cmd_line.parent() { + if let Some(name) = cmd_line.file_name() { + if name.to_ascii_lowercase() == "bin" || name.to_ascii_lowercase() == "scripts" + { + if let Some(cmd_line) = cmd_line.parent() { + return Some(cmd_line.to_str()?.to_string()); + } + } + return Some(cmd_line.to_str()?.to_string()); + } } } } - Some(envs) -} - -fn get_conda_environments_from_conda_directory( - path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut all_envs: Vec = vec![]; - if let Some(envs) = get_environments_from_envs_folder_in_conda_directory(path) { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - if let Some(envs) = - get_environments_from_environments_txt_belonging_to_conda_directory(path, environment) - { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - if let Some(envs) = get_conda_envs_from_conda_rc(path, environment) { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - Some(all_envs) + None } #[cfg(windows)] @@ -509,7 +617,7 @@ fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Optio conda_exe, "run".to_string(), "-p".to_string(), - env.path.to_str().unwrap().to_string(), + env.env_path.to_str().unwrap().to_string(), "python".to_string(), ]) } @@ -543,21 +651,21 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; let mut detected_envs: HashSet = HashSet::new(); let mut detected_managers: HashSet = HashSet::new(); - if possible_conda_folder.is_dir() && possible_conda_folder.exists() { - if let Some(manager) = get_conda_manager(&possible_conda_folder) { - let envs = - get_conda_environments_from_conda_directory(&possible_conda_folder, environment); - - if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { - if let Some(key) = get_environment_key(&env) { + if conda_install_folder.is_dir() && conda_install_folder.exists() { + if let Some(manager) = get_conda_manager(&conda_install_folder) { + // 1. Base environment. + if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { + if let Some(env_path) = env.clone().env_path { + possible_conda_envs.remove(&env_path); + let key = env_path.to_string_lossy().to_string(); if !detected_envs.contains(&key) { detected_envs.insert(key); environments.push(env); @@ -565,7 +673,40 @@ pub fn get_conda_environments_in_specified_path( } } - envs.unwrap_or_default().iter().for_each(|env| { + // 2. All environments in the `/envs` folder + let mut envs: Vec = vec![]; + if let Some(environments) = + get_environments_from_envs_folder_in_conda_directory(conda_install_folder) + { + environments.iter().for_each(|env| { + possible_conda_envs.remove(&env.env_path); + envs.push(env.clone()); + }); + } + + // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + // E.g conda_install_folder is `/` + // Then all folders such as `//envs/env1` can be ignored + // As these would have been discovered in previous step. + for (key, env) in possible_conda_envs.clone().iter() { + if env + .env_path + .to_string_lossy() + .contains(conda_install_folder.to_str().unwrap()) + { + continue; + } + if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { + envs.push(env.clone()); + possible_conda_envs.remove(key); + } + } + + // Finally construct the PythonEnvironment objects + envs.iter().for_each(|env| { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( None, @@ -573,7 +714,7 @@ pub fn get_conda_environments_in_specified_path( exe.clone(), messaging::PythonEnvironmentCategory::Conda, env.version.clone(), - Some(env.path.clone()), + Some(env.env_path.clone()), Some(manager.clone()), get_activation_command(env, &manager), ); @@ -605,32 +746,41 @@ pub fn get_conda_environments_in_specified_path( fn find_conda_environments_from_known_conda_install_locations( environment: &dyn known::Environment, + possible_conda_envs: &mut HashMap, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; - let mut detected_envs: HashSet = HashSet::new(); - let mut detected_managers: HashSet = HashSet::new(); - for possible_conda_folder in get_known_conda_install_locations(environment) { - if let Some(result) = - get_conda_environments_in_specified_path(&possible_conda_folder, environment) - { - result.managers.iter().for_each(|m| { - let key = get_environment_manager_key(m); - if !detected_managers.contains(&key) { - detected_managers.insert(key); - managers.push(m.clone()); - } - }); + // We know conda is installed in `/Anaconda3`, `/miniforge3`, etc + // Look for these and discover all environments in these locations + for possible_conda_install_folder in get_known_conda_install_locations(environment) { + if let Some(mut result) = get_conda_environments_in_specified_install_path( + &possible_conda_install_folder, + possible_conda_envs, + ) { + managers.append(&mut result.managers); + environments.append(&mut result.environments); + } + } - result.environments.iter().for_each(|e| { - if let Some(key) = get_environment_key(e) { - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(e.clone()); - } - } - }); + // We know conda environments are listed in the `environments.txt` file + // Sometimes the base environment is also listed in these paths + // Go through them an look for possible conda install folders in these paths. + // & then look for conda environments in each of them. + // This accounts for cases where Conda install location is in some un-common (custom) location + let mut env_paths_to_remove: Vec = vec![]; + for (key, env) in possible_conda_envs + .clone() + .iter() + .filter(|(_, env)| is_conda_install_location(&env.env_path)) + { + if let Some(mut result) = + get_conda_environments_in_specified_install_path(&env.env_path, possible_conda_envs) + { + possible_conda_envs.remove(key); + managers.append(&mut result.managers); + environments.append(&mut result.environments); + env_paths_to_remove.push(env.env_path.clone()); } } @@ -644,6 +794,11 @@ fn find_conda_environments_from_known_conda_install_locations( }) } +fn is_conda_install_location(path: &PathBuf) -> bool { + let envs_path = path.join("envs"); + return envs_path.exists() && envs_path.is_dir(); +} + pub fn get_conda_version(conda_binary: &PathBuf) -> Option { let mut parent = conda_binary.parent()?; if parent.ends_with("bin") { @@ -661,25 +816,42 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { } } -fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( - known_environment_keys: &HashSet, - known_environment: &Vec, +fn get_known_conda_envs_from_various_locations( environment: &dyn known::Environment, -) -> Option { - let binding = get_conda_envs_from_environment_txt(environment); - let undiscovered_environments_in_txt = binding +) -> HashMap { + let mut env_paths = get_conda_envs_from_environment_txt(environment) .iter() - .filter(|env| { - for known in known_environment_keys.iter() { - if known.contains(*env) { - return false; - } - } - true - }) - .collect::>(); + .map(|e| PathBuf::from(e)) + .collect::>(); + + let mut env_paths_from_conda_rc = get_conda_environment_paths_from_conda_rc(environment); + env_paths.append(&mut env_paths_from_conda_rc); + + let mut envs_from_known_paths = get_conda_environment_paths_from_known_paths(environment); + env_paths.append(&mut envs_from_known_paths); + + let mut envs: Vec = vec![]; + env_paths.iter().for_each(|path| { + if !path.exists() { + return; + } + if let Some(env) = get_conda_environment_info(&path, false) { + envs.push(env); + } + }); + + envs.into_iter().fold(HashMap::new(), |mut acc, env| { + acc.insert(env.env_path.clone(), env); + acc + }) +} - if undiscovered_environments_in_txt.len() == 0 { +fn get_conda_environments_from_known_locations_that_have_not_been_discovered( + known_environment: &Vec, + environment: &dyn known::Environment, + undiscovered_environments: &mut HashMap, +) -> Option { + if undiscovered_environments.is_empty() { return None; } @@ -687,7 +859,7 @@ fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( // Let's try to discover it. warn!( "Found environments in environments.txt that were not discovered: {:?}", - undiscovered_environments_in_txt + undiscovered_environments ); let manager = match known_environment @@ -708,21 +880,19 @@ fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( if let Some(manager) = manager { let mut environments: Vec = vec![]; - for env in undiscovered_environments_in_txt { - if let Some(env) = get_conda_environment_info(&PathBuf::from(env), false) { - let exe = env.python_executable_path.clone(); - let env = PythonEnvironment::new( - None, - Some(env.name.clone()), - exe.clone(), - messaging::PythonEnvironmentCategory::Conda, - env.version.clone(), - Some(env.path.clone()), - Some(manager.clone()), - get_activation_command(&env, &manager), - ); - environments.push(env); - } + for (_, env) in undiscovered_environments { + let exe = env.python_executable_path.clone(); + let env = PythonEnvironment::new( + None, + Some(env.name.clone()), + exe.clone(), + messaging::PythonEnvironmentCategory::Conda, + env.version.clone(), + Some(env.env_path.clone()), + Some(manager.clone()), + get_activation_command(&env, &manager), + ); + environments.push(env); } if environments.len() > 0 { return Some(LocatorResult { @@ -740,7 +910,7 @@ fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( pub struct Conda<'a> { pub manager: Option, pub environment: &'a dyn Environment, - pub discovered_environments: HashSet, + pub discovered_environment_paths: HashSet, pub discovered_managers: HashSet, } @@ -753,7 +923,7 @@ impl Conda<'_> { Conda { environment, manager: None, - discovered_environments: HashSet::new(), + discovered_environment_paths: HashSet::new(), discovered_managers: HashSet::new(), } } @@ -763,11 +933,11 @@ impl Conda<'_> { .environments .iter() .filter(|e| { - if let Some(key) = get_environment_key(e) { - if self.discovered_environments.contains(&key) { + if let Some(env_path) = e.env_path.clone() { + if self.discovered_environment_paths.contains(&env_path) { return false; } - self.discovered_environments.insert(key); + self.discovered_environment_paths.insert(env_path); return true; } false @@ -802,9 +972,10 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { - self.filter_result(get_conda_environments_in_specified_path( + let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); + self.filter_result(get_conda_environments_in_specified_install_path( possible_conda_folder, - self.environment, + &mut possible_conda_envs, )) } } @@ -819,13 +990,16 @@ impl Locator for Conda<'_> { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; let mut detected_managers: HashSet = HashSet::new(); + let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); - if let Some(result) = self.filter_result( - find_conda_environments_from_known_conda_install_locations(self.environment), - ) { + if let Some(result) = + self.filter_result(find_conda_environments_from_known_conda_install_locations( + self.environment, + &mut possible_conda_envs, + )) + { result.managers.iter().for_each(|m| { - let key = get_environment_manager_key(m); - detected_managers.insert(key); + detected_managers.insert(get_environment_manager_key(m)); managers.push(m.clone()); }); @@ -836,10 +1010,10 @@ impl Locator for Conda<'_> { } if let Some(result) = self.filter_result( - get_conda_environments_from_environments_txt_that_have_not_been_discovered( - &self.discovered_environments, + get_conda_environments_from_known_locations_that_have_not_been_discovered( &environments, self.environment, + &mut possible_conda_envs, ), ) { result.managers.iter().for_each(|m| { diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index 52392343eee9..6e37d897157e 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -4,6 +4,10 @@ use std::{env, path::PathBuf}; pub trait Environment { fn get_user_home(&self) -> Option; + /** + * Only used in tests, this is the root `/`. + */ + fn get_root(&self) -> Option; fn get_env_var(&self, key: String) -> Option; fn get_know_global_search_locations(&self) -> Vec; } @@ -15,6 +19,9 @@ impl Environment for EnvironmentApi { fn get_user_home(&self) -> Option { get_user_home() } + fn get_root(&self) -> Option { + None + } fn get_env_var(&self, key: String) -> Option { get_env_var(key) } @@ -28,6 +35,9 @@ impl Environment for EnvironmentApi { fn get_user_home(&self) -> Option { get_user_home() } + fn get_root(&self) -> Option { + None + } fn get_env_var(&self, key: String) -> Option { get_env_var(key) } diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index 18d529a80564..a318c102230a 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -6,7 +6,7 @@ use crate::{ utils::PythonEnv, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LocatorResult { pub managers: Vec, pub environments: Vec, diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 34a2ca1d8c73..73e708dcac5f 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -25,7 +25,7 @@ pub enum EnvManagerType { Pyenv, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] #[derive(Debug)] pub struct EnvManager { @@ -44,16 +44,6 @@ impl EnvManager { } } -impl Clone for EnvManager { - fn clone(&self) -> Self { - Self { - executable_path: self.executable_path.clone(), - version: self.version.clone(), - tool: self.tool.clone(), - } - } -} - #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[derive(Debug)] diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index b0cfb0e6e412..c70efe9654ef 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -24,13 +24,6 @@ impl PythonEnv { version, } } - pub fn from(executable: PathBuf) -> Self { - Self { - executable, - path: None, - version: None, - } - } } #[derive(Debug)] diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 407e93707b7e..bf4c54617f16 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -1,17 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use python_finder::{ - known::Environment, - locator::LocatorResult, - messaging::{EnvManager, PythonEnvironment}, -}; +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 parts: Vec = paths.iter().map(|p| p.to_string()).collect(); let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); paths.iter().for_each(|p| root.push(p)); @@ -32,6 +27,7 @@ pub trait TestMessages { pub struct TestEnvironment { vars: HashMap, home: Option, + root: Option, globals_locations: Vec, } #[allow(dead_code)] @@ -39,11 +35,15 @@ pub fn create_test_environment( vars: HashMap, home: Option, globals_locations: Vec, + root: Option, ) -> TestEnvironment { impl Environment for TestEnvironment { fn get_env_var(&self, key: String) -> Option { self.vars.get(&key).cloned() } + fn get_root(&self) -> Option { + self.root.clone() + } fn get_user_home(&self) -> Option { self.home.clone() } @@ -54,6 +54,7 @@ pub fn create_test_environment( TestEnvironment { vars, home, + root, globals_locations, } } @@ -142,19 +143,3 @@ pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) { } } } - -#[allow(dead_code)] -pub fn get_environments_from_result(result: &Option) -> Vec { - match result { - Some(result) => result.environments.clone(), - None => vec![], - } -} - -#[allow(dead_code)] -pub fn get_managers_from_result(result: &Option) -> Vec { - match result { - Some(result) => result.managers.clone(), - None => vec![], - } -} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 7db280c9496b..d7d71fd5cce1 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -7,30 +7,29 @@ mod common; #[cfg(unix)] fn find_python_in_path_this() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, join_test_paths, - test_file_path, + 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 unix_python = test_file_path(&["tests/unix/known"]); - let unix_python_exe = join_test_paths(&[unix_python.clone().to_str().unwrap(), "python"]); + 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(), - unix_python.clone().to_str().unwrap().to_string(), + user_home.clone().to_string_lossy().to_string(), )]), - Some(unix_python.clone()), + Some(user_home.clone()), Vec::new(), + None, ); let mut locator = common_python::PythonOnPath::with(&known); - let result = locator.find(); + let result = locator.find().unwrap(); - let environments = get_environments_from_result(&result); - assert_eq!(environments.len(), 1); + assert_eq!(result.environments.len(), 1); let env = PythonEnvironment { display_name: None, @@ -41,10 +40,14 @@ fn find_python_in_path_this() { 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(unix_python.clone()), + env_path: Some(user_home.clone()), }; assert_messages( &[json!(env)], - &environments.iter().map(|e| json!(e)).collect::>(), + &result + .environments + .iter() + .map(|e| json!(e)) + .collect::>(), ); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 1705dc72bf47..397ed42b1de6 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -6,7 +6,7 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_conda_envs() { - use crate::common::{create_test_environment, get_environments_from_result}; + use crate::common::create_test_environment; use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; @@ -14,28 +14,102 @@ fn does_not_find_any_conda_envs() { 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(); - let environments = get_environments_from_result(&result); - assert_eq!(environments.len(), 0); + 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) { + 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::{ - assert_messages, create_test_environment, get_managers_from_result, join_test_paths, - test_file_path, - }; + 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"]); - let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); + 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([( @@ -44,11 +118,12 @@ fn find_conda_exe_and_empty_envs() { )]), Some(user_home), Vec::new(), + None, ); let mut locator = conda::Conda::with(&known); - let result = locator.find(); - let managers = get_managers_from_result(&result); + let result = locator.find().unwrap(); + let managers = result.managers; assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[ @@ -62,36 +137,87 @@ fn find_conda_exe_and_empty_envs() { version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; - assert_messages( - &[json!(expected_conda_manager)], - &managers.iter().map(|e| json!(e)).collect::>(), + 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, + }; + 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(), + ]), + }; + 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_txt() { +fn finds_two_conda_envs_from_known_location() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, - get_managers_from_result, join_test_paths, test_file_path, + 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; - use std::fs; - let home = test_file_path(&["tests/unix/conda"]); - let conda_dir = test_file_path(&["tests/unix/conda/anaconda3"]); + 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 _ = fs::write( - "tests/unix/conda/.conda/environments.txt", - format!( - "{}\n{}", - conda_1.clone().to_str().unwrap().to_string(), - conda_2.clone().to_str().unwrap().to_string() - ), - ); let known = create_test_environment( HashMap::from([( @@ -100,13 +226,14 @@ fn finds_two_conda_envs_from_txt() { )]), Some(home), Vec::new(), + None, ); let mut locator = conda::Conda::with(&known); - let result = locator.find(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); - let environments = get_environments_from_result(&result); + 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"]); @@ -118,6 +245,10 @@ fn finds_two_conda_envs_from_txt() { version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; + + 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()), @@ -133,9 +264,9 @@ fn finds_two_conda_envs_from_txt() { "-n".to_string(), "one".to_string(), "python".to_string(), - ]), - }; - let expected_conda_2 = PythonEnvironment { + ]), + }; + let expected_conda_2 = PythonEnvironment { display_name: None, name: Some("two".to_string()), project_path: None, @@ -152,10 +283,6 @@ fn finds_two_conda_envs_from_txt() { "python".to_string(), ]), }; - assert_messages( - &[json!(expected_conda_manager)], - &managers.iter().map(|e| json!(e)).collect::>(), - ); assert_messages( &[json!(expected_conda_1), json!(expected_conda_2)], &environments.iter().map(|e| json!(e)).collect::>(), diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 42249586864d..87761114089d 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -6,9 +6,7 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs() { - use crate::common::{ - create_test_environment, get_environments_from_result, get_managers_from_result, - }; + use crate::common::create_test_environment; use python_finder::{conda::Conda, locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; @@ -16,42 +14,50 @@ fn does_not_find_any_pyenv_envs() { 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!(get_managers_from_result(&result).len(), 0); - assert_eq!(get_environments_from_result(&result).len(), 0); + 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, get_managers_from_result, join_test_paths, - test_file_path, + assert_messages, create_test_environment, join_test_paths, test_file_path, }; 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"]); - let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); + 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(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); + let managers = result.managers; assert_eq!(managers.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); @@ -65,8 +71,8 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { #[cfg(unix)] fn find_pyenv_envs() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, - get_managers_from_result, join_test_paths, test_file_path, + assert_messages, create_test_environment, join_test_paths, + test_file_path, }; use python_finder::conda::Conda; use python_finder::locator::Locator; @@ -77,32 +83,29 @@ fn find_pyenv_envs() { use serde_json::json; use std::{collections::HashMap, path::PathBuf}; - let home = test_file_path(&["tests", "unix", "pyenv"]); - let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); + 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(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); - assert_eq!(managers.len(), 1); + assert_eq!(result.managers.len(), 1); let expected_manager = EnvManager { executable_path: pyenv_exe.clone(), version: None, tool: EnvManagerType::Pyenv, }; - - assert_messages( - &[json!(expected_manager)], - &managers.iter().map(|e| json!(e)).collect::>(), - ); + assert_eq!(json!(expected_manager), json!(result.managers[0])); let expected_3_9_9 = json!(PythonEnvironment { display_name: None, @@ -219,7 +222,6 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), }; - let environments = get_environments_from_result(&result); assert_messages( &[ @@ -229,6 +231,10 @@ fn find_pyenv_envs() { json!(expected_3_13_dev), json!(expected_3_12_1a3), ], - &environments.iter().map(|e| json!(e)).collect::>(), + &result + .environments + .iter() + .map(|e| json!(e)) + .collect::>(), ) } diff --git a/native_locator/tests/unix/conda/.conda/environments.txt b/native_locator/tests/unix/conda/.conda/environments.txt deleted file mode 100644 index 3a9e625c3050..000000000000 --- a/native_locator/tests/unix/conda/.conda/environments.txt +++ /dev/null @@ -1,2 +0,0 @@ -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/one -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/two \ No newline at end of file diff --git a/native_locator/tests/unix/conda_without_envs/.conda/environments.txt b/native_locator/tests/unix/conda/user_home/.conda/environments.txt similarity index 100% rename from native_locator/tests/unix/conda_without_envs/.conda/environments.txt rename to native_locator/tests/unix/conda/user_home/.conda/environments.txt diff --git a/native_locator/tests/unix/conda/anaconda3/bin/conda b/native_locator/tests/unix/conda/user_home/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/bin/conda rename to native_locator/tests/unix/conda/user_home/anaconda3/bin/conda diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/python rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python diff --git a/native_locator/tests/unix/conda/anaconda3/envs/two/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/two/python rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda b/native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda rename to native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda diff --git a/native_locator/tests/unix/known/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python similarity index 100% rename from native_locator/tests/unix/known/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json new file mode 100644 index 000000000000..23127993ac05 --- /dev/null +++ b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json @@ -0,0 +1 @@ +10.1.1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/known/user_home/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python rename to native_locator/tests/unix/known/user_home/python diff --git a/native_locator/tests/unix/known/python.version b/native_locator/tests/unix/known/user_home/python.version similarity index 100% rename from native_locator/tests/unix/known/python.version rename to native_locator/tests/unix/known/user_home/python.version diff --git a/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv similarity index 100% rename from native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv rename to native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1 From f2313f98ad4ad9b6e5179f845ba9c96eaa5c8918 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 12:18:26 +1000 Subject: [PATCH 048/106] Ignore native locator tests folder (#23436) --- .vscodeignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscodeignore b/.vscodeignore index 93e26af08b72..78028d097455 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -68,5 +68,7 @@ tmp/** typings/** types/** native_locator/src/** +native_locator/tests/** +native_locator/bin/** native_locator/target/** native_locator/Cargo.* From a8a0e599ea22346138addbf52daa9a69158e377d Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 15 May 2024 19:09:14 -0700 Subject: [PATCH 049/106] Add Interactive REPL Experiment (#23235) Allow users to use Interactive Window UI with Python custom REPL controller instead of iPykernel. Closes #23175 Closes #23174 Closes https://github.com/microsoft/vscode-python/issues/23029 Majority of: #23332 Context menu under Python for running Python REPL code using IW UI should only appear when user's ```pythonRunREPL``` experiment is enabled. --- .vscode/launch.json | 2 +- package.json | 28 ++- package.nls.json | 2 + python_files/python_server.py | 167 ++++++++++++++++++ src/client/common/application/commands.ts | 1 + src/client/common/constants.ts | 1 + src/client/common/experiments/groups.ts | 5 + src/client/common/types.ts | 1 + src/client/extensionActivation.ts | 16 +- src/client/repl/pythonServer.ts | 81 +++++++++ src/client/repl/replCommands.ts | 104 +++++++++++ src/client/repl/replController.ts | 43 +++++ src/client/terminals/codeExecution/helper.ts | 4 +- .../terminals/codeExecution/helper.test.ts | 4 +- .../terminals/codeExecution/smartSend.test.ts | 4 +- 15 files changed, 453 insertions(+), 10 deletions(-) create mode 100644 python_files/python_server.py create mode 100644 src/client/repl/pythonServer.ts create mode 100644 src/client/repl/replCommands.ts create mode 100644 src/client/repl/replController.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index a4a5104c2d22..4dc107853fc6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -252,7 +252,7 @@ ], "compounds": [ { - "name": "Debug Test Discovery", + "name": "Debug Python and Extension", "configurations": ["Python: Attach Listen", "Extension"] } ] diff --git a/package.json b/package.json index 45e4b5ee3e5c..b3dd496d355c 100644 --- a/package.json +++ b/package.json @@ -306,6 +306,11 @@ "command": "python.execSelectionInTerminal", "title": "%python.command.python.execSelectionInTerminal.title%" }, + { + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%" + }, { "category": "Python", "command": "python.launchTensorBoard", @@ -437,7 +442,8 @@ "pythonDiscoveryUsingWorkers", "pythonTestAdapter", "pythonREPLSmartSend", - "pythonRecommendTensorboardExt" + "pythonRecommendTensorboardExt", + "pythonRunREPL" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -447,7 +453,8 @@ "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRecommendTensorboardExt.description%" + "%python.experiments.pythonRecommendTensorboardExt.description%", + "%python.experiments.pythonRunREPL.description%" ] }, "scope": "window", @@ -465,7 +472,8 @@ "pythonTerminalEnvVarActivation", "pythonDiscoveryUsingWorkers", "pythonTestAdapter", - "pythonREPLSmartSend" + "pythonREPLSmartSend", + "pythonRunREPL" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -474,7 +482,8 @@ "%python.experiments.pythonTerminalEnvVarActivation.description%", "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", - "%python.experiments.pythonREPLSmartSend.description%" + "%python.experiments.pythonREPLSmartSend.description%", + "%python.experiments.pythonRunREPL.description%" ] }, "scope": "window", @@ -1254,6 +1263,12 @@ "title": "%python.command.python.execSelectionInTerminal.title%", "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" }, + { + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%", + "when": "false" + }, { "category": "Python", "command": "python.launchTensorBoard", @@ -1353,6 +1368,11 @@ "command": "python.execSelectionInTerminal", "group": "Python", "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execInREPL", + "group": "Python", + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL" } ], "editor/title": [ diff --git a/package.nls.json b/package.nls.json index e7c82f86b243..aa84f31a91b2 100644 --- a/package.nls.json +++ b/package.nls.json @@ -14,6 +14,7 @@ "python.command.python.configureTests.title": "Configure Tests", "python.command.testing.rerunFailedTests.title": "Rerun Failed Tests", "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", + "python.command.python.execInREPL.title": "Run Selection/Line in Python REPL", "python.command.python.execSelectionInDjangoShell.title": "Run Selection/Line in Django Shell", "python.command.python.reportIssue.title": "Report Issue...", "python.command.python.enableSourceMapSupport.title": "Enable Source Map Support For Extension Debugging", @@ -44,6 +45,7 @@ "python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.", "python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.", "python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.", + "python.experiments.pythonRunREPL.description": "Enables users to run code in interactive Python REPL.", "python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.", "python.languageServer.description": "Defines type of the language server.", "python.languageServer.defaultDescription": "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.", diff --git a/python_files/python_server.py b/python_files/python_server.py new file mode 100644 index 000000000000..4d27a168bc4c --- /dev/null +++ b/python_files/python_server.py @@ -0,0 +1,167 @@ +from typing import Dict, List, Optional, Union + +import sys +import json +import contextlib +import io +import traceback +import uuid + +STDIN = sys.stdin +STDOUT = sys.stdout +STDERR = sys.stderr +USER_GLOBALS = {} + + +def send_message(msg: str): + length_msg = len(msg) + STDOUT.buffer.write(f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8")) + STDOUT.buffer.flush() + + +def print_log(msg: str): + send_message(json.dumps({"jsonrpc": "2.0", "method": "log", "params": msg})) + + +def send_response(response: str, response_id: int): + send_message(json.dumps({"jsonrpc": "2.0", "id": response_id, "result": response})) + + +def send_request(params: Optional[Union[List, Dict]] = None): + request_id = uuid.uuid4().hex + if params is None: + send_message(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input"})) + else: + send_message( + json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input", "params": params}) + ) + return request_id + + +original_input = input + + +def custom_input(prompt=""): + try: + send_request({"prompt": prompt}) + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + message_text = STDIN.read(content_length) + message_json = json.loads(message_text) + our_user_input = message_json["result"]["userInput"] + return our_user_input + except Exception: + print_log(traceback.format_exc()) + + +# Set input to our custom input +USER_GLOBALS["input"] = custom_input +input = custom_input + + +def handle_response(request_id): + while not STDIN.closed: + try: + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + message_text = STDIN.read(content_length) + message_json = json.loads(message_text) + our_user_input = message_json["result"]["userInput"] + if message_json["id"] == request_id: + send_response(our_user_input, message_json["id"]) + elif message_json["method"] == "exit": + sys.exit(0) + + except Exception: + print_log(traceback.format_exc()) + + +def exec_function(user_input): + try: + compile(user_input, "", "eval") + except SyntaxError: + return exec + return eval + + +def execute(request, user_globals): + str_output = CustomIO("", encoding="utf-8") + str_error = CustomIO("", encoding="utf-8") + + with redirect_io("stdout", str_output): + with redirect_io("stderr", str_error): + str_input = CustomIO("", encoding="utf-8", newline="\n") + with redirect_io("stdin", str_input): + exec_user_input(request["params"], user_globals) + send_response(str_output.get_value(), request["id"]) + + +def exec_user_input(user_input, user_globals): + user_input = user_input[0] if isinstance(user_input, list) else user_input + + try: + callable = exec_function(user_input) + retval = callable(user_input, user_globals) + if retval is not None: + print(retval) + except KeyboardInterrupt: + print(traceback.format_exc()) + except Exception: + print(traceback.format_exc()) + + +class CustomIO(io.TextIOWrapper): + """Custom stream object to replace stdio.""" + + def __init__(self, name, encoding="utf-8", newline=None): + self._buffer = io.BytesIO() + self._custom_name = name + super().__init__(self._buffer, encoding=encoding, newline=newline) + + def close(self): + """Provide this close method which is used by some tools.""" + # This is intentionally empty. + + def get_value(self) -> str: + """Returns value from the buffer as string.""" + self.seek(0) + return self.read() + + +@contextlib.contextmanager +def redirect_io(stream: str, new_stream): + """Redirect stdio streams to a custom stream.""" + old_stream = getattr(sys, stream) + setattr(sys, stream, new_stream) + yield + setattr(sys, stream, old_stream) + + +def get_headers(): + headers = {} + while line := STDIN.readline().strip(): + name, value = line.split(":", 1) + headers[name] = value.strip() + return headers + + +if __name__ == "__main__": + while not STDIN.closed: + try: + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + request_text = STDIN.read(content_length) + request_json = json.loads(request_text) + if request_json["method"] == "execute": + execute(request_json, USER_GLOBALS) + elif request_json["method"] == "exit": + sys.exit(0) + + except Exception: + print_log(traceback.format_exc()) diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 30ba5d84cf5f..626321566332 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -38,6 +38,7 @@ interface ICommandNameWithoutArgumentTypeMapping { [Commands.Enable_SourceMap_Support]: []; [Commands.Exec_Selection_In_Terminal]: []; [Commands.Exec_Selection_In_Django_Shell]: []; + [Commands.Exec_In_REPL]: []; [Commands.Create_Terminal]: []; [Commands.PickLocalProcess]: []; [Commands.ClearStorage]: []; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 0eaade703371..663b932c8542 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -46,6 +46,7 @@ export namespace Commands { export const Exec_In_Terminal = 'python.execInTerminal'; export const Exec_In_Terminal_Icon = 'python.execInTerminal-icon'; export const Exec_In_Separate_Terminal = 'python.execInDedicatedTerminal'; + export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 81f157751346..543b1e27516f 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -30,3 +30,8 @@ export enum RecommendTensobardExtension { export enum CreateEnvOnPipInstallTrigger { experiment = 'pythonCreateEnvOnPipInstall', } + +// Experiment to enable running Python REPL using IW. +export enum EnableRunREPL { + experiment = 'pythonRunREPL', +} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 67fcf5c7b700..8edc76ff2bff 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -200,6 +200,7 @@ export interface ITerminalSettings { export interface IREPLSettings { readonly enableREPLSmartSend: boolean; + readonly enableIWREPL: boolean; } export interface IExperiments { diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 543d2d0b7f49..7c582eb63239 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -16,6 +16,7 @@ import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, + IExperimentService, IExtensions, IInterpreterPathService, ILogOutputChannel, @@ -52,6 +53,8 @@ import { initializePersistentStateForTriggers } from './common/persistentState'; import { logAndNotifyOnLegacySettings } from './logging/settingLogs'; import { DebuggerTypeName } from './debugger/constants'; import { StopWatch } from './common/utils/stopWatch'; +import { registerReplCommands } from './repl/replCommands'; +import { EnableRunREPL } from './common/experiments/groups'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -105,6 +108,17 @@ export function activateFeatures(ext: ExtensionState, _components: Components): interpreterService, pathUtils, ); + + // Register native REPL context menu when in experiment + const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); + commands.executeCommand('setContext', 'pythonRunREPL', false); + if (experimentService) { + const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); + if (replExperimentValue) { + registerReplCommands(ext.disposables, interpreterService); + commands.executeCommand('setContext', 'pythonRunREPL', true); + } + } } /// ////////////////////////// diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts new file mode 100644 index 000000000000..e25ba3a25092 --- /dev/null +++ b/src/client/repl/pythonServer.ts @@ -0,0 +1,81 @@ +import * as path from 'path'; +import * as ch from 'child_process'; +import * as rpc from 'vscode-jsonrpc/node'; +import { Disposable, window } from 'vscode'; +import { EXTENSION_ROOT_DIR } from '../constants'; +import { traceError, traceLog } from '../logging'; + +const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); + +export interface PythonServer extends Disposable { + execute(code: string): Promise; + interrupt(): void; + input(): void; +} + +class PythonServerImpl implements Disposable { + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { + this.initialize(); + this.input(); + } + + private initialize(): void { + this.connection.onNotification('log', (message: string) => { + console.log('Log:', message); + }); + this.connection.listen(); + } + + // Register input handler + public input(): void { + // Register input request handler + this.connection.onRequest('input', async (request) => { + // Ask for user input via popup quick input, send it back to Python + let userPrompt = 'Enter your input here: '; + if (request && request.prompt) { + userPrompt = request.prompt; + } + const input = await window.showInputBox({ + title: 'Input Request', + prompt: userPrompt, + ignoreFocusOut: true, + }); + return { userInput: input }; + }); + } + + public execute(code: string): Promise { + return this.connection.sendRequest('execute', code); + } + + public interrupt(): void { + if (this.pythonServer.kill('SIGINT')) { + traceLog('Python server interrupted'); + } + } + + public dispose(): void { + this.connection.sendNotification('exit'); + this.connection.dispose(); + } +} + +export function createPythonServer(interpreter: string[]): PythonServer { + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); + + pythonServer.stderr.on('data', (data) => { + traceError(data.toString()); + }); + pythonServer.on('exit', (code) => { + traceError(`Python server exited with code ${code}`); + }); + pythonServer.on('error', (err) => { + traceError(err); + }); + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(pythonServer.stdout), + new rpc.StreamMessageWriter(pythonServer.stdin), + ); + + return new PythonServerImpl(connection, pythonServer); +} diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts new file mode 100644 index 000000000000..e7a40b01c6be --- /dev/null +++ b/src/client/repl/replCommands.ts @@ -0,0 +1,104 @@ +import { + commands, + NotebookController, + Uri, + workspace, + window, + NotebookControllerAffinity, + ViewColumn, + NotebookEdit, + NotebookCellData, + NotebookCellKind, + WorkspaceEdit, + NotebookEditor, + TextEditor, +} from 'vscode'; +import { Disposable } from 'vscode-jsonrpc'; +import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; +import { noop } from '../common/utils/misc'; +import { IInterpreterService } from '../interpreter/contracts'; +import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; +import { createReplController } from './replController'; + +let notebookController: NotebookController | undefined; +let notebookEditor: NotebookEditor | undefined; +// TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. + +async function getSelectedTextToExecute(textEditor: TextEditor): Promise { + if (!textEditor) { + return undefined; + } + + const { selection } = textEditor; + let code: string; + + if (selection.isEmpty) { + code = textEditor.document.lineAt(selection.start.line).text; + } else if (selection.isSingleLine) { + code = getSingleLineSelectionText(textEditor); + } else { + code = getMultiLineSelectionText(textEditor); + } + + return code; +} + +export async function registerReplCommands( + disposables: Disposable[], + interpreterService: IInterpreterService, +): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return; + } + if (interpreter) { + const interpreterPath = interpreter.path; + + if (!notebookController) { + notebookController = createReplController(interpreterPath); + } + const activeEditor = window.activeTextEditor as TextEditor; + + const code = await getSelectedTextToExecute(activeEditor); + const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + + const notebookDocument = await workspace.openNotebookDocument(ourResource); + // commands.executeCommand('_interactive.open'); command to open interactive window so intellisense is registered. + + // We want to keep notebookEditor, whenever we want to run. + // Find interactive window, or open it. + if (!notebookEditor) { + notebookEditor = await window.showNotebookDocument(notebookDocument, { + viewColumn: ViewColumn.Beside, + }); + } + + notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + + // Auto-Select Python REPL Kernel + await commands.executeCommand('notebook.selectKernel', { + notebookEditor, + id: notebookController?.id, + extension: PVSC_EXTENSION_ID, + }); + + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); + const { cellCount } = notebookDocument; + // Add new cell to interactive window document + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument.uri, [notebookEdit]); + await workspace.applyEdit(workspaceEdit); + + // Execute the cell + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: ourResource, + }); + } + }), + ); +} diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts new file mode 100644 index 000000000000..f7ee7e6d486c --- /dev/null +++ b/src/client/repl/replController.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode'; +import { createPythonServer } from './pythonServer'; + +export function createReplController(interpreterPath: string): vscode.NotebookController { + const server = createPythonServer([interpreterPath]); + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); + controller.supportedLanguages = ['python']; + controller.supportsExecutionOrder = true; + + controller.description = 'Python REPL'; + + controller.interruptHandler = async () => { + server.interrupt(); + }; + + controller.executeHandler = async (cells) => { + for (const cell of cells) { + const exec = controller.createNotebookCellExecution(cell); + exec.start(Date.now()); + try { + const result = await server.execute(cell.document.getText()); + + exec.replaceOutput([ + new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(result, 'text/plain')]), + ]); + exec.end(true); + } catch (err) { + const error = err as Error; + exec.replaceOutput([ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.error({ + name: error.name, + message: error.message, + stack: error.stack, + }), + ]), + ]); + exec.end(false); + } + } + }; + return controller; +} diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 880da969d690..ff1c4f218f8d 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -200,7 +200,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } } -function getSingleLineSelectionText(textEditor: TextEditor): string { +export function getSingleLineSelectionText(textEditor: TextEditor): string { const { selection } = textEditor; const selectionRange = new Range(selection.start, selection.end); const selectionText = textEditor.document.getText(selectionRange); @@ -227,7 +227,7 @@ function getSingleLineSelectionText(textEditor: TextEditor): string { return selectionText; } -function getMultiLineSelectionText(textEditor: TextEditor): string { +export function getMultiLineSelectionText(textEditor: TextEditor): string { const { selection } = textEditor; const selectionRange = new Range(selection.start, selection.end); const selectionText = textEditor.document.getText(selectionRange); diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 2ea00e77c925..9098455c968e 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -110,7 +110,9 @@ suite('Terminal - Code Execution Helper', () => { .setup((c) => c.get(TypeMoq.It.isValue(IActiveResourceService))) .returns(() => activeResourceService.object); activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); - pythonSettings.setup((s) => s.REPL).returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false })); + pythonSettings + .setup((s) => s.REPL) + .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, enableIWREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); configurationService .setup((c) => c.getSettings(TypeMoq.It.isAny())) diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index f93df2ac11ed..ba5101332bf8 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -107,7 +107,9 @@ suite('REPL - Smart Send', () => { .returns(() => activeResourceService.object); activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); - pythonSettings.setup((s) => s.REPL).returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true })); + pythonSettings + .setup((s) => s.REPL) + .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, enableIWREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); From b68fa759c43e0a48b3a08e24cff43da00f71475b Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Wed, 15 May 2024 21:38:55 -0700 Subject: [PATCH 050/106] fix parameterized test duplicate function different classes (#23439) fixes https://github.com/microsoft/vscode-python/issues/23434 switched parameterized function IDs to now be `path/to/file::ClassIfExists::functionName` so it is an absolute ID and will not be confused across classes. --- .../.data/same_function_new_class_param.py | 25 +++ .../expected_discovery_test_output.py | 205 +++++++++++++++++- .../tests/pytestadapter/test_discovery.py | 10 +- python_files/tests/tree_comparison_helper.py | 34 ++- .../tests/unittestadapter/test_discovery.py | 8 +- .../tests/unittestadapter/test_utils.py | 8 +- python_files/vscode_pytest/__init__.py | 26 ++- 7 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 python_files/tests/pytestadapter/.data/same_function_new_class_param.py diff --git a/python_files/tests/pytestadapter/.data/same_function_new_class_param.py b/python_files/tests/pytestadapter/.data/same_function_new_class_param.py new file mode 100644 index 000000000000..6f85051436b8 --- /dev/null +++ b/python_files/tests/pytestadapter/.data/same_function_new_class_param.py @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import pytest + + +class TestNotEmpty: + @pytest.mark.parametrize("a, b", [(1, 1), (2, 2)]) # test_marker--TestNotEmpty::test_integer + def test_integer(self, a, b): + assert a == b + + @pytest.mark.parametrize( # test_marker--TestNotEmpty::test_string + "a, b", [("a", "a"), ("b", "b")] + ) + def test_string(self, a, b): + assert a == b + + +class TestEmpty: + @pytest.mark.parametrize("a, b", [(0, 0)]) # test_marker--TestEmpty::test_integer + def test_integer(self, a, b): + assert a == b + + @pytest.mark.parametrize("a, b", [("", "")]) # test_marker--TestEmpty::test_string + def test_string(self, a, b): + assert a == b diff --git a/python_files/tests/pytestadapter/expected_discovery_test_output.py b/python_files/tests/pytestadapter/expected_discovery_test_output.py index d5db7589cca1..3ddceeb060ac 100644 --- a/python_files/tests/pytestadapter/expected_discovery_test_output.py +++ b/python_files/tests/pytestadapter/expected_discovery_test_output.py @@ -541,7 +541,7 @@ "name": "test_adding", "path": os.fspath(parameterize_tests_path), "type_": "function", - "id_": "parametrize_tests.py::TestClass::test_adding", + "id_": os.fspath(parameterize_tests_path) + "::TestClass::test_adding", "children": [ { "name": "[3+5-8]", @@ -638,7 +638,7 @@ ), }, ], - "id_": "parametrize_tests.py::test_string", + "id_": os.fspath(parameterize_tests_path) + "::test_string", }, ], }, @@ -760,7 +760,7 @@ ), }, ], - "id_": "param_same_name/test_param1.py::test_odd_even", + "id_": os.fspath(param1_path) + "::test_odd_even", } ], }, @@ -818,7 +818,7 @@ ), }, ], - "id_": "param_same_name/test_param2.py::test_odd_even", + "id_": os.fspath(param2_path) + "::test_odd_even", } ], }, @@ -1077,3 +1077,200 @@ ], "id_": str(SYMLINK_FOLDER_PATH), } + +same_function_new_class_param_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "same_function_new_class_param.py", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "file", + "id_": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "children": [ + { + "name": "TestNotEmpty", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "class", + "children": [ + { + "name": "test_integer", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "function", + "children": [ + { + "name": "[1-1]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestNotEmpty::test_integer", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_integer[1-1]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_integer[1-1]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + { + "name": "[2-2]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestNotEmpty::test_integer", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_integer[2-2]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_integer[2-2]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + ], + "id_": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py") + + "::TestNotEmpty::test_integer", + }, + { + "name": "test_string", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "function", + "children": [ + { + "name": "[a-a]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestNotEmpty::test_string", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_string[a-a]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_string[a-a]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + { + "name": "[b-b]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestNotEmpty::test_string", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_string[b-b]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestNotEmpty::test_string[b-b]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + ], + "id_": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py") + + "::TestNotEmpty::test_string", + }, + ], + "id_": "same_function_new_class_param.py::TestNotEmpty", + }, + { + "name": "TestEmpty", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "class", + "children": [ + { + "name": "test_integer", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "function", + "children": [ + { + "name": "[0-0]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestEmpty::test_integer", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestEmpty::test_integer[0-0]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestEmpty::test_integer[0-0]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + ], + "id_": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py") + + "::TestEmpty::test_integer", + }, + { + "name": "test_string", + "path": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py"), + "type_": "function", + "children": [ + { + "name": "[-]", + "path": os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + "lineno": find_test_line_number( + "TestEmpty::test_string", + os.fspath( + TEST_DATA_PATH / "same_function_new_class_param.py" + ), + ), + "type_": "test", + "id_": get_absolute_test_id( + "same_function_new_class_param.py::TestEmpty::test_string[-]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + "runID": get_absolute_test_id( + "same_function_new_class_param.py::TestEmpty::test_string[-]", + TEST_DATA_PATH / "same_function_new_class_param.py", + ), + }, + ], + "id_": os.fspath(TEST_DATA_PATH / "same_function_new_class_param.py") + + "::TestEmpty::test_string", + }, + ], + "id_": "same_function_new_class_param.py::TestEmpty", + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +print(param_same_name_expected_output) diff --git a/python_files/tests/pytestadapter/test_discovery.py b/python_files/tests/pytestadapter/test_discovery.py index e27750dc174c..24960b91c644 100644 --- a/python_files/tests/pytestadapter/test_discovery.py +++ b/python_files/tests/pytestadapter/test_discovery.py @@ -113,6 +113,10 @@ def test_parameterized_error_collect(): "test_multi_class_nest.py", expected_discovery_test_output.nested_classes_expected_test_output, ), + ( + "same_function_new_class_param.py", + expected_discovery_test_output.same_function_new_class_param_expected_output, + ), ( "test_multi_class_nest.py", expected_discovery_test_output.nested_classes_expected_test_output, @@ -187,7 +191,9 @@ def test_pytest_collect(file, expected_const): ), f"Status is not 'success', error is: {actual_item.get('error')}" assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH) assert is_same_tree( - actual_item.get("tests"), expected_const + actual_item.get("tests"), + expected_const, + ["id_", "lineno", "name", "runID"], ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_const, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}" @@ -255,6 +261,7 @@ def test_pytest_root_dir(): assert is_same_tree( actual_item.get("tests"), expected_discovery_test_output.root_with_config_expected_output, + ["id_", "lineno", "name", "runID"], ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_discovery_test_output.root_with_config_expected_output, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}" @@ -281,6 +288,7 @@ def test_pytest_config_file(): assert is_same_tree( actual_item.get("tests"), expected_discovery_test_output.root_with_config_expected_output, + ["id_", "lineno", "name", "runID"], ), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_discovery_test_output.root_with_config_expected_output, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}" diff --git a/python_files/tests/tree_comparison_helper.py b/python_files/tests/tree_comparison_helper.py index edf6aa8ff869..3d9d1d39194b 100644 --- a/python_files/tests/tree_comparison_helper.py +++ b/python_files/tests/tree_comparison_helper.py @@ -1,29 +1,39 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - - -def is_same_tree(tree1, tree2) -> bool: - """Helper function to test if two test trees are the same. +def is_same_tree(tree1, tree2, test_key_arr, path="root") -> bool: + """Helper function to test if two test trees are the same with detailed error logs. `is_same_tree` starts by comparing the root attributes, and then checks if all children are the same. """ # Compare the root. - if any(tree1[key] != tree2[key] for key in ["path", "name", "type_"]): - return False + for key in ["path", "name", "type_", "id_"]: + if tree1.get(key) != tree2.get(key): + print( + f"Difference found at {path}: '{key}' is '{tree1.get(key)}' in tree1 and '{tree2.get(key)}' in tree2." + ) + return False # Compare child test nodes if they exist, otherwise compare test items. if "children" in tree1 and "children" in tree2: - # sort children by path before comparing since order doesn't matter of children + # Sort children by path before comparing since order doesn't matter of children children1 = sorted(tree1["children"], key=lambda x: x["path"]) children2 = sorted(tree2["children"], key=lambda x: x["path"]) # Compare test nodes. if len(children1) != len(children2): + print( + f"Difference in number of children at {path}: {len(children1)} in tree1 and {len(children2)} in tree2." + ) return False else: - return all(is_same_tree(*pair) for pair in zip(children1, children2)) + for i, (child1, child2) in enumerate(zip(children1, children2)): + if not is_same_tree(child1, child2, test_key_arr, path=f"{path} -> child {i}"): + return False elif "id_" in tree1 and "id_" in tree2: # Compare test items. - return all(tree1[key] == tree2[key] for key in ["id_", "lineno"]) + for key in test_key_arr: + if tree1.get(key) != tree2.get(key): + print( + f"Difference found at {path}: '{key}' is '{tree1.get(key)}' in tree1 and '{tree2.get(key)}' in tree2." + ) + return False - return False + return True diff --git a/python_files/tests/unittestadapter/test_discovery.py b/python_files/tests/unittestadapter/test_discovery.py index 74eb5a5fb4f3..94d0bb89c62a 100644 --- a/python_files/tests/unittestadapter/test_discovery.py +++ b/python_files/tests/unittestadapter/test_discovery.py @@ -129,7 +129,7 @@ def test_simple_discovery() -> None: actual = discover_tests(start_dir, pattern, None) assert actual["status"] == "success" - assert is_same_tree(actual.get("tests"), expected) + assert is_same_tree(actual.get("tests"), expected, ["id_", "lineno", "name"]) assert "error" not in actual @@ -185,7 +185,7 @@ def test_simple_discovery_with_top_dir_calculated() -> None: actual = discover_tests(start_dir, pattern, None) assert actual["status"] == "success" - assert is_same_tree(actual.get("tests"), expected) + assert is_same_tree(actual.get("tests"), expected, ["id_", "lineno", "name"]) assert "error" not in actual @@ -256,7 +256,7 @@ def test_error_discovery() -> None: actual = discover_tests(start_dir, pattern, None) assert actual["status"] == "error" - assert is_same_tree(expected, actual.get("tests")) + assert is_same_tree(expected, actual.get("tests"), ["id_", "lineno", "name"]) assert len(actual.get("error", [])) == 1 @@ -274,6 +274,7 @@ def test_unit_skip() -> None: assert is_same_tree( actual.get("tests"), expected_discovery_test_output.skip_unittest_folder_discovery_output, + ["id_", "lineno", "name"], ) assert "error" not in actual @@ -296,4 +297,5 @@ def test_complex_tree() -> None: assert is_same_tree( actual.get("tests"), expected_discovery_test_output.complex_tree_expected_output, + ["id_", "lineno", "name"], ) diff --git a/python_files/tests/unittestadapter/test_utils.py b/python_files/tests/unittestadapter/test_utils.py index f650f12252f7..1cb9a4686399 100644 --- a/python_files/tests/unittestadapter/test_utils.py +++ b/python_files/tests/unittestadapter/test_utils.py @@ -110,7 +110,7 @@ def test_get_existing_child_node() -> None: tree_copy = tree.copy() # Check that the tree didn't get mutated by get_child_node. - assert is_same_tree(tree, tree_copy) + assert is_same_tree(tree, tree_copy, ["id_", "lineno", "name"]) def test_no_existing_child_node() -> None: @@ -164,7 +164,7 @@ def test_no_existing_child_node() -> None: tree_after["children"] = tree_after["children"][:-1] # Check that all pre-existing items in the tree didn't get mutated by get_child_node. - assert is_same_tree(tree_before, tree_after) + assert is_same_tree(tree_before, tree_after, ["id_", "lineno", "name"]) # Check for the added node. last_child = tree["children"][-1] @@ -226,7 +226,7 @@ def test_build_simple_tree() -> None: suite = loader.discover(start_dir, pattern) tests, errors = build_test_tree(suite, start_dir) - assert is_same_tree(expected, tests) + assert is_same_tree(expected, tests, ["id_", "lineno", "name"]) assert not errors @@ -286,7 +286,7 @@ def test_build_decorated_tree() -> None: suite = loader.discover(start_dir, pattern) tests, errors = build_test_tree(suite, start_dir) - assert is_same_tree(expected, tests) + assert is_same_tree(expected, tests, ["id_", "lineno", "name"]) assert not errors diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 1d855232cbd8..3534bf7c699f 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -440,10 +440,23 @@ def build_test_tree(session: pytest.Session) -> TestNode: # parameterized test cases cut the repetitive part of the name off. parent_part, parameterized_section = test_node["name"].split("[", 1) test_node["name"] = "[" + parameterized_section - parent_path = os.fspath(get_node_path(test_case)) + "::" + parent_part + + first_split = test_case.nodeid.rsplit( + "::", 1 + ) # splits the parameterized test name from the rest of the nodeid + second_split = first_split[0].rsplit( + ".py", 1 + ) # splits the file path from the rest of the nodeid + + class_and_method = second_split[1] + "::" # This has "::" separator at both ends + # construct the parent id, so it is absolute path :: any class and method :: parent_part + parent_id = os.fspath(get_node_path(test_case)) + class_and_method + parent_part + # file, middle, param = test_case.nodeid.rsplit("::", 2) + # parent_id = test_case.nodeid.rsplit("::", 1)[0] + "::" + parent_part + # parent_path = os.fspath(get_node_path(test_case)) + "::" + parent_part try: function_name = test_case.originalname # type: ignore - function_test_node = function_nodes_dict[parent_path] + function_test_node = function_nodes_dict[parent_id] except AttributeError: # actual error has occurred ERRORS.append( f"unable to find original name for {test_case.name} with parameterization detected." @@ -451,9 +464,9 @@ def build_test_tree(session: pytest.Session) -> TestNode: raise VSCodePytestError("Unable to find original name for parameterized test case") except KeyError: function_test_node: TestNode = create_parameterized_function_node( - function_name, get_node_path(test_case), test_case.nodeid + function_name, get_node_path(test_case), parent_id ) - function_nodes_dict[parent_path] = function_test_node + function_nodes_dict[parent_id] = function_test_node function_test_node["children"].append(test_node) # Check if the parent node of the function is file, if so create/add to this file node. if isinstance(test_case.parent, pytest.File): @@ -643,17 +656,16 @@ def create_class_node(class_module: pytest.Class) -> TestNode: def create_parameterized_function_node( - function_name: str, test_path: pathlib.Path, test_id: str + function_name: str, test_path: pathlib.Path, function_id: str ) -> TestNode: """Creates a function node to be the parent for the parameterized test nodes. Keyword arguments: function_name -- the name of the function. test_path -- the path to the test file. - test_id -- the id of the test, which is a parameterized test so it + function_id -- the previously constructed function id that fits the pattern- absolute path :: any class and method :: parent_part must be edited to get a unique id for the function node. """ - function_id: str = test_id.split("::")[0] + "::" + function_name return { "name": function_name, "path": test_path, From 0f80b5946eb9f510129a6fbf61d5a78dc085e6b3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 16 May 2024 17:27:22 +1000 Subject: [PATCH 051/106] Activate base conda envs using 'name', add env_path for homebrew (#23443) --- native_locator/src/conda.rs | 2 +- native_locator/src/homebrew.rs | 22 +++++++++++++++++++--- native_locator/tests/conda_test.rs | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 929a991689cc..23a8689ec2a7 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -632,7 +632,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

Option { +fn is_symlinked_python_executable(path: &DirEntry) -> Option { let path = path.path(); let name = path.file_name()?.to_string_lossy(); if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") { @@ -50,7 +50,7 @@ impl Locator for Homebrew<'_> { .ok()? .filter_map(Result::ok) { - if let Some(exe) = is_symlinked_python_executable(file) { + if let Some(exe) = is_symlinked_python_executable(&file) { let python_version = exe.to_string_lossy().to_string(); let version = match python_regex.captures(&python_version) { Some(captures) => match captures.get(1) { @@ -62,6 +62,22 @@ impl Locator for Homebrew<'_> { if reported.contains(&exe.to_string_lossy().to_string()) { continue; } + let env_path = match exe.parent() { + Some(path) => { + if let Some(name) = path.file_name() { + if name.to_ascii_lowercase() == "bin" + || name.to_ascii_lowercase() == "Scripts" + { + Some(path.parent()?.to_path_buf()) + } else { + Some(path.to_path_buf()) + } + } else { + None + } + } + None => continue, + }; reported.insert(exe.to_string_lossy().to_string()); let env = crate::messaging::PythonEnvironment::new( None, @@ -69,7 +85,7 @@ impl Locator for Homebrew<'_> { Some(exe.clone()), crate::messaging::PythonEnvironmentCategory::Homebrew, version, - None, + env_path, None, Some(vec![exe.to_string_lossy().to_string()]), ); diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 397ed42b1de6..925219b098bb 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -182,7 +182,7 @@ fn find_conda_from_custom_install_location() { let expected_conda_env = PythonEnvironment { display_name: None, - name: None, + name: Some("base".to_string()), project_path: None, python_executable_path: Some(conda_dir.clone().join("bin").join("python")), category: python_finder::messaging::PythonEnvironmentCategory::Conda, From 2e56a2150c201ea5aa9c6582eb198c0f0c0747e1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 16 May 2024 17:28:15 +1000 Subject: [PATCH 052/106] Ensure resolvers accept data from native locators (#23444) --- .../pythonEnvironments/base/info/env.ts | 8 ++ .../pythonEnvironments/base/info/index.ts | 6 ++ src/client/pythonEnvironments/base/locator.ts | 15 +++ .../locators/common/nativePythonFinder.ts | 1 + .../base/locators/composite/envsResolver.ts | 6 ++ .../base/locators/composite/resolverUtils.ts | 97 ++++++++++++++++--- .../base/locators/lowLevel/nativeLocator.ts | 6 +- .../composite/envsResolver.unit.test.ts | 4 + .../composite/resolverUtils.unit.test.ts | 10 ++ 9 files changed, 141 insertions(+), 12 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index 930a522ef1e9..5c5b9317e169 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -42,6 +42,12 @@ export function buildEnvInfo(init?: { sysPrefix?: string; searchLocation?: Uri; type?: PythonEnvType; + /** + * Command used to run Python in this environment. + * E.g. `conda run -n envName python` or `python.exe` + */ + pythonRunCommand?: string[]; + identifiedUsingNativeLocator?: boolean; }): PythonEnvInfo { const env: PythonEnvInfo = { name: init?.name ?? '', @@ -69,6 +75,8 @@ export function buildEnvInfo(init?: { org: init?.org ?? '', }, source: init?.source ?? [], + pythonRunCommand: init?.pythonRunCommand, + identifiedUsingNativeLocator: init?.identifiedUsingNativeLocator, }; if (init !== undefined) { updateEnv(env, init); diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index a0bf01c27c97..1806109142fd 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -203,6 +203,12 @@ export type PythonEnvInfo = _PythonEnvInfo & { display?: string; detailedDisplayName?: string; searchLocation?: Uri; + /** + * Command used to run Python in this environment. + * E.g. `conda run -n envName python` or `python.exe` + */ + pythonRunCommand?: string[]; + identifiedUsingNativeLocator?: boolean; }; /** diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 8524c03536c5..e0af1a7a59b8 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -144,9 +144,24 @@ export type BasicEnvInfo = { executablePath: string; source?: PythonEnvSource[]; envPath?: string; + /** + * The project to which this env is related to, if any + * E.g. the project directory when dealing with pipenv virtual environments. + */ searchLocation?: Uri; version?: PythonVersion; name?: string; + /** + * Display name provided by locators, not generated by us. + * E.g. display name as provided by Windows Registry or Windows Store, etc + */ + displayName?: string; + /** + * Command used to run Python in this environment. + * E.g. `conda run -n envName python` or `python.exe` + */ + pythonRunCommand?: string[]; + identifiedUsingNativeLocator?: boolean; }; /** diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 7d005dd7484a..0f252320a52c 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -23,6 +23,7 @@ export interface NativeEnvInfo { version?: string; pythonRunCommand?: string[]; envPath?: string; + envManager?: NativeEnvManagerInfo; /** * Path to the project directory when dealing with pipenv virtual environments. */ diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index a7a25cc41c5c..5a98d0013bbf 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -154,6 +154,12 @@ export class PythonEnvsResolver implements IResolvingLocator { async function setKind(env: BasicEnvInfo, environmentKinds: Map) { const { path } = getEnvPath(env.executablePath, env.envPath); + // For native locators, do not try to identify the environment kind. + // its already set by the native locator & thats accurate. + if (env.identifiedUsingNativeLocator) { + environmentKinds.set(path, env.kind); + return; + } let kind = environmentKinds.get(path); if (!kind) { if (!isIdentifierRegistered(env.kind)) { diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 4fd1e2f68d5d..2fdfd34c5acc 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -139,14 +139,21 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { const { executablePath, kind } = env; const envInfo = buildEnvInfo({ kind, - version: await getPythonVersionFromPath(executablePath), + version: env.version ?? (await getPythonVersionFromPath(executablePath)), executable: executablePath, + sysPrefix: env.envPath, + location: env.envPath, + display: env.displayName, + searchLocation: env.searchLocation, + pythonRunCommand: env.pythonRunCommand, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + name: env.name, type: PythonEnvType.Virtual, }); - const location = getEnvironmentDirFromPath(executablePath); + const location = env.envPath ?? getEnvironmentDirFromPath(executablePath); envInfo.location = location; envInfo.name = path.basename(location); return envInfo; } async function resolveCondaEnv(env: BasicEnvInfo): Promise { + if (env.identifiedUsingNativeLocator) { + // New approach using native locator. + const executable = env.executablePath; + const envPath = env.envPath ?? getEnvironmentDirFromPath(executable); + // TODO: Hacky, `executable` is never undefined in the typedef, + // However, in reality with native locator this can be undefined. + const version = env.version ?? (executable ? await getPythonVersionFromPath(executable) : undefined); + const info = buildEnvInfo({ + executable, + kind: PythonEnvKind.Conda, + org: AnacondaCompanyName, + location: envPath, + sysPrefix: envPath, + display: env.displayName, + pythonRunCommand: env.pythonRunCommand, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + searchLocation: env.searchLocation, + source: [], + version, + type: PythonEnvType.Conda, + name: env.name, + }); + + if (env.envPath && executable && path.basename(executable) === executable) { + // For environments without python, set ID using the predicted executable path after python is installed. + // Another alternative could've been to set ID of all conda environments to the environment path, as that + // remains constant even after python installation. + const predictedExecutable = getCondaInterpreterPath(env.envPath); + info.id = getEnvID(predictedExecutable, env.envPath); + } + return info; + } + + // Old approach (without native locator). + // In this approach we need to find conda. const { executablePath } = env; const conda = await Conda.getConda(); if (conda === undefined) { @@ -210,7 +259,7 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise { async function resolvePyenvEnv(env: BasicEnvInfo): Promise { const { executablePath } = env; - const location = getEnvironmentDirFromPath(executablePath); + const location = env.envPath ?? getEnvironmentDirFromPath(executablePath); const name = path.basename(location); // The sub-directory name sometimes can contain distro and python versions. @@ -218,10 +267,18 @@ async function resolvePyenvEnv(env: BasicEnvInfo): Promise { const versionStrings = parsePyenvVersion(name); const envInfo = buildEnvInfo({ - kind: PythonEnvKind.Pyenv, + // If using native resolver, then we can get the kind from the native resolver. + // E.g. pyenv can have conda environments as well. + kind: env.identifiedUsingNativeLocator && env.kind ? env.kind : PythonEnvKind.Pyenv, executable: executablePath, source: [], location, + searchLocation: env.searchLocation, + sysPrefix: env.envPath, + display: env.displayName, + name: env.name, + pythonRunCommand: env.pythonRunCommand, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, // Pyenv environments can fall in to these three categories: // 1. Global Installs : These are environments that are created when you install // a supported python distribution using `pyenv install ` command. @@ -240,14 +297,17 @@ async function resolvePyenvEnv(env: BasicEnvInfo): Promise { // // Here we look for near by files, or config files to see if we can get python version info // without running python itself. - version: await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer), + version: env.version ?? (await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer)), org: versionStrings && versionStrings.distro ? versionStrings.distro : '', }); - if (await isBaseCondaPyenvEnvironment(executablePath)) { - envInfo.name = 'base'; - } else { - envInfo.name = name; + // Do this only for the old approach, when not using native locators. + if (!env.identifiedUsingNativeLocator) { + if (await isBaseCondaPyenvEnvironment(executablePath)) { + envInfo.name = 'base'; + } else { + envInfo.name = name; + } } return envInfo; } @@ -256,6 +316,14 @@ async function resolveActiveStateEnv(env: BasicEnvInfo): Promise const info = buildEnvInfo({ kind: env.kind, executable: env.executablePath, + display: env.displayName, + version: env.version, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + location: env.envPath, + name: env.name, + pythonRunCommand: env.pythonRunCommand, + searchLocation: env.searchLocation, + sysPrefix: env.envPath, }); const projects = await ActiveState.getState().then((v) => v?.getProjects()); if (projects) { @@ -285,8 +353,15 @@ async function resolveMicrosoftStoreEnv(env: BasicEnvInfo): Promise, IDisposable { envPath: data.envPath, version: parseVersion(data.version), name: data.name === '' ? undefined : data.name, + displayName: data.displayName, + pythonRunCommand: data.pythonRunCommand, + searchLocation: data.projectPath ? Uri.file(data.projectPath) : undefined, + identifiedUsingNativeLocator: true, }); } }), diff --git a/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts index 5cda8a1cf731..0d189da35282 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts @@ -72,6 +72,8 @@ suite('Python envs locator - Environments Resolver', () => { updatedEnv.arch = Architecture.x64; updatedEnv.display = expectedDisplay; updatedEnv.detailedDisplayName = expectedDetailedDisplay; + updatedEnv.identifiedUsingNativeLocator = updatedEnv.identifiedUsingNativeLocator ?? undefined; + updatedEnv.pythonRunCommand = updatedEnv.pythonRunCommand ?? undefined; if (env.kind === PythonEnvKind.Conda) { env.type = PythonEnvType.Conda; } @@ -106,6 +108,8 @@ suite('Python envs locator - Environments Resolver', () => { searchLocation: Uri.file(location), source: [], type, + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, }; } suite('iterEnvs()', () => { diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index dbd41715db0f..22b2f0c01304 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -153,6 +153,8 @@ suite('Resolver Utils', () => { kind: PythonEnvKind.MicrosoftStore, distro: { org: 'Microsoft' }, source: [PythonEnvSource.PathEnvVar], + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, ...createExpectedInterpreterInfo(python38path), }; setEnvDisplayString(expected); @@ -175,6 +177,8 @@ suite('Resolver Utils', () => { kind: PythonEnvKind.MicrosoftStore, distro: { org: 'Microsoft' }, source: [PythonEnvSource.PathEnvVar], + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, ...createExpectedInterpreterInfo(python38path), }; setEnvDisplayString(expected); @@ -239,6 +243,8 @@ suite('Resolver Utils', () => { distro: { org: '' }, searchLocation: undefined, source: [], + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, }; info.type = PythonEnvType.Conda; setEnvDisplayString(info); @@ -351,6 +357,8 @@ suite('Resolver Utils', () => { searchLocation: Uri.file(location), source: [], type: PythonEnvType.Virtual, + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, }; setEnvDisplayString(info); return info; @@ -406,6 +414,8 @@ suite('Resolver Utils', () => { distro: { org: '' }, searchLocation: undefined, source: [], + identifiedUsingNativeLocator: undefined, + pythonRunCommand: undefined, }; setEnvDisplayString(info); return info; From 4323c58f62aa074e749a16521e7e98a25220a9a8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 21 May 2024 09:38:00 +1000 Subject: [PATCH 053/106] Updates to python locator (#23446) The search algorithm has been documented here https://github.com/microsoft/vscode-python/wiki/Python-Environment-Search --- native_locator/src/common_python.rs | 1 + native_locator/src/conda.rs | 1 + native_locator/src/homebrew.rs | 272 +++++++++++++++++---- native_locator/src/main.rs | 22 +- native_locator/src/messaging.rs | 10 + native_locator/src/pipenv.rs | 15 ++ native_locator/src/pyenv.rs | 70 +++++- native_locator/src/utils.rs | 12 +- native_locator/src/venv.rs | 1 + native_locator/src/virtualenv.rs | 1 + native_locator/src/virtualenvwrapper.rs | 61 +++-- native_locator/src/windows_store.rs | 4 +- native_locator/tests/common.rs | 2 + native_locator/tests/common_python_test.rs | 1 + native_locator/tests/conda_test.rs | 3 + native_locator/tests/pyenv_test.rs | 10 +- 16 files changed, 389 insertions(+), 97 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 3d206529035c..ef6c057b1826 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -47,6 +47,7 @@ impl Locator for PythonOnPath<'_> { env_manager: None, project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + arch: None, }) } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 23a8689ec2a7..bec00693d21b 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -646,6 +646,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

Option { - let path = path.path(); +fn is_symlinked_python_executable(path: &PathBuf) -> Option { let name = path.file_name()?.to_string_lossy(); if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") { return None; @@ -23,6 +22,149 @@ fn is_symlinked_python_executable(path: &DirEntry) -> Option { Some(std::fs::canonicalize(path).ok()?) } +fn get_homebrew_prefix_env_var(environment: &dyn Environment) -> Option { + 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 { + 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 { + // 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, + python_version_regex: &Regex, +) -> Option { + // 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, } @@ -34,64 +176,100 @@ impl Homebrew<'_> { } impl Locator for Homebrew<'_> { - fn resolve(&self, _env: &PythonEnv) -> Option { - None + fn resolve(&self, env: &PythonEnv) -> Option { + 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 = 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 { - let homebrew_prefix = self - .environment - .get_env_var("HOMEBREW_PREFIX".to_string())?; - let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); + let homebrew_prefix_bin = get_homebrew_prefix_bin(self.environment)?; let mut reported: HashSet = HashSet::new(); let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); let mut environments: Vec = vec![]; - for file in std::fs::read_dir(homebrew_prefix_bin) + for file in std::fs::read_dir(&homebrew_prefix_bin) .ok()? .filter_map(Result::ok) { - if let Some(exe) = is_symlinked_python_executable(&file) { - let python_version = exe.to_string_lossy().to_string(); - let version = match python_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(&exe.to_string_lossy().to_string()) { + // 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; } - let env_path = match exe.parent() { - Some(path) => { - if let Some(name) = path.file_name() { - if name.to_ascii_lowercase() == "bin" - || name.to_ascii_lowercase() == "Scripts" - { - Some(path.parent()?.to_path_buf()) - } else { - Some(path.to_path_buf()) - } - } else { - None - } - } - None => continue, - }; - reported.insert(exe.to_string_lossy().to_string()); - let env = crate::messaging::PythonEnvironment::new( - None, - None, - Some(exe.clone()), - crate::messaging::PythonEnvironmentCategory::Homebrew, - version, - env_path, - None, - Some(vec![exe.to_string_lossy().to_string()]), - ); + } + + 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 { diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index ee976bf756d2..2e9989347929 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -37,7 +37,7 @@ fn main() { let virtualenv_locator = virtualenv::VirtualEnv::new(); let venv_locator = venv::Venv::new(); - let virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); + let mut virtualenvwrapper = virtualenvwrapper::VirtualEnvWrapper::with(&environment); let pipenv_locator = pipenv::PipEnv::new(); let mut path_locator = common_python::PythonOnPath::with(&environment); let mut conda_locator = conda::Conda::with(&environment); @@ -54,6 +54,7 @@ fn main() { #[cfg(windows)] find_environments(&mut windows_registry, &mut dispatcher); let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); + find_environments(&mut virtualenvwrapper, &mut dispatcher); find_environments(&mut pyenv_locator, &mut dispatcher); #[cfg(unix)] find_environments(&mut homebrew_locator, &mut dispatcher); @@ -61,14 +62,27 @@ fn main() { #[cfg(windows)] find_environments(&mut windows_store, &mut dispatcher); - // Step 2: Search in some global locations. + // Step 2: Search in some global locations for virtual envs. for env in list_global_virtual_envs(&environment).iter() { if dispatcher.was_environment_reported(&env) { continue; } - let _ = resolve_environment(&pipenv_locator, env, &mut dispatcher) - || resolve_environment(&virtualenvwrapper_locator, env, &mut dispatcher) + // First must be homebrew, as it is the most specific and supports symlinks + #[cfg(unix)] + let homebrew_result = resolve_environment(&homebrew_locator, env, &mut dispatcher); + #[cfg(unix)] + if homebrew_result { + continue; + } + + let _ = // Pipeenv before virtualenvwrapper as it is more specific. + // Because pipenv environments are also virtualenvwrapper environments. + resolve_environment(&pipenv_locator, env, &mut dispatcher) + // Before venv, as all venvs are also virtualenvwrapper environments. + || resolve_environment(&virtualenvwrapper, env, &mut dispatcher) + // Before virtualenv as this is more specific. + // All venvs are also virtualenvs environments. || resolve_environment(&venv_locator, env, &mut dispatcher) || resolve_environment(&virtualenv_locator, env, &mut dispatcher); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 73e708dcac5f..1365949e703e 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -80,6 +80,14 @@ pub enum PythonEnvironmentCategory { 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)] @@ -96,6 +104,7 @@ pub struct PythonEnvironment { * The project path for the Pipenv environment. */ pub project_path: Option, + pub arch: Option, } impl PythonEnvironment { @@ -119,6 +128,7 @@ impl PythonEnvironment { env_manager, python_run_command, project_path: None, + arch: None, } } } diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index bb5eab5776fe..6eacb3601dba 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -18,6 +18,17 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { 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 { @@ -28,6 +39,9 @@ impl PipEnv { impl Locator for PipEnv { fn resolve(&self, env: &PythonEnv) -> Option { + if !is_pipenv(env) { + return None; + } let project_path = get_pipenv_project(env)?; Some(PythonEnvironment { display_name: None, @@ -39,6 +53,7 @@ impl Locator for PipEnv { env_manager: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: Some(project_path), + arch: None, }) } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 9137a80df1f7..ba0395f2738c 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -66,7 +66,7 @@ fn get_pyenv_binary(environment: &dyn known::Environment) -> Option { } } -fn get_pyenv_version(folder_name: &String) -> Option { +fn get_version(folder_name: &String) -> Option { // Stable Versions = like 3.10.10 let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap(); match python_regex.captures(&folder_name) { @@ -90,7 +90,17 @@ fn get_pyenv_version(folder_name: &String) -> Option { Some(version) => Some(version.as_str().to_string()), None => None, }, - 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, + } + } } } } @@ -103,8 +113,9 @@ fn get_pure_python_environment( path: &PathBuf, manager: &Option, ) -> Option { - let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?; - Some(messaging::PythonEnvironment::new( + 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()), @@ -117,7 +128,12 @@ fn get_pure_python_environment( .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 { @@ -189,6 +205,47 @@ pub fn list_pyenv_environments( Some(envs) } +#[cfg(windows)] +fn get_pyenv_manager_version( + pyenv_binary_path: &PathBuf, + environment: &dyn known::Environment, +) -> Option { + // 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 { + // 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, @@ -214,7 +271,8 @@ impl Locator for PyEnv<'_> { fn find(&mut self) -> Option { let pyenv_binary = get_pyenv_binary(self.environment)?; - let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); + let version = get_pyenv_manager_version(&pyenv_binary, self.environment); + let manager = messaging::EnvManager::new(pyenv_binary, version, EnvManagerType::Pyenv); let mut environments: Vec = vec![]; if let Some(envs) = list_pyenv_environments(&Some(manager.clone()), self.environment, self.conda_locator) diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index c70efe9654ef..d9a30c3a7f8a 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -6,7 +6,6 @@ use regex::Regex; use std::{ fs, path::{Path, PathBuf}, - process::Command, }; #[derive(Debug)] @@ -96,16 +95,7 @@ pub fn get_version(python_executable: &PathBuf) -> Option { return Some(pyenv_cfg.version); } } - - let output = Command::new(python_executable) - .arg("-c") - .arg("import sys; print(sys.version)") - .output() - .ok()?; - let output = String::from_utf8(output.stdout).ok()?; - let output = output.trim(); - let output = output.split_whitespace().next().unwrap_or(output); - Some(output.to_string()) + None } pub fn find_python_binary_path(env_path: &Path) -> Option { diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 94040a536989..702bf8b6dcc9 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -43,6 +43,7 @@ impl Locator for Venv { env_manager: None, project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + arch: None, }); } None diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 2a6909e63fa2..209d72c5f533 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -74,6 +74,7 @@ impl Locator for VirtualEnv { env_manager: None, project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + arch: None, }); } None diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index d55a89e09dca..54728b4cb644 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -6,6 +6,7 @@ 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)] @@ -29,7 +30,7 @@ fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option

Option { if let Some(home) = environment.get_user_home() { - let home = PathBuf::from(home).join("virtualenvs"); + let home = PathBuf::from(home).join(".virtualenvs"); if home.exists() { return Some(home); } @@ -37,7 +38,7 @@ fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option

Option { +pub fn get_work_on_home_path(environment: &dyn Environment) -> Option { // 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()) { @@ -54,6 +55,7 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b 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 @@ -66,6 +68,17 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b false } +fn get_project(env: &PythonEnv) -> Option { + 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, } @@ -78,28 +91,29 @@ impl VirtualEnvWrapper<'_> { impl Locator for VirtualEnvWrapper<'_> { fn resolve(&self, env: &PythonEnv) -> Option { - if is_virtualenvwrapper(env, self.environment) { - return Some(PythonEnvironment { - display_name: None, - 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::Venv, - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }); + if !is_virtualenvwrapper(env, self.environment) { + return None; } - None + Some(PythonEnvironment { + display_name: None, + 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(), + env_manager: None, + project_path: get_project(env), + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + arch: None, + }) } fn find(&mut self) -> Option { @@ -111,7 +125,6 @@ impl Locator for VirtualEnvWrapper<'_> { environments.push(env); } }); - if environments.is_empty() { None } else { diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index f08622d08127..39f3a01f4ac9 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -61,6 +61,7 @@ fn list_windows_store_python_executables( env_manager: None, project_path: None, python_run_command: Some(vec![exe.to_string_lossy().to_string()]), + arch: None, }; python_envs.push(env); } @@ -94,8 +95,6 @@ fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option< let display_name = package_key.get_value("DisplayName").ok()?; let env_path = package_key.get_value("PackageRootFolder").ok()?; - let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?; - return Some(StorePythonInfo { display_name, env_path, @@ -131,6 +130,7 @@ impl Locator for WindowsStore<'_> { env_manager: None, project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + arch: None, }); } None diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index bf4c54617f16..1df03a005a73 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -20,10 +20,12 @@ pub fn join_test_paths(paths: &[&str]) -> PathBuf { path } +#[allow(dead_code)] pub trait TestMessages { fn get_messages(&self) -> Vec; } +#[allow(dead_code)] pub struct TestEnvironment { vars: HashMap, home: Option, diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index d7d71fd5cce1..c8a5baa3b10a 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -41,6 +41,7 @@ fn find_python_in_path_this() { version: None, python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]), env_path: Some(user_home.clone()), + arch: None, }; assert_messages( &[json!(env)], diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 925219b098bb..95e48917bd82 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -196,6 +196,7 @@ fn find_conda_from_custom_install_location() { conda_dir.to_string_lossy().to_string(), "python".to_string(), ]), + arch: None, }; assert_eq!(json!(expected_conda_env), json!(result.environments[0])); @@ -265,6 +266,7 @@ fn finds_two_conda_envs_from_known_location() { "one".to_string(), "python".to_string(), ]), + arch: None, }; let expected_conda_2 = PythonEnvironment { display_name: None, @@ -282,6 +284,7 @@ fn finds_two_conda_envs_from_known_location() { "two".to_string(), "python".to_string(), ]), + arch: None, }; assert_messages( &[json!(expected_conda_1), json!(expected_conda_2)], diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 87761114089d..c9782e9c5d35 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -71,8 +71,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { #[cfg(unix)] fn find_pyenv_envs() { use crate::common::{ - assert_messages, create_test_environment, join_test_paths, - test_file_path, + assert_messages, create_test_environment, join_test_paths, test_file_path, }; use python_finder::conda::Conda; use python_finder::locator::Locator; @@ -128,7 +127,8 @@ fn find_pyenv_envs() { home.to_str().unwrap(), ".pyenv/versions/3.9.9" ])), - env_manager: Some(expected_manager.clone()) + env_manager: Some(expected_manager.clone()), + arch: None }); let expected_virtual_env = PythonEnvironment { display_name: None, @@ -152,6 +152,7 @@ fn find_pyenv_envs() { ".pyenv/versions/my-virtual-env", ])), env_manager: Some(expected_manager.clone()), + arch: None, }; let expected_3_12_1 = PythonEnvironment { display_name: None, @@ -175,6 +176,7 @@ fn find_pyenv_envs() { ".pyenv/versions/3.12.1", ])), env_manager: Some(expected_manager.clone()), + arch: None, }; let expected_3_13_dev = PythonEnvironment { display_name: None, @@ -198,6 +200,7 @@ fn find_pyenv_envs() { ".pyenv/versions/3.13-dev", ])), env_manager: Some(expected_manager.clone()), + arch: None, }; let expected_3_12_1a3 = PythonEnvironment { display_name: None, @@ -221,6 +224,7 @@ fn find_pyenv_envs() { ".pyenv/versions/3.12.1a3", ])), env_manager: Some(expected_manager.clone()), + arch: None, }; assert_messages( From 86b9791d33e4c1a04290b706846ec16744285124 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 22 May 2024 14:44:19 -0700 Subject: [PATCH 054/106] Build rust python finder for CI and produce a package (#23465) --- .github/actions/build-vsix/action.yml | 27 +- .github/workflows/pr-check.yml | 49 +- noxfile.py | 46 +- package-lock.json | 1059 ++++++++++++++++++++++--- package.json | 2 +- 5 files changed, 1030 insertions(+), 153 deletions(-) diff --git a/.github/actions/build-vsix/action.yml b/.github/actions/build-vsix/action.yml index 16a1af28363c..fc3233b06eff 100644 --- a/.github/actions/build-vsix/action.yml +++ b/.github/actions/build-vsix/action.yml @@ -11,6 +11,12 @@ inputs: artifact_name: description: 'Name to give the artifact containing the VSIX' required: true + cargo_target: + description: 'Cargo build target for the native build' + required: true + vsix_target: + description: 'vsix build target for the native build' + required: true runs: using: 'composite' @@ -47,27 +53,32 @@ runs: run: nox --session install_python_libs shell: bash + - name: Add Rustup target + run: rustup target add ${{ inputs.cargo_target }} + shell: bash + - name: Build Native Binaries run: nox --session native_build shell: bash + env: + CARGO_TARGET: ${{ inputs.cargo_target }} - name: Run npm ci run: npm ci --prefer-offline shell: bash - # Use the GITHUB_RUN_ID environment variable to update the build number. - # GITHUB_RUN_ID is a unique number for each run within a repository. - # This number does not change if you re-run the workflow run. - - name: Update extension build number - run: npm run updateBuildNumber -- --buildNumber $GITHUB_RUN_ID - shell: bash - - name: Update optional extension dependencies run: npm run addExtensionPackDependencies shell: bash + - name: Build Webpack + run: | + npx gulp clean + npx gulp prePublishBundle + shell: bash + - name: Build VSIX - run: npm run package + run: npx vsce package --target ${{ inputs.vsix_target }} --out ms-python-insiders.vsix --pre-release shell: bash - name: Rename VSIX diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index ed9b10e29e2b..47fb13807121 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -12,7 +12,6 @@ env: PYTHON_VERSION: '3.10' # YML treats 3.10 the number as 3.1, so quotes around 3.10 MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it haven't already. ARTIFACT_NAME_VSIX: ms-python-insiders-vsix - VSIX_NAME: ms-python-insiders.vsix TEST_RESULTS_DIRECTORY: . # Force a path with spaces and to test extension works in these scenarios # Unicode characters are causing 2.7 failures so skip that for now. @@ -22,7 +21,38 @@ env: jobs: build-vsix: name: Create VSIX - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + vsix-target: win32-x64 + - os: windows-latest + target: aarch64-pc-windows-msvc + vsix-target: win32-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + vsix-target: linux-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-gnu + # vsix-target: linux-arm64 + # - os: ubuntu-latest + # target: arm-unknown-linux-gnueabihf + # vsix-target: linux-armhf + - os: macos-latest + target: x86_64-apple-darwin + vsix-target: darwin-x64 + - os: macos-14 + target: aarch64-apple-darwin + vsix-target: darwin-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: alpine-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-musl + # vsix-target: alpine-arm64 steps: - name: Checkout uses: actions/checkout@v4 @@ -31,8 +61,10 @@ jobs: uses: ./.github/actions/build-vsix with: node_version: ${{ env.NODE_VERSION}} - vsix_name: ${{ env.VSIX_NAME }} - artifact_name: ${{ env.ARTIFACT_NAME_VSIX }} + vsix_name: 'ms-python-insiders-${{ matrix.vsix-target }}.vsix' + artifact_name: '${{ env.ARTIFACT_NAME_VSIX }}-${{ matrix.vsix-target }}' + cargo_target: ${{ matrix.target }} + vsix_target: ${{ matrix.vsix-target }} lint: name: Lint @@ -345,7 +377,12 @@ jobs: matrix: # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] + include: + - os: windows-latest + vsix-target: win32-x64 + - os: ubuntu-latest + vsix-target: linux-x64 + steps: # Need the source to have the tests available. - name: Checkout @@ -355,7 +392,7 @@ jobs: uses: ./.github/actions/smoke-tests with: node_version: ${{ env.NODE_VERSION }} - artifact_name: ${{ env.ARTIFACT_NAME_VSIX }} + artifact_name: '${{ env.ARTIFACT_NAME_VSIX }}-${{ matrix.vsix-target }}' ### Coverage run coverage: diff --git a/noxfile.py b/noxfile.py index a53828df51c4..0e0e8803c3d1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,10 +1,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import os import pathlib import nox import shutil import sys +import sysconfig @nox.session() @@ -45,26 +47,50 @@ def install_python_libs(session: nox.Session): @nox.session() -def native_build(session:nox.Session): +def native_build(session: nox.Session): with session.cd("./native_locator"): - session.run("cargo", "build", "--release", "--package", "python-finder", external=True) if not pathlib.Path(pathlib.Path.cwd() / "bin").exists(): pathlib.Path(pathlib.Path.cwd() / "bin").mkdir() if not pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").exists(): - pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").write_text("*\n", encoding="utf-8") + pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").write_text( + "*\n", encoding="utf-8" + ) + + ext = sysconfig.get_config_var("EXE") or "" + target = os.environ.get("CARGO_TARGET", None) - if sys.platform == "win32": - shutil.copy( - "./target/release/python-finder.exe", - "./bin/python-finder.exe", + session.run("cargo", "fetch", external=True) + if target: + session.run( + "cargo", + "build", + "--frozen", + "--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) else: - shutil.copy( - "./target/release/python-finder", - "./bin/python-finder", + session.run( + "cargo", + "build", + "--frozen", + "--release", + "--package", + "python-finder", + external=True, ) + source = f"./target/release/python-finder{ext}" + dest = f"./bin/python-finder{ext}" + shutil.copy(source, dest) + @nox.session() def setup_repo(session: nox.Session): diff --git a/package-lock.json b/package-lock.json index 85bcb41dfa5b..c9a04d630549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "@vscode/test-electron": "^2.3.8", - "@vscode/vsce": "^2.18.0", + "@vscode/vsce": "^2.26.1", "bent": "^7.3.12", "chai": "^4.1.2", "chai-arrays": "^2.0.0", @@ -176,6 +176,55 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/@azure/core-rest-pipeline": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", @@ -271,6 +320,62 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/@azure/identity": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.0.tgz", + "integrity": "sha512-ve3aYv79qXOJ8wRxQ5jO0eIz2DZ4o0TyME4m4vlGV5YyePddVZ+pFMzusAMODNAflYAAv1cBIhKnd4xytmXyig==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.11.1", + "@azure/msal-node": "^2.6.6", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity/node_modules/@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity/node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/@azure/logger": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", @@ -287,6 +392,41 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "dev": true, + "dependencies": { + "@azure/msal-common": "14.10.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.8.1.tgz", + "integrity": "sha512-VcZZM+5VvCWRBTOF7SxMKaxrz+EXjntx2u5AQe7QE06e6FuPJElGBrImgNgCh5QmFaNCfVFO+3qNR7UoFD/Gfw==", + "dev": true, + "dependencies": { + "@azure/msal-common": "14.10.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@azure/opentelemetry-instrumentation-azure-sdk": { "version": "1.0.0-beta.5", "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.5.tgz", @@ -1912,15 +2052,18 @@ } }, "node_modules/@vscode/vsce": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.19.0.tgz", - "integrity": "sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz", + "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==", "dev": true, "dependencies": { - "azure-devops-node-api": "^11.0.1", + "@azure/identity": "^4.1.0", + "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", "glob": "^7.0.6", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", @@ -1930,7 +2073,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "semver": "^5.1.0", + "semver": "^7.5.2", "tmp": "^0.2.1", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", @@ -1942,7 +2085,7 @@ "vsce": "vsce" }, "engines": { - "node": ">= 14" + "node": ">= 16" }, "optionalDependencies": { "keytar": "^7.7.0" @@ -1981,15 +2124,6 @@ "node": "*" } }, - "node_modules/@vscode/vsce/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/@vscode/vsce/node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -2890,9 +3024,9 @@ "dev": true }, "node_modules/azure-devops-node-api": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.0.1.tgz", - "integrity": "sha512-YMdjAw9l5p/6leiyIloxj3k7VIvYThKjvqgiQn88r3nhT93ENwsoDS3A83CyJ4uTWzCZ5f5jCi6c27rTU5Pz+A==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, "dependencies": { "tunnel": "0.0.6", @@ -3366,6 +3500,12 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -3456,13 +3596,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4083,6 +4229,15 @@ "semver": "bin/semver" } }, + "node_modules/cockatiel": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.3.tgz", + "integrity": "sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -4755,6 +4910,32 @@ "node": ">= 0.10" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -5047,6 +5228,15 @@ "object.defaults": "^1.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.450", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz", @@ -5204,6 +5394,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -6967,9 +7178,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -7002,14 +7216,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7334,6 +7553,18 @@ "node": ">= 0.10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/got": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", @@ -7591,6 +7822,30 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", @@ -7722,6 +7977,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -8206,6 +8473,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -8545,6 +8827,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8898,6 +9192,49 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jsx-ast-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", @@ -8935,6 +9272,27 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", @@ -9171,12 +9529,54 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "node_modules/lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -10693,9 +11093,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10920,10 +11320,27 @@ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11605,6 +12022,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", @@ -12133,9 +12565,9 @@ } }, "node_modules/rewiremock": { - "version": "3.14.3", - "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.3.tgz", - "integrity": "sha512-6BaUGfp7NtxBjisxcGN73nNiA2fS2AwhEk/9DMUqxfv5v0aDM1wpOYpj5GSArqsJi07YCfLhkD8C74LAN7+FkQ==", + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.5.tgz", + "integrity": "sha512-MdPutvaUd+kKVz/lcEz6N6337s4PxRUR5vhphIp2/TJRgfXIckomIkCsIAbwB53MjiSLwi7KBMdQ9lPWE5WpYA==", "dev": true, "dependencies": { "babel-runtime": "^6.26.0", @@ -12320,6 +12752,23 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -12413,14 +12862,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12956,6 +13409,16 @@ "node": ">=0.10.0" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -14042,10 +14505,9 @@ } }, "node_modules/typed-rest-client": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.5.tgz", - "integrity": "sha512-952/Aegu3lTqUAI1anbDLbewojnF/gh8at9iy1CIrfS1h/+MtNjB1Y9z6ZF5n2kZd+97em56lZ9uu7Zz3y/pwg==", - "deprecated": "1.8.5 contains changes that are not compatible with Node 6", + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", "dev": true, "dependencies": { "qs": "^6.9.1", @@ -14053,21 +14515,6 @@ "underscore": "^1.12.1" } }, - "node_modules/typed-rest-client/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -14157,9 +14604,9 @@ } }, "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, "node_modules/undertaker": { @@ -15439,6 +15886,48 @@ } } }, + "@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", + "dev": true, + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "dev": true, + "requires": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "@azure/core-rest-pipeline": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", @@ -15517,6 +16006,57 @@ } } }, + "@azure/identity": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.0.tgz", + "integrity": "sha512-ve3aYv79qXOJ8wRxQ5jO0eIz2DZ4o0TyME4m4vlGV5YyePddVZ+pFMzusAMODNAflYAAv1cBIhKnd4xytmXyig==", + "dev": true, + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.11.1", + "@azure/msal-node": "^2.6.6", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "dev": true, + "requires": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "@azure/logger": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", @@ -15532,6 +16072,32 @@ } } }, + "@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", + "dev": true, + "requires": { + "@azure/msal-common": "14.10.0" + } + }, + "@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", + "dev": true + }, + "@azure/msal-node": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.8.1.tgz", + "integrity": "sha512-VcZZM+5VvCWRBTOF7SxMKaxrz+EXjntx2u5AQe7QE06e6FuPJElGBrImgNgCh5QmFaNCfVFO+3qNR7UoFD/Gfw==", + "dev": true, + "requires": { + "@azure/msal-common": "14.10.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + } + }, "@azure/opentelemetry-instrumentation-azure-sdk": { "version": "1.0.0-beta.5", "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.5.tgz", @@ -16840,15 +17406,18 @@ } }, "@vscode/vsce": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.19.0.tgz", - "integrity": "sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz", + "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==", "dev": true, "requires": { - "azure-devops-node-api": "^11.0.1", + "@azure/identity": "^4.1.0", + "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", "glob": "^7.0.6", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", @@ -16859,7 +17428,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "semver": "^5.1.0", + "semver": "^7.5.2", "tmp": "^0.2.1", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", @@ -16892,12 +17461,6 @@ "brace-expansion": "^1.1.7" } }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -17609,9 +18172,9 @@ "dev": true }, "azure-devops-node-api": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.0.1.tgz", - "integrity": "sha512-YMdjAw9l5p/6leiyIloxj3k7VIvYThKjvqgiQn88r3nhT93ENwsoDS3A83CyJ4uTWzCZ5f5jCi6c27rTU5Pz+A==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, "requires": { "tunnel": "0.0.6", @@ -17988,6 +18551,12 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -18071,13 +18640,16 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "camelcase": { @@ -18542,6 +19114,12 @@ } } }, + "cockatiel": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.3.tgz", + "integrity": "sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==", + "dev": true + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -19109,6 +19687,23 @@ "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -19348,6 +19943,15 @@ "object.defaults": "^1.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "electron-to-chromium": { "version": "1.4.450", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz", @@ -19482,6 +20086,21 @@ "unbox-primitive": "^1.0.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -20856,9 +21475,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -20885,14 +21504,16 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-package-type": { @@ -21142,6 +21763,15 @@ "sparkles": "^1.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", @@ -21351,6 +21981,21 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, "has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", @@ -21450,6 +22095,15 @@ } } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -21811,6 +22465,12 @@ } } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -22047,6 +22707,15 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -22319,6 +22988,47 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + } + } + }, "jsx-ast-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", @@ -22353,6 +23063,27 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", @@ -22551,12 +23282,54 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -23758,9 +24531,9 @@ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true }, "object-is": { @@ -23934,6 +24707,17 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -24466,6 +25250,15 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.6" + } + }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", @@ -24868,9 +25661,9 @@ "dev": true }, "rewiremock": { - "version": "3.14.3", - "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.3.tgz", - "integrity": "sha512-6BaUGfp7NtxBjisxcGN73nNiA2fS2AwhEk/9DMUqxfv5v0aDM1wpOYpj5GSArqsJi07YCfLhkD8C74LAN7+FkQ==", + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.5.tgz", + "integrity": "sha512-MdPutvaUd+kKVz/lcEz6N6337s4PxRUR5vhphIp2/TJRgfXIckomIkCsIAbwB53MjiSLwi7KBMdQ9lPWE5WpYA==", "dev": true, "requires": { "babel-runtime": "^6.26.0", @@ -25011,6 +25804,20 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -25088,14 +25895,15 @@ } }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -25503,6 +26311,12 @@ } } }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -26335,25 +27149,14 @@ "dev": true }, "typed-rest-client": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.5.tgz", - "integrity": "sha512-952/Aegu3lTqUAI1anbDLbewojnF/gh8at9iy1CIrfS1h/+MtNjB1Y9z6ZF5n2kZd+97em56lZ9uu7Zz3y/pwg==", + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", "dev": true, "requires": { "qs": "^6.9.1", "tunnel": "0.0.6", "underscore": "^1.12.1" - }, - "dependencies": { - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } } }, "typedarray": { @@ -26428,9 +27231,9 @@ "dev": true }, "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, "undertaker": { diff --git a/package.json b/package.json index b3dd496d355c..3b1d1e7fba24 100644 --- a/package.json +++ b/package.json @@ -1546,7 +1546,7 @@ "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "@vscode/test-electron": "^2.3.8", - "@vscode/vsce": "^2.18.0", + "@vscode/vsce": "^2.26.1", "bent": "^7.3.12", "chai": "^4.1.2", "chai-arrays": "^2.0.0", From ed8fabf4b47e871738c0c4567e1cdf23d0071d60 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 24 May 2024 08:01:21 +1000 Subject: [PATCH 055/106] Capture count of envs collected (#23471) --- .../composite/envsCollectionService.ts | 48 ++++- .../base/locators/lowLevel/nativeLocator.ts | 45 +++++ src/client/pythonEnvironments/index.ts | 1 + src/client/telemetry/constants.ts | 1 + src/client/telemetry/index.ts | 175 ++++++++++++++++++ .../envsCollectionService.unit.test.ts | 26 +-- 6 files changed, 278 insertions(+), 18 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index 30da89c87628..25ceb267da85 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -9,7 +9,7 @@ import { traceError, traceInfo, traceVerbose } from '../../../../logging'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { normalizePath } from '../../../common/externalDependencies'; -import { PythonEnvInfo } from '../../info'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { getEnvPath } from '../../info/env'; import { GetRefreshEnvironmentsOptions, @@ -54,7 +54,11 @@ export class EnvsCollectionService extends PythonEnvsWatcher { const query: PythonLocatorQuery | undefined = event.providerId @@ -258,12 +262,46 @@ export class EnvsCollectionService extends PythonEnvsWatcher getEnvPath(e.executable.filename, e.location).pathType === 'envFolderPath', + ).length; + const activeStateEnvs = envs.filter((e) => e.kind === PythonEnvKind.ActiveState).length; + const condaEnvs = envs.filter((e) => e.kind === PythonEnvKind.Conda).length; + const customEnvs = envs.filter((e) => e.kind === PythonEnvKind.Custom).length; + const hatchEnvs = envs.filter((e) => e.kind === PythonEnvKind.Hatch).length; + const microsoftStoreEnvs = envs.filter((e) => e.kind === PythonEnvKind.MicrosoftStore).length; + const otherGlobalEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherGlobal).length; + const otherVirtualEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherVirtual).length; + const pipEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pipenv).length; + const poetryEnvs = envs.filter((e) => e.kind === PythonEnvKind.Poetry).length; + const pyenvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pyenv).length; + const systemEnvs = envs.filter((e) => e.kind === PythonEnvKind.System).length; + const unknownEnvs = envs.filter((e) => e.kind === PythonEnvKind.Unknown).length; + const venvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Venv).length; + const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length; + const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length; + // Intent is to capture time taken for discovery of all envs to complete the first time. sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { interpreters: this.cache.getAllEnvs().length, - environmentsWithoutPython: this.cache - .getAllEnvs() - .filter((e) => getEnvPath(e.executable.filename, e.location).pathType === 'envFolderPath').length, + usingNativeLocator: this.usingNativeLocator, + environmentsWithoutPython, + activeStateEnvs, + condaEnvs, + customEnvs, + hatchEnvs, + microsoftStoreEnvs, + otherGlobalEnvs, + otherVirtualEnvs, + pipEnvEnvs, + poetryEnvs, + pyenvEnvs, + systemEnvs, + unknownEnvs, + venvEnvs, + virtualEnvEnvs, + virtualEnvWrapperEnvs, }); } this.hasRefreshFinishedForQuery.set(query, true); diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 2586f8697543..0d3f2a588b1a 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -18,6 +18,8 @@ import { } from '../common/nativePythonFinder'; import { disposeAll } from '../../../../common/utils/resourceLifecycle'; import { StopWatch } from '../../../../common/utils/stopWatch'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; function categoryToKind(category: string): PythonEnvKind { switch (category.toLowerCase()) { @@ -106,6 +108,7 @@ export class NativeLocator implements ILocator, IDisposable { const disposable = new Disposable(() => disposeAll(disposables)); this.disposables.push(disposable); promise.finally(() => disposable.dispose()); + let environmentsWithoutPython = 0; disposables.push( this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { // TODO: What if executable is undefined? @@ -121,6 +124,8 @@ export class NativeLocator implements ILocator, IDisposable { searchLocation: data.projectPath ? Uri.file(data.projectPath) : undefined, identifiedUsingNativeLocator: true, }); + } else { + environmentsWithoutPython += 1; } }), this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => { @@ -147,6 +152,7 @@ export class NativeLocator implements ILocator, IDisposable { `Finished searching for Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, ); yield* envs; + sendTelemetry(envs, environmentsWithoutPython, stopWatch); traceInfo( `Finished yielding Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, ); @@ -155,3 +161,42 @@ export class NativeLocator implements ILocator, IDisposable { return iterator(); } } + +function sendTelemetry(envs: BasicEnvInfo[], environmentsWithoutPython: number, stopWatch: StopWatch) { + const activeStateEnvs = envs.filter((e) => e.kind === PythonEnvKind.ActiveState).length; + const condaEnvs = envs.filter((e) => e.kind === PythonEnvKind.Conda).length; + const customEnvs = envs.filter((e) => e.kind === PythonEnvKind.Custom).length; + const hatchEnvs = envs.filter((e) => e.kind === PythonEnvKind.Hatch).length; + const microsoftStoreEnvs = envs.filter((e) => e.kind === PythonEnvKind.MicrosoftStore).length; + const otherGlobalEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherGlobal).length; + const otherVirtualEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherVirtual).length; + const pipEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pipenv).length; + const poetryEnvs = envs.filter((e) => e.kind === PythonEnvKind.Poetry).length; + const pyenvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pyenv).length; + const systemEnvs = envs.filter((e) => e.kind === PythonEnvKind.System).length; + const unknownEnvs = envs.filter((e) => e.kind === PythonEnvKind.Unknown).length; + const venvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Venv).length; + const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length; + const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length; + + // Intent is to capture time taken for discovery of all envs to complete the first time. + sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { + interpreters: envs.length, + environmentsWithoutPython, + activeStateEnvs, + condaEnvs, + customEnvs, + hatchEnvs, + microsoftStoreEnvs, + otherGlobalEnvs, + otherVirtualEnvs, + pipEnvEnvs, + poetryEnvs, + pyenvEnvs, + systemEnvs, + unknownEnvs, + venvEnvs, + virtualEnvEnvs, + virtualEnvWrapperEnvs, + }); +} diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 9406f890c6da..9999da6616be 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -134,6 +134,7 @@ async function createLocator( await createCollectionCache(ext), // This is shared. resolvingLocator, + useNativeLocator(), ); return caching; } diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index d6225a9eb573..62fbf16cbada 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -19,6 +19,7 @@ export enum EventName { ENVIRONMENT_WITHOUT_PYTHON_SELECTED = 'ENVIRONMENT_WITHOUT_PYTHON_SELECTED', PYTHON_ENVIRONMENTS_API = 'PYTHON_ENVIRONMENTS_API', PYTHON_INTERPRETER_DISCOVERY = 'PYTHON_INTERPRETER_DISCOVERY', + PYTHON_INTERPRETER_DISCOVERY_NATIVE = 'PYTHON_INTERPRETER_DISCOVERY_NATIVE', PYTHON_INTERPRETER_AUTO_SELECTION = 'PYTHON_INTERPRETER_AUTO_SELECTION', PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES = 'PYTHON_INTERPRETER.ACTIVATION_ENVIRONMENT_VARIABLES', PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE = 'PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 675cf4d58926..84b4295dedd7 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1144,9 +1144,124 @@ export interface IEventNamePropertyMapping { "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karrtikr" }, "interpreters" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "karrtikr"}, "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karrtikr" } + "usingNativeLocator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" } + "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "hatchEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "microsoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "poetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "systemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "unknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "venvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, } */ [EventName.PYTHON_INTERPRETER_DISCOVERY]: { + /** + * The number of the interpreters discovered + */ + interpreters?: number; + /** + * Whether or not we're using the native locator. + */ + usingNativeLocator?: boolean; + /** + * The number of environments discovered not containing an interpreter + */ + environmentsWithoutPython?: number; + /** + * Number of environments of a specific type + */ + activeStateEnvs?: number; + /** + * Number of environments of a specific type + */ + condaEnvs?: number; + /** + * Number of environments of a specific type + */ + customEnvs?: number; + /** + * Number of environments of a specific type + */ + hatchEnvs?: number; + /** + * Number of environments of a specific type + */ + microsoftStoreEnvs?: number; + /** + * Number of environments of a specific type + */ + otherGlobalEnvs?: number; + /** + * Number of environments of a specific type + */ + otherVirtualEnvs?: number; + /** + * Number of environments of a specific type + */ + pipEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + poetryEnvs?: number; + /** + * Number of environments of a specific type + */ + pyenvEnvs?: number; + /** + * Number of environments of a specific type + */ + systemEnvs?: number; + /** + * Number of environments of a specific type + */ + unknownEnvs?: number; + /** + * Number of environments of a specific type + */ + venvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvWrapperEnvs?: number; + }; + /** + * Telemetry event sent when discovery of all python environments using the native locator(virtualenv, conda, pipenv etc.) finishes. + */ + /* __GDPR__ + "python_interpreter_discovery_native" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "interpreters" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } + "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "hatchEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "microsoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "poetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "systemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "unknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "venvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + } + */ + [EventName.PYTHON_INTERPRETER_DISCOVERY_NATIVE]: { /** * The number of the interpreters discovered */ @@ -1155,6 +1270,66 @@ export interface IEventNamePropertyMapping { * The number of environments discovered not containing an interpreter */ environmentsWithoutPython?: number; + /** + * Number of environments of a specific type + */ + activeStateEnvs?: number; + /** + * Number of environments of a specific type + */ + condaEnvs?: number; + /** + * Number of environments of a specific type + */ + customEnvs?: number; + /** + * Number of environments of a specific type + */ + hatchEnvs?: number; + /** + * Number of environments of a specific type + */ + microsoftStoreEnvs?: number; + /** + * Number of environments of a specific type + */ + otherGlobalEnvs?: number; + /** + * Number of environments of a specific type + */ + otherVirtualEnvs?: number; + /** + * Number of environments of a specific type + */ + pipEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + poetryEnvs?: number; + /** + * Number of environments of a specific type + */ + pyenvEnvs?: number; + /** + * Number of environments of a specific type + */ + systemEnvs?: number; + /** + * Number of environments of a specific type + */ + unknownEnvs?: number; + /** + * Number of environments of a specific type + */ + venvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvWrapperEnvs?: number; }; /** * Telemetry event sent with details when user clicks the prompt with the following message: diff --git a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts index 739a3f1a3f4b..5e528af38a3b 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts @@ -146,7 +146,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = envs; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); }); teardown(async () => { @@ -192,7 +192,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); await collectionService.triggerRefresh(undefined); await collectionService.triggerRefresh(undefined, { ifNotTriggerredAlready: true }); @@ -226,7 +226,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -267,7 +267,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); let events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -318,7 +318,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -371,7 +371,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); let stage: ProgressReportStage | undefined; collectionService.onProgress((e) => { stage = e.stage; @@ -442,7 +442,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, env); }); @@ -472,10 +472,10 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); collectionService.triggerRefresh().ignoreErrors(); await waitDeferred.promise; // Cache should already contain `env` at this point, although it is not complete. - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, resolvedViaLocator); }); @@ -502,7 +502,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, resolvedViaLocator); }); @@ -521,7 +521,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const resolved = await collectionService.resolveEnv(resolvedViaLocator.executable.filename); const envs = collectionService.getEnvs(); assertEnvsEqual(envs, [resolved]); @@ -545,7 +545,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); let resolved = await collectionService.resolveEnv(condaEnvWithoutPython.location); assertEnvEqual(resolved, condaEnvWithoutPython); // Ensure cache is used to resolve such envs. @@ -583,7 +583,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator); + collectionService = new EnvsCollectionService(cache, parentLocator, false); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { events.push(e); From b37a292151a66b4c4a8c9fb59b9ac4f1825b6df2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 24 May 2024 10:33:36 +1000 Subject: [PATCH 056/106] Ensure Windows Store takes precedence over Registry and fix Conda locators (#23463) --- native_locator/src/common_python.rs | 5 +- native_locator/src/conda.rs | 196 +++++++++++------- native_locator/src/homebrew.rs | 1 + native_locator/src/known.rs | 1 + native_locator/src/main.rs | 6 +- native_locator/src/messaging.rs | 63 +++++- native_locator/src/pipenv.rs | 5 +- native_locator/src/pyenv.rs | 2 +- native_locator/src/venv.rs | 5 +- native_locator/src/virtualenv.rs | 5 +- native_locator/src/virtualenvwrapper.rs | 4 +- native_locator/src/windows_registry.rs | 148 +++++++------ native_locator/src/windows_store.rs | 163 +++++++++++---- native_locator/tests/common_python_test.rs | 1 + native_locator/tests/conda_test.rs | 11 +- native_locator/tests/pyenv_test.rs | 25 ++- .../base/info/environmentInfoService.ts | 63 +++++- src/client/pythonEnvironments/base/locator.ts | 4 + .../locators/common/nativePythonFinder.ts | 5 +- .../base/locators/composite/envsResolver.ts | 6 +- .../base/locators/composite/resolverUtils.ts | 14 +- .../base/locators/lowLevel/nativeLocator.ts | 13 +- 22 files changed, 533 insertions(+), 213 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index ef6c057b1826..67ad94ed40a1 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -39,15 +39,12 @@ impl Locator for PythonOnPath<'_> { } Some(PythonEnvironment { display_name: None, - name: None, python_executable_path: Some(env.executable.clone()), version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::System, env_path: env.path.clone(), - env_manager: None, - project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - arch: None, + ..Default::default() }) } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index bec00693d21b..8c7164b472d6 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -6,6 +6,7 @@ use crate::known::Environment; use crate::locator::Locator; use crate::locator::LocatorResult; use crate::messaging; +use crate::messaging::Architecture; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; use crate::messaging::PythonEnvironment; @@ -14,9 +15,11 @@ use crate::utils::{find_python_binary_path, get_environment_key, get_environment use log::trace; use log::warn; use regex::Regex; +use serde::Deserialize; use std::collections::HashMap; use std::collections::HashSet; use std::env; +use std::fs::read_to_string; use std::path::{Path, PathBuf}; /// Specifically returns the file names that are valid for 'conda' on windows @@ -57,6 +60,13 @@ struct CondaPackage { #[allow(dead_code)] path: PathBuf, version: String, + arch: Option, +} + +#[derive(Deserialize, Debug)] +struct CondaMetaPackageStructure { + channel: Option, + // version: Option, } /// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. @@ -72,16 +82,38 @@ fn get_conda_package_json_path(path: &Path, package: &str) -> Option Some(CondaPackage { + if let Some(version) = regex.clone().ok().unwrap().captures(&file_name)?.get(1) { + let mut arch: Option = None; + // Sample contents + // { + // "build": "h966fe2a_2", + // "build_number": 2, + // "channel": "https://repo.anaconda.com/pkgs/main/win-64", + // "constrains": [], + // } + // 32bit channel is https://repo.anaconda.com/pkgs/main/win-32/ + // 64bit channel is "channel": "https://repo.anaconda.com/pkgs/main/osx-arm64", + if let Some(contents) = read_to_string(&path).ok() { + if let Some(js) = + serde_json::from_str::(&contents).ok() + { + if let Some(channel) = js.channel { + if channel.ends_with("64") { + arch = Some(Architecture::X64); + } else if channel.ends_with("32") { + arch = Some(Architecture::X86); + } + } + } + } + return Some(CondaPackage { path: path.clone(), version: version.as_str().to_string(), - }), - None => None, + arch, + }); } - } else { - None } + None }) } @@ -202,6 +234,8 @@ fn get_conda_manager(path: &PathBuf) -> Option { executable_path: conda_exe, version: Some(conda_pkg.version), tool: EnvManagerType::Conda, + company: None, + company_display_name: None, }) } @@ -213,6 +247,7 @@ struct CondaEnvironment { python_executable_path: Option, version: Option, conda_install_folder: Option, + arch: Option, } fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { let metadata = env_path.metadata(); @@ -229,6 +264,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option Option Option Option

Option

= vec![]; let mut detected_envs: HashSet = HashSet::new(); let mut detected_managers: HashSet = HashSet::new(); - if conda_install_folder.is_dir() && conda_install_folder.exists() { - if let Some(manager) = get_conda_manager(&conda_install_folder) { - // 1. Base environment. - if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { - if let Some(env_path) = env.clone().env_path { - possible_conda_envs.remove(&env_path); - let key = env_path.to_string_lossy().to_string(); - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(env); - } + if !conda_install_folder.is_dir() || !conda_install_folder.exists() { + return None; + } + + if let Some(manager) = get_conda_manager(&conda_install_folder) { + // 1. Base environment. + if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { + if let Some(env_path) = env.clone().env_path { + possible_conda_envs.remove(&env_path); + let key = env_path.to_string_lossy().to_string(); + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(env); } } + } + + // 2. All environments in the `/envs` folder + let mut envs: Vec = vec![]; + if let Some(environments) = + get_environments_from_envs_folder_in_conda_directory(conda_install_folder) + { + environments.iter().for_each(|env| { + possible_conda_envs.remove(&env.env_path); + envs.push(env.clone()); + }); + } - // 2. All environments in the `/envs` folder - let mut envs: Vec = vec![]; - if let Some(environments) = - get_environments_from_envs_folder_in_conda_directory(conda_install_folder) + // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + // E.g conda_install_folder is `/` + // Then all folders such as `//envs/env1` can be ignored + // As these would have been discovered in previous step. + for (key, env) in possible_conda_envs.clone().iter() { + if env + .env_path + .to_string_lossy() + .contains(conda_install_folder.to_str().unwrap()) { - environments.iter().for_each(|env| { - possible_conda_envs.remove(&env.env_path); - envs.push(env.clone()); - }); + continue; } - - // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - // E.g conda_install_folder is `/` - // Then all folders such as `//envs/env1` can be ignored - // As these would have been discovered in previous step. - for (key, env) in possible_conda_envs.clone().iter() { - if env - .env_path - .to_string_lossy() - .contains(conda_install_folder.to_str().unwrap()) - { - continue; - } - if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { - envs.push(env.clone()); - possible_conda_envs.remove(key); - } + if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { + envs.push(env.clone()); + possible_conda_envs.remove(key); } + } - // Finally construct the PythonEnvironment objects - envs.iter().for_each(|env| { - let exe = env.python_executable_path.clone(); - let env = PythonEnvironment::new( - None, - Some(env.name.clone()), - exe.clone(), - messaging::PythonEnvironmentCategory::Conda, - env.version.clone(), - Some(env.env_path.clone()), - Some(manager.clone()), - get_activation_command(env, &manager), - ); - if let Some(key) = get_environment_key(&env) { - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(env); - } + // Finally construct the PythonEnvironment objects + envs.iter().for_each(|env| { + let exe = env.python_executable_path.clone(); + let arch = env.arch.clone(); + let mut env = PythonEnvironment::new( + None, + Some(env.name.clone()), + exe.clone(), + messaging::PythonEnvironmentCategory::Conda, + env.version.clone(), + Some(env.env_path.clone()), + Some(manager.clone()), + get_activation_command(env, &manager), + ); + env.arch = arch; + if let Some(key) = get_environment_key(&env) { + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(env); } - }); - - let key = get_environment_manager_key(&manager); - if !detected_managers.contains(&key) { - detected_managers.insert(key); - managers.push(manager); } + }); + + let key = get_environment_manager_key(&manager); + if !detected_managers.contains(&key) { + detected_managers.insert(key); + managers.push(manager); } } @@ -973,6 +1020,9 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { + if !is_conda_install_location(possible_conda_folder) { + return None; + } let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); self.filter_result(get_conda_environments_in_specified_install_path( possible_conda_folder, diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 8709aa16b454..78cefd2cd74b 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -170,6 +170,7 @@ pub struct Homebrew<'a> { } impl Homebrew<'_> { + #[cfg(unix)] pub fn with<'a>(environment: &'a impl Environment) -> Homebrew { Homebrew { environment } } diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index 6e37d897157e..bd9e7bc4de34 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -7,6 +7,7 @@ pub trait Environment { /** * Only used in tests, this is the root `/`. */ + #[allow(dead_code)] fn get_root(&self) -> Option; fn get_env_var(&self, key: String) -> Option; fn get_know_global_search_locations(&self) -> Vec; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 2e9989347929..4964b09cac40 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -29,7 +29,7 @@ mod windows_store; fn main() { let environment = EnvironmentApi {}; - initialize_logger(LevelFilter::Debug); + initialize_logger(LevelFilter::Trace); log::info!("Starting Native Locator"); let now = SystemTime::now(); @@ -52,6 +52,8 @@ fn main() { // Step 1: These environments take precedence over all others. // As they are very specific and guaranteed to be specific type. #[cfg(windows)] + find_environments(&mut windows_store, &mut dispatcher); + #[cfg(windows)] find_environments(&mut windows_registry, &mut dispatcher); let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); find_environments(&mut virtualenvwrapper, &mut dispatcher); @@ -59,8 +61,6 @@ fn main() { #[cfg(unix)] find_environments(&mut homebrew_locator, &mut dispatcher); find_environments(&mut conda_locator, &mut dispatcher); - #[cfg(windows)] - find_environments(&mut windows_store, &mut dispatcher); // Step 2: Search in some global locations for virtual envs. for env in list_global_virtual_envs(&environment).iter() { diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 1365949e703e..808da631f455 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -8,7 +8,7 @@ use crate::{ use env_logger::Builder; use log::LevelFilter; use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, path::PathBuf}; +use std::{collections::HashSet, path::PathBuf, time::UNIX_EPOCH}; pub trait MessageDispatcher { fn was_environment_reported(&self, env: &PythonEnv) -> bool; @@ -32,6 +32,8 @@ pub struct EnvManager { pub executable_path: PathBuf, pub version: Option, pub tool: EnvManagerType, + pub company: Option, + pub company_display_name: Option, } impl EnvManager { @@ -40,6 +42,8 @@ impl EnvManager { executable_path, version, tool, + company: None, + company_display_name: None, } } } @@ -105,6 +109,33 @@ pub struct PythonEnvironment { */ pub project_path: Option, pub arch: Option, + pub symlinks: Option>, + pub creation_time: Option, + pub modified_time: Option, + pub company: Option, + pub company_display_name: Option, +} + +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 { @@ -129,6 +160,11 @@ impl PythonEnvironment { python_run_command, project_path: None, arch: None, + symlinks: None, + creation_time: None, + modified_time: None, + company: None, + company_display_name: None, } } } @@ -222,12 +258,29 @@ impl MessageDispatcher for JsonRpcDispatcher { } 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); - send_message(PythonEnvironmentMessage::new(env.clone())); - } - if let Some(manager) = env.env_manager { - self.report_environment_manager(manager); + + // 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)); } } } diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 6eacb3601dba..cb49c1c6ef33 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -44,16 +44,13 @@ impl Locator for PipEnv { } let project_path = get_pipenv_project(env)?; Some(PythonEnvironment { - display_name: None, - name: None, python_executable_path: Some(env.executable.clone()), category: crate::messaging::PythonEnvironmentCategory::Pipenv, version: env.version.clone(), env_path: env.path.clone(), - env_manager: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: Some(project_path), - arch: None, + ..Default::default() }) } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index ba0395f2738c..e87729de5eda 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -207,7 +207,7 @@ pub fn list_pyenv_environments( #[cfg(windows)] fn get_pyenv_manager_version( - pyenv_binary_path: &PathBuf, + _pyenv_binary_path: &PathBuf, environment: &dyn known::Environment, ) -> Option { // In windows, the version is stored in the `.pyenv/.version` file diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 702bf8b6dcc9..0df22263e0f3 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -26,7 +26,6 @@ impl Locator for Venv { fn resolve(&self, env: &PythonEnv) -> Option { if is_venv(&env) { return Some(PythonEnvironment { - display_name: None, name: Some( env.path .clone() @@ -40,10 +39,8 @@ impl Locator for Venv { version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::Venv, env_path: env.path.clone(), - env_manager: None, - project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - arch: None, + ..Default::default() }); } None diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 209d72c5f533..9532d46faa73 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -57,7 +57,6 @@ impl Locator for VirtualEnv { fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenv(env) { return Some(PythonEnvironment { - display_name: None, name: Some( env.path .clone() @@ -71,10 +70,8 @@ impl Locator for VirtualEnv { version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::VirtualEnv, env_path: env.path.clone(), - env_manager: None, - project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - arch: None, + ..Default::default() }); } None diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 54728b4cb644..9a06fc2494cb 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -95,7 +95,6 @@ impl Locator for VirtualEnvWrapper<'_> { return None; } Some(PythonEnvironment { - display_name: None, name: Some( env.path .clone() @@ -109,10 +108,9 @@ impl Locator for VirtualEnvWrapper<'_> { version: env.version.clone(), category: crate::messaging::PythonEnvironmentCategory::VirtualEnvWrapper, env_path: env.path.clone(), - env_manager: None, project_path: get_project(env), python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - arch: None, + ..Default::default() }) } diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index 4f8e97710fc2..0e7a06eddec1 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -17,16 +17,70 @@ use std::path::PathBuf; use winreg::RegKey; #[cfg(windows)] -fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option> { +fn get_registry_pythons_from_key( + hk: &RegKey, + conda_locator: &mut dyn CondaLocator, +) -> Option { + let mut environments = vec![]; + let mut managers: Vec = vec![]; let python_key = hk.open_subkey("Software\\Python").ok()?; - let company_key = python_key.open_subkey(company).ok()?; + for company in python_key.enum_keys().filter_map(Result::ok) { + if let Some(result) = + get_registry_pythons_from_key_for_company(&hk, &company, conda_locator) + { + managers.extend(result.managers); + environments.extend(result.environments); + } + } + + Some(LocatorResult { + environments, + managers, + }) +} - let mut pythons = vec![]; +#[cfg(windows)] +fn get_registry_pythons_from_key_for_company( + hk: &RegKey, + company: &str, + conda_locator: &mut dyn CondaLocator, +) -> Option { + use crate::messaging::Architecture; + + let mut environments = vec![]; + let mut managers: Vec = vec![]; + let python_key = hk.open_subkey("Software\\Python").ok()?; + let company_key = python_key.open_subkey(company).ok()?; + let company_display_name: Option = company_key.get_value("DisplayName").ok(); for key in company_key.enum_keys().filter_map(Result::ok) { if let Some(key) = company_key.open_subkey(key).ok() { if let Some(install_path_key) = key.open_subkey("InstallPath").ok() { let env_path: String = install_path_key.get_value("").ok().unwrap_or_default(); let env_path = PathBuf::from(env_path); + + // 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 { @@ -44,9 +98,11 @@ fn get_registry_pythons_from_key(hk: &RegKey, company: &str) -> Option Option Option> { - let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); - let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); - - let mut pythons = vec![]; - if let Some(hklm_pythons) = get_registry_pythons_from_key(&hklm, company) { - pythons.extend(hklm_pythons); - } - if let Some(hkcu_pythons) = get_registry_pythons_from_key(&hkcu, company) { - pythons.extend(hkcu_pythons); - } - Some(pythons) + Some(LocatorResult { + environments, + managers, + }) } #[cfg(windows)] -pub fn get_registry_pythons_anaconda(conda_locator: &mut dyn CondaLocator) -> LocatorResult { +fn get_registry_pythons(conda_locator: &mut dyn CondaLocator) -> Option { let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); - let mut pythons = vec![]; - if let Some(hklm_pythons) = get_registry_pythons_from_key(&hklm, "ContinuumAnalytics") { - pythons.extend(hklm_pythons); - } - if let Some(hkcu_pythons) = get_registry_pythons_from_key(&hkcu, "ContinuumAnalytics") { - pythons.extend(hkcu_pythons); - } - - let mut environments: Vec = vec![]; + let mut environments = vec![]; let mut managers: Vec = vec![]; - for env in pythons.iter() { - if let Some(env_path) = env.clone().env_path { - if let Some(mut result) = conda_locator.find_in(&env_path) { - environments.append(&mut result.environments); - managers.append(&mut result.managers); - } - } + if let Some(result) = get_registry_pythons_from_key(&hklm, conda_locator) { + managers.extend(result.managers); + environments.extend(result.environments); } - - LocatorResult { - managers, - environments, + if let Some(result) = get_registry_pythons_from_key(&hkcu, conda_locator) { + managers.extend(result.managers); + environments.extend(result.environments); } + Some(LocatorResult { + environments, + managers, + }) } #[cfg(windows)] @@ -134,23 +176,11 @@ impl Locator for WindowsRegistry<'_> { } fn find(&mut self) -> Option { - let mut environments: Vec = vec![]; - let mut managers: Vec = vec![]; - - let mut result = get_registry_pythons("PythonCore").unwrap_or_default(); - environments.append(&mut result); - - let mut result = get_registry_pythons_anaconda(self.conda_locator) ; - environments.append(&mut result.environments); - managers.append(&mut result.managers); - - if environments.is_empty() && managers.is_empty() { - None - } else { - Some(LocatorResult { - managers, - environments, - }) + if let Some(result) = get_registry_pythons(self.conda_locator) { + if !result.environments.is_empty() && !result.managers.is_empty() { + return Some(result); + } } + None } } diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index 39f3a01f4ac9..f9ed251f9674 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -12,6 +12,8 @@ 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; @@ -29,6 +31,10 @@ pub fn is_windows_python_executable(path: &PathBuf) -> bool { fn list_windows_store_python_executables( environment: &dyn known::Environment, ) -> Option> { + use crate::messaging::Architecture; + use regex::Regex; + use std::collections::HashMap; + let mut python_envs: Vec = vec![]; let home = environment.get_user_home()?; let apps_path = Path::new(&home) @@ -37,46 +43,136 @@ fn list_windows_store_python_executables( .join("Microsoft") .join("WindowsApps"); let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); - for file in std::fs::read_dir(apps_path).ok()?.filter_map(Result::ok) { - let path = file.path(); + 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, + name: Option, + exe: Option, + version: String, + } + let mut potential_matches: HashMap = 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 exe = path.join("python.exe"); - if name - .to_str() - .unwrap_or_default() - .starts_with("PythonSoftwareFoundation.Python.") - && exe.is_file() - && exe.exists() - { - if let Some(result) = - get_package_display_name_and_location(name.to_string_lossy().to_string(), &hkcu) - { - let env = PythonEnvironment { - display_name: Some(result.display_name), - name: None, - python_executable_path: Some(exe.clone()), - version: None, - category: crate::messaging::PythonEnvironmentCategory::WindowsStore, - env_path: Some(PathBuf::from(result.env_path.clone())), - env_manager: None, - project_path: None, - python_run_command: Some(vec![exe.to_string_lossy().to_string()]), - arch: None, + 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() }; - python_envs.push(env); + 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 { +fn get_package_full_name_from_registry(name: &String, hkcu: &RegKey) -> Option { 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").unwrap_or_default(); + let value = package_key.get_value("PackageFullName").ok()?; Some(value) } @@ -85,12 +181,14 @@ fn get_package_full_name_from_registry(name: String, hkcu: &RegKey) -> Option Option { +fn get_package_display_name_and_location(name: &String, hkcu: &RegKey) -> Option { 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()?; @@ -98,6 +196,7 @@ fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option< return Some(StorePythonInfo { display_name, env_path, + is64_bit: name.contains("_x64_"), }); } None @@ -121,16 +220,10 @@ impl Locator for WindowsStore<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { return Some(PythonEnvironment { - display_name: None, - name: None, python_executable_path: Some(env.executable.clone()), - version: None, category: crate::messaging::PythonEnvironmentCategory::WindowsStore, - env_path: None, - env_manager: None, - project_path: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - arch: None, + ..Default::default() }); } None diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index c8a5baa3b10a..ceebf4931ab6 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -42,6 +42,7 @@ fn find_python_in_path_this() { 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)], diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 95e48917bd82..db6c1338ca9f 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -136,6 +136,8 @@ fn find_conda_exe_and_empty_envs() { 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])); @@ -177,12 +179,14 @@ fn find_conda_from_custom_install_location() { 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: Some("base".to_string()), + name: None, project_path: None, python_executable_path: Some(conda_dir.clone().join("bin").join("python")), category: python_finder::messaging::PythonEnvironmentCategory::Conda, @@ -197,6 +201,7 @@ fn find_conda_from_custom_install_location() { "python".to_string(), ]), arch: None, + ..Default::default() }; assert_eq!(json!(expected_conda_env), json!(result.environments[0])); @@ -245,6 +250,8 @@ fn finds_two_conda_envs_from_known_location() { 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); @@ -267,6 +274,7 @@ fn finds_two_conda_envs_from_known_location() { "python".to_string(), ]), arch: None, + ..Default::default() }; let expected_conda_2 = PythonEnvironment { display_name: None, @@ -285,6 +293,7 @@ fn finds_two_conda_envs_from_known_location() { "python".to_string(), ]), arch: None, + ..Default::default() }; assert_messages( &[json!(expected_conda_1), json!(expected_conda_2)], diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index c9782e9c5d35..132cc4160a6c 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -30,6 +30,7 @@ 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; @@ -57,14 +58,17 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { let mut locator = pyenv::PyEnv::with(&known, &mut conda); let result = locator.find().unwrap(); - let managers = result.managers; + let managers = result.clone().managers; assert_eq!(managers.len(), 1); - let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); - assert_messages( - &[expected_json], - &managers.iter().map(|e| json!(e)).collect::>(), - ) + 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] @@ -103,6 +107,8 @@ fn find_pyenv_envs() { 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])); @@ -128,7 +134,8 @@ fn find_pyenv_envs() { ".pyenv/versions/3.9.9" ])), env_manager: Some(expected_manager.clone()), - arch: None + arch: None, + ..Default::default() }); let expected_virtual_env = PythonEnvironment { display_name: None, @@ -153,6 +160,7 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), arch: None, + ..Default::default() }; let expected_3_12_1 = PythonEnvironment { display_name: None, @@ -177,6 +185,7 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), arch: None, + ..Default::default() }; let expected_3_13_dev = PythonEnvironment { display_name: None, @@ -201,6 +210,7 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), arch: None, + ..Default::default() }; let expected_3_12_1a3 = PythonEnvironment { display_name: None, @@ -225,6 +235,7 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), arch: None, + ..Default::default() }; assert_messages( diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index 251834b29683..4c437431823a 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -30,6 +30,19 @@ export interface IEnvironmentInfoService { env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, ): Promise; + /** + * Get the mandatory interpreter information for the given environment. + * E.g. executable path, version and sysPrefix are considered mandatory. + * However if we only have part of the version, thats still sufficient. + * If the fully resolved and acurate information for all parts of the env is required, then + * used `getEnvironmentInfo`. + * @param env The environment to get the interpreter information for. + * @param priority The priority of the request. + */ + getMandatoryEnvironmentInfo( + env: PythonEnvInfo, + priority?: EnvironmentInfoServiceQueuePriority, + ): Promise; /** * Reset any stored interpreter information for the given environment. * @param searchLocation Search location of the environment. @@ -124,6 +137,36 @@ class EnvironmentInfoService implements IEnvironmentInfoService { return deferred.promise; } + public async getMandatoryEnvironmentInfo( + env: PythonEnvInfo, + priority?: EnvironmentInfoServiceQueuePriority, + ): Promise { + const interpreterPath = env.executable.filename; + const result = this.cache.get(normCasePath(interpreterPath)); + if (result !== undefined) { + // Another call for this environment has already been made, return its result. + return result.promise; + } + + const deferred = createDeferred(); + const info = EnvironmentInfoService.getInterpreterInfo(env, true); + if (info !== undefined) { + this.cache.set(normCasePath(interpreterPath), deferred); + deferred.resolve(info); + return info; + } + + this.cache.set(normCasePath(interpreterPath), deferred); + this._getEnvironmentInfo(env, priority) + .then((r) => { + deferred.resolve(r); + }) + .catch((ex) => { + deferred.reject(ex); + }); + return deferred.promise; + } + public async _getEnvironmentInfo( env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, @@ -213,7 +256,25 @@ class EnvironmentInfoService implements IEnvironmentInfoService { }); } - private static getInterpreterInfo(env: PythonEnvInfo): InterpreterInformation | undefined { + private static getInterpreterInfo( + env: PythonEnvInfo, + allowPartialVersions?: boolean, + ): InterpreterInformation | undefined { + if (allowPartialVersions) { + if (env.version.major > -1 && env.version.minor > -1 && env.location) { + return { + arch: env.arch, + executable: { + filename: env.executable.filename, + ctime: -1, + mtime: -1, + sysPrefix: env.location, + }, + version: env.version, + }; + } + } + if (env.version.major > -1 && env.version.minor > -1 && env.version.micro > -1 && env.location) { return { arch: env.arch, diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index e0af1a7a59b8..d61f530f46ab 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -12,6 +12,7 @@ import { PythonEnvsChangedEvent, PythonEnvsWatcher, } from './watcher'; +import type { Architecture } from '../../common/utils/platform'; /** * A single update to a previously provided Python env object. @@ -162,6 +163,9 @@ export type BasicEnvInfo = { */ pythonRunCommand?: string[]; identifiedUsingNativeLocator?: boolean; + arch?: Architecture; + ctime?: number; + mtime?: number; }; /** diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 0f252320a52c..ac89d9e3aaf8 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -28,6 +28,10 @@ export interface NativeEnvInfo { * Path to the project directory when dealing with pipenv virtual environments. */ projectPath?: string; + arch?: 'X64' | 'X86'; + symlinks?: string[]; + creationTime?: number; + modifiedTime?: number; } export interface NativeEnvManagerInfo { @@ -60,7 +64,6 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { const deferred = createDeferred(); const proc = ch.spawn(NATIVE_LOCATOR, [], { env: process.env }); const disposables: Disposable[] = []; - // jsonrpc package cannot handle messages coming through too quicly. // Lets handle the messages and close the stream only when // we have got the exit event. diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 5a98d0013bbf..8c02eab33359 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -137,7 +137,7 @@ export class PythonEnvsResolver implements IResolvingLocator { state.pending += 1; // It's essential we increment the pending call count before any asynchronus calls in this method. // We want this to be run even when `resolveInBackground` is called in background. - const info = await this.environmentInfoService.getEnvironmentInfo(seen[envIndex]); + const info = await this.environmentInfoService.getMandatoryEnvironmentInfo(seen[envIndex]); const old = seen[envIndex]; if (info) { const resolvedEnv = getResolvedEnv(info, seen[envIndex]); @@ -194,7 +194,9 @@ function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: Py resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix; const isEnvLackingPython = getEnvPath(resolvedEnv.executable.filename, resolvedEnv.location).pathType === 'envFolderPath'; - if (isEnvLackingPython) { + // TODO: Shouldn't this only apply to conda, how else can we have an environment and not have Python in it? + // If thats the case, then this should be gated on environment.kind === PythonEnvKind.Conda + if (isEnvLackingPython && environment.kind !== PythonEnvKind.MicrosoftStore) { // Install python later into these envs might change the version, which can be confusing for users. // So avoid displaying any version until it is installed. resolvedEnv.version = getEmptyVersion(); diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 2fdfd34c5acc..d1ad91493eab 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -64,9 +64,17 @@ export async function resolveBasicEnv(env: BasicEnvInfo): Promise await updateEnvUsingRegistry(resolvedEnv); } setEnvDisplayString(resolvedEnv); - const { ctime, mtime } = await getFileInfo(resolvedEnv.executable.filename); - resolvedEnv.executable.ctime = ctime; - resolvedEnv.executable.mtime = mtime; + if (env.arch && !resolvedEnv.arch) { + resolvedEnv.arch = env.arch; + } + if (env.ctime && env.mtime) { + resolvedEnv.executable.ctime = env.ctime; + resolvedEnv.executable.mtime = env.mtime; + } else { + const { ctime, mtime } = await getFileInfo(resolvedEnv.executable.filename); + resolvedEnv.executable.ctime = ctime; + resolvedEnv.executable.mtime = mtime; + } const type = await getEnvType(resolvedEnv); if (type) { resolvedEnv.type = type; diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 0d3f2a588b1a..46f4ad2ddff2 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -18,6 +18,7 @@ import { } from '../common/nativePythonFinder'; import { disposeAll } from '../../../../common/utils/resourceLifecycle'; import { StopWatch } from '../../../../common/utils/stopWatch'; +import { Architecture } from '../../../../common/utils/platform'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; @@ -67,9 +68,9 @@ function parseVersion(version?: string): PythonVersion | undefined { try { const [major, minor, micro] = version.split('.').map((v) => parseInt(v, 10)); return { - major, - minor, - micro, + major: typeof major === 'number' ? major : -1, + minor: typeof minor === 'number' ? minor : -1, + micro: typeof micro === 'number' ? micro : -1, sysVersion: version, }; } catch { @@ -113,6 +114,7 @@ export class NativeLocator implements ILocator, IDisposable { this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { // TODO: What if executable is undefined? if (data.pythonExecutablePath) { + const arch = (data.arch || '').toLowerCase(); envs.push({ kind: categoryToKind(data.category), executablePath: data.pythonExecutablePath, @@ -123,6 +125,11 @@ export class NativeLocator implements ILocator, IDisposable { pythonRunCommand: data.pythonRunCommand, searchLocation: data.projectPath ? Uri.file(data.projectPath) : undefined, identifiedUsingNativeLocator: true, + arch: + // eslint-disable-next-line no-nested-ternary + arch === 'x64' ? Architecture.x64 : arch === 'x86' ? Architecture.x86 : undefined, + ctime: data.creationTime, + mtime: data.modifiedTime, }); } else { environmentsWithoutPython += 1; From 43d59c29ce25bec1500c93449d72ed9590f79f25 Mon Sep 17 00:00:00 2001 From: Courtney Webster <60238438+cwebster-99@users.noreply.github.com> Date: Thu, 23 May 2024 22:15:50 -0500 Subject: [PATCH 057/106] Adding detail to create environment pip install prompt (#23470) We discussed how it might not be clear to the user that this action will create a virtual environment in addition to running their last command to install packages into the newly created environment. Thoughts on adding this detail? --- src/client/common/utils/localize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 87d11ec7d374..beed5a8999ea 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -523,7 +523,7 @@ export namespace CreateEnv { export const createEnvironment = l10n.t('Create'); export const globalPipInstallTriggerMessage = l10n.t( - 'You may have installed Python packages into your global environment, which can cause conflicts between package versions. Would you like to create a virtual environment to isolate your dependencies?', + 'You may have installed Python packages into your global environment, which can cause conflicts between package versions. Would you like to create a virtual environment with these packages to isolate your dependencies?', ); } } From 80a7f90308f88821367beb7689012e662328c8d9 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 24 May 2024 00:01:26 -0700 Subject: [PATCH 058/106] Allow execute on enter and Intellisense for native REPL with notebook UI (#23442) "Smartly" allow execute on enter for the https://github.com/microsoft/vscode-python/pull/23235 experiment. User should be able to execute when they press enter on text input box of interactive window trigger from Python extension, whereas we would "wait" and allow insertion of new line after detecting user's Python command is not complete. When the user finally types enter again on a blank line, we should just proceed to execute whatever code, regardless of whether it is complete/valid or not to replicate Python's original interactive REPL behavior. Basically creating Python command and registering that for keybinding of 'Enter'. This would conditionally call interactive.execute which would then eventually call our execute handler contributed from Python n extension's REPL controller, or go ahead and insert,pass in Enter to the text input box to allow user to type "complete" code. This PR only intends to implement/add changes regarding execute on enter logic, adding Intellisense support, and also adding things into disposables so they can be properly disposed. Trying to also add setting to allow toggling on/off to send Python command to Terminal or IW REPL if the user is in experiment. Handling of interrupt for windows should be on separate PR. Test will be added later as separate PR. --------- Co-authored-by: Courtney Webster <60238438+cwebster-99@users.noreply.github.com> Co-authored-by: Karthik Nadig --- package.json | 42 +++-- package.nls.json | 2 +- python_files/python_server.py | 13 +- src/client/common/application/commands.ts | 1 + src/client/common/constants.ts | 2 + src/client/common/experiments/groups.ts | 5 - src/client/common/types.ts | 2 +- src/client/common/vscodeApis/windowApis.ts | 11 ++ src/client/extensionActivation.ts | 19 +- src/client/repl/pythonServer.ts | 33 +++- src/client/repl/replCommands.ts | 168 ++++++++++++++---- src/client/repl/replController.ts | 8 +- .../terminals/codeExecution/helper.test.ts | 2 +- .../terminals/codeExecution/smartSend.test.ts | 2 +- 14 files changed, 234 insertions(+), 76 deletions(-) diff --git a/package.json b/package.json index 3b1d1e7fba24..2ef08a3511aa 100644 --- a/package.json +++ b/package.json @@ -442,8 +442,7 @@ "pythonDiscoveryUsingWorkers", "pythonTestAdapter", "pythonREPLSmartSend", - "pythonRecommendTensorboardExt", - "pythonRunREPL" + "pythonRecommendTensorboardExt" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -453,8 +452,7 @@ "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRecommendTensorboardExt.description%", - "%python.experiments.pythonRunREPL.description%" + "%python.experiments.pythonRecommendTensorboardExt.description%" ] }, "scope": "window", @@ -472,8 +470,7 @@ "pythonTerminalEnvVarActivation", "pythonDiscoveryUsingWorkers", "pythonTestAdapter", - "pythonREPLSmartSend", - "pythonRunREPL" + "pythonREPLSmartSend" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -482,8 +479,7 @@ "%python.experiments.pythonTerminalEnvVarActivation.description%", "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", - "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRunREPL.description%" + "%python.experiments.pythonREPLSmartSend.description%" ] }, "scope": "window", @@ -628,6 +624,15 @@ "scope": "resource", "type": "boolean" }, + "python.REPL.sendToNativeREPL": { + "default": false, + "description": "%python.REPL.sendToNativeREPL.description%", + "scope": "resource", + "type": "boolean", + "tags": [ + "experimental" + ] + }, "python.testing.autoTestDiscoverOnSaveEnabled": { "default": true, "description": "%python.testing.autoTestDiscoverOnSaveEnabled.description%", @@ -1108,7 +1113,22 @@ { "command": "python.execSelectionInTerminal", "key": "shift+enter", - "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + "when": "!config.python.REPL.sendToNativeREPL && editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + }, + { + "command": "python.execInREPL", + "key": "shift+enter", + "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'" + }, + { + "command": "python.execREPLShiftEnter", + "key": "shift+enter", + "when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter" + }, + { + "command": "python.execInREPLEnter", + "key": "enter", + "when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.interactive'" }, { "command": "python.refreshTensorBoard", @@ -1367,12 +1387,12 @@ { "command": "python.execSelectionInTerminal", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + "when": "!config.python.REPL.sendToNativeREPL && editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" }, { "command": "python.execInREPL", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL" + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && config.python.REPL.sendToNativeREPL" } ], "editor/title": [ diff --git a/package.nls.json b/package.nls.json index aa84f31a91b2..669a14bed528 100644 --- a/package.nls.json +++ b/package.nls.json @@ -45,7 +45,6 @@ "python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.", "python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.", "python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.", - "python.experiments.pythonRunREPL.description": "Enables users to run code in interactive Python REPL.", "python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.", "python.languageServer.description": "Defines type of the language server.", "python.languageServer.defaultDescription": "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.", @@ -63,6 +62,7 @@ "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", + "python.REPL.sendToNativeREPL.description": "Toggle to send code to Python REPL instead of the terminal on execution. Turning this on will change the behavior for both Smart Send and Run Selection/Line in the Context Menu.", "python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.", "python.tensorBoard.logDirectory.markdownDeprecationMessage": "Tensorboard support has been moved to the extension [Tensorboard extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.tensorboard). Instead use the setting `tensorBoard.logDirectory`.", "python.tensorBoard.logDirectory.deprecationMessage": "Tensorboard support has been moved to the extension Tensorboard extension. Instead use the setting `tensorBoard.logDirectory`.", diff --git a/python_files/python_server.py b/python_files/python_server.py index 4d27a168bc4c..30be834631c6 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,11 +1,11 @@ from typing import Dict, List, Optional, Union - import sys import json import contextlib import io import traceback import uuid +import ast STDIN = sys.stdin STDOUT = sys.stdout @@ -88,6 +88,15 @@ def exec_function(user_input): return eval +def check_valid_command(request): + try: + user_input = request["params"] + ast.parse(user_input[0]) + send_response("True", request["id"]) + except SyntaxError: + send_response("False", request["id"]) + + def execute(request, user_globals): str_output = CustomIO("", encoding="utf-8") str_error = CustomIO("", encoding="utf-8") @@ -160,6 +169,8 @@ def get_headers(): request_json = json.loads(request_text) if request_json["method"] == "execute": execute(request_json, USER_GLOBALS) + if request_json["method"] == "check_valid_command": + check_valid_command(request_json) elif request_json["method"] == "exit": sys.exit(0) diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 626321566332..4c00971ffdd5 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -39,6 +39,7 @@ interface ICommandNameWithoutArgumentTypeMapping { [Commands.Exec_Selection_In_Terminal]: []; [Commands.Exec_Selection_In_Django_Shell]: []; [Commands.Exec_In_REPL]: []; + [Commands.Exec_In_REPL_Enter]: []; [Commands.Create_Terminal]: []; [Commands.PickLocalProcess]: []; [Commands.ClearStorage]: []; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 663b932c8542..94bcc78c6c6b 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -48,6 +48,8 @@ export namespace Commands { export const Exec_In_Separate_Terminal = 'python.execInDedicatedTerminal'; export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; + export const Exec_In_REPL_Enter = 'python.execInREPLEnter'; + export const Exec_In_REPL_Shift_Enter = 'python.execREPLShiftEnter'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; export const InstallJupyter = 'python.installJupyter'; diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 543b1e27516f..81f157751346 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -30,8 +30,3 @@ export enum RecommendTensobardExtension { export enum CreateEnvOnPipInstallTrigger { experiment = 'pythonCreateEnvOnPipInstall', } - -// Experiment to enable running Python REPL using IW. -export enum EnableRunREPL { - experiment = 'pythonRunREPL', -} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 8edc76ff2bff..d4a0921140ec 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -200,7 +200,7 @@ export interface ITerminalSettings { export interface IREPLSettings { readonly enableREPLSmartSend: boolean; - readonly enableIWREPL: boolean; + readonly sendToNativeREPL: boolean; } export interface IExperiments { diff --git a/src/client/common/vscodeApis/windowApis.ts b/src/client/common/vscodeApis/windowApis.ts index 6ef02df41d1e..37d302946614 100644 --- a/src/client/common/vscodeApis/windowApis.ts +++ b/src/client/common/vscodeApis/windowApis.ts @@ -21,6 +21,8 @@ import { TerminalShellExecutionStartEvent, } from 'vscode'; import { createDeferred, Deferred } from '../utils/async'; +import { Resource } from '../types'; +import { getWorkspaceFolders } from './workspaceApis'; export function showTextDocument(uri: Uri): Thenable { return window.showTextDocument(uri); @@ -238,3 +240,12 @@ export function createStepForwardEndNode(deferred?: Deferred, result?: T): undefined, ); } + +export function getActiveResource(): Resource { + const editor = window.activeTextEditor; + if (editor && !editor.document.isUntitled) { + return editor.document.uri; + } + const workspaces = getWorkspaceFolders(); + return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined; +} diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 7c582eb63239..37d97b8ccbab 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -16,7 +16,6 @@ import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, - IExperimentService, IExtensions, IInterpreterPathService, ILogOutputChannel, @@ -53,8 +52,7 @@ import { initializePersistentStateForTriggers } from './common/persistentState'; import { logAndNotifyOnLegacySettings } from './logging/settingLogs'; import { DebuggerTypeName } from './debugger/constants'; import { StopWatch } from './common/utils/stopWatch'; -import { registerReplCommands } from './repl/replCommands'; -import { EnableRunREPL } from './common/experiments/groups'; +import { registerReplCommands, registerReplExecuteOnEnter, registerReplExecuteOnShiftEnter } from './repl/replCommands'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -109,16 +107,9 @@ export function activateFeatures(ext: ExtensionState, _components: Components): pathUtils, ); - // Register native REPL context menu when in experiment - const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); - commands.executeCommand('setContext', 'pythonRunREPL', false); - if (experimentService) { - const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); - if (replExperimentValue) { - registerReplCommands(ext.disposables, interpreterService); - commands.executeCommand('setContext', 'pythonRunREPL', true); - } - } + registerReplCommands(ext.disposables, interpreterService); + registerReplExecuteOnEnter(ext.disposables, interpreterService); + registerReplExecuteOnShiftEnter(ext.disposables); } /// ////////////////////////// diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index e25ba3a25092..766fcf4cc92c 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -6,27 +6,32 @@ import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); +let serverInstance: PythonServer | undefined; export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; input(): void; + checkValidCommand(code: string): Promise; } class PythonServerImpl implements Disposable { + private readonly disposables: Disposable[] = []; + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { this.initialize(); this.input(); } private initialize(): void { - this.connection.onNotification('log', (message: string) => { - console.log('Log:', message); - }); + this.disposables.push( + this.connection.onNotification('log', (message: string) => { + console.log('Log:', message); + }), + ); this.connection.listen(); } - // Register input handler public input(): void { // Register input request handler this.connection.onRequest('input', async (request) => { @@ -49,18 +54,32 @@ class PythonServerImpl implements Disposable { } public interrupt(): void { + // Passing SIGINT to interrupt only would work for Mac and Linux if (this.pythonServer.kill('SIGINT')) { - traceLog('Python server interrupted'); + traceLog('Python REPL server interrupted'); + } + } + + public async checkValidCommand(code: string): Promise { + const completeCode = await this.connection.sendRequest('check_valid_command', code); + if (completeCode === 'True') { + return new Promise((resolve) => resolve(true)); } + return new Promise((resolve) => resolve(false)); } public dispose(): void { this.connection.sendNotification('exit'); + this.disposables.forEach((d) => d.dispose()); this.connection.dispose(); } } export function createPythonServer(interpreter: string[]): PythonServer { + if (serverInstance) { + return serverInstance; + } + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); pythonServer.stderr.on('data', (data) => { @@ -76,6 +95,6 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - - return new PythonServerImpl(connection, pythonServer); + serverInstance = new PythonServerImpl(connection, pythonServer); + return serverInstance; } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index e7a40b01c6be..d9d0858a6637 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -12,17 +12,23 @@ import { WorkspaceEdit, NotebookEditor, TextEditor, + Selection, + NotebookDocument, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; +import { createPythonServer } from './pythonServer'; import { createReplController } from './replController'; +import { getActiveResource } from '../common/vscodeApis/windowApis'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; let notebookController: NotebookController | undefined; let notebookEditor: NotebookEditor | undefined; // TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. +let notebookDocument: NotebookDocument | undefined; async function getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { @@ -42,13 +48,26 @@ async function getSelectedTextToExecute(textEditor: TextEditor): Promise('REPL.sendToNativeREPL', false); +} +// Will only be called when user has experiment enabled. export async function registerReplCommands( disposables: Disposable[], interpreterService: IInterpreterService, ): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + const nativeREPLSetting = getSendToNativeREPLSetting(); + + // If nativeREPLSetting is false(Send to Terminal REPL), then fall back to running in Terminal REPL + if (!nativeREPLSetting) { + await commands.executeCommand(Commands.Exec_Selection_In_Terminal); + return; + } + const interpreter = await interpreterService.getActiveInterpreter(uri); if (!interpreter) { commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); @@ -58,47 +77,130 @@ export async function registerReplCommands( const interpreterPath = interpreter.path; if (!notebookController) { - notebookController = createReplController(interpreterPath); + notebookController = createReplController(interpreterPath, disposables); } const activeEditor = window.activeTextEditor as TextEditor; - const code = await getSelectedTextToExecute(activeEditor); - const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); - - const notebookDocument = await workspace.openNotebookDocument(ourResource); - // commands.executeCommand('_interactive.open'); command to open interactive window so intellisense is registered. - // We want to keep notebookEditor, whenever we want to run. - // Find interactive window, or open it. if (!notebookEditor) { - notebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, + const interactiveWindowObject = (await commands.executeCommand( + 'interactive.open', + { + preserveFocus: true, + viewColumn: ViewColumn.Beside, + }, + undefined, + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; + } + + if (notebookDocument) { + notebookController.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + + // Auto-Select Python REPL Kernel + await commands.executeCommand('notebook.selectKernel', { + notebookEditor, + id: notebookController.id, + extension: PVSC_EXTENSION_ID, + }); + + const { cellCount } = notebookDocument; + await addCellToNotebook(code as string); + // Execute the cell + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: notebookDocument.uri, }); } + } + }), + ); +} +/** + * Function that adds cell to notebook. + * This function will only get called when notebook document is defined. + * @param code + * + */ +async function addCellToNotebook(code: string): Promise { + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); + const { cellCount } = notebookDocument!; + // Add new cell to interactive window document + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); + await workspace.applyEdit(workspaceEdit); +} - notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); - - // Auto-Select Python REPL Kernel - await commands.executeCommand('notebook.selectKernel', { - notebookEditor, - id: notebookController?.id, - extension: PVSC_EXTENSION_ID, - }); - - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); - const { cellCount } = notebookDocument; - // Add new cell to interactive window document - const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.set(notebookDocument.uri, [notebookEdit]); - await workspace.applyEdit(workspaceEdit); - - // Execute the cell - commands.executeCommand('notebook.cell.execute', { - ranges: [{ start: cellCount, end: cellCount + 1 }], - document: ourResource, - }); +/** + * Command triggered for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. + * @param disposables + * @param interpreterService + */ +export async function registerReplExecuteOnEnter( + disposables: Disposable[], + interpreterService: IInterpreterService, +): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return; + } + + // Create Separate Python server to check valid command + const pythonServer = createPythonServer([interpreter.path as string]); + + const activeEditor = window.activeTextEditor; + let userTextInput; + let completeCode = false; + + if (activeEditor) { + const { document } = activeEditor; + userTextInput = document.getText(); + } + + // Check if userTextInput is a complete Python command + if (userTextInput) { + completeCode = await pythonServer.checkValidCommand(userTextInput); + } + const editor = window.activeTextEditor; + // Execute right away when complete code and Not multi-line + if (completeCode && !isMultiLineText(editor)) { + await commands.executeCommand('interactive.execute'); + } else { + // Insert new line on behalf of user. "Regular" monaco editor behavior + if (editor) { + const position = editor.selection.active; + const newPosition = position.with(position.line, editor.document.lineAt(position.line).text.length); + editor.selection = new Selection(newPosition, newPosition); + + editor.edit((editBuilder) => { + editBuilder.insert(newPosition, '\n'); + }); + } + + // Handle case when user enters on blank line, just trigger interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand('interactive.execute'); + } } }), ); } + +export async function registerReplExecuteOnShiftEnter(disposables: Disposable[]): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL_Shift_Enter, async () => { + await commands.executeCommand(Commands.Exec_In_REPL_Enter); + }), + ); +} + +function isMultiLineText(textEditor: TextEditor | undefined): boolean { + return (textEditor?.document?.lineCount ?? 0) > 1; +} diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index f7ee7e6d486c..86d021cd1c7a 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -1,8 +1,13 @@ import * as vscode from 'vscode'; import { createPythonServer } from './pythonServer'; -export function createReplController(interpreterPath: string): vscode.NotebookController { +export function createReplController( + interpreterPath: string, + disposables: vscode.Disposable[], +): vscode.NotebookController { const server = createPythonServer([interpreterPath]); + disposables.push(server); + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); controller.supportedLanguages = ['python']; controller.supportsExecutionOrder = true; @@ -39,5 +44,6 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo } } }; + disposables.push(controller); return controller; } diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 9098455c968e..6e2ea2a61061 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -112,7 +112,7 @@ suite('Terminal - Code Execution Helper', () => { activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); pythonSettings .setup((s) => s.REPL) - .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, enableIWREPL: false })); + .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, sendToNativeREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); configurationService .setup((c) => c.getSettings(TypeMoq.It.isAny())) diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index ba5101332bf8..c6f4ae195d16 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -109,7 +109,7 @@ suite('REPL - Smart Send', () => { pythonSettings .setup((s) => s.REPL) - .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, enableIWREPL: false })); + .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, sendToNativeREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); From fc05490b9f1ef051657ab62da6915c5610e29c91 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 24 May 2024 10:53:43 -0700 Subject: [PATCH 059/106] Allow reopening of native REPL after closing (#23478) Resolves: https://github.com/microsoft/vscode-python/issues/23477 --- src/client/repl/replCommands.ts | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index d9d0858a6637..fd9b74f6602e 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -82,20 +82,18 @@ export async function registerReplCommands( const activeEditor = window.activeTextEditor as TextEditor; const code = await getSelectedTextToExecute(activeEditor); - if (!notebookEditor) { - const interactiveWindowObject = (await commands.executeCommand( - 'interactive.open', - { - preserveFocus: true, - viewColumn: ViewColumn.Beside, - }, - undefined, - notebookController.id, - 'Python REPL', - )) as { notebookEditor: NotebookEditor }; - notebookEditor = interactiveWindowObject.notebookEditor; - notebookDocument = interactiveWindowObject.notebookEditor.notebook; - } + const interactiveWindowObject = (await commands.executeCommand( + 'interactive.open', + { + preserveFocus: true, + viewColumn: ViewColumn.Beside, + }, + undefined, + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; if (notebookDocument) { notebookController.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); From 2373901c8f736bf33fb9d43e333e9e4e7d619ad3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 24 May 2024 11:57:29 -0700 Subject: [PATCH 060/106] Fix telemetry annotations (#23481) --- src/client/telemetry/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 84b4295dedd7..2f4b4c19f0eb 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1143,8 +1143,8 @@ export interface IEventNamePropertyMapping { "python_interpreter_discovery" : { "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karrtikr" }, "interpreters" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "karrtikr"}, - "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karrtikr" } - "usingNativeLocator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" } + "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karrtikr" }, + "usingNativeLocator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, @@ -1159,7 +1159,7 @@ export interface IEventNamePropertyMapping { "unknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "venvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "virtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, - "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } } */ [EventName.PYTHON_INTERPRETER_DISCOVERY]: { @@ -1243,7 +1243,7 @@ export interface IEventNamePropertyMapping { "python_interpreter_discovery_native" : { "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "interpreters" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, - "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } + "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, @@ -1258,7 +1258,7 @@ export interface IEventNamePropertyMapping { "unknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "venvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "virtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, - "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } } */ [EventName.PYTHON_INTERPRETER_DISCOVERY_NATIVE]: { From 8af19fa7b5e5d84f3cd362a2a85ceed318abeaca Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 24 May 2024 12:43:10 -0700 Subject: [PATCH 061/106] Ensure native locator is included in the VSIX when built (#23476) This ensures that if we run cargo build to build the native code it gets included in the VSIX. For now this is only for the builds for VSIX generated on github, for testing. As this is not ready for published pre-releases. --- .github/workflows/build.yml | 43 ++++++++++++++++++++++++++++++---- .github/workflows/pr-check.yml | 2 +- noxfile.py | 10 +++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d07f6165315e..b1c56f2535ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,7 @@ jobs: run: shell: python outputs: + vsix_basename: ${{ steps.vsix_names.outputs.vsix_basename }} vsix_name: ${{ steps.vsix_names.outputs.vsix_name }} vsix_artifact_name: ${{ steps.vsix_names.outputs.vsix_artifact_name }} steps: @@ -40,13 +41,45 @@ jobs: else: vsix_type = "release" print(f"::set-output name=vsix_name::ms-python-{vsix_type}.vsix") + print(f"::set-output name=vsix_basename::ms-python-{vsix_type}") print(f"::set-output name=vsix_artifact_name::ms-python-{vsix_type}-vsix") build-vsix: name: Create VSIX if: github.repository == 'microsoft/vscode-python' needs: setup - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + vsix-target: win32-x64 + - os: windows-latest + target: aarch64-pc-windows-msvc + vsix-target: win32-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: linux-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-gnu + # vsix-target: linux-arm64 + # - os: ubuntu-latest + # target: arm-unknown-linux-gnueabihf + # vsix-target: linux-armhf + - os: macos-latest + target: x86_64-apple-darwin + vsix-target: darwin-x64 + - os: macos-14 + target: aarch64-apple-darwin + vsix-target: darwin-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: alpine-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-musl + # vsix-target: alpine-arm64 steps: - name: Checkout uses: actions/checkout@v4 @@ -54,9 +87,11 @@ jobs: - name: Build VSIX uses: ./.github/actions/build-vsix with: - node_version: ${{ env.NODE_VERSION }} - vsix_name: ${{ needs.setup.outputs.vsix_name }} - artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }} + node_version: ${{ env.NODE_VERSION}} + vsix_name: ${{ needs.setup.outputs.vsix_basename }}-${{ matrix.vsix-target }}.vsix' + artifact_name: '${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }}' + cargo_target: ${{ matrix.target }} + vsix_target: ${{ matrix.vsix-target }} lint: name: Lint diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 47fb13807121..0efd568e0346 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -33,7 +33,7 @@ jobs: target: aarch64-pc-windows-msvc vsix-target: win32-arm64 - os: ubuntu-latest - target: x86_64-unknown-linux-gnu + target: x86_64-unknown-linux-musl vsix-target: linux-x64 # - os: ubuntu-latest # target: aarch64-unknown-linux-gnu diff --git a/noxfile.py b/noxfile.py index 0e0e8803c3d1..e0fc26988d5b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -5,9 +5,10 @@ import pathlib import nox import shutil -import sys import sysconfig +EXT_ROOT = pathlib.Path(__file__).parent + @nox.session() def install_python_libs(session: nox.Session): @@ -91,6 +92,13 @@ def native_build(session: nox.Session): dest = f"./bin/python-finder{ext}" shutil.copy(source, dest) + # Remove native_locator/bin exclusion from .vscodeignore + vscode_ignore = EXT_ROOT / ".vscodeignore" + remove_patterns = ("native_locator/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") + @nox.session() def setup_repo(session: nox.Session): From e25fd572d25a57dc7936b47848621d0a298fd4db Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 24 May 2024 13:54:28 -0700 Subject: [PATCH 062/106] Add flags to optimize binary size (#23483) --- native_locator/Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml index c6ae0f27c36f..ea41be66c8a4 100644 --- a/native_locator/Cargo.toml +++ b/native_locator/Cargo.toml @@ -14,6 +14,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 From a9bdf471480decbf73677dd3beb908e6a1cfb832 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 27 May 2024 13:03:01 +1000 Subject: [PATCH 063/106] Fx win reg locator (#23486) --- native_locator/src/windows_registry.rs | 260 +++++++++++++++---------- 1 file changed, 158 insertions(+), 102 deletions(-) diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index 0e7a06eddec1..2f3c6d04a42e 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -16,114 +16,134 @@ use std::path::PathBuf; #[cfg(windows)] use winreg::RegKey; -#[cfg(windows)] -fn get_registry_pythons_from_key( - hk: &RegKey, - conda_locator: &mut dyn CondaLocator, -) -> Option { - let mut environments = vec![]; - let mut managers: Vec = vec![]; - let python_key = hk.open_subkey("Software\\Python").ok()?; - for company in python_key.enum_keys().filter_map(Result::ok) { - if let Some(result) = - get_registry_pythons_from_key_for_company(&hk, &company, conda_locator) - { - managers.extend(result.managers); - environments.extend(result.environments); - } - } - - Some(LocatorResult { - environments, - managers, - }) -} - #[cfg(windows)] fn get_registry_pythons_from_key_for_company( - hk: &RegKey, + key_container: &str, + company_key: &RegKey, company: &str, conda_locator: &mut dyn CondaLocator, ) -> Option { - use crate::messaging::Architecture; + use log::{trace, warn}; - let mut environments = vec![]; + use crate::messaging::Architecture; let mut managers: Vec = vec![]; - let python_key = hk.open_subkey("Software\\Python").ok()?; - let company_key = python_key.open_subkey(company).ok()?; + let mut environments = vec![]; let company_display_name: Option = company_key.get_value("DisplayName").ok(); - for key in company_key.enum_keys().filter_map(Result::ok) { - if let Some(key) = company_key.open_subkey(key).ok() { - if let Some(install_path_key) = key.open_subkey("InstallPath").ok() { - let env_path: String = install_path_key.get_value("").ok().unwrap_or_default(); - let env_path = PathBuf::from(env_path); + 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); + 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; + } - // 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(); + 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(); - 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 { - continue; - } - let executable = PathBuf::from(executable); - if !executable.exists() { - continue; + Err(err) => { + warn!( + "Failed to open {}\\Software\\Python\\{}\\{}\\InstallPath, {:?}", + key_container, company, installed_python, err + ); + } } - let version: String = key.get_value("Version").ok().unwrap_or_default(); - let architecture: String = - key.get_value("SysArchitecture").ok().unwrap_or_default(); - let display_name: String = 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()]), + } + Err(err) => { + warn!( + "Failed to open {}\\Software\\Python\\{}\\{}, {:?}", + key_container, company, installed_python, err ); - 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); } } } @@ -136,19 +156,55 @@ fn get_registry_pythons_from_key_for_company( #[cfg(windows)] fn get_registry_pythons(conda_locator: &mut dyn CondaLocator) -> Option { - let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); - let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + use log::{trace, warn}; let mut environments = vec![]; let mut managers: Vec = vec![]; - if let Some(result) = get_registry_pythons_from_key(&hklm, conda_locator) { - managers.extend(result.managers); - environments.extend(result.environments); + struct RegistryKey { + pub name: &'static str, + pub key: winreg::RegKey, } - if let Some(result) = get_registry_pythons_from_key(&hkcu, conda_locator) { - managers.extend(result.managers); - environments.extend(result.environments); + 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, @@ -177,7 +233,7 @@ impl Locator for WindowsRegistry<'_> { fn find(&mut self) -> Option { if let Some(result) = get_registry_pythons(self.conda_locator) { - if !result.environments.is_empty() && !result.managers.is_empty() { + if !result.environments.is_empty() || !result.managers.is_empty() { return Some(result); } } From 54becad708de9d15e46e7c7b7c2004f590900456 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 27 May 2024 19:20:26 +1000 Subject: [PATCH 064/106] Parallelize locating of global python environments (#23490) --- native_locator/src/conda.rs | 18 +-- native_locator/src/global_virtualenvs.rs | 2 +- native_locator/src/known.rs | 5 + native_locator/src/lib.rs | 196 ++++++++++++++++++++++- native_locator/src/main.rs | 89 +--------- 5 files changed, 206 insertions(+), 104 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 8c7164b472d6..2f2d090adca3 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -117,7 +117,7 @@ fn get_conda_package_json_path(path: &Path, package: &str) -> Option Option { +fn get_conda_executable(path: &Path) -> Option { for relative_path in get_relative_paths_to_conda_executable() { let exe = path.join(&relative_path); if exe.exists() { @@ -226,7 +226,7 @@ pub fn find_conda_binary(environment: &dyn known::Environment) -> Option Option { +fn get_conda_manager(path: &Path) -> Option { let conda_exe = get_conda_executable(path)?; let conda_pkg = get_conda_package_json_path(path, "conda")?; @@ -529,7 +529,7 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { */ fn was_conda_environment_created_by_specific_conda( env: &CondaEnvironment, - root_conda_path: &PathBuf, + root_conda_path: &Path, ) -> bool { if let Some(cmd_line) = env.conda_install_folder.clone() { if cmd_line @@ -661,7 +661,7 @@ fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Optio } } -fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option { +fn get_root_python_environment(path: &Path, manager: &EnvManager) -> Option { let python_exe = path.join(get_relative_paths_to_main_python_executable()); if !python_exe.exists() { return None; @@ -680,7 +680,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

Option

, ) -> Option { let mut managers: Vec = vec![]; @@ -842,7 +842,7 @@ fn find_conda_environments_from_known_conda_install_locations( }) } -fn is_conda_install_location(path: &PathBuf) -> bool { +fn is_conda_install_location(path: &Path) -> bool { let envs_path = path.join("envs"); return envs_path.exists() && envs_path.is_dir(); } @@ -963,7 +963,7 @@ pub struct Conda<'a> { } pub trait CondaLocator { - fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option; + fn find_in(&mut self, possible_conda_folder: &Path) -> Option; } impl Conda<'_> { @@ -1019,7 +1019,7 @@ impl Conda<'_> { } impl CondaLocator for Conda<'_> { - fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { + fn find_in(&mut self, possible_conda_folder: &Path) -> Option { if !is_conda_install_location(possible_conda_folder) { return None; } diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index 8004775e3ee2..e0e4cf8cb991 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -7,7 +7,7 @@ use crate::{ }; use std::{fs, path::PathBuf}; -pub fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { +fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { let mut venv_dirs: Vec = vec![]; if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) { diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index bd9e7bc4de34..600aa45d1034 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -14,6 +14,11 @@ pub trait Environment { } pub struct EnvironmentApi {} +impl EnvironmentApi { + pub fn new() -> Self { + EnvironmentApi {} + } +} #[cfg(windows)] impl Environment for EnvironmentApi { diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index ba353c71ce12..f1335a41f461 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -1,18 +1,198 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -pub mod messaging; -pub mod utils; +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 logging; pub mod conda; -pub mod known; -pub mod pyenv; pub mod global_virtualenvs; -pub mod virtualenvwrapper; +pub mod homebrew; +pub mod known; +pub mod locator; +pub mod logging; +pub mod messaging; pub mod pipenv; -pub mod virtualenv; +pub mod pyenv; +pub mod utils; pub mod venv; -pub mod locator; +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>> { + // 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>> { + // 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 { + // 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, + Box::new(virtualenvwrapper) as Box, + Box::new(venv_locator) as Box, + Box::new(virtualenv_locator) as Box, + ]; + + // 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, 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())); + } +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 4964b09cac40..da0720e242e5 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -2,13 +2,10 @@ // Licensed under the MIT License. use crate::messaging::initialize_logger; -use global_virtualenvs::list_global_virtual_envs; -use known::EnvironmentApi; -use locator::Locator; use log::LevelFilter; -use messaging::{create_dispatcher, JsonRpcDispatcher, MessageDispatcher}; +use messaging::{create_dispatcher, MessageDispatcher}; +use python_finder::find_and_report_envs; use std::time::SystemTime; -use utils::PythonEnv; mod common_python; mod conda; @@ -28,67 +25,13 @@ mod windows_registry; mod windows_store; fn main() { - let environment = EnvironmentApi {}; initialize_logger(LevelFilter::Trace); log::info!("Starting Native Locator"); let now = SystemTime::now(); let mut dispatcher = create_dispatcher(); - let virtualenv_locator = virtualenv::VirtualEnv::new(); - let venv_locator = venv::Venv::new(); - let mut virtualenvwrapper = virtualenvwrapper::VirtualEnvWrapper::with(&environment); - let pipenv_locator = pipenv::PipEnv::new(); - let mut path_locator = common_python::PythonOnPath::with(&environment); - let mut conda_locator = conda::Conda::with(&environment); - - #[cfg(unix)] - let mut homebrew_locator = homebrew::Homebrew::with(&environment); - #[cfg(windows)] - let mut windows_store = windows_store::WindowsStore::with(&environment); - #[cfg(windows)] - let mut windows_registry = windows_registry::WindowsRegistry::with(&mut conda_locator); - - // Step 1: These environments take precedence over all others. - // As they are very specific and guaranteed to be specific type. - #[cfg(windows)] - find_environments(&mut windows_store, &mut dispatcher); - #[cfg(windows)] - find_environments(&mut windows_registry, &mut dispatcher); - let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator); - find_environments(&mut virtualenvwrapper, &mut dispatcher); - find_environments(&mut pyenv_locator, &mut dispatcher); - #[cfg(unix)] - find_environments(&mut homebrew_locator, &mut dispatcher); - find_environments(&mut conda_locator, &mut dispatcher); - - // Step 2: Search in some global locations for virtual envs. - for env in list_global_virtual_envs(&environment).iter() { - if dispatcher.was_environment_reported(&env) { - continue; - } - - // First must be homebrew, as it is the most specific and supports symlinks - #[cfg(unix)] - let homebrew_result = resolve_environment(&homebrew_locator, env, &mut dispatcher); - #[cfg(unix)] - if homebrew_result { - continue; - } - - let _ = // Pipeenv before virtualenvwrapper as it is more specific. - // Because pipenv environments are also virtualenvwrapper environments. - resolve_environment(&pipenv_locator, env, &mut dispatcher) - // Before venv, as all venvs are also virtualenvwrapper environments. - || resolve_environment(&virtualenvwrapper, env, &mut dispatcher) - // Before virtualenv as this is more specific. - // All venvs are also virtualenvs environments. - || resolve_environment(&venv_locator, env, &mut dispatcher) - || resolve_environment(&virtualenv_locator, env, &mut dispatcher); - } - - // Step 3: Finally find in the current PATH variable - find_environments(&mut path_locator, &mut dispatcher); + find_and_report_envs(); match now.elapsed() { Ok(elapsed) => { @@ -101,29 +44,3 @@ fn main() { dispatcher.exit(); } - -fn resolve_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 find_environments(locator: &mut dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { - if let Some(result) = locator.find() { - result - .environments - .iter() - .for_each(|e| dispatcher.report_environment(e.clone())); - result - .managers - .iter() - .for_each(|m| dispatcher.report_environment_manager(m.clone())); - } - Some(()) -} From e777588cdba7a254722d91f5e147b0429311e016 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 27 May 2024 19:58:02 +1000 Subject: [PATCH 065/106] Ignore Win Store Python in Windows Registry search (#23492) --- native_locator/src/windows_registry.rs | 12 ++++++++++++ native_locator/src/windows_store.rs | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs index 2f3c6d04a42e..258b6ad698dd 100644 --- a/native_locator/src/windows_registry.rs +++ b/native_locator/src/windows_registry.rs @@ -12,6 +12,8 @@ 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; @@ -37,6 +39,16 @@ fn get_registry_pythons_from_key_for_company( 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(), diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index f9ed251f9674..97e0352ea385 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -27,6 +27,11 @@ pub fn is_windows_python_executable(path: &PathBuf) -> bool { 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, From 5803a198ff59e2c1428792a31817a461522f77af Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 27 May 2024 17:50:27 -0700 Subject: [PATCH 066/106] Fix build arguments to pipeline (#23494) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1c56f2535ae..f9e1ac1826f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,8 +88,8 @@ jobs: uses: ./.github/actions/build-vsix with: node_version: ${{ env.NODE_VERSION}} - vsix_name: ${{ needs.setup.outputs.vsix_basename }}-${{ matrix.vsix-target }}.vsix' - artifact_name: '${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }}' + vsix_name: ${{ needs.setup.outputs.vsix_basename }}-${{ matrix.vsix-target }}.vsix + artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }} cargo_target: ${{ matrix.target }} vsix_target: ${{ matrix.vsix-target }} From 8909fda4a90c66321b2de8a8cb06df8024d0998d Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Tue, 28 May 2024 14:47:52 -0700 Subject: [PATCH 067/106] Stop creating duplicate REPL and allow new REPL instance (#23496) Resolves: #23495 Resolves: https://github.com/microsoft/vscode-python/issues/23500 --- src/client/repl/replCommands.ts | 39 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index fd9b74f6602e..1260c78a6a56 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -53,6 +53,15 @@ function getSendToNativeREPLSetting(): boolean { const configuration = getConfiguration('python', uri); return configuration.get('REPL.sendToNativeREPL', false); } + +window.onDidChangeVisibleTextEditors((editors) => { + const interactiveWindowIsOpen = editors.some((editor) => editor.document.uri.scheme === 'vscode-interactive-input'); + if (!interactiveWindowIsOpen) { + notebookEditor = undefined; + notebookDocument = undefined; + } +}); + // Will only be called when user has experiment enabled. export async function registerReplCommands( disposables: Disposable[], @@ -82,18 +91,24 @@ export async function registerReplCommands( const activeEditor = window.activeTextEditor as TextEditor; const code = await getSelectedTextToExecute(activeEditor); - const interactiveWindowObject = (await commands.executeCommand( - 'interactive.open', - { - preserveFocus: true, - viewColumn: ViewColumn.Beside, - }, - undefined, - notebookController.id, - 'Python REPL', - )) as { notebookEditor: NotebookEditor }; - notebookEditor = interactiveWindowObject.notebookEditor; - notebookDocument = interactiveWindowObject.notebookEditor.notebook; + if (!notebookEditor) { + const interactiveWindowObject = (await commands.executeCommand( + 'interactive.open', + { + preserveFocus: true, + viewColumn: ViewColumn.Beside, + }, + undefined, + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; + } + // Handle case where user has closed REPL window, and re-opens. + if (notebookEditor && notebookDocument) { + await window.showNotebookDocument(notebookDocument, { viewColumn: ViewColumn.Beside }); + } if (notebookDocument) { notebookController.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); From 3fca3ed295258ea2b5a934b7e2df6eae702786a9 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 29 May 2024 14:18:48 +1000 Subject: [PATCH 068/106] Support VSC in VS Code when opening native_locator (#23516) --- .vscodeignore | 1 + native_locator/.vscode/settings.json | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 native_locator/.vscode/settings.json diff --git a/.vscodeignore b/.vscodeignore index 78028d097455..c2b2a3dd9538 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -67,6 +67,7 @@ test/** tmp/** typings/** types/** +native_locator/.vscode/** native_locator/src/** native_locator/tests/** native_locator/bin/** diff --git a/native_locator/.vscode/settings.json b/native_locator/.vscode/settings.json new file mode 100644 index 000000000000..58d2322dc45f --- /dev/null +++ b/native_locator/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.openRepositoryInParentFolders": "always" +} From a4a9a3b2cfac40b339cb3aa38ec6bc1e30050dc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 22:18:22 -0700 Subject: [PATCH 069/106] Bump typing-extensions from 4.11.0 to 4.12.0 (#23472) Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.11.0 to 4.12.0.

Release notes

Sourced from typing-extensions's releases.

4.12.0

This release focuses on compatibility with the upcoming release of Python 3.13. Most changes are related to the implementation of type parameter defaults (PEP 696).

Thanks to all of the people who contributed patches, especially Alex Waygood, who did most of the work adapting typing-extensions to the CPython PEP 696 implementation.

There is a single change since 4.12.0rc1:

  • Fix incorrect behaviour of typing_extensions.ParamSpec on Python 3.8 and 3.9 that meant that isinstance(typing_extensions.ParamSpec("P"), typing.TypeVar) would have a different result in some situations depending on whether or not a profiling function had been set using sys.setprofile. Patch by Alex Waygood.

Changes included in 4.12.0rc1:

  • Improve the implementation of type parameter defaults (PEP 696)
    • Backport the typing.NoDefault sentinel object from Python 3.13. TypeVars, ParamSpecs and TypeVarTuples without default values now have their __default__ attribute set to this sentinel value.
    • TypeVars, ParamSpecs and TypeVarTuples now have a has_default() method, matching typing.TypeVar, typing.ParamSpec and typing.TypeVarTuple on Python 3.13+.
    • TypeVars, ParamSpecs and TypeVarTuples with default=None passed to their constructors now have their __default__ attribute set to None at runtime rather than types.NoneType.
    • Fix most tests for TypeVar, ParamSpec and TypeVarTuple on Python 3.13.0b1 and newer.
    • Backport CPython PR #118774, allowing type parameters without default values to follow those with default values in some type parameter lists. Patch by Alex Waygood, backporting a CPython PR by Jelle Zijlstra.
    • It is now disallowed to use a TypeVar with a default value after a TypeVarTuple in a type parameter list. This matches the CPython implementation of PEP 696 on Python 3.13+.
    • Fix bug in PEP-696 implementation where a default value for a ParamSpec would be cast to a tuple if a list was provided. Patch by Alex Waygood.
  • Fix Protocol tests on Python 3.13.0a6 and newer. 3.13.0a6 adds a new __static_attributes__ attribute to all classes in Python, which broke some assumptions made by the implementation of typing_extensions.Protocol. Similarly, 3.13.0b1 adds the new __firstlineno__ attribute to all classes.
  • Fix AttributeError when using typing_extensions.runtime_checkable in combination with typing.Protocol on Python 3.12.2 or newer. Patch by Alex Waygood.
  • At runtime, assert_never now includes the repr of the argument

... (truncated)

Changelog

Sourced from typing-extensions's changelog.

Release 4.12.0 (May 23, 2024)

This release is mostly the same as 4.12.0rc1 but fixes one more longstanding bug.

  • Fix incorrect behaviour of typing_extensions.ParamSpec on Python 3.8 and 3.9 that meant that isinstance(typing_extensions.ParamSpec("P"), typing.TypeVar) would have a different result in some situations depending on whether or not a profiling function had been set using sys.setprofile. Patch by Alex Waygood.

Release 4.12.0rc1 (May 16, 2024)

This release focuses on compatibility with the upcoming release of Python 3.13. Most changes are related to the implementation of type parameter defaults (PEP 696).

Thanks to all of the people who contributed patches, especially Alex Waygood, who did most of the work adapting typing-extensions to the CPython PEP 696 implementation.

Full changelog:

  • Improve the implementation of type parameter defaults (PEP 696)
    • Backport the typing.NoDefault sentinel object from Python 3.13. TypeVars, ParamSpecs and TypeVarTuples without default values now have their __default__ attribute set to this sentinel value.
    • TypeVars, ParamSpecs and TypeVarTuples now have a has_default() method, matching typing.TypeVar, typing.ParamSpec and typing.TypeVarTuple on Python 3.13+.
    • TypeVars, ParamSpecs and TypeVarTuples with default=None passed to their constructors now have their __default__ attribute set to None at runtime rather than types.NoneType.
    • Fix most tests for TypeVar, ParamSpec and TypeVarTuple on Python 3.13.0b1 and newer.
    • Backport CPython PR #118774, allowing type parameters without default values to follow those with default values in some type parameter lists. Patch by Alex Waygood, backporting a CPython PR by Jelle Zijlstra.
    • It is now disallowed to use a TypeVar with a default value after a TypeVarTuple in a type parameter list. This matches the CPython implementation of PEP 696 on Python 3.13+.
    • Fix bug in PEP-696 implementation where a default value for a ParamSpec would be cast to a tuple if a list was provided. Patch by Alex Waygood.
  • Fix Protocol tests on Python 3.13.0a6 and newer. 3.13.0a6 adds a new __static_attributes__ attribute to all classes in Python, which broke some assumptions made by the implementation of typing_extensions.Protocol. Similarly, 3.13.0b1 adds the new __firstlineno__ attribute to all classes.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typing-extensions&package-manager=pip&previous-version=4.11.0&new-version=4.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.in | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.in b/requirements.in index b6dd3c9247a5..ffdaec40a9ff 100644 --- a/requirements.in +++ b/requirements.in @@ -4,7 +4,7 @@ # 2) pip-compile --generate-hashes requirements.in # Unittest test adapter -typing-extensions==4.11.0 +typing-extensions==4.12.0 # Fallback env creator for debian microvenv diff --git a/requirements.txt b/requirements.txt index 62b47c4ce547..533a96996a2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,9 +20,9 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via -r requirements.in -typing-extensions==4.11.0 \ - --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ - --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a +typing-extensions==4.12.0 \ + --hash=sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8 \ + --hash=sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594 # via -r requirements.in zipp==3.15.0 \ --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ From 025d1a67f950bb689be22e8716bb75c79aa2f67b Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 29 May 2024 18:17:44 -0700 Subject: [PATCH 070/106] Bug: Fix python.execInREPL stealing shift+enter (#23526) Stop stealing shift+enter if editor is not focused and language is not python. Resolves: https://github.com/microsoft/vscode-python/issues/23525 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2ef08a3511aa..550ff0965a24 100644 --- a/package.json +++ b/package.json @@ -1118,12 +1118,12 @@ { "command": "python.execInREPL", "key": "shift+enter", - "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'" + "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'&& editorLangId == python && editorTextFocus && !jupyter.ownsSelection" }, { "command": "python.execREPLShiftEnter", "key": "shift+enter", - "when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter" + "when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter && editorLangId == python" }, { "command": "python.execInREPLEnter", From f37762048ed29f8697ce483d5d3d38649333b070 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Thu, 30 May 2024 14:14:39 -0700 Subject: [PATCH 071/106] Bug Fix: REPL reuse and do not overwrite Jupyter's IW title (#23530) Resolves: https://github.com/microsoft/vscode-python/issues/23518 Should resolve issue where opening Python REPL -> opening Jupyter IW -> sending commands to REPL were happening instead of reusing existing Python REPL Also seems to resolve issue where Python REPL was overwriting to Jupyter's IW title. Switching to watch onDidCloseNotebook event as suggested. --- src/client/repl/replCommands.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 1260c78a6a56..7290dc2afe9b 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -54,9 +54,8 @@ function getSendToNativeREPLSetting(): boolean { return configuration.get('REPL.sendToNativeREPL', false); } -window.onDidChangeVisibleTextEditors((editors) => { - const interactiveWindowIsOpen = editors.some((editor) => editor.document.uri.scheme === 'vscode-interactive-input'); - if (!interactiveWindowIsOpen) { +workspace.onDidCloseNotebookDocument((nb) => { + if (notebookDocument && nb.uri.toString() === notebookDocument.uri.toString()) { notebookEditor = undefined; notebookDocument = undefined; } From 3cee060630183b5d7351049960d38a438646b2ec Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 3 Jun 2024 11:36:15 -0700 Subject: [PATCH 072/106] dont override shift+enter behavior (#23546) fix: https://github.com/microsoft/vscode-python/issues/23545 --- package.json | 5 ----- src/client/common/constants.ts | 1 - src/client/extensionActivation.ts | 3 +-- src/client/repl/replCommands.ts | 8 -------- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/package.json b/package.json index 550ff0965a24..c750d06552cc 100644 --- a/package.json +++ b/package.json @@ -1120,11 +1120,6 @@ "key": "shift+enter", "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'&& editorLangId == python && editorTextFocus && !jupyter.ownsSelection" }, - { - "command": "python.execREPLShiftEnter", - "key": "shift+enter", - "when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter && editorLangId == python" - }, { "command": "python.execInREPLEnter", "key": "enter", diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 94bcc78c6c6b..d5b82f68ae97 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -49,7 +49,6 @@ export namespace Commands { export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; export const Exec_In_REPL_Enter = 'python.execInREPLEnter'; - export const Exec_In_REPL_Shift_Enter = 'python.execREPLShiftEnter'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; export const InstallJupyter = 'python.installJupyter'; diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 37d97b8ccbab..1c7e8f384ff1 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -52,7 +52,7 @@ import { initializePersistentStateForTriggers } from './common/persistentState'; import { logAndNotifyOnLegacySettings } from './logging/settingLogs'; import { DebuggerTypeName } from './debugger/constants'; import { StopWatch } from './common/utils/stopWatch'; -import { registerReplCommands, registerReplExecuteOnEnter, registerReplExecuteOnShiftEnter } from './repl/replCommands'; +import { registerReplCommands, registerReplExecuteOnEnter } from './repl/replCommands'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -109,7 +109,6 @@ export function activateFeatures(ext: ExtensionState, _components: Components): registerReplCommands(ext.disposables, interpreterService); registerReplExecuteOnEnter(ext.disposables, interpreterService); - registerReplExecuteOnShiftEnter(ext.disposables); } /// ////////////////////////// diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 7290dc2afe9b..534bf984d7e0 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -205,14 +205,6 @@ export async function registerReplExecuteOnEnter( ); } -export async function registerReplExecuteOnShiftEnter(disposables: Disposable[]): Promise { - disposables.push( - commands.registerCommand(Commands.Exec_In_REPL_Shift_Enter, async () => { - await commands.executeCommand(Commands.Exec_In_REPL_Enter); - }), - ); -} - function isMultiLineText(textEditor: TextEditor | undefined): boolean { return (textEditor?.document?.lineCount ?? 0) > 1; } From 7fca1dadea9aa594ce1e4ead3e1b72e3b3e001e5 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:53:09 -0700 Subject: [PATCH 073/106] bump release 2024.8.0 (#23547) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9a04d630549..81e5be9f446e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "python", - "version": "2024.7.0-dev", + "version": "2024.8.0-rc", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "python", - "version": "2024.7.0-dev", + "version": "2024.8.0-rc", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index c750d06552cc..ec8ef88ccb05 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Python language support with extension access points for IntelliSense (Pylance), Debugging (Python Debugger), linting, formatting, refactoring, unit tests, and more.", - "version": "2024.7.0-dev", + "version": "2024.8.0-rc", "featureFlags": { "usingNewInterpreterStorage": true }, From 6fcbe4ecec1f4302fc35bd2526b2805e90efff35 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:27:51 -0700 Subject: [PATCH 074/106] bump-dev-version-2024.9 (#23548) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81e5be9f446e..2b1398e7ad1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "python", - "version": "2024.8.0-rc", + "version": "2024.9.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "python", - "version": "2024.8.0-rc", + "version": "2024.9.0-dev", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index ec8ef88ccb05..cbd4dbdaa848 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Python language support with extension access points for IntelliSense (Pylance), Debugging (Python Debugger), linting, formatting, refactoring, unit tests, and more.", - "version": "2024.8.0-rc", + "version": "2024.9.0-dev", "featureFlags": { "usingNewInterpreterStorage": true }, From dd8da5721657d9ba1740d6dc1353a38f21f067c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:52:07 -0700 Subject: [PATCH 075/106] Bump typing-extensions from 4.12.0 to 4.12.1 (#23542) Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.12.0 to 4.12.1.
Release notes

Sourced from typing-extensions's releases.

4.12.1

  • Preliminary changes for compatibility with the draft implementation of PEP 649 in Python 3.14. Patch by Jelle Zijlstra.
  • Fix regression in v4.12.0 where nested Annotated types would cause TypeError to be raised if the nested Annotated type had unhashable metadata. Patch by Alex Waygood.
Changelog

Sourced from typing-extensions's changelog.

Release 4.12.1 (June 1, 2024)

  • Preliminary changes for compatibility with the draft implementation of PEP 649 in Python 3.14. Patch by Jelle Zijlstra.
  • Fix regression in v4.12.0 where nested Annotated types would cause TypeError to be raised if the nested Annotated type had unhashable metadata. Patch by Alex Waygood.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typing-extensions&package-manager=pip&previous-version=4.12.0&new-version=4.12.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.in | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.in b/requirements.in index ffdaec40a9ff..f9758dcd6e59 100644 --- a/requirements.in +++ b/requirements.in @@ -4,7 +4,7 @@ # 2) pip-compile --generate-hashes requirements.in # Unittest test adapter -typing-extensions==4.12.0 +typing-extensions==4.12.1 # Fallback env creator for debian microvenv diff --git a/requirements.txt b/requirements.txt index 533a96996a2c..b0ddbb5530d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,9 +20,9 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via -r requirements.in -typing-extensions==4.12.0 \ - --hash=sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8 \ - --hash=sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594 +typing-extensions==4.12.1 \ + --hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \ + --hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1 # via -r requirements.in zipp==3.15.0 \ --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ From 2048b9131ec5f5b40a0436ca1a97b5c1f24baab3 Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Tue, 4 Jun 2024 13:04:48 -0700 Subject: [PATCH 076/106] Support pytest parameterized tests spanning multiple classes when calling the same setup function (#23535) fixes #23527 --- .../.data/test_param_span_class.py | 16 +++ .../expected_discovery_test_output.py | 123 +++++++++++++++++- .../tests/pytestadapter/test_discovery.py | 4 + python_files/vscode_pytest/__init__.py | 3 +- 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 python_files/tests/pytestadapter/.data/test_param_span_class.py diff --git a/python_files/tests/pytestadapter/.data/test_param_span_class.py b/python_files/tests/pytestadapter/.data/test_param_span_class.py new file mode 100644 index 000000000000..a024c438bbf9 --- /dev/null +++ b/python_files/tests/pytestadapter/.data/test_param_span_class.py @@ -0,0 +1,16 @@ +import pytest + + +@pytest.fixture(scope="function", params=[1, 2]) +def setup(request): + return request.param + + +class TestClass1: + def test_method1(self, setup): # test_marker--TestClass1::test_method1 + assert 1 == 1 + + +class TestClass2: + def test_method1(self, setup): # test_marker--TestClass2::test_method1 + assert 2 == 2 diff --git a/python_files/tests/pytestadapter/expected_discovery_test_output.py b/python_files/tests/pytestadapter/expected_discovery_test_output.py index 3ddceeb060ac..723adaabc3e5 100644 --- a/python_files/tests/pytestadapter/expected_discovery_test_output.py +++ b/python_files/tests/pytestadapter/expected_discovery_test_output.py @@ -1273,4 +1273,125 @@ "id_": TEST_DATA_PATH_STR, } -print(param_same_name_expected_output) +test_param_span_class_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "test_param_span_class.py", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "type_": "file", + "id_": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "children": [ + { + "name": "TestClass1", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "type_": "class", + "children": [ + { + "name": "test_method1", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "type_": "function", + "children": [ + { + "name": "[1]", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "lineno": find_test_line_number( + "TestClass1::test_method1", + os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + ), + "type_": "test", + "id_": get_absolute_test_id( + "test_param_span_class.py::TestClass1::test_method1[1]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + "runID": get_absolute_test_id( + "test_param_span_class.py::TestClass1::test_method1[1]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + }, + { + "name": "[2]", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "lineno": find_test_line_number( + "TestClass1::test_method1", + os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + ), + "type_": "test", + "id_": get_absolute_test_id( + "test_param_span_class.py::TestClass1::test_method1[2]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + "runID": get_absolute_test_id( + "test_param_span_class.py::TestClass1::test_method1[2]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + }, + ], + "id_": os.fspath( + TEST_DATA_PATH + / "test_param_span_class.py::TestClass1::test_method1" + ), + } + ], + "id_": "test_param_span_class.py::TestClass1", + }, + { + "name": "TestClass2", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "type_": "class", + "children": [ + { + "name": "test_method1", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "type_": "function", + "children": [ + { + "name": "[1]", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "lineno": find_test_line_number( + "TestClass2::test_method1", + os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + ), + "type_": "test", + "id_": get_absolute_test_id( + "test_param_span_class.py::TestClass2::test_method1[1]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + "runID": get_absolute_test_id( + "test_param_span_class.py::TestClass2::test_method1[1]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + }, + { + "name": "[2]", + "path": os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + "lineno": find_test_line_number( + "TestClass2::test_method1", + os.fspath(TEST_DATA_PATH / "test_param_span_class.py"), + ), + "type_": "test", + "id_": get_absolute_test_id( + "test_param_span_class.py::TestClass2::test_method1[2]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + "runID": get_absolute_test_id( + "test_param_span_class.py::TestClass2::test_method1[2]", + TEST_DATA_PATH / "test_param_span_class.py", + ), + }, + ], + "id_": os.fspath( + TEST_DATA_PATH + / "test_param_span_class.py::TestClass2::test_method1" + ), + } + ], + "id_": "test_param_span_class.py::TestClass2", + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} diff --git a/python_files/tests/pytestadapter/test_discovery.py b/python_files/tests/pytestadapter/test_discovery.py index 24960b91c644..f8c4890658c9 100644 --- a/python_files/tests/pytestadapter/test_discovery.py +++ b/python_files/tests/pytestadapter/test_discovery.py @@ -109,6 +109,10 @@ def test_parameterized_error_collect(): @pytest.mark.parametrize( "file, expected_const", [ + ( + "test_param_span_class.py", + expected_discovery_test_output.test_param_span_class_expected_output, + ), ( "test_multi_class_nest.py", expected_discovery_test_output.nested_classes_expected_test_output, diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 3534bf7c699f..6e2e8b8d17d4 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -467,7 +467,8 @@ def build_test_tree(session: pytest.Session) -> TestNode: function_name, get_node_path(test_case), parent_id ) function_nodes_dict[parent_id] = function_test_node - function_test_node["children"].append(test_node) + if test_node not in function_test_node["children"]: + function_test_node["children"].append(test_node) # Check if the parent node of the function is file, if so create/add to this file node. if isinstance(test_case.parent, pytest.File): try: From 40e45b6e48df8477321523f4ff83aadf36a9007c Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Tue, 4 Jun 2024 13:05:28 -0700 Subject: [PATCH 077/106] bug fix to have load bar show during test discovery (#23537) fixes https://github.com/microsoft/vscode-python/issues/23536 --- src/client/testing/testController/controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 710a6cdce425..b55eaa446018 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -261,7 +261,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (workspace && workspace.uri) { const testAdapter = this.testAdapters.get(workspace.uri); if (testAdapter) { - testAdapter.discoverTests( + await testAdapter.discoverTests( this.testController, this.refreshCancellation.token, this.pythonExecFactory, @@ -282,7 +282,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (workspace && workspace.uri) { const testAdapter = this.testAdapters.get(workspace.uri); if (testAdapter) { - testAdapter.discoverTests( + await testAdapter.discoverTests( this.testController, this.refreshCancellation.token, this.pythonExecFactory, From c2a044e4ddcfb78f72ba4ba0e79a4e61cd058536 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 7 Jun 2024 09:20:57 -0700 Subject: [PATCH 078/106] dont send to REPL from a notebook editor (#23572) fix https://github.com/microsoft/vscode-python/issues/23555 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbd4dbdaa848..d8fdbbeddd8a 100644 --- a/package.json +++ b/package.json @@ -1118,7 +1118,7 @@ { "command": "python.execInREPL", "key": "shift+enter", - "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'&& editorLangId == python && editorTextFocus && !jupyter.ownsSelection" + "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'&& editorLangId == python && editorTextFocus && !jupyter.ownsSelection && !notebookEditorFocused" }, { "command": "python.execInREPLEnter", From 807c7f90f635b9f89f21c3df1a99f1e50ff67375 Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Fri, 7 Jun 2024 11:35:27 -0700 Subject: [PATCH 079/106] Update info-needed-closer.yml (#23574) add write permissions for workflow --- .github/workflows/info-needed-closer.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/info-needed-closer.yml b/.github/workflows/info-needed-closer.yml index 442799cd7a16..270715672d9c 100644 --- a/.github/workflows/info-needed-closer.yml +++ b/.github/workflows/info-needed-closer.yml @@ -6,6 +6,9 @@ on: types: [trigger-needs-more-info] workflow_dispatch: +permissions: + issues: write + jobs: main: runs-on: ubuntu-latest From 41a616f750a4479959a6bff3624a934556fdcec8 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 11 Jun 2024 17:29:01 -0700 Subject: [PATCH 080/106] Remove macos runner (#23598) --- .github/workflows/build.yml | 12 ++++++------ .github/workflows/pr-check.yml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f9e1ac1826f5..76751f03a6c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,12 +68,12 @@ jobs: # - os: ubuntu-latest # target: arm-unknown-linux-gnueabihf # vsix-target: linux-armhf - - os: macos-latest - target: x86_64-apple-darwin - vsix-target: darwin-x64 - - os: macos-14 - target: aarch64-apple-darwin - vsix-target: darwin-arm64 + # - os: macos-latest + # target: x86_64-apple-darwin + # vsix-target: darwin-x64 + # - os: macos-14 + # target: aarch64-apple-darwin + # vsix-target: darwin-arm64 - os: ubuntu-latest target: x86_64-unknown-linux-musl vsix-target: alpine-x64 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 0efd568e0346..24f589295ab8 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -41,12 +41,12 @@ jobs: # - os: ubuntu-latest # target: arm-unknown-linux-gnueabihf # vsix-target: linux-armhf - - os: macos-latest - target: x86_64-apple-darwin - vsix-target: darwin-x64 - - os: macos-14 - target: aarch64-apple-darwin - vsix-target: darwin-arm64 + # - os: macos-latest + # target: x86_64-apple-darwin + # vsix-target: darwin-x64 + # - os: macos-14 + # target: aarch64-apple-darwin + # vsix-target: darwin-arm64 - os: ubuntu-latest target: x86_64-unknown-linux-musl vsix-target: alpine-x64 From 9643f42e077c253f4053f12677d5fe27f99a6eb2 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 11 Jun 2024 18:28:17 -0700 Subject: [PATCH 081/106] Restore computing version in legacy locators (#23596) Fixes https://github.com/microsoft/vscode-python/issues/23580 --- .../base/locators/composite/resolverUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index d1ad91493eab..079ed108ee54 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -147,7 +147,7 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { const { executablePath, kind } = env; const envInfo = buildEnvInfo({ kind, - version: env.version ?? (await getPythonVersionFromPath(executablePath)), + version: await getPythonVersionFromPath(executablePath), executable: executablePath, sysPrefix: env.envPath, location: env.envPath, @@ -243,7 +243,7 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise { } else { executable = await conda.getInterpreterPathForEnvironment({ prefix: envPath }); } - const version = env.version ?? (executable ? await getPythonVersionFromPath(executable) : undefined); + const version = executable ? await getPythonVersionFromPath(executable) : undefined; const info = buildEnvInfo({ executable, kind: PythonEnvKind.Conda, From 901cbe32ba42e26218c187336ae2b516cc45fad9 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 11 Jun 2024 21:06:11 -0700 Subject: [PATCH 082/106] Fix smoke tests (#23597) --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76751f03a6c5..eed629895ee1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -377,7 +377,12 @@ jobs: matrix: # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] + include: + - os: windows-latest + vsix-target: win32-x64 + - os: ubuntu-latest + vsix-target: linux-x64 + steps: - name: Checkout uses: actions/checkout@v4 @@ -386,4 +391,4 @@ jobs: uses: ./.github/actions/smoke-tests with: node_version: ${{ env.NODE_VERSION }} - artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }} + artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }} From 83c69a0926ecd9ebcaeda70628c701d1e7991fee Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 11 Jun 2024 22:31:54 -0700 Subject: [PATCH 083/106] Audit fix and some clean up (#23599) --- package-lock.json | 141 ++++++++++-------- .../tests/unittestadapter/test_execution.py | 6 +- python_files/unittestadapter/pvsc_utils.py | 9 +- requirements.txt | 12 +- 4 files changed, 91 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b1398e7ad1b..6bdf4dc759c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -321,9 +321,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@azure/identity": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.0.tgz", - "integrity": "sha512-ve3aYv79qXOJ8wRxQ5jO0eIz2DZ4o0TyME4m4vlGV5YyePddVZ+pFMzusAMODNAflYAAv1cBIhKnd4xytmXyig==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.1.tgz", + "integrity": "sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q==", "dev": true, "dependencies": { "@azure/abort-controller": "^1.0.0", @@ -334,7 +334,7 @@ "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^3.11.1", - "@azure/msal-node": "^2.6.6", + "@azure/msal-node": "^2.9.2", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", @@ -414,12 +414,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.8.1.tgz", - "integrity": "sha512-VcZZM+5VvCWRBTOF7SxMKaxrz+EXjntx2u5AQe7QE06e6FuPJElGBrImgNgCh5QmFaNCfVFO+3qNR7UoFD/Gfw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", + "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", "dev": true, "dependencies": { - "@azure/msal-common": "14.10.0", + "@azure/msal-common": "14.12.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -427,6 +427,15 @@ "node": ">=16" } }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "14.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.12.0.tgz", + "integrity": "sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@azure/opentelemetry-instrumentation-azure-sdk": { "version": "1.0.0-beta.5", "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.5.tgz", @@ -3985,21 +3994,21 @@ } }, "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6671,21 +6680,21 @@ } }, "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -9823,7 +9832,7 @@ "node_modules/matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", "dev": true, "dependencies": { "findup-sync": "^2.0.0", @@ -9838,7 +9847,7 @@ "node_modules/matchdep/node_modules/findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", "dev": true, "dependencies": { "detect-file": "^1.0.0", @@ -14142,12 +14151,12 @@ } }, "node_modules/ts-loader/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -14188,9 +14197,9 @@ "dev": true }, "node_modules/ts-loader/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -16007,9 +16016,9 @@ } }, "@azure/identity": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.0.tgz", - "integrity": "sha512-ve3aYv79qXOJ8wRxQ5jO0eIz2DZ4o0TyME4m4vlGV5YyePddVZ+pFMzusAMODNAflYAAv1cBIhKnd4xytmXyig==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.1.tgz", + "integrity": "sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q==", "dev": true, "requires": { "@azure/abort-controller": "^1.0.0", @@ -16020,7 +16029,7 @@ "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^3.11.1", - "@azure/msal-node": "^2.6.6", + "@azure/msal-node": "^2.9.2", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", @@ -16088,14 +16097,22 @@ "dev": true }, "@azure/msal-node": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.8.1.tgz", - "integrity": "sha512-VcZZM+5VvCWRBTOF7SxMKaxrz+EXjntx2u5AQe7QE06e6FuPJElGBrImgNgCh5QmFaNCfVFO+3qNR7UoFD/Gfw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", + "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", "dev": true, "requires": { - "@azure/msal-common": "14.10.0", + "@azure/msal-common": "14.12.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "14.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.12.0.tgz", + "integrity": "sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==", + "dev": true + } } }, "@azure/opentelemetry-instrumentation-azure-sdk": { @@ -18911,18 +18928,18 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -21066,18 +21083,18 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -23525,7 +23542,7 @@ "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", "dev": true, "requires": { "findup-sync": "^2.0.0", @@ -23537,7 +23554,7 @@ "findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", "dev": true, "requires": { "detect-file": "^1.0.0", @@ -26880,12 +26897,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -26914,9 +26931,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" diff --git a/python_files/tests/unittestadapter/test_execution.py b/python_files/tests/unittestadapter/test_execution.py index 3d43eb4c4b23..44610d5bf6fa 100644 --- a/python_files/tests/unittestadapter/test_execution.py +++ b/python_files/tests/unittestadapter/test_execution.py @@ -5,14 +5,14 @@ import pathlib import sys from unittest.mock import patch -from typing import Dict +from typing import Dict, Optional import pytest script_dir = pathlib.Path(__file__).parent.parent.parent sys.path.insert(0, os.fspath(script_dir / "lib" / "python")) -from unittestadapter.pvsc_utils import ExecutionPayloadDict, TestResultTypeAlias # noqa: E402 +from unittestadapter.pvsc_utils import ExecutionPayloadDict # noqa: E402 from unittestadapter.execution import run_tests # noqa: E402 TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data" @@ -64,7 +64,7 @@ def test_single_ids_run(mock_send_run_data): test_actual = args[0] # first argument is the result assert test_actual - actual_result: TestResultTypeAlias | None = actual["result"] + actual_result: Optional[Dict[str, Dict[str, Optional[str]]]] = actual["result"] if actual_result is None: raise AssertionError("actual_result is None") else: diff --git a/python_files/unittestadapter/pvsc_utils.py b/python_files/unittestadapter/pvsc_utils.py index a039af43c47b..2eba987603c0 100644 --- a/python_files/unittestadapter/pvsc_utils.py +++ b/python_files/unittestadapter/pvsc_utils.py @@ -10,7 +10,7 @@ import pathlib import sys import unittest -from typing import List, Optional, Tuple, Union, Dict +from typing import List, Optional, Tuple, Union, Dict, Literal, TypedDict script_dir = pathlib.Path(__file__).parent.parent @@ -18,7 +18,7 @@ sys.path.append(os.fspath(script_dir / "lib" / "python")) from testing_tools import socket_manager # noqa: E402 -from typing_extensions import Literal, NotRequired, TypeAlias, TypedDict # noqa: E402 +from typing_extensions import NotRequired # noqa: E402 # Types @@ -52,9 +52,6 @@ class TestExecutionStatus(str, enum.Enum): success = "success" -TestResultTypeAlias: TypeAlias = Dict[str, Dict[str, Union[str, None]]] - - class VSCodeUnittestError(Exception): """A custom exception class for unittest errors.""" @@ -72,7 +69,7 @@ class DiscoveryPayloadDict(TypedDict): class ExecutionPayloadDict(TypedDict): cwd: str status: TestExecutionStatus - result: Optional[TestResultTypeAlias] + result: Optional[Dict[str, Dict[str, Optional[str]]]] not_found: NotRequired[List[str]] error: NotRequired[str] diff --git a/requirements.txt b/requirements.txt index b0ddbb5530d0..7feb18e38c2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,9 +12,9 @@ microvenv==2023.5.post1 \ --hash=sha256:32c46afea874e300f69f1add0806eb0795fd02b5fb251092fba0b73c059a7d1f \ --hash=sha256:fd79b3dfea7860e2e84c87dd0aa8a135075f7fa2284174842b7bdeb077a0d8ac # via -r requirements.in -packaging==24.0 \ - --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ - --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via -r requirements.in tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ @@ -24,7 +24,7 @@ typing-extensions==4.12.1 \ --hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \ --hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1 # via -r requirements.in -zipp==3.15.0 \ - --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ - --hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556 +zipp==3.19.2 \ + --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ + --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c # via importlib-metadata From 66b8c0b4bf86cd5773af2333d7d7c5ea4a6df68c Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:27:30 -0700 Subject: [PATCH 084/106] Add missing telemetry for REPL (#23591) add missing telemetry for REPL that should have been attached Resolves: https://github.com/microsoft/vscode-python/issues/23590 --- src/client/repl/pythonServer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 766fcf4cc92c..e125cf5e8d28 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -4,6 +4,8 @@ import * as rpc from 'vscode-jsonrpc/node'; import { Disposable, window } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; +import { captureTelemetry } from '../telemetry'; +import { EventName } from '../telemetry/constants'; const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); let serverInstance: PythonServer | undefined; @@ -49,6 +51,7 @@ class PythonServerImpl implements Disposable { }); } + @captureTelemetry(EventName.EXECUTION_CODE, { scope: 'selection' }, false) public execute(code: string): Promise { return this.connection.sendRequest('execute', code); } From fdc3cbad3c912a16f7b9185558f0152ffaf34a00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:03:04 +0000 Subject: [PATCH 085/106] Bump typing-extensions from 4.12.1 to 4.12.2 (#23583) Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.12.1 to 4.12.2.
Release notes

Sourced from typing-extensions's releases.

4.12.2

  • Fix regression in v4.12.0 where specialization of certain generics with an overridden __eq__ method would raise errors. Patch by Jelle Zijlstra.
  • Fix tests so they pass on 3.13.0b2
Changelog

Sourced from typing-extensions's changelog.

Release 4.12.2 (June 7, 2024)

  • Add typing_extensions.get_annotations, a backport of inspect.get_annotations that adds features specified by PEP 649. Patch by Jelle Zijlstra.
  • Fix regression in v4.12.0 where specialization of certain generics with an overridden __eq__ method would raise errors. Patch by Jelle Zijlstra.
  • Fix tests so they pass on 3.13.0b2
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typing-extensions&package-manager=pip&previous-version=4.12.1&new-version=4.12.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.in | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.in b/requirements.in index f9758dcd6e59..9a490ea1b599 100644 --- a/requirements.in +++ b/requirements.in @@ -4,7 +4,7 @@ # 2) pip-compile --generate-hashes requirements.in # Unittest test adapter -typing-extensions==4.12.1 +typing-extensions==4.12.2 # Fallback env creator for debian microvenv diff --git a/requirements.txt b/requirements.txt index 7feb18e38c2d..648011e62630 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,9 +20,9 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via -r requirements.in -typing-extensions==4.12.1 \ - --hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \ - --hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1 +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via -r requirements.in zipp==3.19.2 \ --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ From 7e562f38fc9804d465e74f0a2774d797e06bf8cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:04:16 -0700 Subject: [PATCH 086/106] Bump braces and gulp (#23589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [braces](https://github.com/micromatch/braces) to 3.0.3 and updates ancestor dependency [gulp](https://github.com/gulpjs/gulp). These dependencies need to be updated together. Updates `braces` from 3.0.2 to 3.0.3
Commits

Updates `gulp` from 4.0.2 to 5.0.0
Release notes

Sourced from gulp's releases.

gulp v5.0.0

We've tried to provide a high-level changelog for gulp v5 below, but it doesn't contain all changes from the 60+ dependencies that we maintain.

Please see individual changelogs to drill down into all changes that were made.

âš  BREAKING CHANGES

  • Drop support for Node.js <10.13
  • Default stream encoding to UTF-8
  • Standardized on anymatch library for globbing paths. All globs should work the same between src and watch now!
  • Removed support for ordered globs. This aligns with the chokidar globbing implementation. If you need your globs to be ordered, you can use ordered-read-stream
  • All globs and paths are normalized to unix-like filepaths
  • Only allow JS variants for .gulp.* config files
  • Removed support for alpha releases of v4 from gulp-cli
  • Removed the --verify flag
  • Renamed the --require flag to --preload to avoid conflicting with Node.js flags
  • Removed many legacy and deprecated loaders
  • Upgrade to chokidar v3
  • Clone Vinyl objects with stream contents using teex, but no longer wait for all streams to flow before cloned streams will receive data
  • Stop using process.umask() to make directories, instead falling back to Node's default mode
  • Throw on non-function, non-string option coercers
  • Drop support of Node.js snake_case flags
  • Use a Symbol for attaching the gulplog namespace to the store
  • Use a Symbol for attaching the gulplog store to the global
  • Use sha256 to hash the v8flags cache into a filename

Features

  • Streamlined the dependency tree
  • Switch all streams implementation to Streamx
  • Rewrote glob-stream to use a custom directory walk that relies on newer Node.js features and is more performant than old implementation
  • Implement translation support for all CLI messages and all messages passing through gulplog
  • Allow users to customize or remove the timestamp from their logs
  • Upgraded gulplog to v2. Messages logged via v1 will also display a deprecated warning. Plugins should update to v2 as the community upgrades to gulp 5
  • Added support for gulpile.cjs and gulpfile.mjs
  • Add support for swc, esbuild, sucrase, and mdx loaders
  • Provide an ESM export (#2760) (b00de68)
  • Support sourcemap handling on streaming Vinyl contents
  • Support extends syntax for .gulp.* config file
  • Allow overriding gulpfile and preloads via .gulp.* config file

Bug Fixes

  • Resolve bugs related to symlinks on various platforms
  • Resolved some reported ReDoS CVEs and improved performance in glob-parent
  • Rework errors surfaced when encountering files or symlinks when trying to create directories
  • Ensure watch allows japanese characters in globs (72668c6)

... (truncated)

Changelog

Sourced from gulp's changelog.

5.0.0 (2024-03-29)

We've tried to provide a high-level changelog for gulp v5 below, but it doesn't contain all changes from the 60+ dependencies that we maintain.

Please see individual changelogs to drill down into all changes that were made.

âš  BREAKING CHANGES

  • Drop support for Node.js <10.13
  • Default stream encoding to UTF-8
  • Standardized on anymatch library for globbing paths. All globs should work the same between src and watch now!
  • Removed support for ordered globs. This aligns with the chokidar globbing implementation. If you need your globs to be ordered, you can use ordered-read-stream
  • All globs and paths are normalized to unix-like filepaths
  • Only allow JS variants for .gulp.* config files
  • Removed support for alpha releases of v4 from gulp-cli
  • Removed the --verify flag
  • Renamed the --require flag to --preload to avoid conflicting with Node.js flags
  • Removed many legacy and deprecated loaders
  • Upgrade to chokidar v3
  • Clone Vinyl objects with stream contents using teex, but no longer wait for all streams to flow before cloned streams will receive data
  • Stop using process.umask() to make directories, instead falling back to Node's default mode
  • Throw on non-function, non-string option coercers
  • Drop support of Node.js snake_case flags
  • Use a Symbol for attaching the gulplog namespace to the store
  • Use a Symbol for attaching the gulplog store to the global
  • Use sha256 to hash the v8flags cache into a filename

Features

  • Streamlined the dependency tree
  • Switch all streams implementation to Streamx
  • Rewrote glob-stream to use a custom directory walk that relies on newer Node.js features and is more performant than old implementation
  • Implement translation support for all CLI messages and all messages passing through gulplog
  • Allow users to customize or remove the timestamp from their logs
  • Upgraded gulplog to v2. Messages logged via v1 will also display a deprecated warning. Plugins should update to v2 as the community upgrades to gulp 5
  • Added support for gulpile.cjs and gulpfile.mjs
  • Add support for swc, esbuild, sucrase, and mdx loaders
  • Provide an ESM export (#2760) (b00de68)
  • Support sourcemap handling on streaming Vinyl contents
  • Support extends syntax for .gulp.* config file
  • Allow overriding gulpfile and preloads via .gulp.* config file

Bug Fixes

  • Resolve bugs related to symlinks on various platforms
  • Resolved some reported ReDoS CVEs and improved performance in glob-parent
  • Rework errors surfaced when encountering files or symlinks when trying to create directories
  • Ensure watch allows japanese characters in globs (72668c6)

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/microsoft/vscode-python/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6301 +++++++++++---------------------------------- package.json | 2 +- 2 files changed, 1447 insertions(+), 4856 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6bdf4dc759c9..cd0d52f5e65a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,7 @@ "expose-loader": "^3.1.0", "flat": "^5.0.2", "get-port": "^5.1.1", - "gulp": "^4.0.0", + "gulp": "^5.0.0", "gulp-typescript": "^5.0.0", "mocha": "^9.2.2", "mocha-junit-reporter": "^2.0.2", @@ -968,6 +968,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -2453,18 +2474,6 @@ "node": ">=0.10.0" } }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2496,25 +2505,16 @@ } }, "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/append-buffer": { @@ -2651,39 +2651,6 @@ "node": ">=0.10.0" } }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -2696,7 +2663,7 @@ "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2721,49 +2688,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", @@ -2773,29 +2697,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2805,15 +2706,6 @@ "node": ">=8" } }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -2925,26 +2817,19 @@ "dev": true }, "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", "dev": true, "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, "node_modules/async-hook-jl": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", @@ -2977,15 +2862,15 @@ } }, "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", "dev": true, "dependencies": { - "async-done": "^1.2.2" + "async-done": "^2.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/asynckit": { @@ -2993,18 +2878,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -3042,6 +2915,12 @@ "typed-rest-client": "^1.8.4" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -3067,97 +2946,42 @@ "dev": true }, "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", "dev": true, "dependencies": { - "is-descriptor": "^1.0.0" + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "node_modules/bach/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "once": "^1.4.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" } }, - "node_modules/base/node_modules/is-data-descriptor": { + "node_modules/balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/bare-events": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.3.1.tgz", + "integrity": "sha512-sJnSOTVESURZ61XgEleqmP255T6zTYwHPwE4r6SssIh0U9/uDvfpdoJYpVUerJJZH2fueO+CdT8ZT+OC/7aZDA==", "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } + "optional": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -3220,16 +3044,6 @@ "node": ">=8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bl": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", @@ -3262,36 +3076,15 @@ } }, "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/brorand": { @@ -3545,26 +3338,6 @@ "integrity": "sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==", "dev": true }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cacheable-request": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", @@ -3980,103 +3753,45 @@ "fsevents": "~2.3.2" } }, - "node_modules/chokidar/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 8" + "node": ">= 6" } }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "tslib": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=6.0" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chokidar/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/chokidar/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "optional": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "node_modules/circular-json": { @@ -4091,33 +3806,6 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4128,35 +3816,14 @@ } }, "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "node_modules/clone": { @@ -4247,54 +3914,6 @@ "node": ">=16" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map/node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4310,15 +3929,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", @@ -4354,32 +3964,11 @@ "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", "dev": true }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -4425,23 +4014,17 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", "dev": true, "dependencies": { - "each-props": "^1.3.2", + "each-props": "^3.0.0", "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, "node_modules/copy-props/node_modules/is-plain-object": { @@ -4613,16 +4196,6 @@ "node": "*" } }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -4868,27 +4441,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-compare/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -4910,15 +4462,6 @@ "node": ">=8" } }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -4957,57 +4500,6 @@ "node": ">= 0.4" } }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", @@ -5051,7 +4543,7 @@ "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5228,13 +4720,25 @@ } }, "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", "dev": true, "dependencies": { - "is-plain-object": "^2.0.1", + "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/each-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/ecdsa-sig-formatter": { @@ -5297,9 +4801,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "dependencies": { "once": "^1.4.0" @@ -5360,15 +4864,6 @@ "node": ">=4" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", @@ -5447,67 +4942,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", "dev": true }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6183,27 +5629,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esniff/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -6290,16 +5715,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -6410,48 +5825,6 @@ "node": ">=8" } }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -6465,7 +5838,7 @@ "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, "dependencies": { "homedir-polyfill": "^1.0.1" @@ -6490,15 +5863,6 @@ "webpack": "^5.0.0" } }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, - "dependencies": { - "type": "^2.5.0" - } - }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -6524,12 +5888,6 @@ "node": ">=4" } }, - "node_modules/ext/node_modules/type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", - "dev": true - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6561,108 +5919,18 @@ "node": ">=0.10.0" } }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -6679,30 +5947,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -6715,40 +5959,6 @@ "node": ">= 6" } }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -6811,13 +6021,6 @@ "node": ">=6" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "node_modules/filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", @@ -6842,30 +6045,15 @@ } }, "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/filter-obj": { @@ -6917,43 +6105,52 @@ } }, "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", "dev": true, "dependencies": { "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", "resolve-dir": "^1.0.1" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", "dev": true, "dependencies": { "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", + "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + } + }, + "node_modules/fined/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/flat": { @@ -6997,12 +6194,24 @@ "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -7079,18 +6288,6 @@ "node": ">= 6" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -7210,10 +6407,13 @@ } }, "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, "node_modules/get-func-name": { "version": "2.0.2", @@ -7289,15 +6489,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -7374,129 +6565,47 @@ "dev": true }, "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", "dev": true, "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" + "async-done": "^2.0.0", + "chokidar": "^3.5.3" }, "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-watcher/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" } }, - "node_modules/glob-watcher/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dev": true, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "brace-expansion": "^1.1.7" }, - "optionalDependencies": { - "fsevents": "^1.2.7" + "engines": { + "node": "*" } }, - "node_modules/glob-watcher/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" }, "engines": { - "node": ">= 4.0" - } - }, - "node_modules/glob-watcher/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" } }, "node_modules/global-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, "dependencies": { "expand-tilde": "^2.0.2", @@ -7551,15 +6660,15 @@ } }, "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", "dev": true, "dependencies": { - "sparkles": "^1.0.0" + "sparkles": "^2.1.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/gopd": { @@ -7612,9 +6721,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graceful-readlink": { "version": "1.0.1", @@ -7632,99 +6741,144 @@ } }, "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", + "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", "dev": true, "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.0.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.0" }, "bin": { "gulp": "bin/gulp.js" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", + "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", + "dev": true, + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.0", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" }, "bin": { "gulp": "bin/gulp.js" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, - "node_modules/gulp-cli/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "node_modules/gulp-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/gulp-cli/node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "node_modules/gulp-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/gulp-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/gulp-cli/node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "node_modules/gulp-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/gulp-cli/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "node_modules/gulp-cli/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-cli/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, "node_modules/gulp-typescript": { @@ -7775,16 +6929,187 @@ "readable-stream": "2 || 3" } }, + "node_modules/gulp/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/gulp/node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", + "dev": true, + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gulp/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp/node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gulp/node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gulp/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", "dev": true, "dependencies": { - "glogg": "^1.0.0" + "glogg": "^2.2.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/gzip-size": { @@ -7903,45 +7228,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hash-base": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", @@ -8030,12 +7316,6 @@ "node": ">=0.10.0" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8276,12 +7556,12 @@ } }, "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/into-stream": { @@ -8302,15 +7582,6 @@ "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.5.tgz", "integrity": "sha512-60QsfPz8NAU/GZqXu8hJ+BhNf/C/c+Hp0eDc6XMIJTxBiP36AQyyQKpBkOVTLWBFDQWYVHpbbEuIsHu9dLuJDA==" }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -8318,31 +7589,7 @@ "dev": true, "dependencies": { "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" + "is-windows": "^1.0.1" }, "engines": { "node": ">=0.10.0" @@ -8364,12 +7611,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "node_modules/is-bigint": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", @@ -8420,30 +7661,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -8459,29 +7676,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -8497,15 +7691,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8516,15 +7701,12 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-generator-function": { @@ -8598,27 +7780,12 @@ } }, "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" } }, "node_modules/is-object": { @@ -9269,12 +8436,6 @@ "setimmediate": "^1.0.5" } }, - "node_modules/just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -9348,16 +8509,12 @@ } }, "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", "dev": true, - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/lazystream": { @@ -9372,18 +8529,6 @@ "node": ">= 0.6.3" } }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lead": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", @@ -9428,22 +8573,30 @@ } }, "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.0.tgz", + "integrity": "sha512-a5BQjbCHnB+cy+gsro8lXJ4kZluzOijzJ1UVVfyJYZC+IP2pLv1h4+aysQeKuTmyO8NAqfyQAk4HWaP/HjcKTg==", "dev": true, "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10.13.0" + } + }, + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/linkify-it": { @@ -9455,31 +8608,6 @@ "uc.micro": "^1.0.1" } }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -9774,35 +8902,11 @@ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, "engines": { "node": ">=0.10.0" } @@ -9829,48 +8933,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -9914,27 +8976,16 @@ } }, "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" } }, "node_modules/miller-rabin": { @@ -10035,31 +9086,6 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -10180,56 +9206,12 @@ "node": ">=6" } }, - "node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/mocha/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/mocha/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/mocha/node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -10290,15 +9272,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10308,15 +9281,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/mocha/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -10405,24 +9369,10 @@ } }, "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" } @@ -10442,23 +9392,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/mocha/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -10506,12 +9439,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/mute-stream": { @@ -10525,41 +9458,12 @@ "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" }, - "node_modules/nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, "node_modules/nanoid": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==", "dev": true }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -10579,12 +9483,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -10883,27 +9781,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -10984,15 +9861,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -11055,44 +9923,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -11135,18 +9965,6 @@ "node": ">= 0.4" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -11168,7 +9986,7 @@ "node_modules/object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", "dev": true, "dependencies": { "array-each": "^1.0.1", @@ -11180,18 +9998,6 @@ "node": ">=0.10.0" } }, - "node_modules/object.defaults/node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.entries": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", @@ -11236,35 +10042,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map/node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", "dev": true, "dependencies": { "isobject": "^3.0.1" @@ -11273,31 +10054,6 @@ "node": ">=0.10.0" } }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce/node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -11396,18 +10152,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -11576,7 +10320,7 @@ "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", "dev": true, "dependencies": { "is-absolute": "^1.0.0", @@ -11587,31 +10331,10 @@ "node": ">=0.8" } }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11650,15 +10373,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", @@ -11705,7 +10419,7 @@ "node_modules/path-root": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", "dev": true, "dependencies": { "path-root-regex": "^0.1.0" @@ -11717,7 +10431,7 @@ "node_modules/path-root-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11853,15 +10567,6 @@ "node": ">= 0.10" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/postinstall-build": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz", @@ -11940,15 +10645,6 @@ "node": ">=10.13.0" } }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -12099,6 +10795,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12162,81 +10864,6 @@ "node": ">=0.8" } }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -12274,15 +10901,15 @@ } }, "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "dependencies": { - "resolve": "^1.1.6" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/reflect-metadata": { @@ -12296,19 +10923,6 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "dev": true }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", @@ -12382,24 +10996,6 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -12410,17 +11006,12 @@ } }, "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/require-directory": { @@ -12470,12 +11061,6 @@ } } }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -12507,7 +11092,7 @@ "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", "dev": true, "dependencies": { "expand-tilde": "^2.0.0", @@ -12538,13 +11123,6 @@ "node": ">= 0.10" } }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, "node_modules/responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -12554,15 +11132,6 @@ "lowercase-keys": "^1.0.0" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -12658,15 +11227,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -12735,15 +11295,15 @@ } }, "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", "dev": true, "dependencies": { - "sver-compat": "^1.5.0" + "sver": "^1.8.3" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/serialize-javascript": { @@ -12778,33 +11338,6 @@ "node": ">= 0.4" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -13091,155 +11624,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -13273,20 +11657,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "dependencies": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -13297,20 +11667,13 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/spawn-wrap": { @@ -13330,50 +11693,6 @@ "node": ">=8" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -13393,31 +11712,6 @@ "node": "*" } }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -13438,6 +11732,15 @@ "readable-stream": "^2.0.2" } }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "dependencies": { + "streamx": "^2.13.2" + } + }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -13463,6 +11766,20 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -13502,38 +11819,17 @@ ] }, "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/string.prototype.matchall": { @@ -13593,18 +11889,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -13675,14 +11959,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", "dev": true, - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/sver/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" } }, "node_modules/table": { @@ -13710,20 +12003,11 @@ "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/table/node_modules/json-schema-traverse": { @@ -13732,20 +12016,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -13846,6 +12116,15 @@ "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/terser": { "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", @@ -13924,6 +12203,15 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13956,15 +12244,6 @@ "xtend": "~4.0.0" } }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -14031,56 +12310,16 @@ "node": ">=4" } }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0" } }, "node_modules/to-through": { @@ -14150,18 +12389,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ts-loader/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-loader/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -14196,18 +12423,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/ts-loader/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-loader/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -14217,28 +12432,6 @@ "node": ">=8" } }, - "node_modules/ts-loader/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/ts-loader/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/ts-loader/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14251,18 +12444,6 @@ "node": ">=8" } }, - "node_modules/ts-loader/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-mockito": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", @@ -14477,12 +12658,6 @@ "node": "*" } }, - "node_modules/type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14524,12 +12699,6 @@ "underscore": "^1.12.1" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -14619,32 +12788,36 @@ "dev": true }, "node_modules/undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dev": true, + "dependencies": { + "fastest-levenshtein": "^1.0.7" } }, "node_modules/unicode": { @@ -14655,21 +12828,6 @@ "node": ">= 0.8.x" } }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -14680,54 +12838,6 @@ "through2-filter": "^3.0.0" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -14736,16 +12846,6 @@ "node": ">=8" } }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -14785,13 +12885,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, "node_modules/url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -14835,15 +12928,6 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -14886,51 +12970,125 @@ "dev": true }, "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, "dependencies": { - "homedir-polyfill": "^1.0.1" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/vinyl-contents/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10" } }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "dev": true, "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", + "clone": "^2.1.2", "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/vinyl-fs": { @@ -15414,12 +13572,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "node_modules/which-typed-array": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", @@ -15493,39 +13645,55 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -15734,44 +13902,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/yargs/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -16520,6 +14656,21 @@ } } }, + "@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "dev": true + }, + "@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "requires": { + "is-negated-glob": "^1.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -17750,15 +15901,6 @@ "ansi-wrap": "^0.1.0" } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -17781,24 +15923,13 @@ "dev": true }, "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, "append-buffer": { @@ -17896,30 +16027,6 @@ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -17929,7 +16036,7 @@ "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", "dev": true }, "array-includes": { @@ -17945,78 +16052,18 @@ "is-string": "^1.0.7" } }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -18109,23 +16156,16 @@ "dev": true }, "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" } }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, "async-hook-jl": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", @@ -18151,12 +16191,12 @@ } }, "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", "dev": true, "requires": { - "async-done": "^1.2.2" + "async-done": "^2.0.0" } }, "asynckit": { @@ -18164,12 +16204,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -18198,6 +16232,12 @@ "typed-rest-client": "^1.8.4" } }, + "b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -18223,20 +16263,25 @@ } }, "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", "dev": true, "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "dependencies": { + "now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + } } }, "balanced-match": { @@ -18244,60 +16289,12 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "bare-events": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.3.1.tgz", + "integrity": "sha512-sJnSOTVESURZ61XgEleqmP255T6zTYwHPwE4r6SssIh0U9/uDvfpdoJYpVUerJJZH2fueO+CdT8ZT+OC/7aZDA==", "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } + "optional": true }, "base64-js": { "version": "1.5.1", @@ -18336,16 +16333,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bl": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", @@ -18378,32 +16365,12 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "fill-range": "^7.1.1" } }, "brorand": { @@ -18604,23 +16571,6 @@ "integrity": "sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==", "dev": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, "cacheable-request": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", @@ -18915,36 +16865,8 @@ "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, + }, + "dependencies": { "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -18953,21 +16875,6 @@ "requires": { "is-glob": "^4.0.1" } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, @@ -19008,29 +16915,6 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -19038,31 +16922,14 @@ "dev": true }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "clone": { @@ -19137,44 +17004,6 @@ "integrity": "sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true - }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -19190,12 +17019,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, "colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", @@ -19228,29 +17051,11 @@ "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", "dev": true }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -19293,19 +17098,13 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", "dev": true, "requires": { - "each-props": "^1.3.2", + "each-props": "^3.0.0", "is-plain-object": "^5.0.0" }, "dependencies": { @@ -19452,16 +17251,6 @@ "randomfill": "^1.0.3" } }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -19664,23 +17453,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "requires": { - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -19698,12 +17470,6 @@ } } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true - }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -19730,47 +17496,6 @@ "object-keys": "^1.0.12" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", @@ -19805,7 +17530,7 @@ "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true }, "detect-libc": { @@ -19951,13 +17676,21 @@ } }, "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", "dev": true, "requires": { - "is-plain-object": "^2.0.1", + "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } } }, "ecdsa-sig-formatter": { @@ -20019,9 +17752,9 @@ "dev": true }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -20066,15 +17799,6 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, "es-abstract": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", @@ -20135,63 +17859,18 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - } - }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", "dev": true }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -20691,26 +18370,6 @@ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, - "esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } - } - }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -20776,16 +18435,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -20865,41 +18514,6 @@ } } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -20910,7 +18524,7 @@ "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, "requires": { "homedir-polyfill": "^1.0.1" @@ -20923,23 +18537,6 @@ "dev": true, "requires": {} }, - "ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, - "requires": { - "type": "^2.5.0" - }, - "dependencies": { - "type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", - "dev": true - } - } - }, "ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -20986,89 +18583,18 @@ } } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -21081,57 +18607,14 @@ "merge2": "^1.3.0", "micromatch": "^4.0.4" }, - "dependencies": { - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-glob": "^4.0.1" } } } @@ -21192,13 +18675,6 @@ "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", "dev": true }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", @@ -21217,26 +18693,12 @@ } }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "filter-obj": { @@ -21275,34 +18737,42 @@ } }, "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", "dev": true, "requires": { "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", "resolve-dir": "^1.0.1" } }, "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", "dev": true, "requires": { "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", + "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } } }, "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", "dev": true }, "flat": { @@ -21340,9 +18810,18 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -21403,15 +18882,6 @@ "mime-types": "^2.1.12" } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -21509,9 +18979,9 @@ "dev": true }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-func-name": { @@ -21561,12 +19031,6 @@ "get-intrinsic": "^1.1.1" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -21643,77 +19107,13 @@ "dev": true }, "glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - } + "async-done": "^2.0.0", + "chokidar": "^3.5.3" } }, "global-modules": { @@ -21730,7 +19130,7 @@ "global-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, "requires": { "expand-tilde": "^2.0.2", @@ -21772,12 +19172,12 @@ } }, "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", "dev": true, "requires": { - "sparkles": "^1.0.0" + "sparkles": "^2.1.0" } }, "gopd": { @@ -21823,9 +19223,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "graceful-readlink": { "version": "1.0.1", @@ -21840,84 +19240,242 @@ "dev": true }, "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", + "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", "dev": true, "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.0.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", + "dev": true, + "requires": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + } + }, + "lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true + }, + "now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true + }, + "resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "requires": { + "value-or-function": "^4.0.0" + } + }, + "to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "requires": { + "streamx": "^2.12.5" + } + }, + "value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + }, + "vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" + } + }, + "vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "requires": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + } + } } }, "gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", + "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", + "dev": true, + "requires": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.0", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" }, "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" + "has-flag": "^4.0.0" } }, - "yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } } } @@ -21961,12 +19519,12 @@ } }, "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", "dev": true, "requires": { - "glogg": "^1.0.0" + "glogg": "^2.2.0" } }, "gzip-size": { @@ -22043,38 +19601,6 @@ "has-symbols": "^1.0.2" } }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "hash-base": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", @@ -22147,12 +19673,6 @@ "parse-passwd": "^1.0.0" } }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -22327,9 +19847,9 @@ } }, "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, "into-stream": { @@ -22344,14 +19864,8 @@ }, "inversify": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.5.tgz", - "integrity": "sha512-60QsfPz8NAU/GZqXu8hJ+BhNf/C/c+Hp0eDc6XMIJTxBiP36AQyyQKpBkOVTLWBFDQWYVHpbbEuIsHu9dLuJDA==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.5.tgz", + "integrity": "sha512-60QsfPz8NAU/GZqXu8hJ+BhNf/C/c+Hp0eDc6XMIJTxBiP36AQyyQKpBkOVTLWBFDQWYVHpbbEuIsHu9dLuJDA==" }, "is-absolute": { "version": "1.0.0", @@ -22363,26 +19877,6 @@ "is-windows": "^1.0.1" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -22393,12 +19887,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "is-bigint": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", @@ -22434,26 +19922,6 @@ "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -22463,37 +19931,12 @@ "has-tostringtag": "^1.0.0" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -22501,13 +19944,10 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-generator-function": { "version": "1.0.10", @@ -22556,24 +19996,10 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-object": { "version": "1.0.1", @@ -23068,12 +20494,6 @@ "setimmediate": "^1.0.5" } }, - "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -23143,14 +20563,10 @@ } }, "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "dev": true }, "lazystream": { "version": "1.0.0", @@ -23161,15 +20577,6 @@ "readable-stream": "^2.0.5" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "lead": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", @@ -23205,19 +20612,26 @@ } }, "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.0.tgz", + "integrity": "sha512-a5BQjbCHnB+cy+gsro8lXJ4kZluzOijzJ1UVVfyJYZC+IP2pLv1h4+aysQeKuTmyO8NAqfyQAk4HWaP/HjcKTg==", "dev": true, "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } } }, "linkify-it": { @@ -23229,27 +20643,6 @@ "uc.micro": "^1.0.1" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - } - } - }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -23494,30 +20887,12 @@ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, "markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -23539,41 +20914,6 @@ } } }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "dependencies": { - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, "md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -23614,24 +20954,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.3", + "picomatch": "^2.3.1" } }, "miller-rabin": { @@ -23710,27 +21039,6 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -23785,47 +21093,12 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -23865,24 +21138,12 @@ "path-exists": "^4.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -23946,17 +21207,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -23966,17 +21216,6 @@ "has-flag": "^4.0.0" } }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -24051,9 +21290,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", "dev": true }, "mute-stream": { @@ -24067,38 +21306,12 @@ "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, "nanoid": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==", "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -24118,12 +21331,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -24370,26 +21577,6 @@ "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -24453,12 +21640,6 @@ "boolbase": "^1.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -24505,42 +21686,11 @@ } } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-hash": { "version": "3.0.0", @@ -24569,15 +21719,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -24593,24 +21734,13 @@ "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", "dev": true, "requires": { "array-each": "^1.0.1", "array-slice": "^1.0.0", "for-own": "^1.0.0", "isobject": "^3.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } } }, "object.entries": { @@ -24645,57 +21775,15 @@ "es-abstract": "^1.19.1" } }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } - } - }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", "dev": true, "requires": { "isobject": "^3.0.1" } }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } - } - }, "object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -24770,15 +21858,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -24904,7 +21983,7 @@ "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", "dev": true, "requires": { "is-absolute": "^1.0.0", @@ -24912,25 +21991,10 @@ "path-root": "^0.1.1" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", "dev": true }, "parse-semver": { @@ -24967,12 +22031,6 @@ } } }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", @@ -25010,7 +22068,7 @@ "path-root": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", "dev": true, "requires": { "path-root-regex": "^0.1.0" @@ -25019,7 +22077,7 @@ "path-root-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true }, "path-to-regexp": { @@ -25124,12 +22182,6 @@ "extend-shallow": "^3.0.2" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "postinstall-build": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz", @@ -25188,12 +22240,6 @@ "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", "dev": true }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -25305,6 +22351,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -25361,67 +22413,6 @@ "mute-stream": "~0.0.4" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -25458,12 +22449,12 @@ } }, "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "^1.20.0" } }, "reflect-metadata": { @@ -25477,16 +22468,6 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "dev": true }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexp.prototype.flags": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", @@ -25539,18 +22520,6 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -25558,15 +22527,10 @@ "dev": true }, "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "dev": true }, "require-directory": { "version": "2.1.1", @@ -25600,12 +22564,6 @@ } } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -25628,7 +22586,7 @@ "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", "dev": true, "requires": { "expand-tilde": "^2.0.0", @@ -25650,12 +22608,6 @@ "value-or-function": "^3.0.0" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -25665,12 +22617,6 @@ "lowercase-keys": "^1.0.0" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -25739,15 +22685,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -25798,12 +22735,12 @@ } }, "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", "dev": true, "requires": { - "sver-compat": "^1.5.0" + "sver": "^1.8.3" } }, "serialize-javascript": { @@ -25835,29 +22772,6 @@ "has-property-descriptors": "^1.0.2" } }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -26055,125 +22969,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } } } }, @@ -26201,19 +22996,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -26224,16 +23006,10 @@ "source-map": "^0.6.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", "dev": true }, "spawn-wrap": { @@ -26250,47 +23026,6 @@ "which": "^2.0.1" } }, - "spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -26304,29 +23039,8 @@ }, "stack-trace": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stoppable": { "version": "1.1.0", @@ -26344,6 +23058,15 @@ "readable-stream": "^2.0.2" } }, + "stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "requires": { + "streamx": "^2.13.2" + } + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -26369,6 +23092,18 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + } + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -26393,31 +23128,14 @@ } }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "string.prototype.matchall": { @@ -26465,15 +23183,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -26523,14 +23232,22 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", "dev": true, "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true + } } }, "table": { @@ -26558,28 +23275,11 @@ "uri-js": "^4.2.2" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } } } }, @@ -26673,6 +23373,15 @@ "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" }, + "teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "requires": { + "streamx": "^2.12.5" + } + }, "terser": { "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", @@ -26720,6 +23429,15 @@ } } }, + "text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "requires": { + "b4a": "^1.6.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -26752,12 +23470,6 @@ "xtend": "~4.0.0" } }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -26809,46 +23521,13 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" } }, "to-through": { @@ -26896,15 +23575,6 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -26930,37 +23600,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -26969,15 +23614,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, @@ -27138,12 +23774,6 @@ "safe-buffer": "^5.0.1" } }, - "type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -27176,12 +23806,6 @@ "underscore": "^1.12.1" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -27254,26 +23878,32 @@ "dev": true }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "dependencies": { + "fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dev": true, + "requires": { + "fastest-levenshtein": "^1.0.7" + } + } } }, "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", "dev": true }, "unicode": { @@ -27281,18 +23911,6 @@ "resolved": "https://registry.npmjs.org/unicode/-/unicode-14.0.0.tgz", "integrity": "sha512-BjinxTXkbm9Jomp/YBTMGusr4fxIG67fNGShHIRAL16Ur2GJTq2xvLi+sxuiJmInCmwqqev2BCFKyvbfp/yAkg==" }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -27303,57 +23921,11 @@ "through2-filter": "^3.0.0" } }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, "untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==" }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, "update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -27373,12 +23945,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -27418,12 +23984,6 @@ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", "dev": true }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -27465,23 +24025,10 @@ "dev": true }, "v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "dev": true }, "value-or-function": { "version": "3.0.0", @@ -27503,6 +24050,69 @@ "replace-ext": "^1.0.0" } }, + "vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "requires": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "dependencies": { + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } + } + }, "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", @@ -27857,12 +24467,6 @@ } } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "which-typed-array": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", @@ -27920,29 +24524,39 @@ "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "color-name": "~1.1.4" } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, @@ -28062,35 +24676,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index d8fdbbeddd8a..fcb7dbbbb805 100644 --- a/package.json +++ b/package.json @@ -1580,7 +1580,7 @@ "expose-loader": "^3.1.0", "flat": "^5.0.2", "get-port": "^5.1.1", - "gulp": "^4.0.0", + "gulp": "^5.0.0", "gulp-typescript": "^5.0.0", "mocha": "^9.2.2", "mocha-junit-reporter": "^2.0.2", From 33c0a83722b71b0b1f04fc000dd4508ada3f5a8e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 14 Jun 2024 16:46:15 +1000 Subject: [PATCH 087/106] Adopt new Python locator (#23617) --- src/client/common/utils/iterable.ts | 11 ++ src/client/common/utils/resourceLifecycle.ts | 153 ++++++++++++++++++ src/client/pythonEnvironments/base/locator.ts | 5 - .../locators/common/nativePythonFinder.ts | 127 ++++++++------- .../base/locators/composite/envsResolver.ts | 13 +- .../base/locators/composite/resolverUtils.ts | 6 - .../base/locators/lowLevel/nativeLocator.ts | 144 +++++------------ 7 files changed, 285 insertions(+), 174 deletions(-) create mode 100644 src/client/common/utils/iterable.ts diff --git a/src/client/common/utils/iterable.ts b/src/client/common/utils/iterable.ts new file mode 100644 index 000000000000..5e04aaa430ea --- /dev/null +++ b/src/client/common/utils/iterable.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Iterable { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + export function is(thing: any): thing is Iterable { + return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; + } +} diff --git a/src/client/common/utils/resourceLifecycle.ts b/src/client/common/utils/resourceLifecycle.ts index 485294392ea1..f41efebc12cb 100644 --- a/src/client/common/utils/resourceLifecycle.ts +++ b/src/client/common/utils/resourceLifecycle.ts @@ -1,12 +1,50 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// eslint-disable-next-line max-classes-per-file import { IDisposable } from '../types'; +import { Iterable } from './iterable'; interface IDisposables extends IDisposable { push(...disposable: IDisposable[]): void; } +export const EmptyDisposable = { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + dispose: () => { + /** */ + }, +}; + +/** + * Disposes of the value(s) passed in. + */ +export function dispose(disposable: T): T; +export function dispose(disposable: T | undefined): T | undefined; +export function dispose = Iterable>(disposables: A): A; +export function dispose(disposables: Array): Array; +export function dispose(disposables: ReadonlyArray): ReadonlyArray; +// eslint-disable-next-line @typescript-eslint/no-explicit-any, consistent-return +export function dispose(arg: T | Iterable | undefined): any { + if (Iterable.is(arg)) { + for (const d of arg) { + if (d) { + try { + d.dispose(); + } catch (e) { + console.warn(`dispose() failed for ${d}`, e); + } + } + } + + return Array.isArray(arg) ? [] : arg; + } + if (arg) { + arg.dispose(); + return arg; + } +} + /** * Safely dispose each of the disposables. */ @@ -43,3 +81,118 @@ export class Disposables implements IDisposables { await disposeAll(disposables); } } + +/** + * Manages a collection of disposable values. + * + * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an + * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a + * store that has already been disposed of. + */ +export class DisposableStore implements IDisposable { + static DISABLE_DISPOSED_WARNING = false; + + private readonly _toDispose = new Set(); + + private _isDisposed = false; + + constructor(...disposables: IDisposable[]) { + disposables.forEach((disposable) => this.add(disposable)); + } + + /** + * Dispose of all registered disposables and mark this object as disposed. + * + * Any future disposables added to this object will be disposed of on `add`. + */ + public dispose(): void { + if (this._isDisposed) { + return; + } + + this._isDisposed = true; + this.clear(); + } + + /** + * @return `true` if this object has been disposed of. + */ + public get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * Dispose of all registered disposables but do not mark this object as disposed. + */ + public clear(): void { + if (this._toDispose.size === 0) { + return; + } + + try { + dispose(this._toDispose); + } finally { + this._toDispose.clear(); + } + } + + /** + * Add a new {@link IDisposable disposable} to the collection. + */ + public add(o: T): T { + if (!o) { + return o; + } + if (((o as unknown) as DisposableStore) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + + if (this._isDisposed) { + if (!DisposableStore.DISABLE_DISPOSED_WARNING) { + console.warn( + new Error( + 'Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!', + ).stack, + ); + } + } else { + this._toDispose.add(o); + } + + return o; + } +} + +/** + * Abstract class for a {@link IDisposable disposable} object. + * + * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. + */ +export abstract class DisposableBase implements IDisposable { + protected readonly _store = new DisposableStore(); + + private _isDisposed = false; + + public get isDisposed(): boolean { + return this._isDisposed; + } + + constructor(...disposables: IDisposable[]) { + disposables.forEach((disposable) => this._store.add(disposable)); + } + + public dispose(): void { + this._store.dispose(); + this._isDisposed = true; + } + + /** + * Adds `o` to the collection of disposables managed by this object. + */ + public _register(o: T): T { + if (((o as unknown) as DisposableBase) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + return this._store.add(o); + } +} diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index d61f530f46ab..da73735cb323 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -157,11 +157,6 @@ export type BasicEnvInfo = { * E.g. display name as provided by Windows Registry or Windows Store, etc */ displayName?: string; - /** - * Command used to run Python in this environment. - * E.g. `conda run -n envName python` or `python.exe` - */ - pythonRunCommand?: string[]; identifiedUsingNativeLocator?: boolean; arch?: Architecture; ctime?: number; diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index ac89d9e3aaf8..e8ed33802a11 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; +import { Disposable, EventEmitter, Event, Uri } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; @@ -10,40 +10,36 @@ import { isWindows } from '../../../../common/platform/platformService'; import { EXTENSION_ROOT_DIR } from '../../../../constants'; import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; import { createDeferred } from '../../../../common/utils/async'; +import { DisposableBase } from '../../../../common/utils/resourceLifecycle'; const NATIVE_LOCATOR = isWindows() - ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe') - : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder'); + ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet.exe') + : path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet'); export interface NativeEnvInfo { displayName?: string; - name: string; - pythonExecutablePath?: string; + name?: string; + executable?: string; category: string; version?: string; - pythonRunCommand?: string[]; - envPath?: string; - envManager?: NativeEnvManagerInfo; + prefix?: string; + manager?: NativeEnvManagerInfo; /** * Path to the project directory when dealing with pipenv virtual environments. */ - projectPath?: string; - arch?: 'X64' | 'X86'; + project?: string; + arch?: 'x64' | 'x86'; symlinks?: string[]; - creationTime?: number; - modifiedTime?: number; } export interface NativeEnvManagerInfo { tool: string; - executablePath: string; + executable: string; version?: string; } export interface NativeGlobalPythonFinder extends Disposable { - startSearch(token?: CancellationToken): Promise; - onDidFindPythonEnvironment: Event; - onDidFindEnvironmentManager: Event; + refresh(paths: Uri[]): AsyncIterable; } interface NativeLog { @@ -51,24 +47,51 @@ interface NativeLog { message: string; } -class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { - private readonly _onDidFindPythonEnvironment = new EventEmitter(); - - private readonly _onDidFindEnvironmentManager = new EventEmitter(); - - public readonly onDidFindPythonEnvironment = this._onDidFindPythonEnvironment.event; - - public readonly onDidFindEnvironmentManager = this._onDidFindEnvironmentManager.event; +class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGlobalPythonFinder { + async *refresh(_paths: Uri[]): AsyncIterable { + const result = this.start(); + let completed = false; + void result.completed.finally(() => { + completed = true; + }); + const envs: NativeEnvInfo[] = []; + let discovered = createDeferred(); + const disposable = result.discovered((data) => envs.push(data)); + + do { + await Promise.race([result.completed, discovered.promise]); + if (envs.length) { + const dataToSend = [...envs]; + envs.length = 0; + for (const data of dataToSend) { + yield data; + } + } + if (!completed) { + discovered = createDeferred(); + envs.length = 0; + } + } while (!completed); + + disposable.dispose(); + } - public startSearch(token?: CancellationToken): Promise { - const deferred = createDeferred(); - const proc = ch.spawn(NATIVE_LOCATOR, [], { env: process.env }); + // eslint-disable-next-line class-methods-use-this + private start(): { completed: Promise; discovered: Event } { + const discovered = new EventEmitter(); + const completed = createDeferred(); + const proc = ch.spawn(NATIVE_LOCATOR, ['server'], { env: process.env }); const disposables: Disposable[] = []; // jsonrpc package cannot handle messages coming through too quicly. // Lets handle the messages and close the stream only when // we have got the exit event. const readable = new PassThrough(); proc.stdout.pipe(readable, { end: false }); + let err = ''; + proc.stderr.on('data', (data) => { + err += data.toString(); + traceError('Native Python Finder', err); + }); const writable = new PassThrough(); writable.pipe(proc.stdin, { end: false }); const disposeStreams = new Disposable(() => { @@ -79,23 +102,30 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { new rpc.StreamMessageReader(readable), new rpc.StreamMessageWriter(writable), ); - disposables.push( connection, disposeStreams, + discovered, connection.onError((ex) => { disposeStreams.dispose(); traceError('Error in Native Python Finder', ex); }), - connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => { - this._onDidFindPythonEnvironment.fire(data); + connection.onNotification('environment', (data: NativeEnvInfo) => { + discovered.fire(data); }), - connection.onNotification('envManager', (data: NativeEnvManagerInfo) => { - this._onDidFindEnvironmentManager.fire(data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + connection.onNotification((method: string, data: any) => { + console.log(method, data); }), - connection.onNotification('exit', () => { - traceInfo('Native Python Finder exited'); + connection.onNotification('exit', (time: number) => { + traceInfo(`Native Python Finder completed after ${time}ms`); disposeStreams.dispose(); + completed.resolve(); + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + connection.onRequest((method: string, args: any) => { + console.error(method, args); + return 'HELLO THERE'; }), connection.onNotification('log', (data: NativeLog) => { switch (data.level) { @@ -116,7 +146,7 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { } }), connection.onClose(() => { - deferred.resolve(); + completed.resolve(); disposables.forEach((d) => d.dispose()); }), { @@ -125,33 +155,20 @@ class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder { if (proc.exitCode === null) { proc.kill(); } - } catch (err) { - traceVerbose('Error while disposing Native Python Finder', err); + } catch (ex) { + traceVerbose('Error while disposing Native Python Finder', ex); } }, }, ); - if (token) { - disposables.push( - token.onCancellationRequested(() => { - deferred.resolve(); - try { - proc.kill(); - } catch (err) { - traceVerbose('Error while handling cancellation request for Native Python Finder', err); - } - }), - ); - } - connection.listen(); - return deferred.promise; - } + connection.sendRequest('initialize', { body: ['This is id', 'Another'], supported: true }).then((r) => { + console.error(r); + void connection.sendNotification('initialized'); + }); - public dispose() { - this._onDidFindPythonEnvironment.dispose(); - this._onDidFindEnvironmentManager.dispose(); + return { completed: completed.promise, discovered: discovered.event }; } } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 8c02eab33359..a823e6f77ec5 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -140,7 +140,7 @@ export class PythonEnvsResolver implements IResolvingLocator { const info = await this.environmentInfoService.getMandatoryEnvironmentInfo(seen[envIndex]); const old = seen[envIndex]; if (info) { - const resolvedEnv = getResolvedEnv(info, seen[envIndex]); + const resolvedEnv = getResolvedEnv(info, seen[envIndex], old.identifiedUsingNativeLocator); seen[envIndex] = resolvedEnv; didUpdate.fire({ old, index: envIndex, update: resolvedEnv }); } else { @@ -188,7 +188,11 @@ function checkIfFinishedAndNotify( } } -function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: PythonEnvInfo) { +function getResolvedEnv( + interpreterInfo: InterpreterInformation, + environment: PythonEnvInfo, + identifiedUsingNativeLocator = false, +) { // Deep copy into a new object const resolvedEnv = cloneDeep(environment); resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix; @@ -196,7 +200,10 @@ function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: Py getEnvPath(resolvedEnv.executable.filename, resolvedEnv.location).pathType === 'envFolderPath'; // TODO: Shouldn't this only apply to conda, how else can we have an environment and not have Python in it? // If thats the case, then this should be gated on environment.kind === PythonEnvKind.Conda - if (isEnvLackingPython && environment.kind !== PythonEnvKind.MicrosoftStore) { + // For non-native do not blow away the versions returned by native locator. + // Windows Store and Home brew have exe and sysprefix in different locations, + // Thus above check is not valid for these envs. + if (isEnvLackingPython && environment.kind !== PythonEnvKind.MicrosoftStore && !identifiedUsingNativeLocator) { // Install python later into these envs might change the version, which can be confusing for users. // So avoid displaying any version until it is installed. resolvedEnv.version = getEmptyVersion(); diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 079ed108ee54..1b7f23d5651a 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -160,7 +160,6 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { location: env.envPath, display: env.displayName, searchLocation: env.searchLocation, - pythonRunCommand: env.pythonRunCommand, identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, name: env.name, type: PythonEnvType.Virtual, @@ -202,7 +200,6 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise { location: envPath, sysPrefix: envPath, display: env.displayName, - pythonRunCommand: env.pythonRunCommand, identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, searchLocation: env.searchLocation, source: [], @@ -285,7 +282,6 @@ async function resolvePyenvEnv(env: BasicEnvInfo): Promise { sysPrefix: env.envPath, display: env.displayName, name: env.name, - pythonRunCommand: env.pythonRunCommand, identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, // Pyenv environments can fall in to these three categories: // 1. Global Installs : These are environments that are created when you install @@ -329,7 +325,6 @@ async function resolveActiveStateEnv(env: BasicEnvInfo): Promise identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, location: env.envPath, name: env.name, - pythonRunCommand: env.pythonRunCommand, searchLocation: env.searchLocation, sysPrefix: env.envPath, }); @@ -368,7 +363,6 @@ async function resolveMicrosoftStoreEnv(env: BasicEnvInfo): Promise, IDisposable { return Promise.resolve(); } - public iterEnvs(): IPythonEnvsIterator { - const stopWatch = new StopWatch(); - traceInfo('Searching for Python environments using Native Locator'); - const promise = this.finder.startSearch(); - const envs: BasicEnvInfo[] = []; + public async *iterEnvs(): IPythonEnvsIterator { const disposables: IDisposable[] = []; const disposable = new Disposable(() => disposeAll(disposables)); this.disposables.push(disposable); - promise.finally(() => disposable.dispose()); - let environmentsWithoutPython = 0; - disposables.push( - this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => { - // TODO: What if executable is undefined? - if (data.pythonExecutablePath) { - const arch = (data.arch || '').toLowerCase(); - envs.push({ - kind: categoryToKind(data.category), - executablePath: data.pythonExecutablePath, - envPath: data.envPath, - version: parseVersion(data.version), - name: data.name === '' ? undefined : data.name, - displayName: data.displayName, - pythonRunCommand: data.pythonRunCommand, - searchLocation: data.projectPath ? Uri.file(data.projectPath) : undefined, - identifiedUsingNativeLocator: true, - arch: - // eslint-disable-next-line no-nested-ternary - arch === 'x64' ? Architecture.x64 : arch === 'x86' ? Architecture.x86 : undefined, - ctime: data.creationTime, - mtime: data.modifiedTime, - }); - } else { - environmentsWithoutPython += 1; - } - }), - this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => { - switch (toolToKnownEnvironmentTool(data.tool)) { + for await (const data of this.finder.refresh([])) { + if (data.manager) { + switch (toolToKnownEnvironmentTool(data.manager.tool)) { case 'Conda': { - Conda.setConda(data.executablePath); + Conda.setConda(data.manager.executable); break; } case 'Pyenv': { - setPyEnvBinary(data.executablePath); + setPyEnvBinary(data.manager.executable); break; } default: { break; } } - }), - ); - - const iterator = async function* (): IPythonEnvsIterator { - // When this promise is complete, we know that the search is complete. - await promise; - traceInfo( - `Finished searching for Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, - ); - yield* envs; - sendTelemetry(envs, environmentsWithoutPython, stopWatch); - traceInfo( - `Finished yielding Python environments using Native Locator: ${stopWatch.elapsedTime} milliseconds`, - ); - }; - - return iterator(); + } + if (data.executable) { + const arch = (data.arch || '').toLowerCase(); + const env: BasicEnvInfo = { + kind: categoryToKind(data.category), + executablePath: data.executable, + envPath: data.prefix, + version: parseVersion(data.version), + name: data.name === '' ? undefined : data.name, + displayName: data.displayName, + searchLocation: data.project ? Uri.file(data.project) : undefined, + identifiedUsingNativeLocator: true, + arch: + // eslint-disable-next-line no-nested-ternary + arch === 'x64' ? Architecture.x64 : arch === 'x86' ? Architecture.x86 : undefined, + }; + yield env; + } + } } } - -function sendTelemetry(envs: BasicEnvInfo[], environmentsWithoutPython: number, stopWatch: StopWatch) { - const activeStateEnvs = envs.filter((e) => e.kind === PythonEnvKind.ActiveState).length; - const condaEnvs = envs.filter((e) => e.kind === PythonEnvKind.Conda).length; - const customEnvs = envs.filter((e) => e.kind === PythonEnvKind.Custom).length; - const hatchEnvs = envs.filter((e) => e.kind === PythonEnvKind.Hatch).length; - const microsoftStoreEnvs = envs.filter((e) => e.kind === PythonEnvKind.MicrosoftStore).length; - const otherGlobalEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherGlobal).length; - const otherVirtualEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherVirtual).length; - const pipEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pipenv).length; - const poetryEnvs = envs.filter((e) => e.kind === PythonEnvKind.Poetry).length; - const pyenvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pyenv).length; - const systemEnvs = envs.filter((e) => e.kind === PythonEnvKind.System).length; - const unknownEnvs = envs.filter((e) => e.kind === PythonEnvKind.Unknown).length; - const venvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Venv).length; - const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length; - const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length; - - // Intent is to capture time taken for discovery of all envs to complete the first time. - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { - interpreters: envs.length, - environmentsWithoutPython, - activeStateEnvs, - condaEnvs, - customEnvs, - hatchEnvs, - microsoftStoreEnvs, - otherGlobalEnvs, - otherVirtualEnvs, - pipEnvEnvs, - poetryEnvs, - pyenvEnvs, - systemEnvs, - unknownEnvs, - venvEnvs, - virtualEnvEnvs, - virtualEnvWrapperEnvs, - }); -} From ad9a9b3742fb9bc17ca9ddda3aca62ac67a9d96c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 17 Jun 2024 16:28:50 +1000 Subject: [PATCH 088/106] Adopt new request with configuration object (#23626) --- .../locators/common/nativePythonFinder.ts | 34 ++++++++----------- .../base/locators/lowLevel/nativeLocator.ts | 10 +++--- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index e8ed33802a11..c3d6f5661628 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable, EventEmitter, Event, Uri } from 'vscode'; +import { Disposable, EventEmitter, Event, Uri, workspace } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; @@ -113,20 +113,9 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba connection.onNotification('environment', (data: NativeEnvInfo) => { discovered.fire(data); }), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - connection.onNotification((method: string, data: any) => { - console.log(method, data); - }), - connection.onNotification('exit', (time: number) => { - traceInfo(`Native Python Finder completed after ${time}ms`); - disposeStreams.dispose(); - completed.resolve(); - }), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - connection.onRequest((method: string, args: any) => { - console.error(method, args); - return 'HELLO THERE'; - }), + // connection.onNotification((method: string, data: any) => { + // console.log(method, data); + // }), connection.onNotification('log', (data: NativeLog) => { switch (data.level) { case 'info': @@ -163,10 +152,17 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba ); connection.listen(); - connection.sendRequest('initialize', { body: ['This is id', 'Another'], supported: true }).then((r) => { - console.error(r); - void connection.sendNotification('initialized'); - }); + connection + .sendRequest('refresh', { + // Send configuration information to the Python finder. + search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), + conda_executable: undefined, + }) + .then((durationInMilliSeconds: number) => { + completed.resolve(); + traceInfo(`Native Python Finder took ${durationInMilliSeconds}ms to complete.`); + }) + .catch((ex) => traceError('Error in Native Python Finder', ex)); return { completed: completed.promise, discovered: discovered.event }; } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index bd9b66e170d1..856314b05742 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -125,11 +125,11 @@ export class NativeLocator implements ILocator, IDisposable { const arch = (data.arch || '').toLowerCase(); const env: BasicEnvInfo = { kind: categoryToKind(data.category), - executablePath: data.executable, - envPath: data.prefix, - version: parseVersion(data.version), - name: data.name === '' ? undefined : data.name, - displayName: data.displayName, + executablePath: data.executable ? data.executable : '', + envPath: data.prefix ? data.prefix : undefined, + version: data.version ? parseVersion(data.version) : undefined, + name: data.name ? data.name : '', + displayName: data.displayName ? data.displayName : '', searchLocation: data.project ? Uri.file(data.project) : undefined, identifiedUsingNativeLocator: true, arch: From 0b103a86e4a18d704f1d9ed456d176d4e09e8b62 Mon Sep 17 00:00:00 2001 From: Christopher Covington Date: Mon, 17 Jun 2024 12:28:33 -0400 Subject: [PATCH 089/106] Restore execute bits on deactivate scripts (#23620) #22921 removed executable bits: ```diff diff --git a/pythonFiles/deactivate/bash/deactivate b/python_files/deactivate/bash/deactivate old mode 100755 new mode 100644 similarity index 100% rename from pythonFiles/deactivate/bash/deactivate rename to python_files/deactivate/bash/deactivate ``` https://github.com/microsoft/vscode-python/pull/22921/files#diff-796809259ce3b33f54a33371e898048faf2f7f912cd1bbe11059feb40a63a58d Set them back. Fixes #23449 and #23195 --- python_files/deactivate/bash/deactivate | 0 python_files/deactivate/fish/deactivate | 0 python_files/deactivate/zsh/deactivate | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 python_files/deactivate/bash/deactivate mode change 100644 => 100755 python_files/deactivate/fish/deactivate mode change 100644 => 100755 python_files/deactivate/zsh/deactivate diff --git a/python_files/deactivate/bash/deactivate b/python_files/deactivate/bash/deactivate old mode 100644 new mode 100755 diff --git a/python_files/deactivate/fish/deactivate b/python_files/deactivate/fish/deactivate old mode 100644 new mode 100755 diff --git a/python_files/deactivate/zsh/deactivate b/python_files/deactivate/zsh/deactivate old mode 100644 new mode 100755 From fd17bd8f2bc8902040559cc0920d97f1e7e62830 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 17 Jun 2024 09:42:36 -0700 Subject: [PATCH 090/106] Update language server modules to latest release (#23623) This is a prerequisite to fixing this pylance issue: https://github.com/microsoft/pylance-release/issues/5402 --------- Co-authored-by: Erik De Bonte --- package-lock.json | 268 ++-------- package.json | 7 +- src/client/activation/hidingMiddleware.ts | 500 ++++++++++++++++++ .../activation/languageClientMiddleware.ts | 3 +- src/client/browser/extension.ts | 2 +- 5 files changed, 545 insertions(+), 235 deletions(-) create mode 100644 src/client/activation/hidingMiddleware.ts diff --git a/package-lock.json b/package-lock.json index cd0d52f5e65a..b360528c4fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@iarna/toml": "^2.2.5", "@vscode/extension-telemetry": "^0.8.4", - "@vscode/jupyter-lsp-middleware": "^0.2.50", "arch": "^2.1.0", "fs-extra": "^10.0.1", "glob": "^7.2.0", @@ -34,9 +33,9 @@ "unicode": "^14.0.0", "untildify": "^4.0.0", "vscode-debugprotocol": "^1.28.0", - "vscode-jsonrpc": "^9.0.0-next.2", - "vscode-languageclient": "^10.0.0-next.2", - "vscode-languageserver-protocol": "^3.17.6-next.3", + "vscode-jsonrpc": "^9.0.0-next.4", + "vscode-languageclient": "^10.0.0-next.8", + "vscode-languageserver-protocol": "^3.17.6-next.6", "vscode-tas-client": "^0.1.84", "which": "^2.0.2", "winreg": "^1.2.4", @@ -1983,89 +1982,6 @@ "vscode": "^1.75.0" } }, - "node_modules/@vscode/jupyter-lsp-middleware": { - "version": "0.2.50", - "resolved": "https://registry.npmjs.org/@vscode/jupyter-lsp-middleware/-/jupyter-lsp-middleware-0.2.50.tgz", - "integrity": "sha512-oOEpRZOJdKjByRMkUDVdGlQDiEO4/Mjr88u5zqktaS/4h0NtX8Hk6+HNQwENp4ur3Dpu47gD8wOTCrkOWzbHlA==", - "dependencies": { - "@vscode/lsp-notebook-concat": "^0.1.16", - "fast-myers-diff": "^3.0.1", - "sha.js": "^2.4.11", - "vscode-languageclient": "^8.0.2-next.4", - "vscode-languageserver-protocol": "^3.17.2-next.5", - "vscode-uri": "^3.0.2" - }, - "engines": { - "vscode": "^1.67.0-insider" - } - }, - "node_modules/@vscode/jupyter-lsp-middleware/node_modules/vscode-jsonrpc": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", - "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@vscode/jupyter-lsp-middleware/node_modules/vscode-languageclient": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz", - "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==", - "dependencies": { - "minimatch": "^5.1.0", - "semver": "^7.3.7", - "vscode-languageserver-protocol": "3.17.3" - }, - "engines": { - "vscode": "^1.67.0" - } - }, - "node_modules/@vscode/jupyter-lsp-middleware/node_modules/vscode-languageserver-protocol": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", - "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", - "dependencies": { - "vscode-jsonrpc": "8.1.0", - "vscode-languageserver-types": "3.17.3" - } - }, - "node_modules/@vscode/jupyter-lsp-middleware/node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" - }, - "node_modules/@vscode/lsp-notebook-concat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@vscode/lsp-notebook-concat/-/lsp-notebook-concat-0.1.16.tgz", - "integrity": "sha512-jN2ut22GR/xelxHx2W9U+uZoylHGCezsNmsMYn20LgVHTcJMGL+4bL5PJeh63yo6P5XjAPtoeeymvp5EafJV+w==", - "dependencies": { - "object-hash": "^3.0.0", - "vscode-languageserver-protocol": "^3.17.2-next.5", - "vscode-uri": "^3.0.2" - } - }, - "node_modules/@vscode/lsp-notebook-concat/node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@vscode/lsp-notebook-concat/node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/@vscode/lsp-notebook-concat/node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" - }, "node_modules/@vscode/test-electron": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", @@ -5971,11 +5887,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "node_modules/fast-myers-diff": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-myers-diff/-/fast-myers-diff-3.0.1.tgz", - "integrity": "sha512-e8p26utONwDXeSDkDqu4jaR3l3r6ZgQO2GWB178ePZxCfFoRPNTJVZylUEHHG6uZeRikL1zCc2sl4sIAs9c0UQ==" - }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -9923,14 +9834,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -11225,7 +11128,8 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -11348,6 +11252,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -13162,24 +13067,24 @@ "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name" }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.2", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz", - "integrity": "sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ==", + "version": "9.0.0-next.4", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz", + "integrity": "sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ==", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "10.0.0-next.2", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.2.tgz", - "integrity": "sha512-ERKtgOkto4pHCxC2u1K3FfsYHSt8AeuZJjg1u/3TvnrCbBkMxrpn5mHWkh4m3rl6qo2Wk4m9YFgU6F7KCWQNZw==", + "version": "10.0.0-next.8", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.8.tgz", + "integrity": "sha512-D9inIHgqKayO9Tv0MeLb3XIL76yTuWmKdHqcGZKzjtQrMGJgASJDYWTapu+yAjEpDp0gmVOaCYyIlLB86ncDoQ==", "dependencies": { "minimatch": "^9.0.3", "semver": "^7.6.0", - "vscode-languageserver-protocol": "3.17.6-next.3" + "vscode-languageserver-protocol": "3.17.6-next.6" }, "engines": { - "vscode": "^1.86.0" + "vscode": "^1.89.0" } }, "node_modules/vscode-languageclient/node_modules/brace-expansion": { @@ -13205,18 +13110,18 @@ } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.3.tgz", - "integrity": "sha512-H8ATH5SAvc3JzttS+AL6g681PiBOZM/l34WP2JZk4akY3y7NqTP+f9cJ+MhrVBbD3aDS8bdAKewZgbFLW6M8Pg==", + "version": "3.17.6-next.6", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz", + "integrity": "sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw==", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.2", - "vscode-languageserver-types": "3.17.6-next.3" + "vscode-jsonrpc": "9.0.0-next.4", + "vscode-languageserver-types": "3.17.6-next.4" } }, "node_modules/vscode-languageserver-types": { - "version": "3.17.6-next.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz", - "integrity": "sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA==" + "version": "3.17.6-next.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz", + "integrity": "sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q==" }, "node_modules/vscode-tas-client": { "version": "0.1.84", @@ -13229,11 +13134,6 @@ "vscode": "^1.85.0" } }, - "node_modules/vscode-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", - "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -15486,81 +15386,6 @@ "applicationinsights": "^2.7.1" } }, - "@vscode/jupyter-lsp-middleware": { - "version": "0.2.50", - "resolved": "https://registry.npmjs.org/@vscode/jupyter-lsp-middleware/-/jupyter-lsp-middleware-0.2.50.tgz", - "integrity": "sha512-oOEpRZOJdKjByRMkUDVdGlQDiEO4/Mjr88u5zqktaS/4h0NtX8Hk6+HNQwENp4ur3Dpu47gD8wOTCrkOWzbHlA==", - "requires": { - "@vscode/lsp-notebook-concat": "^0.1.16", - "fast-myers-diff": "^3.0.1", - "sha.js": "^2.4.11", - "vscode-languageclient": "^8.0.2-next.4", - "vscode-languageserver-protocol": "^3.17.2-next.5", - "vscode-uri": "^3.0.2" - }, - "dependencies": { - "vscode-jsonrpc": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", - "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==" - }, - "vscode-languageclient": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz", - "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==", - "requires": { - "minimatch": "^5.1.0", - "semver": "^7.3.7", - "vscode-languageserver-protocol": "3.17.3" - } - }, - "vscode-languageserver-protocol": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", - "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", - "requires": { - "vscode-jsonrpc": "8.1.0", - "vscode-languageserver-types": "3.17.3" - } - }, - "vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" - } - } - }, - "@vscode/lsp-notebook-concat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@vscode/lsp-notebook-concat/-/lsp-notebook-concat-0.1.16.tgz", - "integrity": "sha512-jN2ut22GR/xelxHx2W9U+uZoylHGCezsNmsMYn20LgVHTcJMGL+4bL5PJeh63yo6P5XjAPtoeeymvp5EafJV+w==", - "requires": { - "object-hash": "^3.0.0", - "vscode-languageserver-protocol": "^3.17.2-next.5", - "vscode-uri": "^3.0.2" - }, - "dependencies": { - "vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==" - }, - "vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "requires": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" - } - } - }, "@vscode/test-electron": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", @@ -18631,11 +18456,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "fast-myers-diff": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-myers-diff/-/fast-myers-diff-3.0.1.tgz", - "integrity": "sha512-e8p26utONwDXeSDkDqu4jaR3l3r6ZgQO2GWB178ePZxCfFoRPNTJVZylUEHHG6uZeRikL1zCc2sl4sIAs9c0UQ==" - }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -21692,11 +21512,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" - }, "object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -22683,7 +22498,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -22782,6 +22598,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -24176,18 +23993,18 @@ "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==" }, "vscode-jsonrpc": { - "version": "9.0.0-next.2", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz", - "integrity": "sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ==" + "version": "9.0.0-next.4", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz", + "integrity": "sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ==" }, "vscode-languageclient": { - "version": "10.0.0-next.2", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.2.tgz", - "integrity": "sha512-ERKtgOkto4pHCxC2u1K3FfsYHSt8AeuZJjg1u/3TvnrCbBkMxrpn5mHWkh4m3rl6qo2Wk4m9YFgU6F7KCWQNZw==", + "version": "10.0.0-next.8", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.8.tgz", + "integrity": "sha512-D9inIHgqKayO9Tv0MeLb3XIL76yTuWmKdHqcGZKzjtQrMGJgASJDYWTapu+yAjEpDp0gmVOaCYyIlLB86ncDoQ==", "requires": { "minimatch": "^9.0.3", "semver": "^7.6.0", - "vscode-languageserver-protocol": "3.17.6-next.3" + "vscode-languageserver-protocol": "3.17.6-next.6" }, "dependencies": { "brace-expansion": { @@ -24209,18 +24026,18 @@ } }, "vscode-languageserver-protocol": { - "version": "3.17.6-next.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.3.tgz", - "integrity": "sha512-H8ATH5SAvc3JzttS+AL6g681PiBOZM/l34WP2JZk4akY3y7NqTP+f9cJ+MhrVBbD3aDS8bdAKewZgbFLW6M8Pg==", + "version": "3.17.6-next.6", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz", + "integrity": "sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw==", "requires": { - "vscode-jsonrpc": "9.0.0-next.2", - "vscode-languageserver-types": "3.17.6-next.3" + "vscode-jsonrpc": "9.0.0-next.4", + "vscode-languageserver-types": "3.17.6-next.4" } }, "vscode-languageserver-types": { - "version": "3.17.6-next.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz", - "integrity": "sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA==" + "version": "3.17.6-next.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz", + "integrity": "sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q==" }, "vscode-tas-client": { "version": "0.1.84", @@ -24230,11 +24047,6 @@ "tas-client": "0.2.33" } }, - "vscode-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", - "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index fcb7dbbbb805..b936cf59fe45 100644 --- a/package.json +++ b/package.json @@ -1506,7 +1506,6 @@ "dependencies": { "@iarna/toml": "^2.2.5", "@vscode/extension-telemetry": "^0.8.4", - "@vscode/jupyter-lsp-middleware": "^0.2.50", "arch": "^2.1.0", "fs-extra": "^10.0.1", "glob": "^7.2.0", @@ -1529,9 +1528,9 @@ "unicode": "^14.0.0", "untildify": "^4.0.0", "vscode-debugprotocol": "^1.28.0", - "vscode-jsonrpc": "^9.0.0-next.2", - "vscode-languageclient": "^10.0.0-next.2", - "vscode-languageserver-protocol": "^3.17.6-next.3", + "vscode-jsonrpc": "^9.0.0-next.4", + "vscode-languageclient": "^10.0.0-next.8", + "vscode-languageserver-protocol": "^3.17.6-next.6", "vscode-tas-client": "^0.1.84", "which": "^2.0.2", "winreg": "^1.2.4", diff --git a/src/client/activation/hidingMiddleware.ts b/src/client/activation/hidingMiddleware.ts new file mode 100644 index 000000000000..91258b7d844c --- /dev/null +++ b/src/client/activation/hidingMiddleware.ts @@ -0,0 +1,500 @@ +/* eslint-disable consistent-return */ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { + CallHierarchyIncomingCall, + CallHierarchyItem, + CallHierarchyOutgoingCall, + CancellationToken, + CodeAction, + CodeActionContext, + CodeLens, + Color, + ColorInformation, + ColorPresentation, + Command, + CompletionContext, + CompletionItem, + Declaration as VDeclaration, + Definition, + DefinitionLink, + Diagnostic, + Disposable, + DocumentHighlight, + DocumentLink, + DocumentSymbol, + FoldingContext, + FoldingRange, + FormattingOptions, + LinkedEditingRanges, + Location, + Position, + Position as VPosition, + ProviderResult, + Range, + SelectionRange, + SemanticTokens, + SemanticTokensEdits, + SignatureHelp, + SignatureHelpContext, + SymbolInformation, + TextDocument, + TextDocumentChangeEvent, + TextEdit, + Uri, + WorkspaceEdit, +} from 'vscode'; +import { HandleDiagnosticsSignature, Middleware } from 'vscode-languageclient/node'; + +import { ProvideDeclarationSignature } from 'vscode-languageclient/lib/common/declaration'; +import { + PrepareCallHierarchySignature, + CallHierarchyIncomingCallsSignature, + CallHierarchyOutgoingCallsSignature, +} from 'vscode-languageclient/lib/common/callHierarchy'; +import { + ProvideDocumentColorsSignature, + ProvideColorPresentationSignature, +} from 'vscode-languageclient/lib/common/colorProvider'; +import { ProvideFoldingRangeSignature } from 'vscode-languageclient/lib/common/foldingRange'; +import { ProvideImplementationSignature } from 'vscode-languageclient/lib/common/implementation'; +import { ProvideLinkedEditingRangeSignature } from 'vscode-languageclient/lib/common/linkedEditingRange'; +import { ProvideSelectionRangeSignature } from 'vscode-languageclient/lib/common/selectionRange'; +import { + DocumentSemanticsTokensSignature, + DocumentSemanticsTokensEditsSignature, + DocumentRangeSemanticTokensSignature, +} from 'vscode-languageclient/lib/common/semanticTokens'; +import { ProvideTypeDefinitionSignature } from 'vscode-languageclient/lib/common/typeDefinition'; +import { ProvideHoverSignature } from 'vscode-languageclient/lib/common/hover'; +import { + ProvideCompletionItemsSignature, + ResolveCompletionItemSignature, +} from 'vscode-languageclient/lib/common/completion'; +import { ProvideDefinitionSignature } from 'vscode-languageclient/lib/common/definition'; +import { ProvideDocumentHighlightsSignature } from 'vscode-languageclient/lib/common/documentHighlight'; +import { ProvideReferencesSignature } from 'vscode-languageclient/lib/common/reference'; +import { ProvideDocumentSymbolsSignature } from 'vscode-languageclient/lib/common/documentSymbol'; +import { ProvideCodeActionsSignature } from 'vscode-languageclient/lib/common/codeAction'; +import { ProvideCodeLensesSignature } from 'vscode-languageclient/lib/common/codeLens'; +import { ProvideDocumentLinksSignature } from 'vscode-languageclient/lib/common/documentLink'; +import { + ProvideDocumentFormattingEditsSignature, + ProvideDocumentRangeFormattingEditsSignature, + ProvideOnTypeFormattingEditsSignature, +} from 'vscode-languageclient/lib/common/formatting'; +import { ProvideRenameEditsSignature, PrepareRenameSignature } from 'vscode-languageclient/lib/common/rename'; +import { ProvideSignatureHelpSignature } from 'vscode-languageclient/lib/common/signatureHelp'; +import { isNotebookCell } from '../common/utils/misc'; + +/** + * This class is used to hide all intellisense requests for notebook cells. + */ +class HidingMiddlewareAddon implements Middleware, Disposable { + constructor() { + // Make sure a bunch of functions are bound to this. VS code can call them without a this context + this.handleDiagnostics = this.handleDiagnostics.bind(this); + this.didOpen = this.didOpen.bind(this); + this.didSave = this.didSave.bind(this); + this.didChange = this.didChange.bind(this); + this.didClose = this.didClose.bind(this); + } + + public dispose(): void { + // Nothing to dispose at the moment + } + + public async didChange(event: TextDocumentChangeEvent, next: (ev: TextDocumentChangeEvent) => void): Promise { + if (!isNotebookCell(event.document.uri)) { + return next(event); + } + } + + public async didOpen(document: TextDocument, next: (ev: TextDocument) => void): Promise { + if (!isNotebookCell(document.uri)) { + return next(document); + } + } + + public async didClose(document: TextDocument, next: (ev: TextDocument) => void): Promise { + if (!isNotebookCell(document.uri)) { + return next(document); + } + } + + // eslint-disable-next-line class-methods-use-this + public async didSave(event: TextDocument, next: (ev: TextDocument) => void): Promise { + if (!isNotebookCell(event.uri)) { + return next(event); + } + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + public provideCompletionItem( + document: TextDocument, + position: Position, + context: CompletionContext, + token: CancellationToken, + next: ProvideCompletionItemsSignature, + ) { + if (!isNotebookCell(document.uri)) { + return next(document, position, context, token); + } + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + public provideHover( + document: TextDocument, + position: Position, + token: CancellationToken, + next: ProvideHoverSignature, + ) { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public resolveCompletionItem( + item: CompletionItem, + token: CancellationToken, + next: ResolveCompletionItemSignature, + ): ProviderResult { + // Range should have already been remapped. + + // TODO: What if the LS needs to read the range? It won't make sense. This might mean + // doing this at the extension level is not possible. + return next(item, token); + } + + public provideSignatureHelp( + document: TextDocument, + position: Position, + context: SignatureHelpContext, + token: CancellationToken, + next: ProvideSignatureHelpSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, context, token); + } + } + + public provideDefinition( + document: TextDocument, + position: Position, + token: CancellationToken, + next: ProvideDefinitionSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + public provideReferences( + document: TextDocument, + position: Position, + options: { + includeDeclaration: boolean; + }, + token: CancellationToken, + next: ProvideReferencesSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, options, token); + } + } + + public provideDocumentHighlights( + document: TextDocument, + position: Position, + token: CancellationToken, + next: ProvideDocumentHighlightsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + public provideDocumentSymbols( + document: TextDocument, + token: CancellationToken, + next: ProvideDocumentSymbolsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideCodeActions( + document: TextDocument, + range: Range, + context: CodeActionContext, + token: CancellationToken, + next: ProvideCodeActionsSignature, + ): ProviderResult<(Command | CodeAction)[]> { + if (!isNotebookCell(document.uri)) { + return next(document, range, context, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideCodeLenses( + document: TextDocument, + token: CancellationToken, + next: ProvideCodeLensesSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideDocumentFormattingEdits( + document: TextDocument, + options: FormattingOptions, + token: CancellationToken, + next: ProvideDocumentFormattingEditsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, options, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideDocumentRangeFormattingEdits( + document: TextDocument, + range: Range, + options: FormattingOptions, + token: CancellationToken, + next: ProvideDocumentRangeFormattingEditsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, range, options, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideOnTypeFormattingEdits( + document: TextDocument, + position: Position, + ch: string, + options: FormattingOptions, + token: CancellationToken, + next: ProvideOnTypeFormattingEditsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, ch, options, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideRenameEdits( + document: TextDocument, + position: Position, + newName: string, + token: CancellationToken, + next: ProvideRenameEditsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, newName, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public prepareRename( + document: TextDocument, + position: Position, + token: CancellationToken, + next: PrepareRenameSignature, + ): ProviderResult< + | Range + | { + range: Range; + placeholder: string; + } + > { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideDocumentLinks( + document: TextDocument, + token: CancellationToken, + next: ProvideDocumentLinksSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, token); + } + } + + // eslint-disable-next-line class-methods-use-this + public provideDeclaration( + document: TextDocument, + position: VPosition, + token: CancellationToken, + next: ProvideDeclarationSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + public handleDiagnostics(uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature): void { + if (isNotebookCell(uri)) { + // Swallow all diagnostics for cells + next(uri, []); + } else { + next(uri, diagnostics); + } + } + + public provideTypeDefinition( + document: TextDocument, + position: Position, + token: CancellationToken, + next: ProvideTypeDefinitionSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + public provideImplementation( + document: TextDocument, + position: VPosition, + token: CancellationToken, + next: ProvideImplementationSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } + + public provideDocumentColors( + document: TextDocument, + token: CancellationToken, + next: ProvideDocumentColorsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, token); + } + } + + public provideColorPresentations( + color: Color, + context: { + document: TextDocument; + range: Range; + }, + token: CancellationToken, + next: ProvideColorPresentationSignature, + ): ProviderResult { + if (!isNotebookCell(context.document.uri)) { + return next(color, context, token); + } + } + + public provideFoldingRanges( + document: TextDocument, + context: FoldingContext, + token: CancellationToken, + next: ProvideFoldingRangeSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, context, token); + } + } + + public provideSelectionRanges( + document: TextDocument, + positions: readonly Position[], + token: CancellationToken, + next: ProvideSelectionRangeSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, positions, token); + } + } + + public prepareCallHierarchy( + document: TextDocument, + positions: Position, + token: CancellationToken, + next: PrepareCallHierarchySignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, positions, token); + } + } + + public provideCallHierarchyIncomingCalls( + item: CallHierarchyItem, + token: CancellationToken, + next: CallHierarchyIncomingCallsSignature, + ): ProviderResult { + if (!isNotebookCell(item.uri)) { + return next(item, token); + } + } + + public provideCallHierarchyOutgoingCalls( + item: CallHierarchyItem, + token: CancellationToken, + next: CallHierarchyOutgoingCallsSignature, + ): ProviderResult { + if (!isNotebookCell(item.uri)) { + return next(item, token); + } + } + + public provideDocumentSemanticTokens( + document: TextDocument, + token: CancellationToken, + next: DocumentSemanticsTokensSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, token); + } + } + + public provideDocumentSemanticTokensEdits( + document: TextDocument, + previousResultId: string, + token: CancellationToken, + next: DocumentSemanticsTokensEditsSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, previousResultId, token); + } + } + + public provideDocumentRangeSemanticTokens( + document: TextDocument, + range: Range, + token: CancellationToken, + next: DocumentRangeSemanticTokensSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, range, token); + } + } + + public provideLinkedEditingRange( + document: TextDocument, + position: Position, + token: CancellationToken, + next: ProvideLinkedEditingRangeSignature, + ): ProviderResult { + if (!isNotebookCell(document.uri)) { + return next(document, position, token); + } + } +} + +export function createHidingMiddleware(): Middleware & Disposable { + return new HidingMiddlewareAddon(); +} diff --git a/src/client/activation/languageClientMiddleware.ts b/src/client/activation/languageClientMiddleware.ts index 110d7461c615..711725e3de62 100644 --- a/src/client/activation/languageClientMiddleware.ts +++ b/src/client/activation/languageClientMiddleware.ts @@ -5,12 +5,11 @@ import { IJupyterExtensionDependencyManager } from '../common/application/types' import { IDisposableRegistry, IExtensions } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { sendTelemetryEvent } from '../telemetry'; +import { createHidingMiddleware } from './hidingMiddleware'; import { LanguageClientMiddlewareBase } from './languageClientMiddlewareBase'; import { LanguageServerType } from './types'; -import { createHidingMiddleware } from '@vscode/jupyter-lsp-middleware'; - export class LanguageClientMiddleware extends LanguageClientMiddlewareBase { public constructor(serviceContainer: IServiceContainer, serverType: LanguageServerType, serverVersion?: string) { super(serviceContainer, serverType, sendTelemetryEvent, serverVersion); diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts index 28e1912f67e4..35854d141cad 100644 --- a/src/client/browser/extension.ts +++ b/src/client/browser/extension.ts @@ -108,7 +108,7 @@ async function runPylance( middleware, }; - const client = new LanguageClient('python', 'Python Language Server', clientOptions, worker); + const client = new LanguageClient('python', 'Python Language Server', worker, clientOptions); languageClient = client; context.subscriptions.push( From adabe96b5792d9d6857209e52d12322b6fee667a Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:43:32 -0700 Subject: [PATCH 091/106] Rename REPL to Terminal REPL (#23613) Resolves: https://github.com/microsoft/vscode-python/issues/23524 Renaming existing "Start REPL" command palette option into "Start Terminal REPL" as we discussed. /cc @luabud @cwebster-99 --- README.md | 8 ++++---- package.nls.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0db0ab73a55b..b69de7351679 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ The Python extension will automatically install the following extensions by defa These extensions are optional dependencies, meaning the Python extension will remain fully functional if they fail to be installed. Any or all of these extensions can be [disabled](https://code.visualstudio.com/docs/editor/extension-marketplace#_disable-an-extension) or [uninstalled](https://code.visualstudio.com/docs/editor/extension-marketplace#_uninstall-an-extension) at the expense of some features. Extensions installed through the marketplace are subject to the [Marketplace Terms of Use](https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf). -## Extensibility +## Extensibility -The Python extension provides pluggable access points for extensions that extend various feature areas to further improve your Python development experience. These extensions are all optional and depend on your project configuration and preferences. +The Python extension provides pluggable access points for extensions that extend various feature areas to further improve your Python development experience. These extensions are all optional and depend on your project configuration and preferences. - [Python formatters](https://code.visualstudio.com/docs/python/formatting#_choose-a-formatter) - [Python linters](https://code.visualstudio.com/docs/python/linting#_choose-a-linter) -If you encounter issues with any of the listed extensions, please file an issue in its corresponding repo. +If you encounter issues with any of the listed extensions, please file an issue in its corresponding repo. ## Quick start @@ -70,7 +70,7 @@ Open the Command Palette (Command+Shift+P on macOS and Ctrl+Shift+P on Windows/L | Command | Description | | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Python: Select Interpreter` | Switch between Python interpreters, versions, and environments. | -| `Python: Start REPL` | Start an interactive Python REPL using the selected interpreter in the VS Code terminal. | +| `Python: Start Terminal REPL` | Start an interactive Python REPL using the selected interpreter in the VS Code terminal. | | `Python: Run Python File in Terminal` | Runs the active Python file in the VS Code terminal. You can also run a Python file by right-clicking on the file and selecting `Run Python File in Terminal`. | | `Python: Configure Tests` | Select a test framework and configure it to display the Test Explorer. | diff --git a/package.nls.json b/package.nls.json index 669a14bed528..50c02073bfda 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,5 +1,5 @@ { - "python.command.python.startREPL.title": "Start REPL", + "python.command.python.startREPL.title": "Start Terminal REPL", "python.command.python.createEnvironment.title": "Create Environment...", "python.command.python.createNewFile.title": "New Python File", "python.command.python.createTerminal.title": "Create Terminal", From 42de48d8d77267d4399c3ac39b11391734afa760 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 18 Jun 2024 08:14:07 +1000 Subject: [PATCH 092/106] Adopt native locator resolve methods (#23631) --- .../locators/common/nativePythonFinder.ts | 147 ++++++++++++++---- .../base/locators/composite/resolverUtils.ts | 18 ++- src/client/pythonEnvironments/index.ts | 23 +-- 3 files changed, 140 insertions(+), 48 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index c3d6f5661628..14567bdc2e81 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -10,7 +10,10 @@ import { isWindows } from '../../../../common/platform/platformService'; import { EXTENSION_ROOT_DIR } from '../../../../constants'; import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; import { createDeferred } from '../../../../common/utils/async'; -import { DisposableBase } from '../../../../common/utils/resourceLifecycle'; +import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle'; +import { getPythonSetting } from '../../../common/externalDependencies'; +import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator'; +import { noop } from '../../../../common/utils/misc'; const NATIVE_LOCATOR = isWindows() ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet.exe') @@ -39,6 +42,7 @@ export interface NativeEnvManagerInfo { } export interface NativeGlobalPythonFinder extends Disposable { + resolve(executable: string): Promise; refresh(paths: Uri[]): AsyncIterable; } @@ -48,18 +52,41 @@ interface NativeLog { } class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGlobalPythonFinder { + private readonly connection: rpc.MessageConnection; + + constructor() { + super(); + this.connection = this.start(); + } + + public async resolve(executable: string): Promise { + const { environment, duration } = await this.connection.sendRequest<{ + duration: number; + environment: NativeEnvInfo; + }>('resolve', { + executable, + }); + + traceInfo(`Resolved Python Environment ${environment.executable} in ${duration}ms`); + return environment; + } + async *refresh(_paths: Uri[]): AsyncIterable { - const result = this.start(); + const result = this.doRefresh(); let completed = false; void result.completed.finally(() => { completed = true; }); const envs: NativeEnvInfo[] = []; let discovered = createDeferred(); - const disposable = result.discovered((data) => envs.push(data)); - + const disposable = result.discovered((data) => { + envs.push(data); + discovered.resolve(); + }); do { - await Promise.race([result.completed, discovered.promise]); + if (!envs.length) { + await Promise.race([result.completed, discovered.promise]); + } if (envs.length) { const dataToSend = [...envs]; envs.length = 0; @@ -69,17 +96,13 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba } if (!completed) { discovered = createDeferred(); - envs.length = 0; } } while (!completed); - disposable.dispose(); } // eslint-disable-next-line class-methods-use-this - private start(): { completed: Promise; discovered: Event } { - const discovered = new EventEmitter(); - const completed = createDeferred(); + private start(): rpc.MessageConnection { const proc = ch.spawn(NATIVE_LOCATOR, ['server'], { env: process.env }); const disposables: Disposable[] = []; // jsonrpc package cannot handle messages coming through too quicly. @@ -87,9 +110,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba // we have got the exit event. const readable = new PassThrough(); proc.stdout.pipe(readable, { end: false }); - let err = ''; proc.stderr.on('data', (data) => { - err += data.toString(); + const err = data.toString(); traceError('Native Python Finder', err); }); const writable = new PassThrough(); @@ -105,17 +127,10 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba disposables.push( connection, disposeStreams, - discovered, connection.onError((ex) => { disposeStreams.dispose(); traceError('Error in Native Python Finder', ex); }), - connection.onNotification('environment', (data: NativeEnvInfo) => { - discovered.fire(data); - }), - // connection.onNotification((method: string, data: any) => { - // console.log(method, data); - // }), connection.onNotification('log', (data: NativeLog) => { switch (data.level) { case 'info': @@ -135,7 +150,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba } }), connection.onClose(() => { - completed.resolve(); disposables.forEach((d) => d.dispose()); }), { @@ -152,19 +166,86 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba ); connection.listen(); - connection - .sendRequest('refresh', { - // Send configuration information to the Python finder. - search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), - conda_executable: undefined, - }) - .then((durationInMilliSeconds: number) => { - completed.resolve(); - traceInfo(`Native Python Finder took ${durationInMilliSeconds}ms to complete.`); - }) - .catch((ex) => traceError('Error in Native Python Finder', ex)); - - return { completed: completed.promise, discovered: discovered.event }; + this._register(Disposable.from(...disposables)); + return connection; + } + + private doRefresh(): { completed: Promise; discovered: Event } { + const disposable = this._register(new DisposableStore()); + const discovered = disposable.add(new EventEmitter()); + const completed = createDeferred(); + const pendingPromises: Promise[] = []; + + const notifyUponCompletion = () => { + const initialCount = pendingPromises.length; + Promise.all(pendingPromises) + .then(() => { + if (initialCount === pendingPromises.length) { + completed.resolve(); + } else { + setTimeout(notifyUponCompletion, 0); + } + }) + .catch(noop); + }; + const trackPromiseAndNotifyOnCompletion = (promise: Promise) => { + pendingPromises.push(promise); + notifyUponCompletion(); + }; + + disposable.add( + this.connection.onNotification('environment', (data: NativeEnvInfo) => { + // We know that in the Python extension if either Version of Prefix is not provided by locator + // Then we end up resolving the information. + // Lets do that here, + // This is a hack, as the other part of the code that resolves the version information + // doesn't work as expected, as its still a WIP. + if (data.executable && (!data.version || !data.prefix)) { + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + const promise = this.connection + .sendRequest<{ duration: number; environment: NativeEnvInfo }>('resolve', { + executable: data.executable, + }) + .then(({ environment, duration }) => { + traceInfo(`Resolved Python Environment ${environment.executable} in ${duration}ms`); + discovered.fire(environment); + }) + .catch((ex) => traceError(`Error in Resolving Python Environment ${data}`, ex)); + trackPromiseAndNotifyOnCompletion(promise); + } else { + discovered.fire(data); + } + }), + ); + + const pythonPathSettings = (workspace.workspaceFolders || []).map((w) => + getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, w.uri.fsPath), + ); + pythonPathSettings.push(getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY)); + const pythonSettings = Array.from(new Set(pythonPathSettings.filter((item) => !!item)).values()).map((p) => + // We only want the parent directories. + path.dirname(p!), + ); + trackPromiseAndNotifyOnCompletion( + this.connection + .sendRequest<{ duration: number }>('refresh', { + // Send configuration information to the Python finder. + search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), + // Also send the python paths that are configured in the settings. + python_path_settings: pythonSettings, + conda_executable: undefined, + }) + .then(({ duration }) => traceInfo(`Native Python Finder completed in ${duration}ms`)) + .catch((ex) => traceError('Error in Native Python Finder', ex)), + ); + completed.promise.finally(() => disposable.dispose()); + return { + completed: completed.promise, + discovered: discovered.event, + }; } } diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 1b7f23d5651a..bd3347dbd334 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -59,7 +59,11 @@ export async function resolveBasicEnv(env: BasicEnvInfo): Promise const resolvedEnv = await resolverForKind(env); resolvedEnv.searchLocation = getSearchLocation(resolvedEnv, searchLocation); resolvedEnv.source = uniq(resolvedEnv.source.concat(source ?? [])); - if (getOSType() === OSType.Windows && resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry)) { + if ( + !env.identifiedUsingNativeLocator && + getOSType() === OSType.Windows && + resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry) + ) { // We can update env further using information we can get from the Windows registry. await updateEnvUsingRegistry(resolvedEnv); } @@ -75,9 +79,11 @@ export async function resolveBasicEnv(env: BasicEnvInfo): Promise resolvedEnv.executable.ctime = ctime; resolvedEnv.executable.mtime = mtime; } - const type = await getEnvType(resolvedEnv); - if (type) { - resolvedEnv.type = type; + if (!env.identifiedUsingNativeLocator) { + const type = await getEnvType(resolvedEnv); + if (type) { + resolvedEnv.type = type; + } } return resolvedEnv; } @@ -147,7 +153,7 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { const { executablePath, kind } = env; const envInfo = buildEnvInfo({ kind, - version: await getPythonVersionFromPath(executablePath), + version: env.identifiedUsingNativeLocator ? env.version : await getPythonVersionFromPath(executablePath), executable: executablePath, sysPrefix: env.envPath, location: env.envPath, diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 9999da6616be..d93d232242c1 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -197,15 +197,20 @@ function watchRoots(args: WatchRootsArgs): IDisposable { } function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { - const locators = new WorkspaceLocators(watchRoots, [ - (root: vscode.Uri) => [ - new WorkspaceVirtualEnvironmentLocator(root.fsPath), - new PoetryLocator(root.fsPath), - new HatchLocator(root.fsPath), - new CustomWorkspaceLocator(root.fsPath), - ], - // Add an ILocator factory func here for each kind of workspace-rooted locator. - ]); + const locators = new WorkspaceLocators( + watchRoots, + useNativeLocator() + ? [] + : [ + (root: vscode.Uri) => [ + new WorkspaceVirtualEnvironmentLocator(root.fsPath), + new PoetryLocator(root.fsPath), + new HatchLocator(root.fsPath), + new CustomWorkspaceLocator(root.fsPath), + ], + // Add an ILocator factory func here for each kind of workspace-rooted locator. + ], + ); ext.disposables.push(locators); return locators; } From 02029de971203da8092f5499f23a0aec1646fa1a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 18 Jun 2024 12:54:55 +1000 Subject: [PATCH 093/106] Refresh environments immediately (#23634) --- .../locators/common/nativePythonFinder.ts | 179 ++++++++++++++---- .../base/locators/lowLevel/nativeLocator.ts | 2 +- 2 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 14567bdc2e81..28cadb42dd39 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable, EventEmitter, Event, Uri, workspace } from 'vscode'; +import { Disposable, EventEmitter, Event, workspace, Uri } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; @@ -9,11 +9,16 @@ import { PassThrough } from 'stream'; import { isWindows } from '../../../../common/platform/platformService'; import { EXTENSION_ROOT_DIR } from '../../../../constants'; import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; -import { createDeferred } from '../../../../common/utils/async'; +import { createDeferred, createDeferredFrom } from '../../../../common/utils/async'; import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle'; -import { getPythonSetting } from '../../../common/externalDependencies'; import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator'; import { noop } from '../../../../common/utils/misc'; +import { getConfiguration } from '../../../../common/vscodeApis/workspaceApis'; +import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda'; +import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator'; +import { getUserHomeDir } from '../../../../common/utils/platform'; + +const untildify = require('untildify'); const NATIVE_LOCATOR = isWindows() ? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'pet.exe') @@ -43,7 +48,7 @@ export interface NativeEnvManagerInfo { export interface NativeGlobalPythonFinder extends Disposable { resolve(executable: string): Promise; - refresh(paths: Uri[]): AsyncIterable; + refresh(): AsyncIterable; } interface NativeLog { @@ -54,9 +59,12 @@ interface NativeLog { class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGlobalPythonFinder { private readonly connection: rpc.MessageConnection; + private firstRefreshResults: undefined | (() => AsyncGenerator); + constructor() { super(); this.connection = this.start(); + this.firstRefreshResults = this.refreshFirstTime(); } public async resolve(executable: string): Promise { @@ -71,41 +79,82 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba return environment; } - async *refresh(_paths: Uri[]): AsyncIterable { + async *refresh(): AsyncIterable { + if (this.firstRefreshResults) { + // If this is the first time we are refreshing, + // Then get the results from the first refresh. + // Those would have started earlier and cached in memory. + const results = this.firstRefreshResults(); + this.firstRefreshResults = undefined; + yield* results; + } else { + const result = this.doRefresh(); + let completed = false; + void result.completed.finally(() => { + completed = true; + }); + const envs: NativeEnvInfo[] = []; + let discovered = createDeferred(); + const disposable = result.discovered((data) => { + envs.push(data); + discovered.resolve(); + }); + do { + if (!envs.length) { + await Promise.race([result.completed, discovered.promise]); + } + if (envs.length) { + const dataToSend = [...envs]; + envs.length = 0; + for (const data of dataToSend) { + yield data; + } + } + if (!completed) { + discovered = createDeferred(); + } + } while (!completed); + disposable.dispose(); + } + } + + refreshFirstTime() { const result = this.doRefresh(); - let completed = false; - void result.completed.finally(() => { - completed = true; - }); + const completed = createDeferredFrom(result.completed); const envs: NativeEnvInfo[] = []; let discovered = createDeferred(); const disposable = result.discovered((data) => { envs.push(data); discovered.resolve(); }); - do { - if (!envs.length) { - await Promise.race([result.completed, discovered.promise]); - } - if (envs.length) { - const dataToSend = [...envs]; - envs.length = 0; - for (const data of dataToSend) { - yield data; + + const iterable = async function* () { + do { + if (!envs.length) { + await Promise.race([completed.promise, discovered.promise]); + } + if (envs.length) { + const dataToSend = [...envs]; + envs.length = 0; + for (const data of dataToSend) { + yield data; + } } - } - if (!completed) { - discovered = createDeferred(); - } - } while (!completed); - disposable.dispose(); + if (!completed.completed) { + discovered = createDeferred(); + } + } while (!completed.completed); + disposable.dispose(); + }; + + return iterable.bind(this); } // eslint-disable-next-line class-methods-use-this private start(): rpc.MessageConnection { const proc = ch.spawn(NATIVE_LOCATOR, ['server'], { env: process.env }); const disposables: Disposable[] = []; - // jsonrpc package cannot handle messages coming through too quicly. + // jsonrpc package cannot handle messages coming through too quickly. // Lets handle the messages and close the stream only when // we have got the exit event. const readable = new PassThrough(); @@ -213,7 +262,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba traceInfo(`Resolved Python Environment ${environment.executable} in ${duration}ms`); discovered.fire(environment); }) - .catch((ex) => traceError(`Error in Resolving Python Environment ${data}`, ex)); + .catch((ex) => traceError(`Error in Resolving Python Environment ${JSON.stringify(data)}`, ex)); trackPromiseAndNotifyOnCompletion(promise); } else { discovered.fire(data); @@ -221,32 +270,78 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba }), ); - const pythonPathSettings = (workspace.workspaceFolders || []).map((w) => - getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, w.uri.fsPath), - ); - pythonPathSettings.push(getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY)); - const pythonSettings = Array.from(new Set(pythonPathSettings.filter((item) => !!item)).values()).map((p) => - // We only want the parent directories. - path.dirname(p!), - ); trackPromiseAndNotifyOnCompletion( - this.connection - .sendRequest<{ duration: number }>('refresh', { - // Send configuration information to the Python finder. - search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), - // Also send the python paths that are configured in the settings. - python_path_settings: pythonSettings, - conda_executable: undefined, - }) + this.sendRefreshRequest() .then(({ duration }) => traceInfo(`Native Python Finder completed in ${duration}ms`)) .catch((ex) => traceError('Error in Native Python Finder', ex)), ); + completed.promise.finally(() => disposable.dispose()); return { completed: completed.promise, discovered: discovered.event, }; } + + private sendRefreshRequest() { + const pythonPathSettings = (workspace.workspaceFolders || []).map((w) => + getPythonSettingAndUntildify(DEFAULT_INTERPRETER_PATH_SETTING_KEY, w.uri), + ); + pythonPathSettings.push(getPythonSettingAndUntildify(DEFAULT_INTERPRETER_PATH_SETTING_KEY)); + // We can have multiple workspaces, each with its own setting. + const pythonSettings = Array.from( + new Set( + pythonPathSettings + .filter((item) => !!item) + // We only want the parent directories. + .map((p) => path.dirname(p!)) + /// If setting value is 'python', then `path.dirname('python')` will yield `.` + .filter((item) => item !== '.'), + ), + ); + + return this.connection.sendRequest<{ duration: number }>( + 'refresh', + // Send configuration information to the Python finder. + { + // This has a special meaning in locator, its lot a low priority + // as we treat this as workspace folders that can contain a large number of files. + search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), + // Also send the python paths that are configured in the settings. + python_interpreter_paths: pythonSettings, + // We do not want to mix this with `search_paths` + virtual_env_paths: getCustomVirtualEnvDirs(), + conda_executable: getPythonSettingAndUntildify(CONDAPATH_SETTING_KEY), + poetry_executable: getPythonSettingAndUntildify('poetryPath'), + pipenv_executable: getPythonSettingAndUntildify('pipenvPath'), + }, + ); + } +} + +/** + * Gets all custom virtual environment locations to look for environments. + */ +async function getCustomVirtualEnvDirs(): Promise { + const venvDirs: string[] = []; + const venvPath = getPythonSettingAndUntildify(VENVPATH_SETTING_KEY); + if (venvPath) { + venvDirs.push(untildify(venvPath)); + } + const venvFolders = getPythonSettingAndUntildify(VENVFOLDERS_SETTING_KEY) ?? []; + const homeDir = getUserHomeDir(); + if (homeDir) { + venvFolders.map((item) => path.join(homeDir, item)).forEach((d) => venvDirs.push(d)); + } + return Array.from(new Set(venvDirs)); +} + +function getPythonSettingAndUntildify(name: string, scope?: Uri): T | undefined { + const value = getConfiguration('python', scope).get(name); + if (typeof value === 'string') { + return value ? ((untildify(value as string) as unknown) as T) : undefined; + } + return value; } export function createNativeGlobalPythonFinder(): NativeGlobalPythonFinder { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts index 856314b05742..a59652b2ce23 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts @@ -105,7 +105,7 @@ export class NativeLocator implements ILocator, IDisposable { const disposables: IDisposable[] = []; const disposable = new Disposable(() => disposeAll(disposables)); this.disposables.push(disposable); - for await (const data of this.finder.refresh([])) { + for await (const data of this.finder.refresh()) { if (data.manager) { switch (toolToKnownEnvironmentTool(data.manager.tool)) { case 'Conda': { From 7473a340d78b4f6cd6e87462bb699110740d227b Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:08:27 -0700 Subject: [PATCH 094/106] Refactor Native REPL code (#23550) Refactor Native REPL code. Resolves: https://github.com/microsoft/vscode-python/issues/23632 --------- Co-authored-by: Aaron Munger --- src/client/repl/nativeRepl.ts | 112 ++++++++++++++ src/client/repl/replCommandHandler.ts | 100 +++++++++++++ src/client/repl/replCommands.ts | 201 +++++--------------------- src/client/repl/replUtils.ts | 111 ++++++++++++++ 4 files changed, 362 insertions(+), 162 deletions(-) create mode 100644 src/client/repl/nativeRepl.ts create mode 100644 src/client/repl/replCommandHandler.ts create mode 100644 src/client/repl/replUtils.ts diff --git a/src/client/repl/nativeRepl.ts b/src/client/repl/nativeRepl.ts new file mode 100644 index 000000000000..e6a596f4434a --- /dev/null +++ b/src/client/repl/nativeRepl.ts @@ -0,0 +1,112 @@ +// Native Repl class that holds instance of pythonServer and replController + +import { NotebookController, NotebookControllerAffinity, NotebookDocument, TextEditor, workspace } from 'vscode'; +import { Disposable } from 'vscode-jsonrpc'; +import { PVSC_EXTENSION_ID } from '../common/constants'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { createPythonServer, PythonServer } from './pythonServer'; +import { executeNotebookCell, openInteractiveREPL, selectNotebookKernel } from './replCommandHandler'; +import { createReplController } from './replController'; + +export class NativeRepl implements Disposable { + private pythonServer: PythonServer; + + private interpreter: PythonEnvironment; + + private disposables: Disposable[] = []; + + private replController: NotebookController; + + private notebookDocument: NotebookDocument | undefined; + + // TODO: In the future, could also have attribute of URI for file specific REPL. + constructor(interpreter: PythonEnvironment) { + this.interpreter = interpreter; + + this.pythonServer = createPythonServer([interpreter.path as string]); + this.replController = this.setReplController(); + + this.watchNotebookClosed(); + } + + dispose(): void { + this.disposables.forEach((d) => d.dispose()); + } + + /** + * Function that watches for Notebook Closed event. + * This is for the purposes of correctly updating the notebookEditor and notebookDocument on close. + */ + private watchNotebookClosed(): void { + this.disposables.push( + workspace.onDidCloseNotebookDocument((nb) => { + if (this.notebookDocument && nb.uri.toString() === this.notebookDocument.uri.toString()) { + this.notebookDocument = undefined; + } + }), + ); + } + + /** + * Function that check if NotebookController for REPL exists, and returns it in Singleton manner. + * @returns NotebookController + */ + public setReplController(): NotebookController { + if (!this.replController) { + return createReplController(this.interpreter.path, this.disposables); + } + return this.replController; + } + + /** + * Function that checks if native REPL's text input box contains complete code. + * @param activeEditor + * @param pythonServer + * @returns Promise - True if complete/Valid code is present, False otherwise. + */ + public async checkUserInputCompleteCode(activeEditor: TextEditor | undefined): Promise { + let completeCode = false; + let userTextInput; + if (activeEditor) { + const { document } = activeEditor; + userTextInput = document.getText(); + } + + // Check if userTextInput is a complete Python command + if (userTextInput) { + completeCode = await this.pythonServer.checkValidCommand(userTextInput); + } + + return completeCode; + } + + /** + * Function that opens interactive repl, selects kernel, and send/execute code to the native repl. + * @param code + */ + public async sendToNativeRepl(code: string): Promise { + const notebookEditor = await openInteractiveREPL(this.replController, this.notebookDocument); + this.notebookDocument = notebookEditor.notebook; + + if (this.notebookDocument) { + this.replController.updateNotebookAffinity(this.notebookDocument, NotebookControllerAffinity.Default); + await selectNotebookKernel(notebookEditor, this.replController.id, PVSC_EXTENSION_ID); + await executeNotebookCell(this.notebookDocument, code); + } + } +} + +let nativeRepl: NativeRepl | undefined; // In multi REPL scenario, hashmap of URI to Repl. + +/** + * Get Singleton Native REPL Instance + * @param interpreter + * @returns Native REPL instance + */ +export function getNativeRepl(interpreter: PythonEnvironment, disposables: Disposable[]): NativeRepl { + if (!nativeRepl) { + nativeRepl = new NativeRepl(interpreter); + disposables.push(nativeRepl); + } + return nativeRepl; +} diff --git a/src/client/repl/replCommandHandler.ts b/src/client/repl/replCommandHandler.ts new file mode 100644 index 000000000000..599692a4300e --- /dev/null +++ b/src/client/repl/replCommandHandler.ts @@ -0,0 +1,100 @@ +import { + commands, + window, + NotebookController, + NotebookEditor, + ViewColumn, + NotebookDocument, + NotebookCellData, + NotebookCellKind, + NotebookEdit, + WorkspaceEdit, + workspace, +} from 'vscode'; +import { getExistingReplViewColumn } from './replUtils'; + +/** + * Function that opens/show REPL using IW UI. + * @param notebookController + * @param notebookEditor + * @returns notebookEditor + */ +export async function openInteractiveREPL( + notebookController: NotebookController, + notebookDocument: NotebookDocument | undefined, +): Promise { + let notebookEditor: NotebookEditor | undefined; + + // Case where NotebookDocument (REPL document already exists in the tab) + if (notebookDocument) { + const existingReplViewColumn = getExistingReplViewColumn(notebookDocument); + const replViewColumn = existingReplViewColumn ?? ViewColumn.Beside; + notebookEditor = await window.showNotebookDocument(notebookDocument!, { viewColumn: replViewColumn }); + } else if (!notebookDocument) { + // Case where NotebookDocument doesnt exist, open new REPL tab + const interactiveWindowObject = (await commands.executeCommand( + 'interactive.open', + { + preserveFocus: true, + viewColumn: ViewColumn.Beside, + }, + undefined, + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; + } + return notebookEditor!; +} + +/** + * Function that selects notebook Kernel. + * @param notebookEditor + * @param notebookControllerId + * @param extensionId + * @return Promise + */ +export async function selectNotebookKernel( + notebookEditor: NotebookEditor, + notebookControllerId: string, + extensionId: string, +): Promise { + await commands.executeCommand('notebook.selectKernel', { + notebookEditor, + id: notebookControllerId, + extension: extensionId, + }); +} + +/** + * Function that executes notebook cell given code. + * @param notebookDocument + * @param code + * @return Promise + */ +export async function executeNotebookCell(notebookDocument: NotebookDocument, code: string): Promise { + const { cellCount } = notebookDocument; + await addCellToNotebook(notebookDocument, code); + // Execute the cell + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: notebookDocument.uri, + }); +} + +/** + * Function that adds cell to notebook. + * This function will only get called when notebook document is defined. + * @param code + * + */ +async function addCellToNotebook(notebookDocument: NotebookDocument, code: string): Promise { + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); + const { cellCount } = notebookDocument!; + // Add new cell to interactive window document + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); + await workspace.applyEdit(workspaceEdit); +} diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 534bf984d7e0..7b76ece74116 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -1,67 +1,24 @@ -import { - commands, - NotebookController, - Uri, - workspace, - window, - NotebookControllerAffinity, - ViewColumn, - NotebookEdit, - NotebookCellData, - NotebookCellKind, - WorkspaceEdit, - NotebookEditor, - TextEditor, - Selection, - NotebookDocument, -} from 'vscode'; +import { commands, Uri, window } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; -import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; +import { Commands } from '../common/constants'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; -import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; -import { createPythonServer } from './pythonServer'; -import { createReplController } from './replController'; -import { getActiveResource } from '../common/vscodeApis/windowApis'; -import { getConfiguration } from '../common/vscodeApis/workspaceApis'; - -let notebookController: NotebookController | undefined; -let notebookEditor: NotebookEditor | undefined; -// TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. -let notebookDocument: NotebookDocument | undefined; - -async function getSelectedTextToExecute(textEditor: TextEditor): Promise { - if (!textEditor) { - return undefined; - } - - const { selection } = textEditor; - let code: string; - - if (selection.isEmpty) { - code = textEditor.document.lineAt(selection.start.line).text; - } else if (selection.isSingleLine) { - code = getSingleLineSelectionText(textEditor); - } else { - code = getMultiLineSelectionText(textEditor); - } - - return code; -} -function getSendToNativeREPLSetting(): boolean { - const uri = getActiveResource(); - const configuration = getConfiguration('python', uri); - return configuration.get('REPL.sendToNativeREPL', false); -} - -workspace.onDidCloseNotebookDocument((nb) => { - if (notebookDocument && nb.uri.toString() === notebookDocument.uri.toString()) { - notebookEditor = undefined; - notebookDocument = undefined; - } -}); +import { getNativeRepl } from './nativeRepl'; +import { + executeInTerminal, + getActiveInterpreter, + getSelectedTextToExecute, + getSendToNativeREPLSetting, + insertNewLineToREPLInput, + isMultiLineText, +} from './replUtils'; -// Will only be called when user has experiment enabled. +/** + * Registers REPL command for shift+enter if sendToNativeREPL setting is enabled. + * @param disposables + * @param interpreterService + * @returns Promise + */ export async function registerReplCommands( disposables: Disposable[], interpreterService: IInterpreterService, @@ -70,82 +27,26 @@ export async function registerReplCommands( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { const nativeREPLSetting = getSendToNativeREPLSetting(); - // If nativeREPLSetting is false(Send to Terminal REPL), then fall back to running in Terminal REPL if (!nativeREPLSetting) { - await commands.executeCommand(Commands.Exec_Selection_In_Terminal); + await executeInTerminal(); return; } - const interpreter = await interpreterService.getActiveInterpreter(uri); - if (!interpreter) { - commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); - return; - } - if (interpreter) { - const interpreterPath = interpreter.path; + const interpreter = await getActiveInterpreter(uri, interpreterService); - if (!notebookController) { - notebookController = createReplController(interpreterPath, disposables); - } - const activeEditor = window.activeTextEditor as TextEditor; - const code = await getSelectedTextToExecute(activeEditor); - - if (!notebookEditor) { - const interactiveWindowObject = (await commands.executeCommand( - 'interactive.open', - { - preserveFocus: true, - viewColumn: ViewColumn.Beside, - }, - undefined, - notebookController.id, - 'Python REPL', - )) as { notebookEditor: NotebookEditor }; - notebookEditor = interactiveWindowObject.notebookEditor; - notebookDocument = interactiveWindowObject.notebookEditor.notebook; - } - // Handle case where user has closed REPL window, and re-opens. - if (notebookEditor && notebookDocument) { - await window.showNotebookDocument(notebookDocument, { viewColumn: ViewColumn.Beside }); - } - - if (notebookDocument) { - notebookController.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); - - // Auto-Select Python REPL Kernel - await commands.executeCommand('notebook.selectKernel', { - notebookEditor, - id: notebookController.id, - extension: PVSC_EXTENSION_ID, - }); - - const { cellCount } = notebookDocument; - await addCellToNotebook(code as string); - // Execute the cell - commands.executeCommand('notebook.cell.execute', { - ranges: [{ start: cellCount, end: cellCount + 1 }], - document: notebookDocument.uri, - }); + if (interpreter) { + const nativeRepl = getNativeRepl(interpreter, disposables); + const activeEditor = window.activeTextEditor; + if (activeEditor) { + const code = await getSelectedTextToExecute(activeEditor); + if (code) { + await nativeRepl.sendToNativeRepl(code); + } } } }), ); } -/** - * Function that adds cell to notebook. - * This function will only get called when notebook document is defined. - * @param code - * - */ -async function addCellToNotebook(code: string): Promise { - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); - const { cellCount } = notebookDocument!; - // Add new cell to interactive window document - const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); - await workspace.applyEdit(workspaceEdit); -} /** * Command triggered for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. @@ -164,47 +65,23 @@ export async function registerReplExecuteOnEnter( return; } - // Create Separate Python server to check valid command - const pythonServer = createPythonServer([interpreter.path as string]); - - const activeEditor = window.activeTextEditor; - let userTextInput; - let completeCode = false; - - if (activeEditor) { - const { document } = activeEditor; - userTextInput = document.getText(); - } - - // Check if userTextInput is a complete Python command - if (userTextInput) { - completeCode = await pythonServer.checkValidCommand(userTextInput); - } + const nativeRepl = getNativeRepl(interpreter, disposables); + const completeCode = await nativeRepl?.checkUserInputCompleteCode(window.activeTextEditor); const editor = window.activeTextEditor; - // Execute right away when complete code and Not multi-line - if (completeCode && !isMultiLineText(editor)) { - await commands.executeCommand('interactive.execute'); - } else { - // Insert new line on behalf of user. "Regular" monaco editor behavior - if (editor) { - const position = editor.selection.active; - const newPosition = position.with(position.line, editor.document.lineAt(position.line).text.length); - editor.selection = new Selection(newPosition, newPosition); - editor.edit((editBuilder) => { - editBuilder.insert(newPosition, '\n'); - }); - } - - // Handle case when user enters on blank line, just trigger interactive.execute - if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + if (editor) { + // Execute right away when complete code and Not multi-line + if (completeCode && !isMultiLineText(editor)) { await commands.executeCommand('interactive.execute'); + } else { + insertNewLineToREPLInput(editor); + + // Handle case when user enters on blank line, just trigger interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand('interactive.execute'); + } } } }), ); } - -function isMultiLineText(textEditor: TextEditor | undefined): boolean { - return (textEditor?.document?.lineCount ?? 0) > 1; -} diff --git a/src/client/repl/replUtils.ts b/src/client/repl/replUtils.ts new file mode 100644 index 000000000000..ec68f0a59bb6 --- /dev/null +++ b/src/client/repl/replUtils.ts @@ -0,0 +1,111 @@ +import { NotebookDocument, TextEditor, Selection, Uri, commands, window, TabInputNotebook, ViewColumn } from 'vscode'; +import { Commands } from '../common/constants'; +import { noop } from '../common/utils/misc'; +import { getActiveResource } from '../common/vscodeApis/windowApis'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; +import { IInterpreterService } from '../interpreter/contracts'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; + +/** + * Function that executes selected code in the terminal. + * @returns Promise + */ +export async function executeInTerminal(): Promise { + await commands.executeCommand(Commands.Exec_Selection_In_Terminal); +} + +/** + * Function that returns selected text to execute in the REPL. + * @param textEditor + * @returns code - Code to execute in the REPL. + */ +export async function getSelectedTextToExecute(textEditor: TextEditor): Promise { + const { selection } = textEditor; + let code: string; + + if (selection.isEmpty) { + code = textEditor.document.lineAt(selection.start.line).text; + } else if (selection.isSingleLine) { + code = getSingleLineSelectionText(textEditor); + } else { + code = getMultiLineSelectionText(textEditor); + } + + return code; +} + +/** + * Function that returns user's Native REPL setting. + * @returns boolean - True if sendToNativeREPL setting is enabled, False otherwise. + */ +export function getSendToNativeREPLSetting(): boolean { + const uri = getActiveResource(); + const configuration = getConfiguration('python', uri); + return configuration.get('REPL.sendToNativeREPL', false); +} + +/** + * Function that inserts new line in the given (input) text editor + * @param activeEditor + * @returns void + */ + +export function insertNewLineToREPLInput(activeEditor: TextEditor | undefined): void { + if (activeEditor) { + const position = activeEditor.selection.active; + const newPosition = position.with(position.line, activeEditor.document.lineAt(position.line).text.length); + activeEditor.selection = new Selection(newPosition, newPosition); + + activeEditor.edit((editBuilder) => { + editBuilder.insert(newPosition, '\n'); + }); + } +} + +export function isMultiLineText(textEditor: TextEditor): boolean { + return (textEditor?.document?.lineCount ?? 0) > 1; +} + +/** + * Function that trigger interpreter warning if invalid interpreter. + * Function will also return undefined or active interpreter + * @parm uri + * @param interpreterService + * @returns Promise + */ +export async function getActiveInterpreter( + uri: Uri, + interpreterService: IInterpreterService, +): Promise { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return undefined; + } + return interpreter; +} + +/** + * Function that will return ViewColumn for existing Native REPL that belongs to given NotebookDocument. + * @returns ViewColumn | undefined + */ +export function getExistingReplViewColumn(notebookDocument: NotebookDocument): ViewColumn | undefined { + const ourNotebookUri = notebookDocument.uri.toString(); + // Use Tab groups, to locate previously opened Python REPL tab and fetch view column. + const ourTb = window.tabGroups; + for (const tabGroup of ourTb.all) { + for (const tab of tabGroup.tabs) { + if (tab.label === 'Python REPL') { + const tabInput = (tab.input as unknown) as TabInputNotebook; + const tabUri = tabInput.uri.toString(); + if (tab.input && tabUri === ourNotebookUri) { + // This is the tab we are looking for. + const existingReplViewColumn = tab.group.viewColumn; + return existingReplViewColumn; + } + } + } + } + return undefined; +} From 49e2ed7f28d3b3519b0d2f69530e0a9681cd60de Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:05:59 -0700 Subject: [PATCH 095/106] Allow Smart send with new REPL (#23638) Resolves: https://github.com/microsoft/vscode-python/issues/23521 --- src/client/extensionActivation.ts | 6 +++--- src/client/repl/replCommands.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 1c7e8f384ff1..f401f2493eed 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -32,7 +32,7 @@ import { TerminalProvider } from './providers/terminalProvider'; import { setExtensionInstallTelemetryProperties } from './telemetry/extensionInstallTelemetry'; import { registerTypes as tensorBoardRegisterTypes } from './tensorBoard/serviceRegistry'; import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; -import { ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types'; +import { ICodeExecutionHelper, ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types'; import { registerTypes as unitTestsRegisterTypes } from './testing/serviceRegistry'; // components @@ -106,8 +106,8 @@ export function activateFeatures(ext: ExtensionState, _components: Components): interpreterService, pathUtils, ); - - registerReplCommands(ext.disposables, interpreterService); + const executionHelper = ext.legacyIOC.serviceContainer.get(ICodeExecutionHelper); + registerReplCommands(ext.disposables, interpreterService, executionHelper); registerReplExecuteOnEnter(ext.disposables, interpreterService); } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 7b76ece74116..7c4977b1aeff 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -3,6 +3,7 @@ import { Disposable } from 'vscode-jsonrpc'; import { Commands } from '../common/constants'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; +import { ICodeExecutionHelper } from '../terminals/types'; import { getNativeRepl } from './nativeRepl'; import { executeInTerminal, @@ -22,6 +23,7 @@ import { export async function registerReplCommands( disposables: Disposable[], interpreterService: IInterpreterService, + executionHelper: ICodeExecutionHelper, ): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { @@ -40,7 +42,13 @@ export async function registerReplCommands( if (activeEditor) { const code = await getSelectedTextToExecute(activeEditor); if (code) { - await nativeRepl.sendToNativeRepl(code); + // Smart Send + let wholeFileContent = ''; + if (activeEditor && activeEditor.document) { + wholeFileContent = activeEditor.document.getText(); + } + const normalizedCode = await executionHelper.normalizeLines(code!, wholeFileContent); + await nativeRepl.sendToNativeRepl(normalizedCode); } } } From 29b708e7c2c69ff7294f313e69c09cd7385bf384 Mon Sep 17 00:00:00 2001 From: Nick Warters <51464280+nickwarters@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:08:24 +0100 Subject: [PATCH 096/106] Activate extension when .venv or .conda found in workspace (#23642) resolves #22814 --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b936cf59fe45..3a9dd5ab40ef 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,9 @@ "workspaceContains:setup.py", "workspaceContains:requirements.txt", "workspaceContains:manage.py", - "workspaceContains:app.py" + "workspaceContains:app.py", + "workspaceContains:.venv", + "workspaceContains:.conda" ], "main": "./out/client/extension", "browser": "./dist/extension.browser.js", From 06ecbd682f957136bfcf069f6a7a20bf3f9a5f7e Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 20 Jun 2024 19:38:40 +0200 Subject: [PATCH 097/106] Add locator for pixi environments (#22968) Closes https://github.com/microsoft/vscode-python/issues/22978 This adds a locator implementation that properly detects [Pixi](https://pixi.sh/) environments. Pixi environments are essentially conda environments but placed in a specific directory inside the project/workspace. This PR properly detects these and does not do much else. This would unblock a lot of pixi users. I would prefer to use a custom pixi plugin but since the [contribution endpoints are not available yet](https://github.com/microsoft/vscode-python/issues/22797) I think this is the next best thing. Before I put more effort into tests I just want to verify that this approach is valid. Let me know what you think! :) --------- Co-authored-by: Tim de Jager --- package.json | 6 + package.nls.json | 1 + resources/report_issue_user_settings.json | 1 + src/client/common/configSettings.ts | 5 + src/client/common/installer/pixiInstaller.ts | 81 +++++ .../common/installer/productInstaller.ts | 2 +- .../common/installer/serviceRegistry.ts | 2 + .../common/process/pythonEnvironment.ts | 18 + .../common/process/pythonExecutionFactory.ts | 30 +- src/client/common/serviceRegistry.ts | 6 + .../pixiActivationProvider.ts | 110 +++++++ src/client/common/terminal/helper.ts | 14 +- src/client/common/terminal/types.ts | 1 + src/client/common/types.ts | 1 + .../commands/setInterpreter.ts | 1 + .../pythonEnvironments/base/info/envKind.ts | 3 + .../pythonEnvironments/base/info/index.ts | 2 + .../base/locators/lowLevel/pixiLocator.ts | 77 +++++ .../common/environmentIdentifier.ts | 2 + .../common/environmentManagers/pixi.ts | 308 ++++++++++++++++++ src/client/pythonEnvironments/index.ts | 2 + src/client/pythonEnvironments/info/index.ts | 7 +- src/client/pythonEnvironments/legacyIOC.ts | 1 + .../configSettings.unit.test.ts | 2 + .../terminals/activation.conda.unit.test.ts | 2 + src/test/common/terminals/helper.unit.test.ts | 4 + .../base/info/envKind.unit.test.ts | 1 + .../lowLevel/pixiLocator.unit.test.ts | 92 ++++++ .../environmentManagers/pixi.unit.test.ts | 139 ++++++++ .../multi-env/.pixi/envs/py310/bin/python | 1 + .../.pixi/envs/py310/conda-meta/pixi | 0 .../multi-env/.pixi/envs/py311/bin/python | 1 + .../.pixi/envs/py311/conda-meta/pixi | 0 .../pixi/multi-env/.pixi/envs/py311/python | 0 .../envlayouts/pixi/multi-env/pixi.toml | 14 + .../non-windows/.pixi/envs/default/bin/python | 0 .../.pixi/envs/default/conda-meta/pixi | 0 .../envlayouts/pixi/non-windows/pixi.toml | 11 + .../windows/.pixi/envs/default/python.exe | 1 + .../common/envlayouts/pixi/windows/pixi.toml | 12 + 40 files changed, 956 insertions(+), 5 deletions(-) create mode 100644 src/client/common/installer/pixiInstaller.ts create mode 100644 src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts create mode 100644 src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts create mode 100644 src/client/pythonEnvironments/common/environmentManagers/pixi.ts create mode 100644 src/test/pythonEnvironments/base/locators/lowLevel/pixiLocator.unit.test.ts create mode 100644 src/test/pythonEnvironments/common/environmentManagers/pixi.unit.test.ts create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/bin/python create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/conda-meta/pixi create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/bin/python create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/conda-meta/pixi create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/python create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/pixi.toml create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/bin/python create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/conda-meta/pixi create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/pixi.toml create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/windows/.pixi/envs/default/python.exe create mode 100644 src/test/pythonEnvironments/common/envlayouts/pixi/windows/pixi.toml diff --git a/package.json b/package.json index 3a9dd5ab40ef..27800f2dae54 100644 --- a/package.json +++ b/package.json @@ -582,6 +582,12 @@ "scope": "machine-overridable", "type": "string" }, + "python.pixiToolPath": { + "default": "pixi", + "description": "%python.pixiToolPath.description%", + "scope": "machine-overridable", + "type": "string" + }, "python.tensorBoard.logDirectory": { "default": "", "description": "%python.tensorBoard.logDirectory.description%", diff --git a/package.nls.json b/package.nls.json index 50c02073bfda..dcf8a2ddf5f9 100644 --- a/package.nls.json +++ b/package.nls.json @@ -61,6 +61,7 @@ "python.locator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.", "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", + "python.pixiToolPath.description": "Path to the pixi executable.", "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", "python.REPL.sendToNativeREPL.description": "Toggle to send code to Python REPL instead of the terminal on execution. Turning this on will change the behavior for both Smart Send and Run Selection/Line in the Context Menu.", "python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.", diff --git a/resources/report_issue_user_settings.json b/resources/report_issue_user_settings.json index eea4ca007da6..ef85267c0e65 100644 --- a/resources/report_issue_user_settings.json +++ b/resources/report_issue_user_settings.json @@ -11,6 +11,7 @@ "condaPath": "placeholder", "pipenvPath": "placeholder", "poetryPath": "placeholder", + "pixiToolPath": "placeholder", "devOptions": false, "globalModuleInstallation": false, "languageServer": true, diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 88a5007467bb..dbd78c5287e5 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -103,6 +103,8 @@ export class PythonSettings implements IPythonSettings { public poetryPath = ''; + public pixiToolPath = ''; + public devOptions: string[] = []; public autoComplete!: IAutoCompleteSettings; @@ -260,6 +262,9 @@ export class PythonSettings implements IPythonSettings { this.pipenvPath = pipenvPath && pipenvPath.length > 0 ? getAbsolutePath(pipenvPath, workspaceRoot) : pipenvPath; const poetryPath = systemVariables.resolveAny(pythonSettings.get('poetryPath'))!; this.poetryPath = poetryPath && poetryPath.length > 0 ? getAbsolutePath(poetryPath, workspaceRoot) : poetryPath; + const pixiToolPath = systemVariables.resolveAny(pythonSettings.get('pixiToolPath'))!; + this.pixiToolPath = + pixiToolPath && pixiToolPath.length > 0 ? getAbsolutePath(pixiToolPath, workspaceRoot) : pixiToolPath; this.interpreter = pythonSettings.get('interpreter') ?? { infoVisibility: 'onPythonRelated', diff --git a/src/client/common/installer/pixiInstaller.ts b/src/client/common/installer/pixiInstaller.ts new file mode 100644 index 000000000000..8a2278830b51 --- /dev/null +++ b/src/client/common/installer/pixiInstaller.ts @@ -0,0 +1,81 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; +import { getEnvPath } from '../../pythonEnvironments/base/info/env'; +import { EnvironmentType, ModuleInstallerType } from '../../pythonEnvironments/info'; +import { ExecutionInfo, IConfigurationService } from '../types'; +import { isResource } from '../utils/misc'; +import { ModuleInstaller } from './moduleInstaller'; +import { InterpreterUri } from './types'; +import { getPixiEnvironmentFromInterpreter } from '../../pythonEnvironments/common/environmentManagers/pixi'; + +/** + * A Python module installer for a pixi project. + */ +@injectable() +export class PixiInstaller extends ModuleInstaller { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + ) { + super(serviceContainer); + } + + public get name(): string { + return 'Pixi'; + } + + public get displayName(): string { + return 'pixi'; + } + + public get type(): ModuleInstallerType { + return ModuleInstallerType.Pixi; + } + + public get priority(): number { + return 20; + } + + public async isSupported(resource?: InterpreterUri): Promise { + if (isResource(resource)) { + const interpreter = await this.serviceContainer + .get(IInterpreterService) + .getActiveInterpreter(resource); + if (!interpreter || interpreter.envType !== EnvironmentType.Pixi) { + return false; + } + + const pixiEnv = await getPixiEnvironmentFromInterpreter(interpreter.path); + return pixiEnv !== undefined; + } + return resource.envType === EnvironmentType.Pixi; + } + + /** + * Return the commandline args needed to install the module. + */ + protected async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { + const pythonPath = isResource(resource) + ? this.configurationService.getSettings(resource).pythonPath + : getEnvPath(resource.path, resource.envPath).path ?? ''; + + const pixiEnv = await getPixiEnvironmentFromInterpreter(pythonPath); + const execPath = pixiEnv?.pixi.command; + + let args = ['add', moduleName]; + const manifestPath = pixiEnv?.manifestPath; + if (manifestPath !== undefined) { + args = args.concat(['--manifest-path', manifestPath]); + } + + return { + args, + execPath, + }; + } +} diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index fba860aaa383..831eb33efbc6 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -43,7 +43,7 @@ export { Product } from '../types'; // Installer implementations can check this to determine a suitable installation channel for a product // This is temporary and can be removed when https://github.com/microsoft/vscode-jupyter/issues/5034 is unblocked const UnsupportedChannelsForProduct = new Map>([ - [Product.torchProfilerInstallName, new Set([EnvironmentType.Conda])], + [Product.torchProfilerInstallName, new Set([EnvironmentType.Conda, EnvironmentType.Pixi])], ]); abstract class BaseInstaller implements IBaseInstaller { diff --git a/src/client/common/installer/serviceRegistry.ts b/src/client/common/installer/serviceRegistry.ts index d4d8a05c3a49..1e273ada818c 100644 --- a/src/client/common/installer/serviceRegistry.ts +++ b/src/client/common/installer/serviceRegistry.ts @@ -8,12 +8,14 @@ import { InstallationChannelManager } from './channelManager'; import { CondaInstaller } from './condaInstaller'; import { PipEnvInstaller } from './pipEnvInstaller'; import { PipInstaller } from './pipInstaller'; +import { PixiInstaller } from './pixiInstaller'; import { PoetryInstaller } from './poetryInstaller'; import { DataScienceProductPathService, TestFrameworkProductPathService } from './productPath'; import { ProductService } from './productService'; import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types'; export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IModuleInstaller, PixiInstaller); serviceManager.addSingleton(IModuleInstaller, CondaInstaller); serviceManager.addSingleton(IModuleInstaller, PipInstaller); serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); diff --git a/src/client/common/process/pythonEnvironment.ts b/src/client/common/process/pythonEnvironment.ts index 9566f373aa91..cbf898ac5f50 100644 --- a/src/client/common/process/pythonEnvironment.ts +++ b/src/client/common/process/pythonEnvironment.ts @@ -12,6 +12,7 @@ import { isTestExecution } from '../constants'; import { IFileSystem } from '../platform/types'; import * as internalPython from './internal/python'; import { ExecutionResult, IProcessService, IPythonEnvironment, ShellOptions, SpawnOptions } from './types'; +import { PixiEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/pixi'; const cachedExecutablePath: Map> = new Map>(); @@ -173,6 +174,23 @@ export async function createCondaEnv( return new PythonEnvironment(interpreterPath, deps); } +export async function createPixiEnv( + pixiEnv: PixiEnvironmentInfo, + // These are used to generate the deps. + procs: IProcessService, + fs: IFileSystem, +): Promise { + const pythonArgv = pixiEnv.pixi.getRunPythonArgs(pixiEnv.manifestPath, pixiEnv.envName); + const deps = createDeps( + async (filename) => fs.pathExists(filename), + pythonArgv, + pythonArgv, + (file, args, opts) => procs.exec(file, args, opts), + (command, opts) => procs.shellExec(command, opts), + ); + return new PythonEnvironment(pixiEnv.interpreterPath, deps); +} + export function createMicrosoftStoreEnv( pythonPath: string, // These are used to generate the deps. diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index fc13e7f2346c..939c91514952 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -10,7 +10,7 @@ import { EventName } from '../../telemetry/constants'; import { IFileSystem } from '../platform/types'; import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } from '../types'; import { ProcessService } from './proc'; -import { createCondaEnv, createPythonEnv, createMicrosoftStoreEnv } from './pythonEnvironment'; +import { createCondaEnv, createPythonEnv, createMicrosoftStoreEnv, createPixiEnv } from './pythonEnvironment'; import { createPythonProcessService } from './pythonProcess'; import { ExecutionFactoryCreateWithEnvironmentOptions, @@ -25,6 +25,7 @@ import { import { IInterpreterAutoSelectionService } from '../../interpreter/autoSelection/types'; import { sleep } from '../utils/async'; import { traceError } from '../../logging'; +import { getPixiEnvironmentFromInterpreter } from '../../pythonEnvironments/common/environmentManagers/pixi'; @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { @@ -79,6 +80,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { } const processService: IProcessService = await this.processServiceFactory.create(options.resource); + const pixiExecutionService = await this.createPixiExecutionService(pythonPath, processService); + if (pixiExecutionService) { + return pixiExecutionService; + } + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); if (condaExecutionService) { return condaExecutionService; @@ -116,6 +122,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { processService.on('exec', this.logger.logProcess.bind(this.logger)); this.disposables.push(processService); + const pixiExecutionService = await this.createPixiExecutionService(pythonPath, processService); + if (pixiExecutionService) { + return pixiExecutionService; + } + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); if (condaExecutionService) { return condaExecutionService; @@ -139,6 +150,23 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { } return createPythonService(processService, env); } + + public async createPixiExecutionService( + pythonPath: string, + processService: IProcessService, + ): Promise { + const pixiEnvironment = await getPixiEnvironmentFromInterpreter(pythonPath); + if (!pixiEnvironment) { + return undefined; + } + + const env = await createPixiEnv(pixiEnvironment, processService, this.fileSystem); + if (!env) { + return undefined; + } + + return createPythonService(processService, env); + } } function createPythonService(procService: IProcessService, env: IPythonEnvironment): IPythonExecutionService { diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index 8c872c3113ba..307d3ffe038f 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -89,6 +89,7 @@ import { ContextKeyManager } from './application/contextKeyManager'; import { CreatePythonFileCommandHandler } from './application/commands/createPythonFile'; import { RequireJupyterPrompt } from '../jupyter/requireJupyterPrompt'; import { isWindows } from './platform/platformService'; +import { PixiActivationCommandProvider } from './terminal/environmentActivationProviders/pixiActivationProvider'; export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingletonInstance(IsWindows, isWindows()); @@ -161,6 +162,11 @@ export function registerTypes(serviceManager: IServiceManager): void { CondaActivationCommandProvider, TerminalActivationProviders.conda, ); + serviceManager.addSingleton( + ITerminalActivationCommandProvider, + PixiActivationCommandProvider, + TerminalActivationProviders.pixi, + ); serviceManager.addSingleton( ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, diff --git a/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts new file mode 100644 index 000000000000..f9110f6be60c --- /dev/null +++ b/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts @@ -0,0 +1,110 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; +import { traceError } from '../../../logging'; +import { + getPixiEnvironmentFromInterpreter, + isNonDefaultPixiEnvironmentName, +} from '../../../pythonEnvironments/common/environmentManagers/pixi'; +import { exec } from '../../../pythonEnvironments/common/externalDependencies'; +import { splitLines } from '../../stringUtils'; + +@injectable() +export class PixiActivationCommandProvider implements ITerminalActivationCommandProvider { + constructor(@inject(IInterpreterService) private readonly interpreterService: IInterpreterService) {} + + // eslint-disable-next-line class-methods-use-this + public isShellSupported(targetShell: TerminalShellType): boolean { + return shellTypeToPixiShell(targetShell) !== undefined; + } + + public async getActivationCommands( + resource: Uri | undefined, + targetShell: TerminalShellType, + ): Promise { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (!interpreter) { + return undefined; + } + + return this.getActivationCommandsForInterpreter(interpreter.path, targetShell); + } + + public async getActivationCommandsForInterpreter( + pythonPath: string, + targetShell: TerminalShellType, + ): Promise { + const pixiEnv = await getPixiEnvironmentFromInterpreter(pythonPath); + if (!pixiEnv) { + return undefined; + } + + const command = ['shell-hook', '--manifest-path', pixiEnv.manifestPath]; + if (isNonDefaultPixiEnvironmentName(pixiEnv.envName)) { + command.push('--environment'); + command.push(pixiEnv.envName); + } + + const pixiTargetShell = shellTypeToPixiShell(targetShell); + if (pixiTargetShell) { + command.push('--shell'); + command.push(pixiTargetShell); + } + + const shellHookOutput = await exec(pixiEnv.pixi.command, command, { + throwOnStdErr: false, + }).catch(traceError); + if (!shellHookOutput) { + return undefined; + } + + return splitLines(shellHookOutput.stdout, { + removeEmptyEntries: true, + trim: true, + }); + } +} + +/** + * Returns the name of a terminal shell type within Pixi. + */ +function shellTypeToPixiShell(targetShell: TerminalShellType): string | undefined { + switch (targetShell) { + case TerminalShellType.powershell: + case TerminalShellType.powershellCore: + return 'powershell'; + case TerminalShellType.commandPrompt: + return 'cmd'; + + case TerminalShellType.zsh: + return 'zsh'; + + case TerminalShellType.fish: + return 'fish'; + + case TerminalShellType.nushell: + return 'nushell'; + + case TerminalShellType.xonsh: + return 'xonsh'; + + case TerminalShellType.cshell: + // Explicitly unsupported + return undefined; + + case TerminalShellType.gitbash: + case TerminalShellType.bash: + case TerminalShellType.wsl: + case TerminalShellType.tcshell: + case TerminalShellType.other: + default: + return 'bash'; + } +} diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index f1a89df10786..9fcdd98bd289 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -50,6 +50,9 @@ export class TerminalHelper implements ITerminalHelper { @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pipenv) private readonly pipenv: ITerminalActivationCommandProvider, + @inject(ITerminalActivationCommandProvider) + @named(TerminalActivationProviders.pixi) + private readonly pixi: ITerminalActivationCommandProvider, @multiInject(IShellDetector) shellDetectors: IShellDetector[], ) { this.shellDetector = new ShellDetector(this.platform, shellDetectors); @@ -75,7 +78,14 @@ export class TerminalHelper implements ITerminalHelper { resource?: Uri, interpreter?: PythonEnvironment, ): Promise { - const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; + const providers = [ + this.pixi, + this.pipenv, + this.pyenv, + this.bashCShellFish, + this.commandPromptAndPowerShell, + this.nushell, + ]; const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers); this.sendTelemetry( terminalShellType, @@ -93,7 +103,7 @@ export class TerminalHelper implements ITerminalHelper { if (this.platform.osType === OSType.Unknown) { return; } - const providers = [this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; + const providers = [this.pixi, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; const promise = this.getActivationCommands(resource, interpreter, shell, providers); this.sendTelemetry( shell, diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index 303188682378..49f42e7c19f6 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -15,6 +15,7 @@ export enum TerminalActivationProviders { pyenv = 'pyenv', conda = 'conda', pipenv = 'pipenv', + pixi = 'pixi', } export enum TerminalShellType { powershell = 'powershell', diff --git a/src/client/common/types.ts b/src/client/common/types.ts index d4a0921140ec..754e08004213 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -167,6 +167,7 @@ export interface IPythonSettings { readonly condaPath: string; readonly pipenvPath: string; readonly poetryPath: string; + readonly pixiToolPath: string; readonly devOptions: string[]; readonly testing: ITestingSettings; readonly autoComplete: IAutoCompleteSettings; diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts index 5d01cbaafb7a..0a663ac9f0d3 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts @@ -75,6 +75,7 @@ export namespace EnvGroups { export const Venv = 'Venv'; export const Poetry = 'Poetry'; export const Hatch = 'Hatch'; + export const Pixi = 'Pixi'; export const VirtualEnvWrapper = 'VirtualEnvWrapper'; export const ActiveState = 'ActiveState'; export const Recommended = Common.recommended; diff --git a/src/client/pythonEnvironments/base/info/envKind.ts b/src/client/pythonEnvironments/base/info/envKind.ts index 77ed7f22533e..08f4ce55d464 100644 --- a/src/client/pythonEnvironments/base/info/envKind.ts +++ b/src/client/pythonEnvironments/base/info/envKind.ts @@ -16,6 +16,7 @@ export function getKindDisplayName(kind: PythonEnvKind): string { [PythonEnvKind.Pyenv, 'pyenv'], [PythonEnvKind.Poetry, 'Poetry'], [PythonEnvKind.Hatch, 'Hatch'], + [PythonEnvKind.Pixi, 'Pixi'], [PythonEnvKind.Custom, 'custom'], // For now we treat OtherGlobal like Unknown. [PythonEnvKind.Venv, 'venv'], @@ -47,6 +48,7 @@ export function getKindDisplayName(kind: PythonEnvKind): string { * 4. Pyenv * 5. Poetry * 6. Hatch + * 7. Pixi * * Next level we have the following virtual environment tools. The are here because they * are consumed by the tools above, and can also be used independently. @@ -59,6 +61,7 @@ export function getKindDisplayName(kind: PythonEnvKind): string { export function getPrioritizedEnvKinds(): PythonEnvKind[] { return [ PythonEnvKind.Pyenv, + PythonEnvKind.Pixi, // Placed here since Pixi environments are essentially Conda envs PythonEnvKind.Conda, PythonEnvKind.MicrosoftStore, PythonEnvKind.Pipenv, diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 1806109142fd..4547e7606308 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -16,6 +16,7 @@ export enum PythonEnvKind { Pyenv = 'global-pyenv', Poetry = 'poetry', Hatch = 'hatch', + Pixi = 'pixi', ActiveState = 'activestate', Custom = 'global-custom', OtherGlobal = 'global-other', @@ -46,6 +47,7 @@ export interface EnvPathType { export const virtualEnvKinds = [ PythonEnvKind.Poetry, PythonEnvKind.Hatch, + PythonEnvKind.Pixi, PythonEnvKind.Pipenv, PythonEnvKind.Venv, PythonEnvKind.VirtualEnvWrapper, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts new file mode 100644 index 000000000000..7cdc78ec6f10 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { chain, iterable } from '../../../../common/utils/async'; +import { traceError, traceVerbose } from '../../../../logging'; +import { getCondaInterpreterPath } from '../../../common/environmentManagers/conda'; +import { Pixi } from '../../../common/environmentManagers/pixi'; +import { pathExists } from '../../../common/externalDependencies'; +import { PythonEnvKind } from '../../info'; +import { IPythonEnvsIterator, BasicEnvInfo } from '../../locator'; +import { FSWatcherKind, FSWatchingLocator } from './fsWatchingLocator'; + +/** + * Returns all virtual environment locations to look for in a workspace. + */ +async function getVirtualEnvDirs(root: string): Promise { + const pixi = await Pixi.getPixi(); + const envDirs = (await pixi?.getEnvList(root)) ?? []; + return asyncFilter(envDirs, pathExists); +} + +/** + * Returns all virtual environment locations to look for in a workspace. + */ +function getVirtualEnvRootDirs(root: string): string[] { + return [path.join(path.join(root, '.pixi'), 'envs')]; +} + +export class PixiLocator extends FSWatchingLocator { + public readonly providerId: string = 'pixi'; + + public constructor(private readonly root: string) { + super( + async () => getVirtualEnvRootDirs(this.root), + async () => PythonEnvKind.Pixi, + { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. + delayOnCreated: 1000, + }, + FSWatcherKind.Workspace, + ); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envDirs = await getVirtualEnvDirs(root); + const envGenerators = envDirs.map((envDir) => { + async function* generator() { + traceVerbose(`Searching for Pixi virtual envs in: ${envDir}`); + const filename = await getCondaInterpreterPath(envDir); + if (filename !== undefined) { + try { + yield { + executablePath: filename, + kind: PythonEnvKind.Pixi, + envPath: envDir, + }; + + traceVerbose(`Pixi Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for Pixi envs`); + } + + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 880eed52598c..89ff84823673 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -16,6 +16,7 @@ import { } from './environmentManagers/simplevirtualenvs'; import { isMicrosoftStoreEnvironment } from './environmentManagers/microsoftStoreEnv'; import { isActiveStateEnvironment } from './environmentManagers/activestate'; +import { isPixiEnvironment } from './environmentManagers/pixi'; const notImplemented = () => Promise.resolve(false); @@ -31,6 +32,7 @@ function getIdentifiers(): Map Promise identifier.set(PythonEnvKind.Pipenv, isPipenvEnvironment); identifier.set(PythonEnvKind.Pyenv, isPyenvEnvironment); identifier.set(PythonEnvKind.Poetry, isPoetryEnvironment); + identifier.set(PythonEnvKind.Pixi, isPixiEnvironment); identifier.set(PythonEnvKind.Venv, isVenvEnvironment); identifier.set(PythonEnvKind.VirtualEnvWrapper, isVirtualEnvWrapperEnvironment); identifier.set(PythonEnvKind.VirtualEnv, isVirtualEnvEnvironment); diff --git a/src/client/pythonEnvironments/common/environmentManagers/pixi.ts b/src/client/pythonEnvironments/common/environmentManagers/pixi.ts new file mode 100644 index 000000000000..f3d6dc3e081e --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/pixi.ts @@ -0,0 +1,308 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { readJSON } from 'fs-extra'; +import { OSType, getOSType, getUserHomeDir } from '../../../common/utils/platform'; +import { exec, getPythonSetting, onDidChangePythonSetting, pathExists, pathExistsSync } from '../externalDependencies'; +import { cache } from '../../../common/utils/decorators'; +import { isTestExecution } from '../../../common/constants'; +import { traceError, traceVerbose, traceWarn } from '../../../logging'; +import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; + +export const PIXITOOLPATH_SETTING_KEY = 'pixiToolPath'; + +// This type corresponds to the output of 'pixi info --json', and property +// names must be spelled exactly as they are in order to match the schema. +export type PixiInfo = { + platform: string; + virtual_packages: string[]; // eslint-disable-line camelcase + version: string; + cache_dir: string; // eslint-disable-line camelcase + cache_size?: number; // eslint-disable-line camelcase + auth_dir: string; // eslint-disable-line camelcase + + project_info?: PixiProjectInfo /* eslint-disable-line camelcase */; + + environments_info: /* eslint-disable-line camelcase */ { + name: string; + features: string[]; + solve_group: string; // eslint-disable-line camelcase + environment_size: number; // eslint-disable-line camelcase + dependencies: string[]; + tasks: string[]; + channels: string[]; + prefix: string; + }[]; +}; + +export type PixiProjectInfo = { + manifest_path: string; // eslint-disable-line camelcase + last_updated: string; // eslint-disable-line camelcase + pixi_folder_size?: number; // eslint-disable-line camelcase + version: string; +}; + +export type PixiEnvMetadata = { + manifest_path: string; // eslint-disable-line camelcase + pixi_version: string; // eslint-disable-line camelcase + environment_name: string; // eslint-disable-line camelcase +}; + +export async function isPixiEnvironment(interpreterPath: string): Promise { + const prefix = getPrefixFromInterpreterPath(interpreterPath); + return ( + pathExists(path.join(prefix, 'conda-meta/pixi')) || pathExists(path.join(prefix, 'conda-meta/pixi_env_prefix')) + ); +} + +/** + * Returns the path to the environment directory based on the interpreter path. + */ +export function getPrefixFromInterpreterPath(interpreterPath: string): string { + const interpreterDir = path.dirname(interpreterPath); + if (getOSType() === OSType.Windows) { + return interpreterDir; + } + return path.dirname(interpreterDir); +} + +/** Wraps the "pixi" utility, and exposes its functionality. + */ +export class Pixi { + /** + * Locating pixi binary can be expensive, since it potentially involves spawning or + * trying to spawn processes; so we only do it once per session. + */ + private static pixiPromise: Promise | undefined; + + /** + * Creates a Pixi service corresponding to the corresponding "pixi" command. + * + * @param command - Command used to run pixi. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + */ + constructor(public readonly command: string) { + onDidChangePythonSetting(PIXITOOLPATH_SETTING_KEY, () => { + Pixi.pixiPromise = undefined; + }); + } + + /** + * Returns a Pixi instance corresponding to the binary which can be used to run commands for the cwd. + * + * Pixi commands can be slow and so can be bottleneck to overall discovery time. So trigger command + * execution as soon as possible. To do that we need to ensure the operations before the command are + * performed synchronously. + */ + public static async getPixi(): Promise { + if (Pixi.pixiPromise === undefined || isTestExecution()) { + Pixi.pixiPromise = Pixi.locate(); + } + return Pixi.pixiPromise; + } + + private static async locate(): Promise { + // First thing this method awaits on should be pixi command execution, hence perform all operations + // before that synchronously. + + traceVerbose(`Getting pixi`); + // Produce a list of candidate binaries to be probed by exec'ing them. + function* getCandidates() { + // Read the pixi location from the settings. + try { + const customPixiToolPath = getPythonSetting(PIXITOOLPATH_SETTING_KEY); + if (customPixiToolPath && customPixiToolPath !== 'pixi') { + // If user has specified a custom pixi path, use it first. + yield customPixiToolPath; + } + } catch (ex) { + traceError(`Failed to get pixi setting`, ex); + } + + // Check unqualified filename, in case it's on PATH. + yield 'pixi'; + + // Check the default installation location + const home = getUserHomeDir(); + if (home) { + const defaultpixiToolPath = path.join(home, '.pixi', 'bin', 'pixi'); + if (pathExistsSync(defaultpixiToolPath)) { + yield defaultpixiToolPath; + } + } + } + + // Probe the candidates, and pick the first one that exists and does what we need. + for (const pixiToolPath of getCandidates()) { + traceVerbose(`Probing pixi binary: ${pixiToolPath}`); + const pixi = new Pixi(pixiToolPath); + const pixiVersion = await pixi.getVersion(); + if (pixiVersion !== undefined) { + traceVerbose(`Found pixi ${pixiVersion} via filesystem probing: ${pixiToolPath}`); + return pixi; + } + traceVerbose(`Failed to find pixi: ${pixiToolPath}`); + } + + // Didn't find anything. + traceVerbose(`No pixi binary found`); + return undefined; + } + + /** + * Retrieves list of Python environments known to this pixi for the specified directory. + * + * Corresponds to "pixi info --json" and extracting the environments. Swallows errors if any. + */ + public async getEnvList(cwd: string): Promise { + const pixiInfo = await this.getPixiInfo(cwd); + // eslint-disable-next-line camelcase + return pixiInfo?.environments_info.map((env) => env.prefix); + } + + /** + * Method that runs `pixi info` and returns the result. The value is cached for "only" 1 second + * because the output changes if the project manifest is modified. + */ + @cache(1_000, true, 1_000) + public async getPixiInfo(cwd: string): Promise { + const infoOutput = await exec(this.command, ['info', '--json'], { + cwd, + throwOnStdErr: false, + }).catch(traceError); + if (!infoOutput) { + return undefined; + } + + const pixiInfo: PixiInfo = JSON.parse(infoOutput.stdout); + return pixiInfo; + } + + /** + * Runs `pixi --version` and returns the version part of the output. + */ + @cache(30_000, true, 10_000) + public async getVersion(): Promise { + const versionOutput = await exec(this.command, ['--version'], { + throwOnStdErr: false, + }).catch(traceError); + if (!versionOutput) { + return undefined; + } + + return versionOutput.stdout.split(' ')[1].trim(); + } + + /** + * Returns the command line arguments to run `python` within a specific pixi environment. + * @param manifestPath The path to the manifest file used by pixi. + * @param envName The name of the environment in the pixi project + * @param isolatedFlag Whether to add `-I` to the python invocation. + * @returns A list of arguments that can be passed to exec. + */ + public getRunPythonArgs(manifestPath: string, envName?: string, isolatedFlag = false): string[] { + let python = [this.command, 'run', '--manifest-path', manifestPath]; + if (isNonDefaultPixiEnvironmentName(envName)) { + python = python.concat(['--environment', envName]); + } + + python.push('python'); + if (isolatedFlag) { + python.push('-I'); + } + return [...python, OUTPUT_MARKER_SCRIPT]; + } + + /** + * Starting from Pixi 0.24.0, each environment has a special file that records some information + * about which manifest created the environment. + * + * @param envDir The root directory (or prefix) of a conda environment + */ + @cache(5_000, true, 10_000) + // eslint-disable-next-line class-methods-use-this + async getPixiEnvironmentMetadata(envDir: string): Promise { + const pixiPath = path.join(envDir, 'conda-meta/pixi'); + const result: PixiEnvMetadata | undefined = await readJSON(pixiPath).catch(traceVerbose); + return result; + } +} + +export type PixiEnvironmentInfo = { + interpreterPath: string; + pixi: Pixi; + pixiVersion: string; + manifestPath: string; + envName?: string; +}; + +/** + * Given the location of an interpreter, try to deduce information about the environment in which it + * resides. + * @param interpreterPath The full path to the interpreter. + * @param pixi Optionally a pixi instance. If this is not specified it will be located. + * @returns Information about the pixi environment. + */ +export async function getPixiEnvironmentFromInterpreter( + interpreterPath: string, + pixi?: Pixi, +): Promise { + if (!interpreterPath) { + return undefined; + } + + const prefix = getPrefixFromInterpreterPath(interpreterPath); + + // Find the pixi executable for the project + pixi = pixi || (await Pixi.getPixi()); + if (!pixi) { + traceWarn(`could not find a pixi interpreter for the interpreter at ${interpreterPath}`); + return undefined; + } + + // Check if the environment has pixi metadata that we can source. + const metadata = await pixi.getPixiEnvironmentMetadata(prefix); + if (metadata !== undefined) { + return { + interpreterPath, + pixi, + pixiVersion: metadata.pixi_version, + manifestPath: metadata.manifest_path, + envName: metadata.environment_name, + }; + } + + // Otherwise, we'll have to try to deduce this information. + + // Usually the pixi environments are stored under `/.pixi/envs//`. So, + // we walk backwards to determine the project directory. + const envName = path.basename(prefix); + const envsDir = path.dirname(prefix); + const dotPixiDir = path.dirname(envsDir); + const pixiProjectDir = path.dirname(dotPixiDir); + + // Invoke pixi to get information about the pixi project + const pixiInfo = await pixi.getPixiInfo(pixiProjectDir); + if (!pixiInfo || !pixiInfo.project_info) { + traceWarn(`failed to determine pixi project information for the interpreter at ${interpreterPath}`); + return undefined; + } + + return { + interpreterPath, + pixi, + pixiVersion: pixiInfo.version, + manifestPath: pixiInfo.project_info.manifest_path, + envName, + }; +} + +/** + * Returns true if the given environment name is *not* the default environment. + */ +export function isNonDefaultPixiEnvironmentName(envName?: string): envName is string { + return envName !== undefined && envName !== 'default'; +} diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index d93d232242c1..33a6136d35a5 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -39,6 +39,7 @@ import { IDisposable } from '../common/types'; import { traceError } from '../logging'; import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; +import { PixiLocator } from './base/locators/lowLevel/pixiLocator'; import { NativeLocator } from './base/locators/lowLevel/nativeLocator'; import { getConfiguration } from '../common/vscodeApis/workspaceApis'; @@ -206,6 +207,7 @@ function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath), new HatchLocator(root.fsPath), + new PixiLocator(root.fsPath), new CustomWorkspaceLocator(root.fsPath), ], // Add an ILocator factory func here for each kind of workspace-rooted locator. diff --git a/src/client/pythonEnvironments/info/index.ts b/src/client/pythonEnvironments/info/index.ts index 716d4bcd938f..08310767914a 100644 --- a/src/client/pythonEnvironments/info/index.ts +++ b/src/client/pythonEnvironments/info/index.ts @@ -20,6 +20,7 @@ export enum EnvironmentType { MicrosoftStore = 'MicrosoftStore', Poetry = 'Poetry', Hatch = 'Hatch', + Pixi = 'Pixi', VirtualEnvWrapper = 'VirtualEnvWrapper', ActiveState = 'ActiveState', Global = 'Global', @@ -28,7 +29,7 @@ export enum EnvironmentType { /** * These envs are only created for a specific workspace, which we're able to detect. */ -export const workspaceVirtualEnvTypes = [EnvironmentType.Poetry, EnvironmentType.Pipenv]; +export const workspaceVirtualEnvTypes = [EnvironmentType.Poetry, EnvironmentType.Pipenv, EnvironmentType.Pixi]; export const virtualEnvTypes = [ ...workspaceVirtualEnvTypes, @@ -48,6 +49,7 @@ export enum ModuleInstallerType { Pip = 'Pip', Poetry = 'Poetry', Pipenv = 'Pipenv', + Pixi = 'Pixi', } /** @@ -123,6 +125,9 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType): string case EnvironmentType.Hatch: { return 'Hatch'; } + case EnvironmentType.Pixi: { + return 'pixi'; + } case EnvironmentType.VirtualEnvWrapper: { return 'virtualenvwrapper'; } diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index 4ef0894a470d..9d161f8b1b9f 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -39,6 +39,7 @@ const convertedKinds = new Map( [PythonEnvKind.Pipenv]: EnvironmentType.Pipenv, [PythonEnvKind.Poetry]: EnvironmentType.Poetry, [PythonEnvKind.Hatch]: EnvironmentType.Hatch, + [PythonEnvKind.Pixi]: EnvironmentType.Pixi, [PythonEnvKind.Venv]: EnvironmentType.Venv, [PythonEnvKind.VirtualEnvWrapper]: EnvironmentType.VirtualEnvWrapper, [PythonEnvKind.ActiveState]: EnvironmentType.ActiveState, diff --git a/src/test/common/configSettings/configSettings.unit.test.ts b/src/test/common/configSettings/configSettings.unit.test.ts index 83b5b4a3d524..c4389629e0ec 100644 --- a/src/test/common/configSettings/configSettings.unit.test.ts +++ b/src/test/common/configSettings/configSettings.unit.test.ts @@ -83,6 +83,7 @@ suite('Python Settings', async () => { 'pipenvPath', 'envFile', 'poetryPath', + 'pixiToolPath', 'defaultInterpreterPath', ]) { config @@ -141,6 +142,7 @@ suite('Python Settings', async () => { 'pipenvPath', 'envFile', 'poetryPath', + 'pixiToolPath', 'defaultInterpreterPath', ].forEach(async (settingName) => { testIfValueIsUpdated(settingName, 'stringValue'); diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index 84e4bffacfc1..39bf58a9a36b 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -31,6 +31,7 @@ import { getNamesAndValues } from '../../../client/common/utils/enum'; import { IComponentAdapter, ICondaService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { IServiceContainer } from '../../../client/ioc/types'; +import { PixiActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pixiActivationProvider'; suite('Terminal Environment Activation conda', () => { let terminalHelper: TerminalHelper; @@ -114,6 +115,7 @@ suite('Terminal Environment Activation conda', () => { mock(Nushell), mock(PyEnvActivationCommandProvider), mock(PipEnvActivationCommandProvider), + mock(PixiActivationCommandProvider), [], ); }); diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index b6a8d44ac030..e4a0ab9bd3e8 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -32,6 +32,7 @@ import { IComponentAdapter } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; +import { PixiActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pixiActivationProvider'; suite('Terminal Service helpers', () => { let helper: TerminalHelper; @@ -46,6 +47,7 @@ suite('Terminal Service helpers', () => { let nushellActivationProvider: ITerminalActivationCommandProvider; let pyenvActivationProvider: ITerminalActivationCommandProvider; let pipenvActivationProvider: ITerminalActivationCommandProvider; + let pixiActivationProvider: ITerminalActivationCommandProvider; let pythonSettings: PythonSettings; let shellDetectorIdentifyTerminalShell: sinon.SinonStub<[(Terminal | undefined)?], TerminalShellType>; let mockDetector: IShellDetector; @@ -72,6 +74,7 @@ suite('Terminal Service helpers', () => { nushellActivationProvider = mock(Nushell); pyenvActivationProvider = mock(PyEnvActivationCommandProvider); pipenvActivationProvider = mock(PipEnvActivationCommandProvider); + pixiActivationProvider = mock(PixiActivationCommandProvider); pythonSettings = mock(PythonSettings); shellDetectorIdentifyTerminalShell = sinon.stub(ShellDetector.prototype, 'identifyTerminalShell'); helper = new TerminalHelper( @@ -86,6 +89,7 @@ suite('Terminal Service helpers', () => { instance(nushellActivationProvider), instance(pyenvActivationProvider), instance(pipenvActivationProvider), + instance(pixiActivationProvider), [instance(mockDetector)], ); } diff --git a/src/test/pythonEnvironments/base/info/envKind.unit.test.ts b/src/test/pythonEnvironments/base/info/envKind.unit.test.ts index 997c8a08c7f2..6d0866754330 100644 --- a/src/test/pythonEnvironments/base/info/envKind.unit.test.ts +++ b/src/test/pythonEnvironments/base/info/envKind.unit.test.ts @@ -14,6 +14,7 @@ const KIND_NAMES: [PythonEnvKind, string][] = [ [PythonEnvKind.Pyenv, 'pyenv'], [PythonEnvKind.Poetry, 'poetry'], [PythonEnvKind.Hatch, 'hatch'], + [PythonEnvKind.Pixi, 'pixi'], [PythonEnvKind.Custom, 'customGlobal'], [PythonEnvKind.OtherGlobal, 'otherGlobal'], [PythonEnvKind.Venv, 'venv'], diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/pixiLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/pixiLocator.unit.test.ts new file mode 100644 index 000000000000..6bb147b41832 --- /dev/null +++ b/src/test/pythonEnvironments/base/locators/lowLevel/pixiLocator.unit.test.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as sinon from 'sinon'; +import * as path from 'path'; +import { PixiLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/pixiLocator'; +import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import * as platformUtils from '../../../../../client/common/utils/platform'; +import { makeExecHandler, projectDirs } from '../../../common/environmentManagers/pixi.unit.test'; +import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; +import { createBasicEnv } from '../../common'; +import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import { assertBasicEnvsEqual } from '../envTestUtils'; + +suite('Pixi Locator', () => { + let exec: sinon.SinonStub; + let getPythonSetting: sinon.SinonStub; + let getOSType: sinon.SinonStub; + let locator: PixiLocator; + + suiteSetup(() => { + getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting'); + getPythonSetting.returns('pixi'); + getOSType = sinon.stub(platformUtils, 'getOSType'); + exec = sinon.stub(externalDependencies, 'exec'); + }); + + suiteTeardown(() => sinon.restore()); + + suite('iterEnvs()', () => { + interface TestArgs { + projectDir: string; + osType: platformUtils.OSType; + pythonBin: string; + } + + const testProject = async ({ projectDir, osType, pythonBin }: TestArgs) => { + getOSType.returns(osType); + + locator = new PixiLocator(projectDir); + exec.callsFake(makeExecHandler({ pixiPath: 'pixi', cwd: projectDir })); + + const iterator = locator.iterEnvs(); + const actualEnvs = await getEnvs(iterator); + + const envPath = path.join(projectDir, '.pixi', 'envs', 'default'); + const expectedEnvs = [ + createBasicEnv(PythonEnvKind.Pixi, path.join(envPath, pythonBin), undefined, envPath), + ]; + assertBasicEnvsEqual(actualEnvs, expectedEnvs); + }; + + test('project with only the default env', () => + testProject({ + projectDir: projectDirs.nonWindows.path, + osType: platformUtils.OSType.Linux, + pythonBin: 'bin/python', + })); + test('project with only the default env on Windows', () => + testProject({ + projectDir: projectDirs.windows.path, + osType: platformUtils.OSType.Windows, + pythonBin: 'python.exe', + })); + + test('project with multiple environments', async () => { + getOSType.returns(platformUtils.OSType.Linux); + + exec.callsFake(makeExecHandler({ pixiPath: 'pixi', cwd: projectDirs.multiEnv.path })); + + locator = new PixiLocator(projectDirs.multiEnv.path); + const iterator = locator.iterEnvs(); + const actualEnvs = await getEnvs(iterator); + + const expectedEnvs = [ + createBasicEnv( + PythonEnvKind.Pixi, + path.join(projectDirs.multiEnv.info.environments_info[1].prefix, 'bin/python'), + undefined, + projectDirs.multiEnv.info.environments_info[1].prefix, + ), + createBasicEnv( + PythonEnvKind.Pixi, + path.join(projectDirs.multiEnv.info.environments_info[2].prefix, 'bin/python'), + undefined, + projectDirs.multiEnv.info.environments_info[2].prefix, + ), + ]; + assertBasicEnvsEqual(actualEnvs, expectedEnvs); + }); + }); +}); diff --git a/src/test/pythonEnvironments/common/environmentManagers/pixi.unit.test.ts b/src/test/pythonEnvironments/common/environmentManagers/pixi.unit.test.ts new file mode 100644 index 000000000000..d6b283c69fd3 --- /dev/null +++ b/src/test/pythonEnvironments/common/environmentManagers/pixi.unit.test.ts @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import * as path from 'path'; +import * as sinon from 'sinon'; +import { ExecutionResult, ShellOptions } from '../../../../client/common/process/types'; +import * as externalDependencies from '../../../../client/pythonEnvironments/common/externalDependencies'; +import { TEST_LAYOUT_ROOT } from '../commonTestConstants'; +import { Pixi } from '../../../../client/pythonEnvironments/common/environmentManagers/pixi'; + +export type PixiCommand = { cmd: 'info --json' } | { cmd: '--version' } | { cmd: null }; + +const textPixiDir = path.join(TEST_LAYOUT_ROOT, 'pixi'); +export const projectDirs = { + windows: { + path: path.join(textPixiDir, 'windows'), + info: { + environments_info: [ + { + prefix: path.join(textPixiDir, 'windows', '.pixi', 'envs', 'default'), + }, + ], + }, + }, + nonWindows: { + path: path.join(textPixiDir, 'non-windows'), + info: { + environments_info: [ + { + prefix: path.join(textPixiDir, 'non-windows', '.pixi', 'envs', 'default'), + }, + ], + }, + }, + multiEnv: { + path: path.join(textPixiDir, 'multi-env'), + info: { + environments_info: [ + { + prefix: path.join(textPixiDir, 'multi-env', '.pixi', 'envs', 'default'), + }, + { + prefix: path.join(textPixiDir, 'multi-env', '.pixi', 'envs', 'py310'), + }, + { + prefix: path.join(textPixiDir, 'multi-env', '.pixi', 'envs', 'py311'), + }, + ], + }, + }, +}; + +/** + * Convert the command line arguments into a typed command. + */ +export function pixiCommand(args: string[]): PixiCommand { + if (args[0] === '--version') { + return { cmd: '--version' }; + } + + if (args.length < 2) { + return { cmd: null }; + } + if (args[0] === 'info' && args[1] === '--json') { + return { cmd: 'info --json' }; + } + return { cmd: null }; +} +interface VerifyOptions { + pixiPath?: string; + cwd?: string; +} + +export function makeExecHandler(verify: VerifyOptions = {}) { + return async (file: string, args: string[], options: ShellOptions): Promise> => { + /// Verify that the executable path is indeed the one we expect it to be + if (verify.pixiPath && file !== verify.pixiPath) { + throw new Error('Command failed: not the correct pixi path'); + } + + const cmd = pixiCommand(args); + if (cmd.cmd === '--version') { + return { stdout: 'pixi 0.24.1' }; + } + + /// Verify that the working directory is the expected one + const cwd = typeof options.cwd === 'string' ? options.cwd : options.cwd?.toString(); + if (verify.cwd) { + if (!cwd || !externalDependencies.arePathsSame(cwd, verify.cwd)) { + throw new Error(`Command failed: not the correct path, expected: ${verify.cwd}, got: ${cwd}`); + } + } + + /// Convert the command into a single string + if (cmd.cmd === 'info --json') { + const project = Object.values(projectDirs).find((p) => cwd?.startsWith(p.path)); + if (!project) { + throw new Error('Command failed: could not find project'); + } + return { stdout: JSON.stringify(project.info) }; + } + + throw new Error(`Command failed: unknown command ${args}`); + }; +} + +suite('Pixi binary is located correctly', async () => { + let exec: sinon.SinonStub; + let getPythonSetting: sinon.SinonStub; + + setup(() => { + getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting'); + exec = sinon.stub(externalDependencies, 'exec'); + }); + + teardown(() => { + sinon.restore(); + }); + + const testPath = async (pixiPath: string, verify = true) => { + getPythonSetting.returns(pixiPath); + // If `verify` is false, don’t verify that the command has been called with that path + exec.callsFake(makeExecHandler(verify ? { pixiPath } : undefined)); + const pixi = await Pixi.getPixi(); + expect(pixi?.command).to.equal(pixiPath); + }; + + test('Return a Pixi instance in an empty directory', () => testPath('pixiPath', false)); + test('When user has specified a valid Pixi path, use it', () => testPath('path/to/pixi/binary')); + // 'pixi' is the default value + test('When user hasn’t specified a path, use Pixi on PATH if available', () => testPath('pixi')); + + test('Return undefined if Pixi cannot be found', async () => { + getPythonSetting.returns('pixi'); + exec.callsFake((_file: string, _args: string[], _options: ShellOptions) => + Promise.reject(new Error('Command failed')), + ); + const pixi = await Pixi.getPixi(); + expect(pixi?.command).to.equal(undefined); + }); +}); diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/bin/python b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/bin/python new file mode 100644 index 000000000000..a37b666d049e --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/bin/python @@ -0,0 +1 @@ +Not real python exe diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/conda-meta/pixi b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py310/conda-meta/pixi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/bin/python b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/bin/python new file mode 100644 index 000000000000..a37b666d049e --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/bin/python @@ -0,0 +1 @@ +Not real python exe diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/conda-meta/pixi b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/conda-meta/pixi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/python b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/.pixi/envs/py311/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/pixi.toml b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/pixi.toml new file mode 100644 index 000000000000..9b93e638e9ab --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/multi-env/pixi.toml @@ -0,0 +1,14 @@ +[project] +name = "multi-env" +channels = ["conda-forge"] +platforms = ["win-64"] + +[feature.py310.dependencies] +python = "~=3.10" + +[feature.py311.dependencies] +python = "~=3.11" + +[environments] +py310 = ["py310"] +py311 = ["py311"] diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/bin/python b/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/conda-meta/pixi b/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/.pixi/envs/default/conda-meta/pixi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/pixi.toml b/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/pixi.toml new file mode 100644 index 000000000000..f11ab3b42360 --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/non-windows/pixi.toml @@ -0,0 +1,11 @@ +[project] +name = "non-windows" +version = "0.1.0" +description = "Add a short description here" +authors = ["Bas Zalmstra "] +channels = ["conda-forge"] +platforms = ["win-64"] + +[tasks] + +[dependencies] diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/windows/.pixi/envs/default/python.exe b/src/test/pythonEnvironments/common/envlayouts/pixi/windows/.pixi/envs/default/python.exe new file mode 100644 index 000000000000..a37b666d049e --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/windows/.pixi/envs/default/python.exe @@ -0,0 +1 @@ +Not real python exe diff --git a/src/test/pythonEnvironments/common/envlayouts/pixi/windows/pixi.toml b/src/test/pythonEnvironments/common/envlayouts/pixi/windows/pixi.toml new file mode 100644 index 000000000000..1341496c5590 --- /dev/null +++ b/src/test/pythonEnvironments/common/envlayouts/pixi/windows/pixi.toml @@ -0,0 +1,12 @@ +[project] +name = "windows" +version = "0.1.0" +description = "Add a short description here" +authors = ["Bas Zalmstra "] +channels = ["conda-forge"] +platforms = ["win-64"] + +[tasks] + +[dependencies] +python = "~=3.8.0" From 1503558217bea41197f8cee4828884e4b09a9afd Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Fri, 21 Jun 2024 04:23:03 +1000 Subject: [PATCH 098/106] Add hook to `vscode_pytest` to determine number `xdist` workers to use based on count of selected tests (#23539) fixes https://github.com/microsoft/vscode-python-debugger/issues/336 --------- Co-authored-by: detachhead Co-authored-by: Karthik Nadig --- python_files/vscode_pytest/__init__.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 6e2e8b8d17d4..a7b197ca26a5 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -14,9 +14,17 @@ script_dir = pathlib.Path(__file__).parent.parent sys.path.append(os.fspath(script_dir)) sys.path.append(os.fspath(script_dir / "lib" / "python")) - from testing_tools import socket_manager # noqa: E402 -from typing import Any, Dict, List, Optional, Union, TypedDict, Literal # noqa: E402 +from typing import ( # noqa: E402 + Any, + Dict, + List, + Optional, + Union, + TypedDict, + Literal, + Generator, +) class TestData(TypedDict): @@ -882,3 +890,15 @@ def send_post_request( f"Plugin error, exception thrown while attempting to send data[vscode-pytest]: {error} \n[vscode-pytest] data: \n{data}\n", file=sys.stderr, ) + + +class DeferPlugin: + @pytest.hookimpl(wrapper=True) + def pytest_xdist_auto_num_workers(self, config: pytest.Config) -> Generator[None, int, int]: + """determine how many workers to use based on how many tests were selected in the test explorer""" + return min((yield), len(config.option.file_or_dir)) + + +def pytest_plugin_registered(plugin: object, manager: pytest.PytestPluginManager): + if manager.hasplugin("xdist") and not isinstance(plugin, DeferPlugin): + manager.register(DeferPlugin()) From 29652d3e8f4a230da87c422a1cb9563c28975b38 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 21 Jun 2024 06:49:28 +1000 Subject: [PATCH 099/106] Separate output channel for native finder (#23648) Will be very useful for diagnostics, and the likel Screenshot 2024-06-20 at 18 02 57 --- .../locators/common/nativePythonFinder.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 28cadb42dd39..1319209659f3 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable, EventEmitter, Event, workspace, Uri } from 'vscode'; +import { Disposable, EventEmitter, Event, workspace, window, Uri } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; import { PassThrough } from 'stream'; import { isWindows } from '../../../../common/platform/platformService'; import { EXTENSION_ROOT_DIR } from '../../../../constants'; -import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging'; import { createDeferred, createDeferredFrom } from '../../../../common/utils/async'; import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle'; import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator'; @@ -61,6 +60,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba private firstRefreshResults: undefined | (() => AsyncGenerator); + private readonly outputChannel = this._register(window.createOutputChannel('Python Locator', { log: true })); + constructor() { super(); this.connection = this.start(); @@ -75,7 +76,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba executable, }); - traceInfo(`Resolved Python Environment ${environment.executable} in ${duration}ms`); + this.outputChannel.info(`Resolved Python Environment ${environment.executable} in ${duration}ms`); return environment; } @@ -152,6 +153,7 @@ 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 }); const disposables: Disposable[] = []; // jsonrpc package cannot handle messages coming through too quickly. @@ -159,10 +161,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba // we have got the exit event. const readable = new PassThrough(); proc.stdout.pipe(readable, { end: false }); - proc.stderr.on('data', (data) => { - const err = data.toString(); - traceError('Native Python Finder', err); - }); + proc.stderr.on('data', (data) => this.outputChannel.error(data.toString())); const writable = new PassThrough(); writable.pipe(proc.stdin, { end: false }); const disposeStreams = new Disposable(() => { @@ -178,24 +177,24 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba disposeStreams, connection.onError((ex) => { disposeStreams.dispose(); - traceError('Error in Native Python Finder', ex); + this.outputChannel.error('Connection Error:', ex); }), connection.onNotification('log', (data: NativeLog) => { switch (data.level) { case 'info': - traceInfo(`Native Python Finder: ${data.message}`); + this.outputChannel.info(data.message); break; case 'warning': - traceWarn(`Native Python Finder: ${data.message}`); + this.outputChannel.warn(data.message); break; case 'error': - traceError(`Native Python Finder: ${data.message}`); + this.outputChannel.error(data.message); break; case 'debug': - traceVerbose(`Native Python Finder: ${data.message}`); + this.outputChannel.debug(data.message); break; default: - traceLog(`Native Python Finder: ${data.message}`); + this.outputChannel.trace(data.message); } }), connection.onClose(() => { @@ -208,7 +207,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba proc.kill(); } } catch (ex) { - traceVerbose('Error while disposing Native Python Finder', ex); + this.outputChannel.error('Error disposing finder', ex); } }, }, @@ -244,6 +243,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba disposable.add( this.connection.onNotification('environment', (data: NativeEnvInfo) => { + this.outputChannel.info(`Discovered env: ${data.executable || data.executable}`); + this.outputChannel.trace(`Discovered env info:\n ${JSON.stringify(data, undefined, 4)}`); // We know that in the Python extension if either Version of Prefix is not provided by locator // Then we end up resolving the information. // Lets do that here, @@ -259,10 +260,11 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba executable: data.executable, }) .then(({ environment, duration }) => { - traceInfo(`Resolved Python Environment ${environment.executable} in ${duration}ms`); + this.outputChannel.info(`Resolved ${environment.executable} in ${duration}ms`); + this.outputChannel.trace(`Environment resolved:\n ${JSON.stringify(data, undefined, 4)}`); discovered.fire(environment); }) - .catch((ex) => traceError(`Error in Resolving Python Environment ${JSON.stringify(data)}`, ex)); + .catch((ex) => this.outputChannel.error(`Error in Resolving ${JSON.stringify(data)}`, ex)); trackPromiseAndNotifyOnCompletion(promise); } else { discovered.fire(data); @@ -272,8 +274,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba trackPromiseAndNotifyOnCompletion( this.sendRefreshRequest() - .then(({ duration }) => traceInfo(`Native Python Finder completed in ${duration}ms`)) - .catch((ex) => traceError('Error in Native Python Finder', ex)), + .then(({ duration }) => this.outputChannel.info(`Refresh completed in ${duration}ms`)) + .catch((ex) => this.outputChannel.error('Refresh error', ex)), ); completed.promise.finally(() => disposable.dispose()); From 03b97013ff50e3d6740fcba3a648cb73d656bd8d Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 20 Jun 2024 22:20:04 -0700 Subject: [PATCH 100/106] 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> --- .github/workflows/build.yml | 33 + .github/workflows/pr-check.yml | 70 +- .gitignore | 3 +- .vscode/settings.json | 2 +- .vscodeignore | 11 +- build/azure-pipeline.pre-release.yml | 93 +- native_locator/.vscode/settings.json | 3 - native_locator/Cargo.toml | 23 - native_locator/src/common_python.rs | 89 -- native_locator/src/conda.rs | 1097 ----------------- native_locator/src/global_virtualenvs.rs | 69 -- native_locator/src/homebrew.rs | 283 ----- native_locator/src/known.rs | 81 -- native_locator/src/lib.rs | 198 --- native_locator/src/locator.rs | 27 - native_locator/src/logging.rs | 41 - native_locator/src/main.rs | 46 - native_locator/src/messaging.rs | 297 ----- native_locator/src/pipenv.rs | 60 - native_locator/src/pyenv.rs | 290 ----- native_locator/src/utils.rs | 148 --- native_locator/src/venv.rs | 54 - native_locator/src/virtualenv.rs | 85 -- native_locator/src/virtualenvwrapper.rs | 135 -- native_locator/src/windows_registry.rs | 254 ---- native_locator/src/windows_store.rs | 249 ---- native_locator/tests/common.rs | 147 --- native_locator/tests/common_python_test.rs | 55 - native_locator/tests/conda_test.rs | 302 ----- native_locator/tests/pyenv_test.rs | 255 ---- .../conda/user_home/.conda/environments.txt | 0 .../unix/conda/user_home/anaconda3/bin/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 - .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../anaconda3/envs/.conda_envs_dir_test | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 - .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../conda/user_home/anaconda3/envs/one/python | 0 .../conda/user_home/anaconda3/envs/two/python | 0 .../user_home/.conda/environments.txt | 0 .../some_location/anaconda3/bin/conda | 0 .../some_location/anaconda3/bin/python | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 - .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../anaconda3/envs/.conda_envs_dir_test | 0 .../user_home/.conda/environments.txt | 0 .../user_home/anaconda3/bin/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 - .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../anaconda3/envs/.conda_envs_dir_test | 0 .../tests/unix/known/user_home/python | 0 .../tests/unix/known/user_home/python.version | 1 - .../unix/pyenv/home/opt/homebrew/bin/pyenv | 0 .../.pyenv/versions/3.12.1/bin/python | 0 .../.pyenv/versions/3.12.1a3/bin/python | 0 .../.pyenv/versions/3.13-dev/bin/python | 0 .../.pyenv/versions/3.9.9/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../.pyenv/versions/anaconda-4.0.0/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/anaconda3-2021.04/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/mambaforge-4.10.1-4/bin/python | 0 .../mambaforge/bin/envs/.conda_envs_dir_test | 0 .../.pyenv/versions/mambaforge/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda-latest/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../miniconda3-3.10-22.11.1-1/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda3-3.10.1/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniconda3-4.0.5/bin/python | 0 .../bin/envs/.conda_envs_dir_test | 0 .../versions/miniforge3-4.11.0-1/bin/python | 0 .../.pyenv/versions/my-virtual-env/bin/python | 0 .../.pyenv/versions/my-virtual-env/pyvenv.cfg | 3 - .../.pyenv/versions/nogil-3.9.10/bin/python | 0 .../versions/pypy3.10-7.3.14/bin/python | 0 .../.pyenv/versions/pyston-2.3.5/bin/python | 0 .../versions/stacklets-3.7.5/bin/python | 0 .../home/opt/homebrew/bin/pyenv | 0 noxfile.py | 122 +- package-lock.json | 243 +++- package.json | 2 +- .../locators/common/nativePythonFinder.ts | 10 +- 89 files changed, 527 insertions(+), 4358 deletions(-) delete mode 100644 native_locator/.vscode/settings.json delete mode 100644 native_locator/Cargo.toml delete mode 100644 native_locator/src/common_python.rs delete mode 100644 native_locator/src/conda.rs delete mode 100644 native_locator/src/global_virtualenvs.rs delete mode 100644 native_locator/src/homebrew.rs delete mode 100644 native_locator/src/known.rs delete mode 100644 native_locator/src/lib.rs delete mode 100644 native_locator/src/locator.rs delete mode 100644 native_locator/src/logging.rs delete mode 100644 native_locator/src/main.rs delete mode 100644 native_locator/src/messaging.rs delete mode 100644 native_locator/src/pipenv.rs delete mode 100644 native_locator/src/pyenv.rs delete mode 100644 native_locator/src/utils.rs delete mode 100644 native_locator/src/venv.rs delete mode 100644 native_locator/src/virtualenv.rs delete mode 100644 native_locator/src/virtualenvwrapper.rs delete mode 100644 native_locator/src/windows_registry.rs delete mode 100644 native_locator/src/windows_store.rs delete mode 100644 native_locator/tests/common.rs delete mode 100644 native_locator/tests/common_python_test.rs delete mode 100644 native_locator/tests/conda_test.rs delete mode 100644 native_locator/tests/pyenv_test.rs delete mode 100644 native_locator/tests/unix/conda/user_home/.conda/environments.txt delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/bin/conda delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python delete mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json delete mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/known/user_home/python delete mode 100644 native_locator/tests/unix/known/user_home/python.version delete mode 100644 native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python delete mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python delete mode 100644 native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eed629895ee1..0fc789d8c69b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,6 +84,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Build VSIX uses: ./.github/actions/build-vsix with: @@ -201,6 +212,17 @@ jobs: with: path: ${{ env.special-working-directory-relative }} + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Install Node uses: actions/setup-node@v4 with: @@ -387,6 +409,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Smoke tests uses: ./.github/actions/smoke-tests with: diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 24f589295ab8..34c8c6cc8e79 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -57,6 +57,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Build VSIX uses: ./.github/actions/build-vsix with: @@ -90,6 +101,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Install base Python requirements uses: brettcannon/pip-secure-install@v1 with: @@ -186,6 +208,17 @@ jobs: with: path: ${{ env.special-working-directory-relative }} + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Install Node uses: actions/setup-node@v4 with: @@ -363,9 +396,20 @@ jobs: with: path: ${{ env.special-working-directory-relative }} - - name: Native Locator tests + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Python Environment Tools tests run: cargo test -- --nocapture - working-directory: ${{ env.special-working-directory }}/native_locator + working-directory: ${{ env.special-working-directory }}/python-env-tools smoke-tests: name: Smoke tests @@ -388,6 +432,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Smoke tests uses: ./.github/actions/smoke-tests with: @@ -409,6 +464,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Checkout Python Environment Tools + uses: actions/checkout@v4 + with: + repository: 'microsoft/python-environment-tools' + path: python-env-tools + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + - name: Install Node uses: actions/setup-node@v4 with: diff --git a/.gitignore b/.gitignore index 192e293bb50a..f703e34173fd 100644 --- a/.gitignore +++ b/.gitignore @@ -48,5 +48,4 @@ dist/** *.xlf package.nls.*.json l10n/ -native_locator/target/** -native_locator/Cargo.lock +python-env-tools/** diff --git a/.vscode/settings.json b/.vscode/settings.json index 76501f1f6d1c..01de0d907706 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,6 +73,6 @@ "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "rust-analyzer.linkedProjects": [ - ".\\native_locator\\Cargo.toml" + ".\\python-env-tools\\Cargo.toml" ] } diff --git a/.vscodeignore b/.vscodeignore index c2b2a3dd9538..f6df04a2b585 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -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.* diff --git a/build/azure-pipeline.pre-release.yml b/build/azure-pipeline.pre-release.yml index d87f482d320c..0996332948cc 100644 --- a/build/azure-pipeline.pre-release.yml +++ b/build/azure-pipeline.pre-release.yml @@ -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 diff --git a/native_locator/.vscode/settings.json b/native_locator/.vscode/settings.json deleted file mode 100644 index 58d2322dc45f..000000000000 --- a/native_locator/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "git.openRepositoryInParentFolders": "always" -} diff --git a/native_locator/Cargo.toml b/native_locator/Cargo.toml deleted file mode 100644 index ea41be66c8a4..000000000000 --- a/native_locator/Cargo.toml +++ /dev/null @@ -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 diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs deleted file mode 100644 index 67ad94ed40a1..000000000000 --- a/native_locator/src/common_python.rs +++ /dev/null @@ -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 { - 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 { - 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 { - 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 = 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![], - }) - } - } -} diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs deleted file mode 100644 index 2f2d090adca3..000000000000 --- a/native_locator/src/conda.rs +++ /dev/null @@ -1,1097 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -use crate::known; -use crate::known::Environment; -use crate::locator::Locator; -use crate::locator::LocatorResult; -use crate::messaging; -use crate::messaging::Architecture; -use crate::messaging::EnvManager; -use crate::messaging::EnvManagerType; -use crate::messaging::PythonEnvironment; -use crate::utils::PythonEnv; -use crate::utils::{find_python_binary_path, get_environment_key, get_environment_manager_key}; -use log::trace; -use log::warn; -use regex::Regex; -use serde::Deserialize; -use std::collections::HashMap; -use std::collections::HashSet; -use std::env; -use std::fs::read_to_string; -use std::path::{Path, PathBuf}; - -/// Specifically returns the file names that are valid for 'conda' on windows -/// Path is relative to the installation folder of conda. -#[cfg(windows)] -fn get_relative_paths_to_conda_executable() -> Vec { - vec![ - PathBuf::from("Scripts").join("conda.exe"), - PathBuf::from("Scripts").join("conda.bat"), - ] -} - -/// Specifically returns the file names that are valid for 'conda' on linux/Mac -/// Path is relative to the installation folder of conda. -#[cfg(unix)] -fn get_relative_paths_to_conda_executable() -> Vec { - vec![PathBuf::from("bin").join("conda")] -} - -/// Returns the relative path to the python executable for the conda installation. -/// Path is relative to the installation folder of conda. -/// In windows the python.exe for the conda installation is in the root folder. -#[cfg(windows)] -fn get_relative_paths_to_main_python_executable() -> PathBuf { - PathBuf::from("python.exe") -} - -/// Returns the relative path to the python executable for the conda installation. -/// Path is relative to the installation folder of conda. -/// In windows the python.exe for the conda installation is in the bin folder. -#[cfg(unix)] -fn get_relative_paths_to_main_python_executable() -> PathBuf { - PathBuf::from("bin").join("python") -} - -#[derive(Debug)] -struct CondaPackage { - #[allow(dead_code)] - path: PathBuf, - version: String, - arch: Option, -} - -#[derive(Deserialize, Debug)] -struct CondaMetaPackageStructure { - channel: Option, - // version: Option, -} - -/// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory. -fn get_conda_package_json_path(path: &Path, package: &str) -> Option { - // conda-meta is in the root of the conda installation folder - let path = path.join("conda-meta"); - let package_name = format!("{}-", package); - let regex = Regex::new(format!("^{}-((\\d+\\.*)*)-.*.json$", package).as_str()); - std::fs::read_dir(path) - .ok()? - .filter_map(Result::ok) - .find_map(|entry| { - let path = entry.path(); - let file_name = path.file_name()?.to_string_lossy(); - if file_name.starts_with(&package_name) && file_name.ends_with(".json") { - if let Some(version) = regex.clone().ok().unwrap().captures(&file_name)?.get(1) { - let mut arch: Option = None; - // Sample contents - // { - // "build": "h966fe2a_2", - // "build_number": 2, - // "channel": "https://repo.anaconda.com/pkgs/main/win-64", - // "constrains": [], - // } - // 32bit channel is https://repo.anaconda.com/pkgs/main/win-32/ - // 64bit channel is "channel": "https://repo.anaconda.com/pkgs/main/osx-arm64", - if let Some(contents) = read_to_string(&path).ok() { - if let Some(js) = - serde_json::from_str::(&contents).ok() - { - if let Some(channel) = js.channel { - if channel.ends_with("64") { - arch = Some(Architecture::X64); - } else if channel.ends_with("32") { - arch = Some(Architecture::X86); - } - } - } - } - return Some(CondaPackage { - path: path.clone(), - version: version.as_str().to_string(), - arch, - }); - } - } - None - }) -} - -fn get_conda_executable(path: &Path) -> Option { - for relative_path in get_relative_paths_to_conda_executable() { - let exe = path.join(&relative_path); - if exe.exists() { - return Some(exe); - } - } - - None -} - -/// Specifically returns the file names that are valid for 'conda' on windows -#[cfg(windows)] -fn get_conda_bin_names() -> Vec<&'static str> { - vec!["conda.exe", "conda.bat"] -} - -/// Specifically returns the file names that are valid for 'conda' on linux/Mac -#[cfg(unix)] -fn get_conda_bin_names() -> Vec<&'static str> { - vec!["conda"] -} - -/// Find the conda binary on the PATH environment variable -fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { - let paths = environment.get_env_var("PATH".to_string())?; - for path in env::split_paths(&paths) { - for bin in get_conda_bin_names() { - let conda_path = path.join(bin); - if let Ok(metadata) = std::fs::metadata(&conda_path) { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - } - } - None -} - -#[cfg(windows)] -fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { - let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); - let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); - let all_user_profile = environment - .get_env_var("ALLUSERSPROFILE".to_string()) - .unwrap(); - let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); - let mut known_paths = vec![ - Path::new(&user_profile).join("Anaconda3\\Scripts"), - Path::new(&program_data).join("Anaconda3\\Scripts"), - Path::new(&all_user_profile).join("Anaconda3\\Scripts"), - Path::new(&home_drive).join("Anaconda3\\Scripts"), - Path::new(&user_profile).join("Miniconda3\\Scripts"), - Path::new(&program_data).join("Miniconda3\\Scripts"), - Path::new(&all_user_profile).join("Miniconda3\\Scripts"), - Path::new(&home_drive).join("Miniconda3\\Scripts"), - ]; - known_paths.append(&mut environment.get_know_global_search_locations()); - known_paths -} - -#[cfg(unix)] -fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { - let mut known_paths = vec![ - PathBuf::from("/opt/anaconda3/bin"), - PathBuf::from("/opt/miniconda3/bin"), - PathBuf::from("/usr/local/anaconda3/bin"), - PathBuf::from("/usr/local/miniconda3/bin"), - PathBuf::from("/usr/anaconda3/bin"), - PathBuf::from("/usr/miniconda3/bin"), - PathBuf::from("/home/anaconda3/bin"), - PathBuf::from("/home/miniconda3/bin"), - PathBuf::from("/anaconda3/bin"), - PathBuf::from("/miniconda3/bin"), - ]; - if let Some(home) = environment.get_user_home() { - known_paths.push(PathBuf::from(home.clone()).join("anaconda3/bin")); - known_paths.push(PathBuf::from(home).join("miniconda3/bin")); - } - known_paths.append(&mut environment.get_know_global_search_locations()); - known_paths -} - -/// Find conda binary in known locations -fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option { - let conda_bin_names = get_conda_bin_names(); - let known_locations = get_known_conda_locations(environment); - for location in known_locations { - for bin in &conda_bin_names { - let conda_path = location.join(bin); - if let Some(metadata) = std::fs::metadata(&conda_path).ok() { - if metadata.is_file() || metadata.is_symlink() { - return Some(conda_path); - } - } - } - } - None -} - -/// Find the conda binary on the system -pub fn find_conda_binary(environment: &dyn known::Environment) -> Option { - let conda_binary_on_path = find_conda_binary_on_path(environment); - match conda_binary_on_path { - Some(conda_binary_on_path) => Some(conda_binary_on_path), - None => find_conda_binary_in_known_locations(environment), - } -} - -fn get_conda_manager(path: &Path) -> Option { - let conda_exe = get_conda_executable(path)?; - let conda_pkg = get_conda_package_json_path(path, "conda")?; - - Some(EnvManager { - executable_path: conda_exe, - version: Some(conda_pkg.version), - tool: EnvManagerType::Conda, - company: None, - company_display_name: None, - }) -} - -#[derive(Debug, Clone)] -struct CondaEnvironment { - name: String, - named: bool, - env_path: PathBuf, - python_executable_path: Option, - version: Option, - conda_install_folder: Option, - arch: Option, -} -fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { - let metadata = env_path.metadata(); - if let Ok(metadata) = metadata { - if metadata.is_dir() { - let conda_install_folder = get_conda_installation_used_to_create_conda_env(env_path); - let env_path = env_path.clone(); - if let Some(python_binary) = find_python_binary_path(&env_path) { - if let Some(package_info) = get_conda_package_json_path(&env_path, "python") { - return Some(CondaEnvironment { - name: env_path.file_name()?.to_string_lossy().to_string(), - env_path, - named, - python_executable_path: Some(python_binary), - version: Some(package_info.version), - conda_install_folder, - arch: package_info.arch, - }); - } else { - return Some(CondaEnvironment { - name: env_path.file_name()?.to_string_lossy().to_string(), - env_path, - named, - python_executable_path: Some(python_binary), - version: None, - conda_install_folder, - arch: None, - }); - } - } else { - return Some(CondaEnvironment { - name: env_path.file_name()?.to_string_lossy().to_string(), - env_path, - named, - python_executable_path: None, - version: None, - conda_install_folder, - arch: None, - }); - } - } - } - - None -} - -fn get_environments_from_envs_folder_in_conda_directory( - path: &Path, -) -> Option> { - let mut envs: Vec = vec![]; - // iterate through all sub directories in the env folder - // for each sub directory, check if it has a python executable - // if it does, create a PythonEnvironment object and add it to the list - for entry in std::fs::read_dir(path.join("envs")) - .ok()? - .filter_map(Result::ok) - { - if let Some(env) = get_conda_environment_info(&entry.path(), true) { - envs.push(env); - } - } - - Some(envs) -} - -fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { - let mut envs = vec![]; - if let Some(home) = environment.get_user_home() { - let home = Path::new(&home); - let environment_txt = home.join(".conda").join("environments.txt"); - if let Ok(reader) = std::fs::read_to_string(environment_txt.clone()) { - trace!("Found environments.txt file {:?}", environment_txt); - for line in reader.lines() { - envs.push(line.to_string()); - } - } - } - envs -} - -#[derive(Debug)] -struct Condarc { - env_dirs: Vec, -} - -/** - * Get the list of conda environments found in other locations such as - * /.conda/envs - * /AppData/Local/conda/conda/envs - */ -pub fn get_conda_environment_paths_from_conda_rc( - environment: &dyn known::Environment, -) -> Vec { - if let Some(paths) = get_conda_conda_rc(environment) { - paths.env_dirs - } else { - vec![] - } -} - -fn get_conda_environment_paths_from_known_paths( - environment: &dyn known::Environment, -) -> Vec { - if let Some(home) = environment.get_user_home() { - let mut env_paths: Vec = vec![]; - let _ = [ - PathBuf::from(".conda").join("envs"), - PathBuf::from("AppData") - .join("Local") - .join("conda") - .join("conda") - .join("envs"), - ] - .iter() - .map(|path| { - let full_path = home.join(path); - for entry in std::fs::read_dir(full_path).ok()?.filter_map(Result::ok) { - if entry.path().is_dir() { - trace!("Search for conda envs in location {:?}", entry.path()); - env_paths.push(entry.path()); - } - } - None::<()> - }); - return env_paths; - } - vec![] -} - -#[cfg(windows)] -fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { - let mut search_paths: Vec = vec![ - "C:\\ProgramData\\conda\\.condarc", - "C:\\ProgramData\\conda\\condarc", - "C:\\ProgramData\\conda\\condarc.d", - ] - .iter() - .map(|p| PathBuf::from(p)) - .collect(); - - if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { - search_paths.append(&mut vec![ - PathBuf::from(conda_root.clone()).join(".condarc"), - PathBuf::from(conda_root.clone()).join("condarc"), - PathBuf::from(conda_root.clone()).join(".condarc.d"), - ]); - } - if let Some(home) = environment.get_user_home() { - search_paths.append(&mut vec![ - home.join(".config").join("conda").join(".condarc"), - home.join(".config").join("conda").join("condarc"), - home.join(".config").join("conda").join("condarc.d"), - home.join(".conda").join(".condarc"), - home.join(".conda").join("condarc"), - home.join(".conda").join("condarc.d"), - home.join(".condarc"), - ]); - } - if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { - search_paths.append(&mut vec![ - PathBuf::from(conda_prefix.clone()).join(".condarc"), - PathBuf::from(conda_prefix.clone()).join("condarc"), - PathBuf::from(conda_prefix.clone()).join(".condarc.d"), - ]); - } - if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { - search_paths.append(&mut vec![PathBuf::from(condarc)]); - } - - search_paths -} -#[cfg(unix)] -fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { - let mut search_paths: Vec = vec![ - "/etc/conda/.condarc", - "/etc/conda/condarc", - "/etc/conda/condarc.d/", - "/var/lib/conda/.condarc", - "/var/lib/conda/condarc", - "/var/lib/conda/condarc.d/", - ] - .iter() - .map(|p| PathBuf::from(p)) - .map(|p| { - // This only applies in tests. - // We need this, as the root folder cannot be mocked. - if let Some(root) = environment.get_root() { - root.join(p.to_string_lossy()[1..].to_string()) - } else { - p - } - }) - .collect(); - - if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { - search_paths.append(&mut vec![ - PathBuf::from(conda_root.clone()).join(".condarc"), - PathBuf::from(conda_root.clone()).join("condarc"), - PathBuf::from(conda_root.clone()).join(".condarc.d"), - ]); - } - if let Some(xdg_config_home) = environment.get_env_var("XDG_CONFIG_HOME".to_string()) { - search_paths.append(&mut vec![ - PathBuf::from(xdg_config_home.clone()).join(".condarc"), - PathBuf::from(xdg_config_home.clone()).join("condarc"), - PathBuf::from(xdg_config_home.clone()).join(".condarc.d"), - ]); - } - if let Some(home) = environment.get_user_home() { - search_paths.append(&mut vec![ - home.join(".config").join("conda").join(".condarc"), - home.join(".config").join("conda").join("condarc"), - home.join(".config").join("conda").join("condarc.d"), - home.join(".conda").join(".condarc"), - home.join(".conda").join("condarc"), - home.join(".conda").join("condarc.d"), - home.join(".condarc"), - ]); - } - if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { - search_paths.append(&mut vec![ - PathBuf::from(conda_prefix.clone()).join(".condarc"), - PathBuf::from(conda_prefix.clone()).join("condarc"), - PathBuf::from(conda_prefix.clone()).join(".condarc.d"), - ]); - } - if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { - search_paths.append(&mut vec![PathBuf::from(condarc)]); - } - - search_paths -} - -/** - * The .condarc file contains a list of directories where conda environments are created. - * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs - * - * TODO: Search for the .condarc file in the following locations: - * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc - */ -fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { - let conda_rc = get_conda_rc_search_paths(environment) - .into_iter() - .find(|p| p.exists())?; - let mut start_consuming_values = false; - trace!("conda_rc: {:?}", conda_rc); - let reader = std::fs::read_to_string(conda_rc).ok()?; - let mut env_dirs = vec![]; - for line in reader.lines() { - if line.starts_with("envs_dirs:") && !start_consuming_values { - start_consuming_values = true; - continue; - } - if start_consuming_values { - if line.trim().starts_with("-") { - if let Some(env_dir) = line.splitn(2, '-').nth(1) { - let env_dir = PathBuf::from(env_dir.trim()).join("envs"); - if env_dir.exists() { - env_dirs.push(env_dir); - } - } - continue; - } else { - break; - } - } - } - return Some(Condarc { env_dirs }); -} - -/** - * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. - * If the user has multiple conda installations, any one of those could have created that specific environment. - * Fortunately the conda-meta/history file contains the path to the conda executable (script) that was used to create the environment. - * The format of the file is as follows: - * # cmd: C:\Users\user\miniconda3\Scripts\conda-script.py create --name myenv - * - * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. - */ -fn was_conda_environment_created_by_specific_conda( - env: &CondaEnvironment, - root_conda_path: &Path, -) -> bool { - if let Some(cmd_line) = env.conda_install_folder.clone() { - if cmd_line - .to_lowercase() - .contains(&root_conda_path.to_string_lossy().to_lowercase()) - { - return true; - } else { - return false; - } - } - - false -} - -/** - * The conda-meta/history file in conda environments contain the command used to create the conda environment. - * And example is `# cmd: \Scripts\conda-script.py create -n sample`` - * And example is `# cmd: conda create -n sample`` - * - * Sometimes the cmd line contains the fully qualified path to the conda install folder. - * This function returns the path to the conda installation that was used to create the environment. - */ -fn get_conda_installation_used_to_create_conda_env(env_path: &PathBuf) -> Option { - let conda_meta_history = env_path.join("conda-meta").join("history"); - if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { - if let Some(line) = reader.lines().map(|l| l.trim()).find(|l| { - l.to_lowercase().starts_with("# cmd:") && l.to_lowercase().contains(" create -") - }) { - // Sample lines - // # cmd: \Scripts\conda-script.py create -n samlpe1 - // # cmd: \Scripts\conda-script.py create -p - // # cmd: /Users/donjayamanne/miniconda3/bin/conda create -n conda1 - let start_index = line.to_lowercase().find("# cmd:")? + "# cmd:".len(); - let end_index = line.to_lowercase().find(" create -")?; - let cmd_line = PathBuf::from(line[start_index..end_index].trim().to_string()); - if let Some(cmd_line) = cmd_line.parent() { - if let Some(name) = cmd_line.file_name() { - if name.to_ascii_lowercase() == "bin" || name.to_ascii_lowercase() == "scripts" - { - if let Some(cmd_line) = cmd_line.parent() { - return Some(cmd_line.to_str()?.to_string()); - } - } - return Some(cmd_line.to_str()?.to_string()); - } - } - } - } - - None -} - -#[cfg(windows)] -fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { - let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); - let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); - let all_user_profile = environment - .get_env_var("ALLUSERSPROFILE".to_string()) - .unwrap(); - let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap(); - let mut known_paths = vec![ - Path::new(&user_profile).join("Anaconda3"), - Path::new(&program_data).join("Anaconda3"), - Path::new(&all_user_profile).join("Anaconda3"), - Path::new(&home_drive).join("Anaconda3"), - Path::new(&user_profile).join("Miniconda3"), - Path::new(&program_data).join("Miniconda3"), - Path::new(&all_user_profile).join("Miniconda3"), - Path::new(&home_drive).join("Miniconda3"), - Path::new(&all_user_profile).join("miniforge3"), - Path::new(&home_drive).join("miniforge3"), - ]; - if let Some(home) = environment.get_user_home() { - known_paths.push(PathBuf::from(home.clone()).join("anaconda3")); - known_paths.push(PathBuf::from(home.clone()).join("miniconda3")); - known_paths.push(PathBuf::from(home.clone()).join("miniforge3")); - known_paths.push(PathBuf::from(home).join(".conda")); - } - known_paths -} - -#[cfg(unix)] -fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { - let mut known_paths = vec![ - PathBuf::from("/opt/anaconda3"), - PathBuf::from("/opt/miniconda3"), - PathBuf::from("/usr/local/anaconda3"), - PathBuf::from("/usr/local/miniconda3"), - PathBuf::from("/usr/anaconda3"), - PathBuf::from("/usr/miniconda3"), - PathBuf::from("/home/anaconda3"), - PathBuf::from("/home/miniconda3"), - PathBuf::from("/anaconda3"), - PathBuf::from("/miniconda3"), - PathBuf::from("/miniforge3"), - PathBuf::from("/miniforge3"), - ]; - if let Some(home) = environment.get_user_home() { - known_paths.push(PathBuf::from(home.clone()).join("anaconda3")); - known_paths.push(PathBuf::from(home.clone()).join("miniconda3")); - known_paths.push(PathBuf::from(home.clone()).join("miniforge3")); - known_paths.push(PathBuf::from(home).join(".conda")); - } - known_paths -} - -fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Option> { - if env.python_executable_path.is_none() { - return None; - } - let conda_exe = manager.executable_path.to_str().unwrap().to_string(); - if env.named { - Some(vec![ - conda_exe, - "run".to_string(), - "-n".to_string(), - env.name.clone(), - "python".to_string(), - ]) - } else { - Some(vec![ - conda_exe, - "run".to_string(), - "-p".to_string(), - env.env_path.to_str().unwrap().to_string(), - "python".to_string(), - ]) - } -} - -fn get_root_python_environment(path: &Path, manager: &EnvManager) -> Option { - let python_exe = path.join(get_relative_paths_to_main_python_executable()); - if !python_exe.exists() { - return None; - } - if let Some(package_info) = get_conda_package_json_path(&path, "python") { - let conda_exe = manager.executable_path.to_str().unwrap().to_string(); - return Some(PythonEnvironment { - // Do not set the name to `base` - // Ideally we would like to see this idetnfieid as a base env. - // However if user has 2 conda installations, then the second base env - // will be activated in python extension using first conda executable and -n base, - // I.e. base env of the first install will be activated instead of this. - // Hence lets always just give the path. - // name: Some("base".to_string()), - category: messaging::PythonEnvironmentCategory::Conda, - python_executable_path: Some(python_exe), - version: Some(package_info.version), - arch: package_info.arch, - env_path: Some(path.to_path_buf().clone()), - env_manager: Some(manager.clone()), - python_run_command: Some(vec![ - conda_exe, - "run".to_string(), - "-p".to_string(), - path.to_str().unwrap().to_string(), - "python".to_string(), - ]), - ..Default::default() - }); - } - None -} - -fn get_conda_environments_in_specified_install_path( - conda_install_folder: &Path, - possible_conda_envs: &mut HashMap, -) -> Option { - let mut managers: Vec = vec![]; - let mut environments: Vec = vec![]; - let mut detected_envs: HashSet = HashSet::new(); - let mut detected_managers: HashSet = HashSet::new(); - if !conda_install_folder.is_dir() || !conda_install_folder.exists() { - return None; - } - - if let Some(manager) = get_conda_manager(&conda_install_folder) { - // 1. Base environment. - if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { - if let Some(env_path) = env.clone().env_path { - possible_conda_envs.remove(&env_path); - let key = env_path.to_string_lossy().to_string(); - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(env); - } - } - } - - // 2. All environments in the `/envs` folder - let mut envs: Vec = vec![]; - if let Some(environments) = - get_environments_from_envs_folder_in_conda_directory(conda_install_folder) - { - environments.iter().for_each(|env| { - possible_conda_envs.remove(&env.env_path); - envs.push(env.clone()); - }); - } - - // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - // E.g conda_install_folder is `/` - // Then all folders such as `//envs/env1` can be ignored - // As these would have been discovered in previous step. - for (key, env) in possible_conda_envs.clone().iter() { - if env - .env_path - .to_string_lossy() - .contains(conda_install_folder.to_str().unwrap()) - { - continue; - } - if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { - envs.push(env.clone()); - possible_conda_envs.remove(key); - } - } - - // Finally construct the PythonEnvironment objects - envs.iter().for_each(|env| { - let exe = env.python_executable_path.clone(); - let arch = env.arch.clone(); - let mut env = PythonEnvironment::new( - None, - Some(env.name.clone()), - exe.clone(), - messaging::PythonEnvironmentCategory::Conda, - env.version.clone(), - Some(env.env_path.clone()), - Some(manager.clone()), - get_activation_command(env, &manager), - ); - env.arch = arch; - if let Some(key) = get_environment_key(&env) { - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(env); - } - } - }); - - let key = get_environment_manager_key(&manager); - if !detected_managers.contains(&key) { - detected_managers.insert(key); - managers.push(manager); - } - } - - if managers.is_empty() && environments.is_empty() { - return None; - } - - Some(LocatorResult { - managers, - environments, - }) -} - -fn find_conda_environments_from_known_conda_install_locations( - environment: &dyn known::Environment, - possible_conda_envs: &mut HashMap, -) -> Option { - let mut managers: Vec = vec![]; - let mut environments: Vec = vec![]; - - // We know conda is installed in `/Anaconda3`, `/miniforge3`, etc - // Look for these and discover all environments in these locations - for possible_conda_install_folder in get_known_conda_install_locations(environment) { - if let Some(mut result) = get_conda_environments_in_specified_install_path( - &possible_conda_install_folder, - possible_conda_envs, - ) { - managers.append(&mut result.managers); - environments.append(&mut result.environments); - } - } - - // We know conda environments are listed in the `environments.txt` file - // Sometimes the base environment is also listed in these paths - // Go through them an look for possible conda install folders in these paths. - // & then look for conda environments in each of them. - // This accounts for cases where Conda install location is in some un-common (custom) location - let mut env_paths_to_remove: Vec = vec![]; - for (key, env) in possible_conda_envs - .clone() - .iter() - .filter(|(_, env)| is_conda_install_location(&env.env_path)) - { - if let Some(mut result) = - get_conda_environments_in_specified_install_path(&env.env_path, possible_conda_envs) - { - possible_conda_envs.remove(key); - managers.append(&mut result.managers); - environments.append(&mut result.environments); - env_paths_to_remove.push(env.env_path.clone()); - } - } - - if managers.is_empty() && environments.is_empty() { - return None; - } - - Some(LocatorResult { - managers, - environments, - }) -} - -fn is_conda_install_location(path: &Path) -> bool { - let envs_path = path.join("envs"); - return envs_path.exists() && envs_path.is_dir(); -} - -pub fn get_conda_version(conda_binary: &PathBuf) -> Option { - let mut parent = conda_binary.parent()?; - if parent.ends_with("bin") { - parent = parent.parent()?; - } - if parent.ends_with("Library") { - parent = parent.parent()?; - } - match get_conda_package_json_path(&parent, "conda") { - Some(result) => Some(result.version), - None => match get_conda_package_json_path(&parent.parent()?, "conda") { - Some(result) => Some(result.version), - None => None, - }, - } -} - -fn get_known_conda_envs_from_various_locations( - environment: &dyn known::Environment, -) -> HashMap { - let mut env_paths = get_conda_envs_from_environment_txt(environment) - .iter() - .map(|e| PathBuf::from(e)) - .collect::>(); - - let mut env_paths_from_conda_rc = get_conda_environment_paths_from_conda_rc(environment); - env_paths.append(&mut env_paths_from_conda_rc); - - let mut envs_from_known_paths = get_conda_environment_paths_from_known_paths(environment); - env_paths.append(&mut envs_from_known_paths); - - let mut envs: Vec = vec![]; - env_paths.iter().for_each(|path| { - if !path.exists() { - return; - } - if let Some(env) = get_conda_environment_info(&path, false) { - envs.push(env); - } - }); - - envs.into_iter().fold(HashMap::new(), |mut acc, env| { - acc.insert(env.env_path.clone(), env); - acc - }) -} - -fn get_conda_environments_from_known_locations_that_have_not_been_discovered( - known_environment: &Vec, - environment: &dyn known::Environment, - undiscovered_environments: &mut HashMap, -) -> Option { - if undiscovered_environments.is_empty() { - return None; - } - - // Ok, weird, we have an environment in environments.txt file that was not discovered. - // Let's try to discover it. - warn!( - "Found environments in environments.txt that were not discovered: {:?}", - undiscovered_environments - ); - - let manager = match known_environment - .iter() - .find_map(|env| env.env_manager.as_ref()) - { - Some(manager) => Some(manager.clone()), - None => { - // Old approach of finding the conda executable. - let conda_binary = find_conda_binary(environment)?; - Some(EnvManager::new( - conda_binary.clone(), - get_conda_version(&conda_binary), - EnvManagerType::Conda, - )) - } - }; - - if let Some(manager) = manager { - let mut environments: Vec = vec![]; - for (_, env) in undiscovered_environments { - let exe = env.python_executable_path.clone(); - let env = PythonEnvironment::new( - None, - Some(env.name.clone()), - exe.clone(), - messaging::PythonEnvironmentCategory::Conda, - env.version.clone(), - Some(env.env_path.clone()), - Some(manager.clone()), - get_activation_command(&env, &manager), - ); - environments.push(env); - } - if environments.len() > 0 { - return Some(LocatorResult { - managers: vec![manager], - environments, - }); - } - } else { - warn!("Could not find conda executable to discover environments in environments.txt"); - } - - None -} - -pub struct Conda<'a> { - pub manager: Option, - pub environment: &'a dyn Environment, - pub discovered_environment_paths: HashSet, - pub discovered_managers: HashSet, -} - -pub trait CondaLocator { - fn find_in(&mut self, possible_conda_folder: &Path) -> Option; -} - -impl Conda<'_> { - pub fn with<'a>(environment: &'a impl Environment) -> Conda { - Conda { - environment, - manager: None, - discovered_environment_paths: HashSet::new(), - discovered_managers: HashSet::new(), - } - } - fn filter_result(&mut self, result: Option) -> Option { - if let Some(result) = result { - let envs: Vec = result - .environments - .iter() - .filter(|e| { - if let Some(env_path) = e.env_path.clone() { - if self.discovered_environment_paths.contains(&env_path) { - return false; - } - self.discovered_environment_paths.insert(env_path); - return true; - } - false - }) - .cloned() - .collect(); - - let managers: Vec = result - .managers - .iter() - .filter(|e| { - let key = get_environment_manager_key(e); - if self.discovered_managers.contains(&key) { - return false; - } - self.discovered_managers.insert(key); - return true; - }) - .cloned() - .collect(); - - if envs.len() > 0 || managers.len() > 0 { - return Some(LocatorResult { - managers: managers, - environments: envs, - }); - } - } - None - } -} - -impl CondaLocator for Conda<'_> { - fn find_in(&mut self, possible_conda_folder: &Path) -> Option { - if !is_conda_install_location(possible_conda_folder) { - return None; - } - let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); - self.filter_result(get_conda_environments_in_specified_install_path( - possible_conda_folder, - &mut possible_conda_envs, - )) - } -} - -impl Locator for Conda<'_> { - fn resolve(&self, _env: &PythonEnv) -> Option { - // We will find everything in find - None - } - - fn find(&mut self) -> Option { - let mut managers: Vec = vec![]; - let mut environments: Vec = vec![]; - let mut detected_managers: HashSet = HashSet::new(); - let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); - - if let Some(result) = - self.filter_result(find_conda_environments_from_known_conda_install_locations( - self.environment, - &mut possible_conda_envs, - )) - { - result.managers.iter().for_each(|m| { - detected_managers.insert(get_environment_manager_key(m)); - managers.push(m.clone()); - }); - - result - .environments - .iter() - .for_each(|e| environments.push(e.clone())); - } - - if let Some(result) = self.filter_result( - get_conda_environments_from_known_locations_that_have_not_been_discovered( - &environments, - self.environment, - &mut possible_conda_envs, - ), - ) { - result.managers.iter().for_each(|m| { - let key = get_environment_manager_key(m); - if !detected_managers.contains(&key) { - warn!("Found a new manager using the fallback mechanism: {:?}", m); - detected_managers.insert(key); - managers.push(m.clone()); - } - }); - - result.environments.iter().for_each(|e| { - warn!( - "Found a new conda environment using the fallback mechanism: {:?}", - e - ); - environments.push(e.clone()); - }); - } - - if managers.is_empty() && environments.is_empty() { - return None; - } - - Some(LocatorResult { - managers, - environments, - }) - } -} diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs deleted file mode 100644 index e0e4cf8cb991..000000000000 --- a/native_locator/src/global_virtualenvs.rs +++ /dev/null @@ -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 { - let mut venv_dirs: Vec = 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 { - let mut python_envs: Vec = 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 -} diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs deleted file mode 100644 index 78cefd2cd74b..000000000000 --- a/native_locator/src/homebrew.rs +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - // 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, - python_version_regex: &Regex, -) -> Option { - // 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 { - 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 = 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 { - let homebrew_prefix_bin = get_homebrew_prefix_bin(self.environment)?; - let mut reported: HashSet = HashSet::new(); - let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); - let mut environments: Vec = 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, - }) - } - } -} diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs deleted file mode 100644 index 600aa45d1034..000000000000 --- a/native_locator/src/known.rs +++ /dev/null @@ -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; - /** - * Only used in tests, this is the root `/`. - */ - #[allow(dead_code)] - fn get_root(&self) -> Option; - fn get_env_var(&self, key: String) -> Option; - fn get_know_global_search_locations(&self) -> Vec; -} - -pub struct EnvironmentApi {} -impl EnvironmentApi { - pub fn new() -> Self { - EnvironmentApi {} - } -} - -#[cfg(windows)] -impl Environment for EnvironmentApi { - fn get_user_home(&self) -> Option { - get_user_home() - } - fn get_root(&self) -> Option { - None - } - fn get_env_var(&self, key: String) -> Option { - get_env_var(key) - } - fn get_know_global_search_locations(&self) -> Vec { - vec![] - } -} - -#[cfg(unix)] -impl Environment for EnvironmentApi { - fn get_user_home(&self) -> Option { - get_user_home() - } - fn get_root(&self) -> Option { - None - } - fn get_env_var(&self, key: String) -> Option { - get_env_var(key) - } - fn get_know_global_search_locations(&self) -> Vec { - 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 { - 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 { - match env::var(key) { - Ok(path) => Some(path), - Err(_) => None, - } -} diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs deleted file mode 100644 index f1335a41f461..000000000000 --- a/native_locator/src/lib.rs +++ /dev/null @@ -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>> { - // 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>> { - // 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 { - // 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, - Box::new(virtualenvwrapper) as Box, - Box::new(venv_locator) as Box, - Box::new(virtualenv_locator) as Box, - ]; - - // 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, 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())); - } -} diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs deleted file mode 100644 index a318c102230a..000000000000 --- a/native_locator/src/locator.rs +++ /dev/null @@ -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, - pub environments: Vec, -} - -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; - /** - * Finds all environments specific to this locator. - */ - fn find(&mut self) -> Option; -} diff --git a/native_locator/src/logging.rs b/native_locator/src/logging.rs deleted file mode 100644 index 66532ff67eff..000000000000 --- a/native_locator/src/logging.rs +++ /dev/null @@ -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 }, - } - } -} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs deleted file mode 100644 index da0720e242e5..000000000000 --- a/native_locator/src/main.rs +++ /dev/null @@ -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(); -} diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs deleted file mode 100644 index 808da631f455..000000000000 --- a/native_locator/src/messaging.rs +++ /dev/null @@ -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, - pub tool: EnvManagerType, - pub company: Option, - pub company_display_name: Option, -} - -impl EnvManager { - pub fn new(executable_path: PathBuf, version: Option, 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, - pub name: Option, - pub python_executable_path: Option, - pub category: PythonEnvironmentCategory, - pub version: Option, - pub env_path: Option, - pub env_manager: Option, - pub python_run_command: Option>, - /** - * The project path for the Pipenv environment. - */ - pub project_path: Option, - pub arch: Option, - pub symlinks: Option>, - pub creation_time: Option, - pub modified_time: Option, - pub company: Option, - pub company_display_name: Option, -} - -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, - name: Option, - python_executable_path: Option, - category: PythonEnvironmentCategory, - version: Option, - env_path: Option, - env_manager: Option, - python_run_command: Option>, - ) -> 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, - pub reported_environments: HashSet, -} -pub fn send_message(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(), - } -} diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs deleted file mode 100644 index cb49c1c6ef33..000000000000 --- a/native_locator/src/pipenv.rs +++ /dev/null @@ -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 { - 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 { - 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 { - None - } -} diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs deleted file mode 100644 index e87729de5eda..000000000000 --- a/native_locator/src/pyenv.rs +++ /dev/null @@ -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 { - 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 { - let home = environment.get_user_home()?; - Some(PathBuf::from(home).join(".pyenv")) -} - -fn get_binary_from_known_paths(environment: &dyn known::Environment) -> Option { - 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 { - // 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 { - 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 { - // 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, -) -> Option { - 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, -) -> Option { - 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, - environment: &dyn known::Environment, - conda_locator: &mut dyn CondaLocator, -) -> Option> { - let pyenv_dir = get_pyenv_dir(environment)?; - let mut envs: Vec = 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 { - // 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 { - // 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 { - // We will find everything in gather - None - } - - fn find(&mut self) -> Option { - 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 = 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, - }) - } -} diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs deleted file mode 100644 index d9a30c3a7f8a..000000000000 --- a/native_locator/src/utils.rs +++ /dev/null @@ -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, - pub version: Option, -} - -impl PythonEnv { - pub fn new(executable: PathBuf, path: Option, version: Option) -> 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 { - // 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 { - 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 { - 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 { - 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> { - let mut python_envs: Vec = 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 { - 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(); -} diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs deleted file mode 100644 index 0df22263e0f3..000000000000 --- a/native_locator/src/venv.rs +++ /dev/null @@ -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 { - 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 { - // There are no common global locations for virtual environments. - // We expect the user of this class to call `is_compatible` - None - } -} diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs deleted file mode 100644 index 9532d46faa73..000000000000 --- a/native_locator/src/virtualenv.rs +++ /dev/null @@ -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 { - 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 { - // There are no common global locations for virtual environments. - // We expect the user of this class to call `is_compatible` - None - } -} diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs deleted file mode 100644 index 9a06fc2494cb..000000000000 --- a/native_locator/src/virtualenvwrapper.rs +++ /dev/null @@ -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 { - // 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 { - 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 { - // 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 { - 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 { - 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 { - let work_on_home = get_work_on_home_path(self.environment)?; - let envs = list_python_environments(&work_on_home)?; - let mut environments: Vec = 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, - }) - } - } -} diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs deleted file mode 100644 index 258b6ad698dd..000000000000 --- a/native_locator/src/windows_registry.rs +++ /dev/null @@ -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 { - use log::{trace, warn}; - - use crate::messaging::Architecture; - let mut managers: Vec = vec![]; - let mut environments = vec![]; - let company_display_name: Option = 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 { - use log::{trace, warn}; - - let mut environments = vec![]; - let mut managers: Vec = 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 { - None - } - - fn find(&mut self) -> Option { - if let Some(result) = get_registry_pythons(self.conda_locator) { - if !result.environments.is_empty() || !result.managers.is_empty() { - return Some(result); - } - } - None - } -} diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs deleted file mode 100644 index 97e0352ea385..000000000000 --- a/native_locator/src/windows_store.rs +++ /dev/null @@ -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> { - use crate::messaging::Architecture; - use regex::Regex; - use std::collections::HashMap; - - let mut python_envs: Vec = 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, - name: Option, - exe: Option, - version: String, - } - let mut potential_matches: HashMap = 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 { - 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 { - 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 { - 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 { - let environments = list_windows_store_python_executables(self.environment)?; - - if environments.is_empty() { - None - } else { - Some(LocatorResult { - managers: vec![], - environments, - }) - } - } -} diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs deleted file mode 100644 index 1df03a005a73..000000000000 --- a/native_locator/tests/common.rs +++ /dev/null @@ -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; -} - -#[allow(dead_code)] -pub struct TestEnvironment { - vars: HashMap, - home: Option, - root: Option, - globals_locations: Vec, -} -#[allow(dead_code)] -pub fn create_test_environment( - vars: HashMap, - home: Option, - globals_locations: Vec, - root: Option, -) -> TestEnvironment { - impl Environment for TestEnvironment { - fn get_env_var(&self, key: String) -> Option { - self.vars.get(&key).cloned() - } - fn get_root(&self) -> Option { - self.root.clone() - } - fn get_user_home(&self) -> Option { - self.home.clone() - } - fn get_know_global_search_locations(&self) -> Vec { - 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 = 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); - } - } -} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs deleted file mode 100644 index ceebf4931ab6..000000000000 --- a/native_locator/tests/common_python_test.rs +++ /dev/null @@ -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::>(), - ); -} diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs deleted file mode 100644 index db6c1338ca9f..000000000000 --- a/native_locator/tests/conda_test.rs +++ /dev/null @@ -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) { - 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::>(), - ); -} diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs deleted file mode 100644 index 132cc4160a6c..000000000000 --- a/native_locator/tests/pyenv_test.rs +++ /dev/null @@ -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::>(), - ) -} diff --git a/native_locator/tests/unix/conda/user_home/.conda/environments.txt b/native_locator/tests/unix/conda/user_home/.conda/environments.txt deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/bin/conda b/native_locator/tests/unix/conda/user_home/anaconda3/bin/conda deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json deleted file mode 100644 index 23127993ac05..000000000000 --- a/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json +++ /dev/null @@ -1 +0,0 @@ -10.1.1 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json deleted file mode 100644 index 23127993ac05..000000000000 --- a/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json +++ /dev/null @@ -1 +0,0 @@ -10.1.1 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt b/native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json deleted file mode 100644 index 23127993ac05..000000000000 --- a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json +++ /dev/null @@ -1 +0,0 @@ -10.1.1 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt b/native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json deleted file mode 100644 index 23127993ac05..000000000000 --- a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json +++ /dev/null @@ -1 +0,0 @@ -10.1.1 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/known/user_home/python b/native_locator/tests/unix/known/user_home/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/known/user_home/python.version b/native_locator/tests/unix/known/user_home/python.version deleted file mode 100644 index 4044f90867df..000000000000 --- a/native_locator/tests/unix/known/user_home/python.version +++ /dev/null @@ -1 +0,0 @@ -12.0.0 diff --git a/native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg deleted file mode 100644 index 6190a656901f..000000000000 --- a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg +++ /dev/null @@ -1,3 +0,0 @@ -home = /Users/donjayamanne/.pyenv/versions/3.10.13/bin -include-system-site-packages = false -version = 3.10.13 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/noxfile.py b/noxfile.py index e0fc26988d5b..c08458d21310 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,6 +6,7 @@ import nox import shutil import sysconfig +import uuid EXT_ROOT = pathlib.Path(__file__).parent @@ -47,14 +48,48 @@ def install_python_libs(session: nox.Session): shutil.rmtree("./python_files/lib/temp") +@nox.session() +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()) + + +@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): - with session.cd("./native_locator"): - if not pathlib.Path(pathlib.Path.cwd() / "bin").exists(): - pathlib.Path(pathlib.Path.cwd() / "bin").mkdir() + 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(pathlib.Path.cwd() / "bin" / ".gitignore").exists(): - pathlib.Path(pathlib.Path.cwd() / "bin" / ".gitignore").write_text( + 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) diff --git a/package-lock.json b/package-lock.json index b360528c4fe6..c78c864334c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "@vscode/test-electron": "^2.3.8", - "@vscode/vsce": "^2.26.1", + "@vscode/vsce": "^2.27.0", "bent": "^7.3.12", "chai": "^4.1.2", "chai-arrays": "^2.0.0", @@ -1998,12 +1998,13 @@ } }, "node_modules/@vscode/vsce": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz", - "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz", + "integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==", "dev": true, "dependencies": { "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", @@ -2037,6 +2038,141 @@ "keytar": "^7.7.0" } }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz", + "integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==", + "dev": true, + "hasInstallScript": true, + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz", + "integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz", + "integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz", + "integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz", + "integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz", + "integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz", + "integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz", + "integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz", + "integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vscode/vsce/node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -13612,9 +13748,9 @@ } }, "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" @@ -15399,12 +15535,13 @@ } }, "@vscode/vsce": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz", - "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz", + "integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==", "dev": true, "requires": { "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", @@ -15465,6 +15602,86 @@ } } }, + "@vscode/vsce-sign": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz", + "integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==", + "dev": true, + "requires": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } + }, + "@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz", + "integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-alpine-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz", + "integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz", + "integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz", + "integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz", + "integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz", + "integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz", + "integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz", + "integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==", + "dev": true, + "optional": true + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -24390,9 +24607,9 @@ } }, "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 27800f2dae54..4d450504ceea 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 1319209659f3..968886339fef 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -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 From 0da0412bc1684234065b2c6a34c0dd2d0b2de80b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:57:59 +0000 Subject: [PATCH 101/106] Bump importlib-metadata from 7.1.0 to 7.2.0 (#23652) Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 7.1.0 to 7.2.0.
Changelog

Sourced from importlib-metadata's changelog.

v7.2.0

Features

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=importlib-metadata&package-manager=pip&previous-version=7.1.0&new-version=7.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 648011e62630..7c6e48d227d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,9 @@ # # pip-compile --generate-hashes requirements.in # -importlib-metadata==7.1.0 \ - --hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \ - --hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2 +importlib-metadata==7.2.0 \ + --hash=sha256:04e4aad329b8b948a5711d394fa8759cb80f009225441b4f2a02bd4d8e5f426c \ + --hash=sha256:3ff4519071ed42740522d494d04819b666541b9752c43012f85afb2cc220fcc6 # via -r requirements.in microvenv==2023.5.post1 \ --hash=sha256:32c46afea874e300f69f1add0806eb0795fd02b5fb251092fba0b73c059a7d1f \ From 092e78017559c0a8252a2728e8d8cfd6bb548bbf Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 20 Jun 2024 23:26:30 -0700 Subject: [PATCH 102/106] Get native finder telemetry (#23646) Closes https://github.com/microsoft/vscode-python/issues/23565 --------- Co-authored-by: Don Jayamanne --- src/client/common/vscodeApis/windowApis.ts | 9 ++ .../locators/common/nativePythonFinder.ts | 89 +++++++++++++---- .../composite/envsCollectionService.ts | 95 +++++++++++++++++-- .../base/locators/lowLevel/nativeLocator.ts | 38 +------- src/client/pythonEnvironments/index.ts | 73 ++++++-------- src/client/telemetry/index.ts | 64 +++++++++++++ src/test/mocks/vsc/index.ts | 27 +++++- .../envsCollectionService.unit.test.ts | 56 ++++++++--- 8 files changed, 324 insertions(+), 127 deletions(-) diff --git a/src/client/common/vscodeApis/windowApis.ts b/src/client/common/vscodeApis/windowApis.ts index 37d302946614..80825018c4a3 100644 --- a/src/client/common/vscodeApis/windowApis.ts +++ b/src/client/common/vscodeApis/windowApis.ts @@ -19,6 +19,8 @@ import { QuickPickItemButtonEvent, Uri, TerminalShellExecutionStartEvent, + LogOutputChannel, + OutputChannel, } from 'vscode'; import { createDeferred, Deferred } from '../utils/async'; import { Resource } from '../types'; @@ -249,3 +251,10 @@ export function getActiveResource(): Resource { const workspaces = getWorkspaceFolders(); return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined; } + +export function createOutputChannel(name: string, languageId?: string): OutputChannel { + return window.createOutputChannel(name, languageId); +} +export function createLogOutputChannel(name: string, options: { log: true }): LogOutputChannel { + return window.createOutputChannel(name, options); +} diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 968886339fef..f47448806aeb 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable, EventEmitter, Event, workspace, window, Uri } from 'vscode'; +import { Disposable, EventEmitter, Event, Uri } from 'vscode'; import * as ch from 'child_process'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; @@ -12,10 +12,16 @@ import { createDeferred, createDeferredFrom } from '../../../../common/utils/asy import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle'; import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator'; import { noop } from '../../../../common/utils/misc'; -import { getConfiguration } from '../../../../common/vscodeApis/workspaceApis'; +import { + getConfiguration, + getWorkspaceFolderPaths, + getWorkspaceFolders, +} from '../../../../common/vscodeApis/workspaceApis'; import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda'; import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator'; import { getUserHomeDir } from '../../../../common/utils/platform'; +import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis'; +import { PythonEnvKind } from '../../info'; const untildify = require('untildify'); @@ -48,6 +54,7 @@ export interface NativeEnvManagerInfo { export interface NativeGlobalPythonFinder extends Disposable { resolve(executable: string): Promise; refresh(): AsyncIterable; + categoryToKind(category: string): PythonEnvKind; } interface NativeLog { @@ -60,7 +67,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba private firstRefreshResults: undefined | (() => AsyncGenerator); - private readonly outputChannel = this._register(window.createOutputChannel('Python Locator', { log: true })); + private readonly outputChannel = this._register(createLogOutputChannel('Python Locator', { log: true })); constructor() { super(); @@ -80,6 +87,40 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba return environment; } + categoryToKind(category: string): PythonEnvKind { + switch (category.toLowerCase()) { + case 'conda': + return PythonEnvKind.Conda; + case 'system': + case 'homebrew': + case 'mac-python-org': + case 'mac-command-line-tools': + case 'windows-registry': + return PythonEnvKind.System; + case 'pyenv': + case 'pyenv-other': + return PythonEnvKind.Pyenv; + case 'pipenv': + return PythonEnvKind.Pipenv; + case 'pyenv-virtualenv': + return PythonEnvKind.VirtualEnv; + case 'venv': + return PythonEnvKind.Venv; + case 'virtualenv': + return PythonEnvKind.VirtualEnv; + case 'virtualenvwrapper': + return PythonEnvKind.VirtualEnvWrapper; + case 'windows-store': + return PythonEnvKind.MicrosoftStore; + case 'unknown': + return PythonEnvKind.Unknown; + default: { + this.outputChannel.info(`Unknown Python Environment category '${category}' from Native Locator.`); + return PythonEnvKind.Unknown; + } + } + } + async *refresh(): AsyncIterable { if (this.firstRefreshResults) { // If this is the first time we are refreshing, @@ -154,16 +195,33 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba // eslint-disable-next-line class-methods-use-this private start(): rpc.MessageConnection { 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 // we have got the exit event. const readable = new PassThrough(); - proc.stdout.pipe(readable, { end: false }); - proc.stderr.on('data', (data) => this.outputChannel.error(data.toString())); const writable = new PassThrough(); - writable.pipe(proc.stdin, { end: false }); + const disposables: Disposable[] = []; + try { + const proc = ch.spawn(PYTHON_ENV_TOOLS_PATH, ['server'], { env: process.env }); + proc.stdout.pipe(readable, { end: false }); + proc.stderr.on('data', (data) => this.outputChannel.error(data.toString())); + writable.pipe(proc.stdin, { end: false }); + + disposables.push({ + dispose: () => { + try { + if (proc.exitCode === null) { + proc.kill(); + } + } catch (ex) { + this.outputChannel.error('Error disposing finder', ex); + } + }, + }); + } catch (ex) { + this.outputChannel.error(`Error starting Python Finder ${PYTHON_ENV_TOOLS_PATH} server`, ex); + } const disposeStreams = new Disposable(() => { readable.end(); writable.end(); @@ -200,17 +258,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba connection.onClose(() => { disposables.forEach((d) => d.dispose()); }), - { - dispose: () => { - try { - if (proc.exitCode === null) { - proc.kill(); - } - } catch (ex) { - this.outputChannel.error('Error disposing finder', ex); - } - }, - }, ); connection.listen(); @@ -286,7 +333,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba } private sendRefreshRequest() { - const pythonPathSettings = (workspace.workspaceFolders || []).map((w) => + const pythonPathSettings = (getWorkspaceFolders() || []).map((w) => getPythonSettingAndUntildify(DEFAULT_INTERPRETER_PATH_SETTING_KEY, w.uri), ); pythonPathSettings.push(getPythonSettingAndUntildify(DEFAULT_INTERPRETER_PATH_SETTING_KEY)); @@ -308,7 +355,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba { // This has a special meaning in locator, its lot a low priority // as we treat this as workspace folders that can contain a large number of files. - search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath), + search_paths: getWorkspaceFolderPaths(), // Also send the python paths that are configured in the settings. python_interpreter_paths: pythonSettings, // We do not want to mix this with `search_paths` diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index 25ceb267da85..a7ff031c21ff 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -24,6 +24,7 @@ import { import { getQueryFilter } from '../../locatorUtils'; import { PythonEnvCollectionChangedEvent, PythonEnvsWatcher } from '../../watcher'; import { IEnvsCollectionCache } from './envsCollectionCache'; +import { createNativeGlobalPythonFinder } from '../common/nativePythonFinder'; /** * A service which maintains the collection of known environments. @@ -43,6 +44,8 @@ export class EnvsCollectionService extends PythonEnvsWatcher(); + private nativeFinder = createNativeGlobalPythonFinder(); + public refreshState = ProgressReportStage.discoveryFinished; public get onProgress(): Event { @@ -54,11 +57,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher { const query: PythonLocatorQuery | undefined = event.providerId @@ -260,9 +259,21 @@ export class EnvsCollectionService extends PythonEnvsWatcher getEnvPath(e.executable.filename, e.location).pathType === 'envFolderPath', ).length; @@ -281,11 +292,65 @@ export class EnvsCollectionService extends PythonEnvsWatcher e.kind === PythonEnvKind.Venv).length; const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length; const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length; + const global = envs.filter( + (e) => + e.kind === PythonEnvKind.OtherGlobal || + e.kind === PythonEnvKind.System || + e.kind === PythonEnvKind.Custom || + e.kind === PythonEnvKind.OtherVirtual, + ).length; + + const nativeEnvironmentsWithoutPython = nativeEnvs.filter((e) => e.executable === undefined).length; + const nativeCondaEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Conda, + ).length; + const nativeCustomEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Custom, + ).length; + const nativeMicrosoftStoreEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.MicrosoftStore, + ).length; + const nativeOtherGlobalEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherGlobal, + ).length; + const nativeOtherVirtualEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherVirtual, + ).length; + const nativePipEnvEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Pipenv, + ).length; + const nativePoetryEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Poetry, + ).length; + const nativePyenvEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Pyenv, + ).length; + const nativeSystemEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.System, + ).length; + const nativeUnknownEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Unknown, + ).length; + const nativeVenvEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Venv, + ).length; + const nativeVirtualEnvEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.VirtualEnv, + ).length; + const nativeVirtualEnvWrapperEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.VirtualEnvWrapper, + ).length; + const nativeGlobal = nativeEnvs.filter( + (e) => + this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherGlobal || + this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.System || + this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Custom || + this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherVirtual, + ).length; // Intent is to capture time taken for discovery of all envs to complete the first time. sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { interpreters: this.cache.getAllEnvs().length, - usingNativeLocator: this.usingNativeLocator, environmentsWithoutPython, activeStateEnvs, condaEnvs, @@ -302,6 +367,22 @@ export class EnvsCollectionService extends PythonEnvsWatcher, IDisposable { if (data.executable) { const arch = (data.arch || '').toLowerCase(); const env: BasicEnvInfo = { - kind: categoryToKind(data.category), + kind: this.finder.categoryToKind(data.category), executablePath: data.executable ? data.executable : '', envPath: data.prefix ? data.prefix : undefined, version: data.version ? parseVersion(data.version) : undefined, diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 33a6136d35a5..0bd766b4553d 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -40,8 +40,6 @@ import { traceError } from '../logging'; import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; import { PixiLocator } from './base/locators/lowLevel/pixiLocator'; -import { NativeLocator } from './base/locators/lowLevel/nativeLocator'; -import { getConfiguration } from '../common/vscodeApis/workspaceApis'; const PYTHON_ENV_INFO_CACHE_KEY = 'PYTHON_ENV_INFO_CACHEv2'; @@ -135,43 +133,33 @@ async function createLocator( await createCollectionCache(ext), // This is shared. resolvingLocator, - useNativeLocator(), ); return caching; } -function useNativeLocator(): boolean { - const config = getConfiguration('python'); - return config.get('locator', 'js') === 'native'; -} - function createNonWorkspaceLocators(ext: ExtensionState): ILocator[] { const locators: (ILocator & Partial)[] = []; - if (useNativeLocator()) { - locators.push(new NativeLocator()); + locators.push( + // OS-independent locators go here. + new PyenvLocator(), + new CondaEnvironmentLocator(), + new ActiveStateLocator(), + new GlobalVirtualEnvironmentLocator(), + new CustomVirtualEnvironmentLocator(), + ); + + if (getOSType() === OSType.Windows) { + locators.push( + // Windows specific locators go here. + new WindowsRegistryLocator(), + new MicrosoftStoreLocator(), + new WindowsPathEnvVarLocator(), + ); } else { locators.push( - // OS-independent locators go here. - new PyenvLocator(), - new CondaEnvironmentLocator(), - new ActiveStateLocator(), - new GlobalVirtualEnvironmentLocator(), - new CustomVirtualEnvironmentLocator(), + // Linux/Mac locators go here. + new PosixKnownPathsLocator(), ); - - if (getOSType() === OSType.Windows) { - locators.push( - // Windows specific locators go here. - new WindowsRegistryLocator(), - new MicrosoftStoreLocator(), - new WindowsPathEnvVarLocator(), - ); - } else { - locators.push( - // Linux/Mac locators go here. - new PosixKnownPathsLocator(), - ); - } } const disposables = locators.filter((d) => d.dispose !== undefined) as IDisposable[]; @@ -198,21 +186,16 @@ function watchRoots(args: WatchRootsArgs): IDisposable { } function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { - const locators = new WorkspaceLocators( - watchRoots, - useNativeLocator() - ? [] - : [ - (root: vscode.Uri) => [ - new WorkspaceVirtualEnvironmentLocator(root.fsPath), - new PoetryLocator(root.fsPath), - new HatchLocator(root.fsPath), - new PixiLocator(root.fsPath), - new CustomWorkspaceLocator(root.fsPath), - ], - // Add an ILocator factory func here for each kind of workspace-rooted locator. - ], - ); + const locators = new WorkspaceLocators(watchRoots, [ + (root: vscode.Uri) => [ + new WorkspaceVirtualEnvironmentLocator(root.fsPath), + new PoetryLocator(root.fsPath), + new HatchLocator(root.fsPath), + new PixiLocator(root.fsPath), + new CustomWorkspaceLocator(root.fsPath), + ], + // Add an ILocator factory func here for each kind of workspace-rooted locator. + ]); ext.disposables.push(locators); return locators; } diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 2f4b4c19f0eb..3cc257d3a41f 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1235,6 +1235,70 @@ export interface IEventNamePropertyMapping { * Number of environments of a specific type */ virtualEnvWrapperEnvs?: number; + /** + * Number of all known Globals (System, Custom, GlobalCustom, etc) + */ + global?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeEnvironmentsWithoutPython?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeCondaEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeCustomEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeMicrosoftStoreEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeOtherGlobalEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeOtherVirtualEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePipEnvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePoetryEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePyenvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeSystemEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeUnknownEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVenvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVirtualEnvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVirtualEnvWrapperEnvs?: number; + /** + * Number of all known Globals (System, Custom, GlobalCustom, etc) + */ + nativeGlobal?: number; }; /** * Telemetry event sent when discovery of all python environments using the native locator(virtualenv, conda, pipenv etc.) finishes. diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index 7678bef4e53c..774ba388bb4e 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -54,11 +54,30 @@ export enum QuickPickItemKind { } export class Disposable { - constructor(private callOnDispose: () => void) {} + static from(...disposables: { dispose(): () => void }[]): Disposable { + return new Disposable(() => { + if (disposables) { + for (const disposable of disposables) { + if (disposable && typeof disposable.dispose === 'function') { + disposable.dispose(); + } + } + + disposables = []; + } + }); + } - public dispose(): void { - if (this.callOnDispose) { - this.callOnDispose(); + private _callOnDispose: (() => void) | undefined; + + constructor(callOnDispose: () => void) { + this._callOnDispose = callOnDispose; + } + + dispose(): void { + if (typeof this._callOnDispose === 'function') { + this._callOnDispose(); + this._callOnDispose = undefined; } } } diff --git a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts index 5e528af38a3b..572d6b904419 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts @@ -1,6 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/* eslint-disable class-methods-use-this */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { assert, expect } from 'chai'; import { cloneDeep } from 'lodash'; @@ -25,8 +26,33 @@ import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { SimpleLocator } from '../../common'; import { assertEnvEqual, assertEnvsEqual, createFile, deleteFile } from '../envTestUtils'; import { OSType, getOSType } from '../../../../common'; +import * as nativeFinder from '../../../../../client/pythonEnvironments/base/locators/common/nativePythonFinder'; + +class MockNativePythonFinder implements nativeFinder.NativeGlobalPythonFinder { + categoryToKind(_category: string): PythonEnvKind { + throw new Error('Method not implemented.'); + } + + resolve(_executable: string): Promise { + throw new Error('Method not implemented.'); + } + + refresh(): AsyncIterable { + const envs: nativeFinder.NativeEnvInfo[] = []; + return (async function* () { + for (const env of envs) { + yield env; + } + })(); + } + + dispose() { + /** noop */ + } +} suite('Python envs locator - Environments Collection', async () => { + let createNativeGlobalPythonFinderStub: sinon.SinonStub; let collectionService: EnvsCollectionService; let storage: PythonEnvInfo[]; @@ -138,6 +164,8 @@ suite('Python envs locator - Environments Collection', async () => { } setup(async () => { + createNativeGlobalPythonFinderStub = sinon.stub(nativeFinder, 'createNativeGlobalPythonFinder'); + createNativeGlobalPythonFinderStub.returns(new MockNativePythonFinder()); storage = []; const parentLocator = new SimpleLocator(getLocatorEnvs()); const cache = await createCollectionCache({ @@ -146,7 +174,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = envs; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); }); teardown(async () => { @@ -192,7 +220,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); await collectionService.triggerRefresh(undefined); await collectionService.triggerRefresh(undefined, { ifNotTriggerredAlready: true }); @@ -226,7 +254,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -267,7 +295,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); let events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -318,7 +346,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { @@ -371,7 +399,7 @@ suite('Python envs locator - Environments Collection', async () => { storage = e; }, }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); let stage: ProgressReportStage | undefined; collectionService.onProgress((e) => { stage = e.stage; @@ -442,7 +470,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, env); }); @@ -472,10 +500,10 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); collectionService.triggerRefresh().ignoreErrors(); await waitDeferred.promise; // Cache should already contain `env` at this point, although it is not complete. - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, resolvedViaLocator); }); @@ -502,7 +530,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const resolved = await collectionService.resolveEnv(env.executable.filename); assertEnvEqual(resolved, resolvedViaLocator); }); @@ -521,7 +549,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const resolved = await collectionService.resolveEnv(resolvedViaLocator.executable.filename); const envs = collectionService.getEnvs(); assertEnvsEqual(envs, [resolved]); @@ -545,7 +573,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => cachedEnvs, store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); let resolved = await collectionService.resolveEnv(condaEnvWithoutPython.location); assertEnvEqual(resolved, condaEnvWithoutPython); // Ensure cache is used to resolve such envs. @@ -583,7 +611,7 @@ suite('Python envs locator - Environments Collection', async () => { get: () => [], store: async () => noop(), }); - collectionService = new EnvsCollectionService(cache, parentLocator, false); + collectionService = new EnvsCollectionService(cache, parentLocator); const events: PythonEnvCollectionChangedEvent[] = []; collectionService.onChanged((e) => { events.push(e); From 42b8eace811386fc7ed9db9ab1f9820e1c2cae04 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 21 Jun 2024 10:29:23 -0700 Subject: [PATCH 103/106] Use conda-meta to get python version (#23650) potential fix for https://github.com/microsoft/vscode-python/issues/23649 --- .../base/locators/composite/resolverUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index bd3347dbd334..088ae9cc97c1 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -19,6 +19,7 @@ import { AnacondaCompanyName, Conda, getCondaInterpreterPath, + getPythonVersionFromConda, isCondaEnvironment, } from '../../../common/environmentManagers/conda'; import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environmentManagers/pyenv'; @@ -246,7 +247,7 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise { } else { executable = await conda.getInterpreterPathForEnvironment({ prefix: envPath }); } - const version = executable ? await getPythonVersionFromPath(executable) : undefined; + const version = executable ? await getPythonVersionFromConda(executable) : undefined; const info = buildEnvInfo({ executable, kind: PythonEnvKind.Conda, From 10f9613a2ea9898fc02872c2094e03ed01d7bd7b Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:03:33 -0700 Subject: [PATCH 104/106] Unify Terminal REPL triggers (#23641) Resolves: https://github.com/microsoft/vscode-python/issues/22242 Attempting to keep instance of REPL. Did not end up using shell integration for this, but shell integration and exit code will come in handy when we have to keep track of 'opened' state of REPL instance for cases when user wants to enter 'exit()' --- src/client/common/terminal/service.ts | 1 + src/client/providers/replProvider.ts | 2 +- .../terminals/codeExecution/terminalCodeExecution.ts | 7 ++++++- src/test/providers/repl.unit.test.ts | 5 +++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index c3b90181d563..de276762de4b 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -58,6 +58,7 @@ export class TerminalService implements ITerminalService, Disposable { if (!this.options?.hideFromUser) { this.terminal!.show(true); } + this.terminal!.sendText(text, true); } public async sendText(text: string): Promise { diff --git a/src/client/providers/replProvider.ts b/src/client/providers/replProvider.ts index db0e459c12dd..ba01dea3390d 100644 --- a/src/client/providers/replProvider.ts +++ b/src/client/providers/replProvider.ts @@ -40,7 +40,7 @@ export class ReplProvider implements Disposable { .then(noop, noop); return; } - const replProvider = this.serviceContainer.get(ICodeExecutionService, 'repl'); + const replProvider = this.serviceContainer.get(ICodeExecutionService, 'standard'); await replProvider.initializeRepl(resource); } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 4d775dbf6f97..ce317dec20e7 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -17,11 +17,13 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { traceInfo } from '../../logging'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { ICodeExecutionService } from '../../terminals/types'; + @injectable() export class TerminalCodeExecutionProvider implements ICodeExecutionService { private hasRanOutsideCurrentDrive = false; protected terminalTitle!: string; private replActive?: Promise; + constructor( @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @@ -58,12 +60,14 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.getTerminalService(resource).sendText(code); } } + public async initializeRepl(resource: Resource) { const terminalService = this.getTerminalService(resource); if (this.replActive && (await this.replActive)) { await terminalService.show(); return; } + this.replActive = new Promise(async (resolve) => { const replCommandArgs = await this.getExecutableInfo(resource); let listener: IDisposable; @@ -93,7 +97,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { } resolve(true); }); - terminalService.sendCommand(replCommandArgs.command, replCommandArgs.args); + + await terminalService.sendCommand(replCommandArgs.command, replCommandArgs.args); }); this.disposables.push( terminalService.onDidCloseTerminal(() => { diff --git a/src/test/providers/repl.unit.test.ts b/src/test/providers/repl.unit.test.ts index 87811e243bfd..72adfa95a4a0 100644 --- a/src/test/providers/repl.unit.test.ts +++ b/src/test/providers/repl.unit.test.ts @@ -36,7 +36,7 @@ suite('REPL Provider', () => { serviceContainer.setup((c) => c.get(ICommandManager)).returns(() => commandManager.object); serviceContainer.setup((c) => c.get(IWorkspaceService)).returns(() => workspace.object); serviceContainer - .setup((c) => c.get(ICodeExecutionService, TypeMoq.It.isValue('repl'))) + .setup((s) => s.get(TypeMoq.It.isValue(ICodeExecutionService), TypeMoq.It.isValue('standard'))) .returns(() => codeExecutionService.object); serviceContainer.setup((c) => c.get(IDocumentManager)).returns(() => documentManager.object); serviceContainer.setup((c) => c.get(IActiveResourceService)).returns(() => activeResourceService.object); @@ -80,6 +80,7 @@ suite('REPL Provider', () => { const resource = Uri.parse('a'); const disposable = TypeMoq.Mock.ofType(); let commandHandler: undefined | (() => Promise); + commandManager .setup((c) => c.registerCommand(TypeMoq.It.isValue(Commands.Start_REPL), TypeMoq.It.isAny(), TypeMoq.It.isAny()), @@ -98,7 +99,7 @@ suite('REPL Provider', () => { await commandHandler!.call(replProvider); serviceContainer.verify( - (c) => c.get(TypeMoq.It.isValue(ICodeExecutionService), TypeMoq.It.isValue('repl')), + (c) => c.get(TypeMoq.It.isValue(ICodeExecutionService), TypeMoq.It.isValue('standard')), TypeMoq.Times.once(), ); codeExecutionService.verify((c) => c.initializeRepl(TypeMoq.It.isValue(resource)), TypeMoq.Times.once()); From 3c5f46f8753f5657fb2a61f4d9217825ba7fccc3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 24 Jun 2024 16:34:58 +1000 Subject: [PATCH 105/106] Revert changes introduced for Native Locator (#23663) --- .../base/info/environmentInfoService.ts | 43 ------------------- .../base/locators/composite/envsResolver.ts | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index 4c437431823a..38fcc7bb29be 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -30,19 +30,6 @@ export interface IEnvironmentInfoService { env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, ): Promise; - /** - * Get the mandatory interpreter information for the given environment. - * E.g. executable path, version and sysPrefix are considered mandatory. - * However if we only have part of the version, thats still sufficient. - * If the fully resolved and acurate information for all parts of the env is required, then - * used `getEnvironmentInfo`. - * @param env The environment to get the interpreter information for. - * @param priority The priority of the request. - */ - getMandatoryEnvironmentInfo( - env: PythonEnvInfo, - priority?: EnvironmentInfoServiceQueuePriority, - ): Promise; /** * Reset any stored interpreter information for the given environment. * @param searchLocation Search location of the environment. @@ -137,36 +124,6 @@ class EnvironmentInfoService implements IEnvironmentInfoService { return deferred.promise; } - public async getMandatoryEnvironmentInfo( - env: PythonEnvInfo, - priority?: EnvironmentInfoServiceQueuePriority, - ): Promise { - const interpreterPath = env.executable.filename; - const result = this.cache.get(normCasePath(interpreterPath)); - if (result !== undefined) { - // Another call for this environment has already been made, return its result. - return result.promise; - } - - const deferred = createDeferred(); - const info = EnvironmentInfoService.getInterpreterInfo(env, true); - if (info !== undefined) { - this.cache.set(normCasePath(interpreterPath), deferred); - deferred.resolve(info); - return info; - } - - this.cache.set(normCasePath(interpreterPath), deferred); - this._getEnvironmentInfo(env, priority) - .then((r) => { - deferred.resolve(r); - }) - .catch((ex) => { - deferred.reject(ex); - }); - return deferred.promise; - } - public async _getEnvironmentInfo( env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index a823e6f77ec5..6bd342d14d9c 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -137,7 +137,7 @@ export class PythonEnvsResolver implements IResolvingLocator { state.pending += 1; // It's essential we increment the pending call count before any asynchronus calls in this method. // We want this to be run even when `resolveInBackground` is called in background. - const info = await this.environmentInfoService.getMandatoryEnvironmentInfo(seen[envIndex]); + const info = await this.environmentInfoService.getEnvironmentInfo(seen[envIndex]); const old = seen[envIndex]; if (info) { const resolvedEnv = getResolvedEnv(info, seen[envIndex], old.identifiedUsingNativeLocator); From 0ae3f0c1d8dd55688296e328bd78a464145915db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 06:36:39 +0000 Subject: [PATCH 106/106] Bump actions/upload-artifact from 3 to 4 in /.github/actions/build-vsix Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/actions/build-vsix/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-vsix/action.yml b/.github/actions/build-vsix/action.yml index fc3233b06eff..929ecb31a6d3 100644 --- a/.github/actions/build-vsix/action.yml +++ b/.github/actions/build-vsix/action.yml @@ -87,7 +87,7 @@ runs: shell: bash - name: Upload VSIX - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact_name }} path: ${{ inputs.vsix_name }}