Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8faf5de

Browse files
committed
#1147, use conda env from registry to get conda environments
1 parent 5aab302 commit 8faf5de

File tree

16 files changed

+243
-65
lines changed

16 files changed

+243
-65
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,7 @@
14791479
"node-static": "^0.7.9",
14801480
"prepend-file": "^1.3.0",
14811481
"rx": "^4.1.0",
1482+
"semver": "^5.4.1",
14821483
"socket.io": "^1.4.8",
14831484
"tmp": "0.0.29",
14841485
"transformime": "^3.1.2",
@@ -1518,4 +1519,4 @@
15181519
"vscode": "^1.0.3",
15191520
"webpack": "^1.13.2"
15201521
}
1521-
}
1522+
}

src/client/common/registry.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export enum Hive {
1515

1616
export interface IRegistry {
1717
getKeys(key: string, hive: Hive, arch?: Architecture): Promise<string[]>;
18-
getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise<string | void>;
18+
getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise<string | undefined | null>;
1919
}
2020

2121
export class RegistryImplementation implements IRegistry {
@@ -27,7 +27,7 @@ export class RegistryImplementation implements IRegistry {
2727
}
2828
}
2929

30-
export function getArchitectureDislayName(arch: Architecture) {
30+
export function getArchitectureDislayName(arch?: Architecture) {
3131
switch (arch) {
3232
case Architecture.x64:
3333
return '64-bit';
@@ -42,7 +42,7 @@ function getRegistryValue(options: Registry.Options, name: string = '') {
4242
return new Promise<string | undefined | null>((resolve, reject) => {
4343
new Registry(options).get(name, (error, result) => {
4444
if (error) {
45-
return resolve();
45+
return resolve(undefined);
4646
}
4747
resolve(result.value);
4848
});
@@ -59,7 +59,7 @@ function getRegistryKeys(options: Registry.Options): Promise<string[]> {
5959
});
6060
});
6161
}
62-
function translateArchitecture(arch: Architecture) {
62+
function translateArchitecture(arch?: Architecture): RegistryArchitectures | null | undefined {
6363
switch (arch) {
6464
case Architecture.x86:
6565
return RegistryArchitectures.x86;
@@ -69,7 +69,7 @@ function translateArchitecture(arch: Architecture) {
6969
return;
7070
}
7171
}
72-
function translateHive(hive: Hive) {
72+
function translateHive(hive: Hive): string | null | undefined {
7373
switch (hive) {
7474
case Hive.HKCU:
7575
return Registry.HKCU;

src/client/common/versionUtils.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as semver from 'semver';
2+
3+
export class VersionUtils {
4+
public static convertToSemver(version: string) {
5+
const versionParts = (version || '').split('.').filter(item => item.length > 0);
6+
while (versionParts.length < 3) {
7+
versionParts.push('0');
8+
}
9+
return versionParts.join('.');
10+
}
11+
public static compareVersion(versionA: string, versionB: string) {
12+
try {
13+
versionA = VersionUtils.convertToSemver(versionA);
14+
versionB = VersionUtils.convertToSemver(versionB);
15+
return semver.gt(versionA, versionB) ? 1 : 0;
16+
}
17+
catch {
18+
return 0;
19+
}
20+
}
21+
22+
}

src/client/interpreter/display/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StatusBarItem, Disposable } from 'vscode';
33
import { PythonSettings } from '../../common/configSettings';
44
import * as path from 'path';
55
import { EOL } from 'os';
6-
import { IInterpreterProvider, PythonInterpreter } from '../index';
6+
import { IInterpreterProvider } from '../index';
77
import * as utils from '../../common/utils';
88
import { VirtualEnvironmentManager } from '../virtualEnvs/index';
99
import { getFirstNonEmptyLineFromMultilineString } from '../sources/helpers';
@@ -30,7 +30,7 @@ export class InterpreterDisplay implements Disposable {
3030
this.statusBar.color = '';
3131
this.statusBar.tooltip = pythonPath;
3232
if (interpreter) {
33-
this.statusBar.text = interpreter.displayName;
33+
this.statusBar.text = interpreter.displayName!;
3434
if (interpreter.companyDisplayName) {
3535
const toolTipSuffix = `${EOL}${interpreter.companyDisplayName}`;
3636
this.statusBar.tooltip += toolTipSuffix;

src/client/interpreter/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const settings = PythonSettings.getInstance();
1414

1515
export class InterpreterManager implements Disposable {
1616
private disposables: Disposable[] = [];
17-
private display: InterpreterDisplay;
17+
private display: InterpreterDisplay | null | undefined;
1818
private interpreterProvider: PythonInterpreterProvider;
1919
constructor() {
2020
const virtualEnvMgr = new VirtualEnvironmentManager([new VEnv(), new VirtualEnv()]);
@@ -35,7 +35,7 @@ export class InterpreterManager implements Disposable {
3535
return;
3636
}
3737
const interpreters = await this.interpreterProvider.getInterpreters();
38-
const rootPath = workspace.rootPath.toUpperCase();
38+
const rootPath = workspace.rootPath!.toUpperCase();
3939
const interpretersInWorkspace = interpreters.filter(interpreter => interpreter.path.toUpperCase().startsWith(rootPath));
4040
if (interpretersInWorkspace.length !== 1) {
4141
return;
@@ -45,16 +45,16 @@ export class InterpreterManager implements Disposable {
4545
// In windows the interpreter is under scripts/python.exe on linux it is under bin/python
4646
// Meaning the sub directory must be either scripts, bin or other (but only one level deep)
4747
const pythonPath = interpretersInWorkspace[0].path;
48-
const relativePath = path.dirname(pythonPath).substring(workspace.rootPath.length);
48+
const relativePath = path.dirname(pythonPath).substring(workspace.rootPath!.length);
4949
if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) {
5050
this.setPythonPath(pythonPath);
5151
}
5252
}
5353

5454
public setPythonPath(pythonPath: string) {
5555
pythonPath = IS_WINDOWS ? pythonPath.replace(/\\/g, "/") : pythonPath;
56-
if (pythonPath.startsWith(workspace.rootPath)) {
57-
pythonPath = path.join('${workspaceRoot}', path.relative(workspace.rootPath, pythonPath));
56+
if (pythonPath.startsWith(workspace.rootPath!)) {
57+
pythonPath = path.join('${workspaceRoot}', path.relative(workspace.rootPath!, pythonPath));
5858
}
5959
const pythonConfig = workspace.getConfiguration('python');
6060
pythonConfig.update('pythonPath', pythonPath).then(() => {

src/client/interpreter/sources/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function fixInterpreterDisplayName(item: PythonInterpreter) {
1414
if (!item.displayName) {
1515
const arch = getArchitectureDislayName(item.architecture);
1616
const version = item.version || '';
17-
item.displayName = ['Python', version, arch].filter(item => item.length > 0).join(' ');
17+
item.displayName = ['Python', version, arch].filter(item => item.length > 0).join(' ').trim();
1818
}
1919
return item;
2020
}

src/client/interpreter/sources/index.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ export class PythonInterpreterProvider implements IInterpreterProvider {
1919
constructor(private virtualEnvMgr: VirtualEnvironmentManager) {
2020
// The order of the providers is important
2121
if (IS_WINDOWS) {
22-
this.providers.push(new WindowsRegistryProvider(new RegistryImplementation(), Is_64Bit));
22+
const windowsRegistryProvider = new WindowsRegistryProvider(new RegistryImplementation(), Is_64Bit);
23+
this.providers.push(windowsRegistryProvider);
24+
this.providers.push(new CondaEnvProvider(windowsRegistryProvider));
2325
}
24-
this.providers.push(...[
25-
new CondaEnvProvider(),
26-
new VirtualEnvProvider(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr)
27-
]);
26+
else {
27+
this.providers.push(new CondaEnvProvider());
28+
}
29+
this.providers.push(new VirtualEnvProvider(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr));
30+
2831
if (!IS_WINDOWS) {
2932
// This must be last, it is possible we have paths returned here that are already returned
3033
// in one of the above lists

src/client/interpreter/sources/providers/KnownPathsProvider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use strict";
22
import * as path from 'path';
3+
import * as _ from 'lodash';
34
import { IInterpreterProvider } from '../contracts';
45
import { fsExistsAsync, getInterpreterDisplayName, IS_WINDOWS } from '../../../common/utils';
56
import { lookForInterpretersInDirectory } from '../helpers';
6-
import * as _ from 'lodash';
7-
import * as untildify from 'untildify';
7+
const untildify = require('untildify');
88

99
export class KnownPathsProvider implements IInterpreterProvider {
1010
public constructor(private knownSearchPaths: string[]) { }

src/client/interpreter/sources/providers/condaEnvProvider.ts

+55-24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"use strict";
22
import * as child_process from 'child_process';
3-
import * as path from "path";
3+
import * as path from 'path';
44
import { IInterpreterProvider, PythonInterpreter } from '../contracts';
5-
import { IS_WINDOWS } from "../../../common/utils";
5+
import { IS_WINDOWS, fsExistsAsync } from "../../../common/utils";
6+
import { VersionUtils } from "../../../common/versionUtils";
67

78
// where to find the Python binary within a conda env
89
const CONDA_RELATIVE_PY_PATH = IS_WINDOWS ? ['python.exe'] : ['bin', 'python'];
@@ -15,32 +16,62 @@ type CondaInfo = {
1516
default_prefix: string;
1617
}
1718
export class CondaEnvProvider implements IInterpreterProvider {
19+
constructor(private registryLookupForConda?: IInterpreterProvider) {
20+
}
1821
public getInterpreters() {
19-
return this.suggestionsFromConda();
22+
return this.getSuggestionsFromConda();
2023
}
21-
private suggestionsFromConda(): Promise<PythonInterpreter[]> {
22-
return new Promise((resolve, reject) => {
23-
// interrogate conda (if it's on the path) to find all environments
24-
child_process.execFile('conda', ['info', '--json'], (_, stdout) => {
25-
if (stdout.length === 0) {
26-
return resolve([]);
27-
}
2824

29-
try {
30-
const info = JSON.parse(stdout);
31-
resolve(this.parseCondaInfo(info));
32-
} catch (e) {
33-
// Failed because either:
34-
// 1. conda is not installed
35-
// 2. `conda info --json` has changed signature
36-
// 3. output of `conda info --json` has changed in structure
37-
// In all cases, we can't offer conda pythonPath suggestions.
38-
return resolve([]);
39-
}
25+
private getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
26+
return this.getCondaFile()
27+
.then(condaFile => {
28+
return new Promise<PythonInterpreter[]>((resolve, reject) => {
29+
// interrogate conda (if it's on the path) to find all environments
30+
child_process.execFile(condaFile, ['info', '--json'], (_, stdout) => {
31+
if (stdout.length === 0) {
32+
return resolve([]);
33+
}
34+
35+
try {
36+
const info = JSON.parse(stdout);
37+
resolve(this.parseCondaInfo(info));
38+
} catch (e) {
39+
// Failed because either:
40+
// 1. conda is not installed
41+
// 2. `conda info --json` has changed signature
42+
// 3. output of `conda info --json` has changed in structure
43+
// In all cases, we can't offer conda pythonPath suggestions.
44+
return resolve([]);
45+
}
46+
});
47+
});
4048
});
41-
});
4249
}
43-
50+
public getCondaFile() {
51+
if (this.registryLookupForConda) {
52+
return this.registryLookupForConda.getInterpreters()
53+
.then(interpreters => interpreters.filter(this.isCondaEnvironment))
54+
.then(condaInterpreters => this.getLatestVersion(condaInterpreters))
55+
.then(condaInterpreter => {
56+
return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'Scripts', 'conda.exe') : 'conda';
57+
})
58+
.then(condaPath => {
59+
return fsExistsAsync(condaPath).then(exists => exists ? condaPath : 'conda');
60+
});
61+
}
62+
return Promise.resolve('conda');
63+
}
64+
public isCondaEnvironment(interpreter: PythonInterpreter) {
65+
return (interpreter.displayName || '').toUpperCase().indexOf('ANACONDA') >= 0 ||
66+
(interpreter.companyDisplayName || '').toUpperCase().indexOf('CONTINUUM') >= 0;
67+
}
68+
public getLatestVersion(interpreters: PythonInterpreter[]) {
69+
const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0);
70+
sortedInterpreters.sort((a, b) => VersionUtils.compareVersion(a.version!, b.version!));
71+
if (sortedInterpreters.length > 0) {
72+
return sortedInterpreters[sortedInterpreters.length - 1];
73+
}
74+
}
4475
public async parseCondaInfo(info: CondaInfo) {
4576
// "sys.version": "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]",
4677
const displayName = this.getDisplayNameFromVersionInfo(info['sys.version']);
@@ -76,4 +107,4 @@ export class CondaEnvProvider implements IInterpreterProvider {
76107
}
77108
return AnacondaDisplayName;
78109
}
79-
}
110+
}

src/client/interpreter/sources/providers/virtualEnvProvider.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
"use strict";
22
import * as _ from 'lodash';
33
import * as path from 'path';
4-
import * as untildify from 'untildify';
4+
import * as settings from './../../../common/configSettings';
55
import { VirtualEnvironmentManager } from '../../virtualEnvs';
66
import { IInterpreterProvider } from '../contracts';
77
import { IS_WINDOWS, fsReaddirAsync, getInterpreterDisplayName } from "../../../common/utils";
88
import { PythonInterpreter } from '../index';
99
import { lookForInterpretersInDirectory } from '../helpers';
10-
import * as settings from './../../../common/configSettings';
1110
import { workspace } from 'vscode';
11+
const untildify = require('untildify');
1212

1313
export class VirtualEnvProvider implements IInterpreterProvider {
1414
public constructor(private knownSearchPaths: string[], private virtualEnvMgr: VirtualEnvironmentManager) { }
@@ -56,7 +56,7 @@ export class VirtualEnvProvider implements IInterpreterProvider {
5656
}
5757

5858
export function getKnownSearchPathsForVirtualEnvs(): string[] {
59-
let paths = [];
59+
const paths: string[] = [];
6060
if (!IS_WINDOWS) {
6161
const defaultPaths = ['/Envs', '/.virtualenvs', '/.pyenv', '/.pyenv/versions'];
6262
defaultPaths.forEach(p => {

src/client/interpreter/sources/providers/windowsRegistryProvider.ts

+23-13
Original file line numberDiff line numberDiff line change
@@ -63,39 +63,49 @@ export class WindowsRegistryProvider implements IInterpreterProvider {
6363
const tagKeys = await this.registry.getKeys(companyKey, hive, arch);
6464
return Promise.all(tagKeys.map(tagKey => this.getInreterpreterDetailsForCompany(tagKey, companyKey, hive, arch)));
6565
}
66-
private getInreterpreterDetailsForCompany(tagKey: string, companyKey: string, hive: Hive, arch?: Architecture): Promise<PythonInterpreter> {
66+
private getInreterpreterDetailsForCompany(tagKey: string, companyKey: string, hive: Hive, arch?: Architecture): Promise<PythonInterpreter | undefined | null> {
6767
const key = `${tagKey}\\InstallPath`;
68+
type InterpreterInformation = null | undefined | {
69+
installPath: string,
70+
executablePath?: string,
71+
displayName?: string,
72+
version?: string,
73+
companyDisplayName?: string
74+
};
6875
return this.registry.getValue(key, hive, arch)
6976
.then(installPath => {
7077
// Install path is mandatory
7178
if (!installPath) {
72-
return Promise.resolve([, , , ,]);
79+
return Promise.resolve(null);
7380
}
74-
7581
// Check if 'ExecutablePath' exists
7682
// Remember Python 2.7 doesn't have 'ExecutablePath' (there could be others)
7783
// Treat all other values as optional
7884
return Promise.all([
7985
Promise.resolve(installPath),
8086
this.registry.getValue(key, hive, arch, 'ExecutablePath'),
81-
this.getInterpreterDisplayName(tagKey, companyKey, hive, arch),
82-
this.registry.getValue(tagKey, hive, arch, 'Version'),
83-
this.getCompanyDisplayName(companyKey, hive, arch)
84-
]);
87+
this.getInterpreterDisplayName(tagKey, companyKey, hive, arch)!,
88+
this.registry.getValue(tagKey, hive, arch, 'Version')!,
89+
this.getCompanyDisplayName(companyKey, hive, arch)!
90+
])
91+
.then(([installPath, executablePath, displayName, version, companyDisplayName]) => {
92+
return { installPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation;
93+
});
8594
})
86-
.then(([installPath, executablePath, displayName, version, companyDisplayName]) => {
87-
if (installPath === undefined) {
95+
.then((interpreterInfo?: InterpreterInformation) => {
96+
if (!interpreterInfo) {
8897
return;
8998
}
9099

91-
executablePath = executablePath && executablePath.length > 0 ? executablePath : path.join(installPath, DefaultPythonExecutable);
92-
100+
const executablePath = interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 ? interpreterInfo.executablePath : path.join(interpreterInfo.installPath, DefaultPythonExecutable);
101+
const displayName = interpreterInfo.displayName;
102+
const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : path.basename(tagKey);
93103
return {
94104
architecture: arch,
95105
displayName,
96106
path: executablePath,
97-
version: version ? path.basename(version) : path.basename(tagKey),
98-
companyDisplayName
107+
version,
108+
companyDisplayName: interpreterInfo.companyDisplayName
99109
} as PythonInterpreter;
100110
})
101111
.catch(error => {

src/test/common/versionUtils.test.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as assert from 'assert';
2+
import { VersionUtils } from '../../client/common/versionUtils';
3+
4+
suite('Version Utils', () => {
5+
test('Must handle invalid versions', async () => {
6+
const version = 'ABC';
7+
assert.equal(VersionUtils.convertToSemver(version), `${version}.0.0`, 'Version is incorrect');
8+
});
9+
test('Must handle null, empty and undefined', async () => {
10+
assert.equal(VersionUtils.convertToSemver(''), '0.0.0', 'Version is incorrect for empty string');
11+
assert.equal(VersionUtils.convertToSemver(<any>null), '0.0.0', 'Version is incorrect for null value');
12+
assert.equal(VersionUtils.convertToSemver(<any>undefined), '0.0.0', 'Version is incorrect for undefined value');
13+
});
14+
test('Must be able to compare versions correctly', async () => {
15+
assert.equal(VersionUtils.compareVersion('', '1'), 0, '1. Comparison failed');
16+
assert.equal(VersionUtils.compareVersion('1', '0.1'), 1, '2. Comparison failed');
17+
assert.equal(VersionUtils.compareVersion('2.10', '2.9'), 1, '3. Comparison failed');
18+
assert.equal(VersionUtils.compareVersion('2.99.9', '3'), 0, '4. Comparison failed');
19+
});
20+
});

0 commit comments

Comments
 (0)