Skip to content

Commit 1370b6d

Browse files
authored
Add Registry Locator (microsoft#14349)
* Add Registry Locator * Address comments
1 parent d7a903b commit 1370b6d

9 files changed

Lines changed: 524 additions & 8 deletions

File tree

src/client/pythonEnvironments/base/info/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,11 @@ export type PythonEnvInfo = _PythonEnvInfo & {
150150
defaultDisplayName?: string;
151151
searchLocation?: Uri;
152152
};
153+
154+
export const UNKNOWN_PYTHON_VERSION:PythonVersion = {
155+
major: -1,
156+
minor: -1,
157+
micro: -1,
158+
release: { level: PythonReleaseLevel.Final, serial: -1 },
159+
sysVersion: undefined,
160+
};

src/client/pythonEnvironments/common/windowsUtils.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,23 @@ export function isWindowsPythonExe(interpreterPath:string): boolean {
2929
return windowsPythonExes.test(path.basename(interpreterPath));
3030
}
3131

32-
export async function readRegistryValues(options: Options): Promise<RegistryItem[]> {
32+
export interface IRegistryKey{
33+
hive:string;
34+
arch:string;
35+
key:string;
36+
parentKey?:IRegistryKey;
37+
}
38+
39+
export interface IRegistryValue{
40+
hive:string;
41+
arch:string;
42+
key:string;
43+
name:string;
44+
type:string;
45+
value:string;
46+
}
47+
48+
export async function readRegistryValues(options: Options): Promise<IRegistryValue[]> {
3349
// tslint:disable-next-line:no-require-imports
3450
const WinReg = require('winreg');
3551
const regKey = new WinReg(options);
@@ -43,7 +59,7 @@ export async function readRegistryValues(options: Options): Promise<RegistryItem
4359
return deferred.promise;
4460
}
4561

46-
export async function readRegistryKeys(options: Options): Promise<Registry[]> {
62+
export async function readRegistryKeys(options: Options): Promise<IRegistryKey[]> {
4763
// tslint:disable-next-line:no-require-imports
4864
const WinReg = require('winreg');
4965
const regKey = new WinReg(options);
@@ -67,15 +83,15 @@ export interface IRegistryInterpreterData{
6783
}
6884

6985
async function getInterpreterDataFromKey(
70-
{ arch, hive, key }:Registry,
86+
{ arch, hive, key }:IRegistryKey,
7187
distroOrgName:string,
7288
): Promise<IRegistryInterpreterData | undefined> {
7389
const result:IRegistryInterpreterData = {
7490
interpreterPath: '',
7591
distroOrgName,
7692
};
7793

78-
const values:RegistryItem[] = await readRegistryValues({ arch, hive, key });
94+
const values:IRegistryValue[] = await readRegistryValues({ arch, hive, key });
7995
for (const value of values) {
8096
switch (value.name) {
8197
case 'SysArchitecture':
@@ -95,10 +111,10 @@ async function getInterpreterDataFromKey(
95111
}
96112
}
97113

98-
const subKeys:Registry[] = await readRegistryKeys({ arch, hive, key });
114+
const subKeys:IRegistryKey[] = await readRegistryKeys({ arch, hive, key });
99115
const subKey = subKeys.map((s) => s.key).find((s) => s.endsWith('InstallPath'));
100116
if (subKey) {
101-
const subKeyValues:RegistryItem[] = await readRegistryValues({ arch, hive, key: subKey });
117+
const subKeyValues:IRegistryValue[] = await readRegistryValues({ arch, hive, key: subKey });
102118
const value = subKeyValues.find((v) => v.name === 'ExecutablePath');
103119
if (value) {
104120
result.interpreterPath = value.value;

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

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,30 @@
33

44
import { uniqBy } from 'lodash';
55
import { HKCU, HKLM } from 'winreg';
6+
import { traceError, traceVerbose } from '../../../../common/logger';
7+
import { Architecture } from '../../../../common/utils/platform';
8+
import {
9+
PythonEnvInfo, PythonEnvKind, PythonVersion, UNKNOWN_PYTHON_VERSION,
10+
} from '../../../base/info';
11+
import { parseVersion } from '../../../base/info/pythonVersion';
12+
import { ILocator, IPythonEnvsIterator } from '../../../base/locator';
13+
import { PythonEnvsWatcher } from '../../../base/watcher';
14+
import { getFileInfo } from '../../../common/externalDependencies';
615
import { getInterpreterDataFromRegistry, IRegistryInterpreterData, readRegistryKeys } from '../../../common/windowsUtils';
716

8-
export async function getRegistryInterpreters() : Promise<IRegistryInterpreterData[]> {
17+
async function getRegistryInterpreters() : Promise<IRegistryInterpreterData[]> {
918
let registryData:IRegistryInterpreterData[] = [];
1019

1120
for (const arch of ['x64', 'x86']) {
1221
for (const hive of [HKLM, HKCU]) {
13-
const keys = (await readRegistryKeys({ arch, hive, key: '\\SOFTWARE\\Python' })).map((k) => k.key);
22+
const root = '\\SOFTWARE\\Python';
23+
let keys:string[] = [];
24+
try {
25+
keys = (await readRegistryKeys({ arch, hive, key: root })).map((k) => k.key);
26+
} catch (ex) {
27+
traceError(`Failed to access Registry: ${arch}\\${hive}\\${root}`, ex);
28+
}
29+
1430
for (const key of keys) {
1531
registryData = registryData.concat(await getInterpreterDataFromRegistry(arch, hive, key));
1632
}
@@ -19,3 +35,61 @@ export async function getRegistryInterpreters() : Promise<IRegistryInterpreterDa
1935

2036
return uniqBy(registryData, (r:IRegistryInterpreterData) => r.interpreterPath);
2137
}
38+
39+
function getArchitecture(data:IRegistryInterpreterData) {
40+
let arch = Architecture.Unknown;
41+
if (data.bitnessStr) {
42+
arch = data.bitnessStr === '32bit' ? Architecture.x86 : Architecture.x64;
43+
}
44+
return arch;
45+
}
46+
47+
export class WindowsRegistryLocator extends PythonEnvsWatcher implements ILocator {
48+
private kind:PythonEnvKind = PythonEnvKind.OtherGlobal;
49+
50+
public iterEnvs(): IPythonEnvsIterator {
51+
const buildEnvInfo = (data:IRegistryInterpreterData) => this.buildEnvInfo(data);
52+
const iterator = async function* () {
53+
const interpreters = await getRegistryInterpreters();
54+
yield* interpreters.map(buildEnvInfo);
55+
};
56+
return iterator();
57+
}
58+
59+
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
60+
const executablePath = typeof env === 'string' ? env : env.executable.filename;
61+
const interpreters = await getRegistryInterpreters();
62+
const selected = interpreters.find((i) => i.interpreterPath.toUpperCase() === executablePath.toUpperCase());
63+
if (selected) {
64+
return this.buildEnvInfo(selected);
65+
}
66+
67+
return undefined;
68+
}
69+
70+
private async buildEnvInfo(data:IRegistryInterpreterData): Promise<PythonEnvInfo> {
71+
const versionStr = (data.versionStr ?? data.sysVersionStr) ?? data.interpreterPath;
72+
let version:PythonVersion = UNKNOWN_PYTHON_VERSION;
73+
74+
try {
75+
version = parseVersion(versionStr);
76+
} catch (ex) {
77+
traceVerbose(`Failed to parse version: ${versionStr}`, ex);
78+
}
79+
80+
return {
81+
name: '',
82+
location: '',
83+
kind: this.kind,
84+
executable: {
85+
filename: data.interpreterPath,
86+
sysPrefix: '',
87+
...(await getFileInfo(data.interpreterPath)),
88+
},
89+
version,
90+
arch: getArchitecture(data),
91+
distro: { org: data.distroOrgName ?? '' },
92+
defaultDisplayName: data.displayName,
93+
};
94+
}
95+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Usually contains command that was used to create or update the conda environment with time stamps.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Not real python exe

src/test/pythonEnvironments/common/envlayouts/winreg/py39/python.exe

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/winreg/python37/python.exe

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/winreg/python38/python.exe

Whitespace-only changes.

0 commit comments

Comments
 (0)