Skip to content

Commit a2cde39

Browse files
author
Kartik Raj
authored
Use resolveEnv from windows registry locator to create registry resolver (#16641)
* Use resolveEnv from Windows registry to create windows reg resolver * Force OS type to windows * Fix environment resolver test * Log more details * Fix tests
1 parent f63dac9 commit a2cde39

File tree

8 files changed

+313
-87
lines changed

8 files changed

+313
-87
lines changed

src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
import * as path from 'path';
55
import { Uri } from 'vscode';
6+
import { uniq } from 'lodash';
67
import { traceError, traceWarning } from '../../../../common/logger';
7-
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info';
8-
import { buildEnvInfo, getEnvMatcher } from '../../info/env';
8+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, UNKNOWN_PYTHON_VERSION } from '../../info';
9+
import { buildEnvInfo, comparePythonVersionSpecificity, getEnvMatcher } from '../../info/env';
910
import {
1011
getEnvironmentDirFromPath,
1112
getInterpreterPathFromDir,
@@ -15,8 +16,9 @@ import { identifyEnvironment } from '../../../common/environmentIdentifier';
1516
import { getFileInfo, getWorkspaceFolders, isParentPath } from '../../../common/externalDependencies';
1617
import { AnacondaCompanyName, Conda } from '../../../discovery/locators/services/conda';
1718
import { parsePyenvVersion } from '../../../discovery/locators/services/pyenvLocator';
18-
import { Architecture } from '../../../../common/utils/platform';
19-
import { getPythonVersionFromPath as parsePythonVersionFromPath } from '../../info/pythonVersion';
19+
import { Architecture, getOSType, OSType } from '../../../../common/utils/platform';
20+
import { getPythonVersionFromPath as parsePythonVersionFromPath, parseVersion } from '../../info/pythonVersion';
21+
import { getRegistryInterpreters } from '../../../common/windowsUtils';
2022

2123
function getResolvers(): Map<PythonEnvKind, (executablePath: string) => Promise<PythonEnvInfo>> {
2224
const resolvers = new Map<PythonEnvKind, (_: string) => Promise<PythonEnvInfo>>();
@@ -40,8 +42,17 @@ export async function resolveEnv(executablePath: string): Promise<PythonEnvInfo>
4042
const resolvers = getResolvers();
4143
const resolverForKind = resolvers.get(kind)!;
4244
const resolvedEnv = await resolverForKind(executablePath);
45+
resolvedEnv.searchLocation = getSearchLocation(resolvedEnv);
46+
if (getOSType() === OSType.Windows) {
47+
// We can update env further using information we can get from the Windows registry.
48+
await updateEnvUsingRegistry(resolvedEnv);
49+
}
50+
return resolvedEnv;
51+
}
52+
53+
function getSearchLocation(env: PythonEnvInfo): Uri | undefined {
4354
const folders = getWorkspaceFolders();
44-
const isRootedEnv = folders.some((f) => isParentPath(executablePath, f));
55+
const isRootedEnv = folders.some((f) => isParentPath(env.executable.filename, f));
4556
if (isRootedEnv) {
4657
// For environments inside roots, we need to set search location so they can be queried accordingly.
4758
// Search location particularly for virtual environments is intended as the directory in which the
@@ -52,17 +63,37 @@ export async function resolveEnv(executablePath: string): Promise<PythonEnvInfo>
5263
// |__ env
5364
// |__ bin or Scripts
5465
// |__ python <--- executable
55-
resolvedEnv.searchLocation = Uri.file(path.dirname(resolvedEnv.location));
66+
return Uri.file(path.dirname(env.location));
67+
}
68+
return undefined;
69+
}
70+
71+
async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise<void> {
72+
const interpreters = await getRegistryInterpreters();
73+
const data = interpreters.find((i) => i.interpreterPath.toUpperCase() === env.executable.filename.toUpperCase());
74+
if (data) {
75+
const versionStr = data.versionStr ?? data.sysVersionStr ?? data.interpreterPath;
76+
let version;
77+
try {
78+
version = parseVersion(versionStr);
79+
} catch (ex) {
80+
version = UNKNOWN_PYTHON_VERSION;
81+
}
82+
env.kind = env.kind === PythonEnvKind.Unknown ? PythonEnvKind.OtherGlobal : env.kind;
83+
env.version = comparePythonVersionSpecificity(version, env.version) > 0 ? version : env.version;
84+
env.distro.defaultDisplayName = data.companyDisplayName;
85+
env.arch = data.bitnessStr === '32bit' ? Architecture.x86 : Architecture.x64;
86+
env.distro.org = data.distroOrgName ?? env.distro.org;
87+
env.distro.defaultDisplayName = data.companyDisplayName;
88+
env.source = uniq(env.source.concat(PythonEnvSource.WindowsRegistry));
5689
}
57-
return resolvedEnv;
5890
}
5991

6092
async function resolveSimpleEnv(executablePath: string, kind: PythonEnvKind): Promise<PythonEnvInfo> {
6193
const envInfo = buildEnvInfo({
6294
kind,
6395
version: await getPythonVersionFromPath(executablePath),
6496
executable: executablePath,
65-
source: [PythonEnvSource.Other],
6697
});
6798
const location = getEnvironmentDirFromPath(executablePath);
6899
envInfo.location = location;
@@ -109,14 +140,35 @@ async function resolvePyenvEnv(executablePath: string): Promise<PythonEnvInfo> {
109140
const location = getEnvironmentDirFromPath(executablePath);
110141
const name = path.basename(location);
111142

143+
// The sub-directory name sometimes can contain distro and python versions.
144+
// here we attempt to extract the texts out of the name.
112145
const versionStrings = await parsePyenvVersion(name);
113146

114147
const envInfo = buildEnvInfo({
115148
kind: PythonEnvKind.Pyenv,
116149
executable: executablePath,
117150
source: [PythonEnvSource.Pyenv],
118151
location,
152+
// Pyenv environments can fall in to these three categories:
153+
// 1. Global Installs : These are environments that are created when you install
154+
// a supported python distribution using `pyenv install <distro>` command.
155+
// These behave similar to globally installed version of python or distribution.
156+
//
157+
// 2. Virtual Envs : These are environments that are created when you use
158+
// `pyenv virtualenv <distro> <env-name>`. These are similar to environments
159+
// created using `python -m venv <env-name>`.
160+
//
161+
// 3. Conda Envs : These are environments that are created when you use
162+
// `pyenv virtualenv <miniconda|anaconda> <env-name>`. These are similar to
163+
// environments created using `conda create -n <env-name>.
164+
//
165+
// All these environments are fully handled by `pyenv` and should be activated using
166+
// `pyenv local|global <env-name>` or `pyenv shell <env-name>`
167+
//
168+
// For the display name we are going to treat these as `pyenv` environments.
119169
display: `${name}:pyenv`,
170+
// Here we look for near by files, or config files to see if we can get python version info
171+
// without running python itself.
120172
version: await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer),
121173
org: versionStrings && versionStrings.distro ? versionStrings.distro : '',
122174
fileInfo: await getFileInfo(executablePath),

src/client/pythonEnvironments/common/windowsUtils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { uniqBy } from 'lodash';
55
import * as path from 'path';
6+
import { isTestExecution } from '../../common/constants';
67
import { traceError, traceVerbose } from '../../common/logger';
78
import {
89
HKCU,
@@ -109,7 +110,13 @@ export async function getInterpreterDataFromRegistry(
109110
return (allData.filter((data) => data !== undefined) || []) as IRegistryInterpreterData[];
110111
}
111112

112-
export async function getRegistryInterpreters(): Promise<IRegistryInterpreterData[]> {
113+
let registryInterpreters: IRegistryInterpreterData[] | undefined;
114+
115+
export async function getRegistryInterpreters(ignoreCache = false): Promise<IRegistryInterpreterData[]> {
116+
if (!isTestExecution() && !ignoreCache && registryInterpreters !== undefined) {
117+
return registryInterpreters;
118+
}
119+
113120
let registryData: IRegistryInterpreterData[] = [];
114121

115122
for (const arch of ['x64', 'x86']) {
@@ -127,6 +134,6 @@ export async function getRegistryInterpreters(): Promise<IRegistryInterpreterDat
127134
}
128135
}
129136
}
130-
131-
return uniqBy(registryData, (r: IRegistryInterpreterData) => r.interpreterPath);
137+
registryInterpreters = uniqBy(registryData, (r: IRegistryInterpreterData) => r.interpreterPath);
138+
return registryInterpreters;
132139
}

src/client/pythonEnvironments/discovery/locators/services/windowsRegistryLocator.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { uniq } from 'lodash';
54
import { traceError, traceVerbose } from '../../../../common/logger';
65
import { Architecture } from '../../../../common/utils/platform';
76
import {
@@ -31,7 +30,7 @@ export class WindowsRegistryLocator extends Locator {
3130
public iterEnvs(): IPythonEnvsIterator {
3231
const buildRegistryEnvInfo = (data: IRegistryInterpreterData) => this.buildRegistryEnvInfo(data);
3332
const iterator = async function* () {
34-
const interpreters = await getRegistryInterpreters();
33+
const interpreters = await getRegistryInterpreters(true);
3534
for (const interpreter of interpreters) {
3635
try {
3736
const env = await buildRegistryEnvInfo(interpreter);
@@ -44,19 +43,6 @@ export class WindowsRegistryLocator extends Locator {
4443
return iterator();
4544
}
4645

47-
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
48-
const executablePath = typeof env === 'string' ? env : env.executable.filename;
49-
const interpreters = await getRegistryInterpreters();
50-
const selected = interpreters.find((i) => i.interpreterPath.toUpperCase() === executablePath.toUpperCase());
51-
if (selected) {
52-
const regEnv = await this.buildRegistryEnvInfo(selected);
53-
regEnv.source = typeof env === 'string' ? regEnv.source : uniq(regEnv.source.concat(env.source));
54-
return regEnv;
55-
}
56-
57-
return undefined;
58-
}
59-
6046
private async buildRegistryEnvInfo(data: IRegistryInterpreterData): Promise<PythonEnvInfo> {
6147
const versionStr = data.versionStr ?? data.sysVersionStr ?? data.interpreterPath;
6248
let version: PythonVersion = UNKNOWN_PYTHON_VERSION;

src/client/pythonEnvironments/legacyIOC.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import { EnvironmentInfoServiceQueuePriority, getEnvironmentInfoService } from '
7171

7272
const convertedKinds = new Map(
7373
Object.entries({
74+
[PythonEnvKind.OtherGlobal]: EnvironmentType.Global,
7475
[PythonEnvKind.System]: EnvironmentType.System,
7576
[PythonEnvKind.MacDefault]: EnvironmentType.System,
7677
[PythonEnvKind.WindowsStore]: EnvironmentType.WindowsStore,

src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import * as platformApis from '../../../../../client/common/utils/platform';
1414
import {
1515
PythonEnvInfo,
1616
PythonEnvKind,
17-
PythonEnvSource,
1817
PythonVersion,
1918
UNKNOWN_PYTHON_VERSION,
2019
} from '../../../../../client/pythonEnvironments/base/info';
@@ -235,7 +234,7 @@ suite('Python envs locator - Environments Resolver', () => {
235234
arch: Architecture.Unknown,
236235
distro: { org: '' },
237236
searchLocation: Uri.file(path.dirname(location)),
238-
source: [PythonEnvSource.Other],
237+
source: [],
239238
};
240239
}
241240
setup(() => {

0 commit comments

Comments
 (0)