From c655ced33615ace9b90e5972e73c33e45c304fbb Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:31:35 -0700 Subject: [PATCH 1/2] feat: implement caching for conda hook path to optimize filesystem checks --- src/managers/conda/condaUtils.ts | 70 ++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/managers/conda/condaUtils.ts b/src/managers/conda/condaUtils.ts index 9fe73e01..92d4d574 100644 --- a/src/managers/conda/condaUtils.ts +++ b/src/managers/conda/condaUtils.ts @@ -1053,6 +1053,9 @@ export async function checkForNoPythonCondaEnvironment( return environment; } +// Cache for conda hook paths to avoid redundant filesystem checks +const condaHookPathCache = new Map>(); + /** * Returns the best guess path to conda-hook.ps1 given a conda executable path. * @@ -1063,32 +1066,45 @@ export async function checkForNoPythonCondaEnvironment( * - etc/profile.d/ */ async function getCondaHookPs1Path(condaPath: string): Promise { - const condaRoot = path.dirname(path.dirname(condaPath)); - - const condaRootCandidates: string[] = [ - path.join(condaRoot, 'shell', 'condabin'), - path.join(condaRoot, 'Library', 'shell', 'condabin'), - path.join(condaRoot, 'condabin'), - path.join(condaRoot, 'etc', 'profile.d'), - ]; - - const checks = condaRootCandidates.map(async (hookSearchDir) => { - const candidate = path.join(hookSearchDir, 'conda-hook.ps1'); - if (await fse.pathExists(candidate)) { - traceInfo(`Conda hook found at: ${candidate}`); - return candidate; - } - return undefined; - }); - const results = await Promise.all(checks); - const found = results.find(Boolean); - if (found) { - return found as string; + // Check cache first + const cachedPath = condaHookPathCache.get(condaPath); + if (cachedPath) { + return cachedPath; } - traceError( - `Conda hook not found in any of the expected locations: ${condaRootCandidates.join( - ', ', - )}, given conda path: ${condaPath}`, - ); - return path.join(condaRoot, 'shell', 'condabin', 'conda-hook.ps1'); + + // Create the promise for finding the hook path + const hookPathPromise = (async () => { + const condaRoot = path.dirname(path.dirname(condaPath)); + + const condaRootCandidates: string[] = [ + path.join(condaRoot, 'shell', 'condabin'), + path.join(condaRoot, 'Library', 'shell', 'condabin'), + path.join(condaRoot, 'condabin'), + path.join(condaRoot, 'etc', 'profile.d'), + ]; + + const checks = condaRootCandidates.map(async (hookSearchDir) => { + const candidate = path.join(hookSearchDir, 'conda-hook.ps1'); + if (await fse.pathExists(candidate)) { + traceInfo(`Conda hook found at: ${candidate}`); + return candidate; + } + return undefined; + }); + const results = await Promise.all(checks); + const found = results.find(Boolean); + if (found) { + return found as string; + } + traceError( + `Conda hook not found in any of the expected locations: ${condaRootCandidates.join( + ', ', + )}, given conda path: ${condaPath}`, + ); + return path.join(condaRoot, 'shell', 'condabin', 'conda-hook.ps1'); + })(); + + // Store in cache and return + condaHookPathCache.set(condaPath, hookPathPromise); + return hookPathPromise; } From d588d4d23d2586fb620b0e4b147560ae95e50f32 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:50:08 -0700 Subject: [PATCH 2/2] add doc strings to conda utility functions --- src/managers/conda/condaUtils.ts | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/managers/conda/condaUtils.ts b/src/managers/conda/condaUtils.ts index 92d4d574..383b186a 100644 --- a/src/managers/conda/condaUtils.ts +++ b/src/managers/conda/condaUtils.ts @@ -132,6 +132,14 @@ async function findConda(): Promise { } } +/** + * Resolves the path to the conda executable using multiple fallback strategies: + * 1. Cached path if available + * 2. Previously saved path from persistent workspace state + * 3. Search in system PATH + * 4. Query native finder for conda installations + * Throws if conda cannot be found through any method. + */ async function getCondaExecutable(native?: NativePythonFinder): Promise { if (condaPath) { traceInfo(`Using conda from cache: ${condaPath}`); @@ -180,6 +188,11 @@ export async function getConda(native?: NativePythonFinder): Promise { return await getCondaExecutable(native); } +/** + * Internal function to execute conda commands with proper error handling, logging, and cancellation support. + * Spawns a shell process to run conda and collects both stdout and stderr output. + * Handles command cancellation and non-zero exit codes. + */ async function _runConda( conda: string, args: string[], @@ -280,6 +293,12 @@ function isPrefixOf(roots: string[], e: string): boolean { return false; } +/** + * Constructs environment info for a named conda environment with shell-specific activation/deactivation commands. + * Handles different shell types (bash, zsh, cmd, powershell, git bash) and their specific activation requirements. + * For absolute conda paths, includes the full conda.sh/conda-hook.ps1 activation sequence. + * For conda on PATH, uses direct conda activate/deactivate commands. + */ async function getNamedCondaPythonInfo( name: string, prefix: string, @@ -468,6 +487,10 @@ async function getPrefixesCondaPythonInfo( }; } +/** + * Creates a special PythonEnvironmentInfo object for conda environments that don't have Python installed. + * These environments are marked with a stop icon and version 'no-python' to indicate their incomplete state. + */ function getCondaWithoutPython(name: string, prefix: string, conda: string): PythonEnvironmentInfo { return { name: name, @@ -487,6 +510,14 @@ function getCondaWithoutPython(name: string, prefix: string, conda: string): Pyt }; } +/** + * Converts a native environment info object (found via PET) into a Python environment object. + * Handles different types of conda environments: + * - Base environment + * - Named environments in conda prefixes + * - Prefix-based environments outside conda prefixes + * - Environments without Python installed + */ async function nativeToPythonEnv( e: NativeEnvInfo, api: PythonEnvironmentApi, @@ -551,6 +582,11 @@ export async function resolveCondaPath( } } +/** + * Discovers and refreshes the list of conda environments in the system. + * Uses the native finder (PET) to detect environments (both on and not on the PATH) and converts them to PythonEnvironment objects. + * Returns a sorted list of all found conda environments. + */ export async function refreshCondaEnvs( hardRefresh: boolean, nativeFinder: NativePythonFinder, @@ -629,6 +665,13 @@ async function getLocation(api: PythonEnvironmentApi, uris: Uri | Uri[]): Promis return api.getPythonProject(Array.isArray(uris) ? uris[0] : uris)?.uri.fsPath; } +/** + * Creates a new conda environment with user interaction to determine the type and location. + * Supports two types of environments: + * 1. Named environments (stored in conda's envs directory) + * 2. Prefix environments (stored in a specific location) + * Handles both single and multi-project scenarios for environment creation. + */ export async function createCondaEnvironment( api: PythonEnvironmentApi, log: LogOutputChannel, @@ -891,6 +934,13 @@ export async function refreshPackages( return packages; } +/** + * Handles package installation, uninstallation and updates in a conda environment. + * Can perform multiple operations in sequence: + * 1. Uninstall specified packages + * 2. Install new packages with optional upgrade of existing packages + * Returns the updated list of installed packages after operations complete. + */ export async function managePackages( environment: PythonEnvironment, options: PackageManagementOptions,