|
1 | 1 | import * as path from 'path'; |
2 | 2 | import { ConfigurationScope, ConfigurationTarget, Uri, WorkspaceConfiguration, WorkspaceFolder } from 'vscode'; |
3 | 3 | import { PythonProject } from '../../api'; |
4 | | -import { DEFAULT_ENV_MANAGER_ID, DEFAULT_PACKAGE_MANAGER_ID } from '../../common/constants'; |
| 4 | +import { DEFAULT_ENV_MANAGER_ID, DEFAULT_PACKAGE_MANAGER_ID, SYSTEM_MANAGER_ID } from '../../common/constants'; |
5 | 5 | import { traceError, traceInfo, traceVerbose, traceWarn } from '../../common/logging'; |
| 6 | +import { getGlobalPersistentState } from '../../common/persistentState'; |
| 7 | +import { EventNames } from '../../common/telemetry/constants'; |
| 8 | +import { sendTelemetryEvent } from '../../common/telemetry/sender'; |
6 | 9 | import * as workspaceApis from '../../common/workspace.apis'; |
7 | 10 | import { PythonProjectManager, PythonProjectSettings } from '../../internal.api'; |
8 | 11 |
|
@@ -576,3 +579,93 @@ export function getSettingUserScope<T>(section: string, key: string): T | undefi |
576 | 579 | } |
577 | 580 | return undefined; |
578 | 581 | } |
| 582 | + |
| 583 | +const MIGRATION_KEY = 'globalSettingsMigration.systemEnvManagerRemoved'; |
| 584 | + |
| 585 | +/** |
| 586 | + * Returns true if any user-scope slot of the inspection result equals `value`. |
| 587 | + * For window-scoped settings VS Code may populate `globalRemoteValue` and/or |
| 588 | + * `globalLocalValue` in addition to `globalValue` depending on context. |
| 589 | + */ |
| 590 | +function userScopeHasValue(inspect: { globalValue?: string } | undefined, value: string): boolean { |
| 591 | + if (!inspect) { |
| 592 | + return false; |
| 593 | + } |
| 594 | + const record = inspect as Record<string, unknown>; |
| 595 | + if (record.globalRemoteValue === value) { |
| 596 | + return true; |
| 597 | + } |
| 598 | + if (record.globalLocalValue === value) { |
| 599 | + return true; |
| 600 | + } |
| 601 | + if (inspect.globalValue === value) { |
| 602 | + return true; |
| 603 | + } |
| 604 | + return false; |
| 605 | +} |
| 606 | + |
| 607 | +/** |
| 608 | + * One-time migration: removes `defaultEnvManager` from User (global) settings if it was |
| 609 | + * set to `system` by the extension. This was an unintentional side effect of a bug where |
| 610 | + * the extension wrote to User scope when no workspace was open. Having `system` at the |
| 611 | + * User level causes all workspaces to ignore local .venv environments. |
| 612 | + * |
| 613 | + * Because `python-envs.defaultEnvManager` is a window-scoped setting, the stale value can |
| 614 | + * land in any of `globalValue`, `globalLocalValue`, or `globalRemoteValue` depending on |
| 615 | + * which context (local vs remote) hit the bug. We check all three, attempt removal via |
| 616 | + * `ConfigurationTarget.Global` (which clears the slot for the current context), then |
| 617 | + * re-inspect. If any user-scope slot still holds the stale value we do NOT mark the |
| 618 | + * migration complete, so a future activation in the other context can finish the job. |
| 619 | + * |
| 620 | + * See: https://github.com/microsoft/vscode-python-environments/issues/1468 |
| 621 | + */ |
| 622 | +export async function migrateGlobalDefaultEnvManagerSetting(): Promise<void> { |
| 623 | + const state = await getGlobalPersistentState(); |
| 624 | + const alreadyMigrated = await state.get<boolean>(MIGRATION_KEY); |
| 625 | + if (alreadyMigrated) { |
| 626 | + return; |
| 627 | + } |
| 628 | + |
| 629 | + const config = workspaceApis.getConfiguration('python-envs', undefined); |
| 630 | + const inspect = config.inspect<string>('defaultEnvManager'); |
| 631 | + |
| 632 | + if (!userScopeHasValue(inspect, SYSTEM_MANAGER_ID)) { |
| 633 | + sendTelemetryEvent(EventNames.MIGRATION_SYSTEM_ENV_MANAGER, undefined, { outcome: 'not_set' }); |
| 634 | + await state.set(MIGRATION_KEY, true); |
| 635 | + return; |
| 636 | + } |
| 637 | + |
| 638 | + try { |
| 639 | + await config.update('defaultEnvManager', undefined, ConfigurationTarget.Global); |
| 640 | + } catch (err) { |
| 641 | + // Don't mark migration done; we'll retry on a future activation. |
| 642 | + traceWarn( |
| 643 | + `[migration] Failed to remove 'python-envs.defaultEnvManager: ${SYSTEM_MANAGER_ID}' from User settings: ${err}`, |
| 644 | + ); |
| 645 | + sendTelemetryEvent(EventNames.MIGRATION_SYSTEM_ENV_MANAGER, undefined, { |
| 646 | + outcome: 'failed', |
| 647 | + errorType: err instanceof Error ? err.name : typeof err, |
| 648 | + }); |
| 649 | + return; |
| 650 | + } |
| 651 | + |
| 652 | + // Re-inspect: `update(Global)` only clears the current context's slot. If another |
| 653 | + // context's slot (local vs remote) still holds the stale value, leave the flag unset |
| 654 | + // so the next activation in that context can clear it. |
| 655 | + const after = config.inspect<string>('defaultEnvManager'); |
| 656 | + if (userScopeHasValue(after, SYSTEM_MANAGER_ID)) { |
| 657 | + traceInfo( |
| 658 | + `[migration] Partially removed 'python-envs.defaultEnvManager: ${SYSTEM_MANAGER_ID}' from User settings; ` + |
| 659 | + `another context still has it set. Will retry on next activation.`, |
| 660 | + ); |
| 661 | + sendTelemetryEvent(EventNames.MIGRATION_SYSTEM_ENV_MANAGER, undefined, { outcome: 'partial' }); |
| 662 | + return; |
| 663 | + } |
| 664 | + |
| 665 | + traceInfo( |
| 666 | + `[migration] Removed 'python-envs.defaultEnvManager: ${SYSTEM_MANAGER_ID}' from User settings ` + |
| 667 | + `(was set unintentionally by a previous version). See https://github.com/microsoft/vscode-python-environments/issues/1468`, |
| 668 | + ); |
| 669 | + sendTelemetryEvent(EventNames.MIGRATION_SYSTEM_ENV_MANAGER, undefined, { outcome: 'removed' }); |
| 670 | + await state.set(MIGRATION_KEY, true); |
| 671 | +} |
0 commit comments