|
| 1 | + |
| 2 | +# Startup Flow for Python Environments Extension |
| 3 | + |
| 4 | + |
| 5 | +user opens VS Code |
| 6 | +python environments extension begins activation |
| 7 | + |
| 8 | +SYNC (`activate` in extension.ts): |
| 9 | +1. create core objects: ProjectManager, EnvironmentManagers, ManagerReady |
| 10 | +2. `setPythonApi()` — API object created, deferred resolved (API is now available to consumers) |
| 11 | +3. create views (EnvManagerView, ProjectView), status bar, terminal manager |
| 12 | +4. register all commands |
| 13 | +5. activate() returns — extension is "active" from VS Code's perspective |
| 14 | + |
| 15 | + 📊 TELEMETRY: EXTENSION.ACTIVATION_DURATION { duration } |
| 16 | + |
| 17 | +ASYNC (setImmediate callback, still in extension.ts): |
| 18 | +1. spawn PET process (`createNativePythonFinder`) |
| 19 | + 1. sets up a JSON-RPC connection to it over stdin/stdout |
| 20 | +2. register all built-in managers in parallel (Promise.all): |
| 21 | + - for each manager (system, conda, pyenv, pipenv, poetry): |
| 22 | + 1. check if tool exists (e.g. `getConda(nativeFinder)` asks PET for the conda binary) |
| 23 | + 2. if tool not found → log, return early (manager not registered) |
| 24 | + 3. if tool found → create manager, call `api.registerEnvironmentManager(manager)` |
| 25 | + - this adds it to the `EnvironmentManagers` map |
| 26 | + - fires `onDidChangeEnvironmentManager` → `ManagerReady` deferred resolves for this manager |
| 27 | +3. all registrations complete (Promise.all resolves) |
| 28 | + |
| 29 | +--- gate point: `applyInitialEnvironmentSelection` --- |
| 30 | + 📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount } |
| 31 | + |
| 32 | +1. for each workspace folder + global scope (no workspace case), run `resolvePriorityChainCore` to find manager: |
| 33 | + - P1: pythonProjects[] setting → specific manager for this project |
| 34 | + - P2: user-configured defaultEnvManager setting |
| 35 | + - P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path) |
| 36 | + - P4: auto-discovery → try venv manager (local .venv), fall back to system python |
| 37 | + - for workspace scope: ask venv manager if there's a local env (.venv/venv in the folder) |
| 38 | + - if found → use venv manager with that env |
| 39 | + - if not found → fall back to system python manager |
| 40 | + - for global scope: use system python manager directly |
| 41 | + |
| 42 | +2. get the environment from the winning priority level: |
| 43 | + |
| 44 | + --- fork point: `result.environment ?? await result.manager.get(folder.uri)` --- |
| 45 | + left side truthy = envPreResolved | left side undefined = managerDiscovery |
| 46 | + |
| 47 | + envPreResolved — P3 won (interpreter → manager): |
| 48 | + `resolvePriorityChainCore` calls `tryResolveInterpreterPath()`: |
| 49 | + 1. `nativeFinder.resolve(path)` — single PET call, resolves just this one binary |
| 50 | + 2. find which manager owns the resolved env (by managerId) |
| 51 | + 3. return { manager, environment } — BOTH are known |
| 52 | + → result.environment is set → the `??` short-circuits |
| 53 | + → no `manager.get()` called, no `initialize()`, no full discovery |
| 54 | + |
| 55 | + managerDiscovery — P1, P2, or P4 won (manager → interpreter): |
| 56 | + `resolvePriorityChainCore` returns { manager, environment: undefined } |
| 57 | + → result.environment is undefined → falls through to `await result.manager.get(scope)` |
| 58 | + `manager.get(scope)` (e.g. `CondaEnvManager.get()`): |
| 59 | + 4. `initialize()` — lazy, once-only per manager (guarded by deferred) |
| 60 | + a. `nativeFinder.refresh(hardRefresh=false)`: |
| 61 | + → `handleSoftRefresh()` checks in-memory cache (Map) for key 'all' (bc one big scan, shared cache, all managers benefit) |
| 62 | + - on reload: cache is empty (Map was destroyed) → cache miss |
| 63 | + - falls through to `handleHardRefresh()` |
| 64 | + → `handleHardRefresh()`: |
| 65 | + - adds request to WorkerPool queue (concurrency 1, so serialized) |
| 66 | + - when its turn comes, calls `doRefresh()`: |
| 67 | + 1. `configure()` — JSON-RPC to PET with search paths, conda/poetry/pipenv paths, cache dir |
| 68 | + 2. `refresh` — JSON-RPC to PET, PET scans filesystem |
| 69 | + - PET may use its own on-disk cache (cacheDirectory) to speed this up |
| 70 | + - PET streams back results as 'environment' and 'manager' notifications |
| 71 | + - envs missing version/prefix get an inline resolve() call |
| 72 | + 3. returns NativeInfo[] (all envs of all types) |
| 73 | + - result stored in in-memory cache under key 'all' |
| 74 | + → subsequent managers calling nativeFinder.refresh(false) get cache hit → instant |
| 75 | + b. filter results to this manager's env type (e.g. conda filters to kind=conda) |
| 76 | + c. convert NativeEnvInfo → PythonEnvironment objects → populate collection |
| 77 | + d. `loadEnvMap()` — reads persisted env path from workspace state |
| 78 | + → matches path against freshly discovered collection via `findEnvironmentByPath()` |
| 79 | + → populates `fsPathToEnv` map |
| 80 | + 5. look up scope in `fsPathToEnv` → return the matched env |
| 81 | + |
| 82 | + 📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection } |
| 83 | + |
| 84 | +3. env is cached in memory (no settings.json write) |
| 85 | +4. Python extension / status bar can now get the selected env via `api.getEnvironment(scope)` |
| 86 | + |
| 87 | + 📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration (activation→here), result, failureStage?, errorType? } |
| 88 | + |
| 89 | +POST-INIT: |
| 90 | +1. register terminal package watcher |
| 91 | +2. register settings change listener (`registerInterpreterSettingsChangeListener`) — re-runs priority chain if settings change |
| 92 | +3. initialize terminal manager |
| 93 | +4. send telemetry (manager selection, project structure, discovery summary) |
0 commit comments