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