FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix #1147 (search environments.txt file for conda environments) (#1240) · git-lucky/pythonVSCode@b46ae28 · GitHub

Commit b46ae28

Browse files
authored
fix #1147 (search environments.txt file for conda environments) (DonJayamanne#1240)
* check envfile for conda environments * add support for linux * fixed tests * fixed typo * fix test on linux * renamed provider to service * oopsy * code review fixes * force a new line
1 parent ec5d69d commit b46ae28

22 files changed

Lines changed: 375 additions & 186 deletions

src/client/interpreter/sources/contracts.ts renamed to src/client/interpreter/contracts.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { PythonInterpreter } from "../index";
2-
import { Architecture } from "../../common/registry";
1+
import { Architecture } from "../common/registry";
32

4-
export interface IInterpreterProvider {
3+
export interface IInterpreterLocatorService {
54
getInterpreters(): Promise<PythonInterpreter[]>;
65
}
76
export interface PythonInterpreter {

‎src/client/interpreter/display/index.ts‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
'use strict';
2+
import * as path from 'path';
3+
import * as utils from '../../common/utils';
4+
import * as child_process from 'child_process';
25
import { StatusBarItem, Disposable } from 'vscode';
36
import { PythonSettings } from '../../common/configSettings';
4-
import * as path from 'path';
57
import { EOL } from 'os';
6-
import { IInterpreterProvider } from '../index';
7-
import * as utils from '../../common/utils';
8+
import { IInterpreterLocatorService } from '../contracts';
9+
import { IInterpreterVersionService } from '../interpreterVersion';
810
import { VirtualEnvironmentManager } from '../virtualEnvs/index';
9-
import { getFirstNonEmptyLineFromMultilineString } from '../sources/helpers';
10-
import * as child_process from 'child_process';
11+
import { getFirstNonEmptyLineFromMultilineString } from '../helpers';
1112

1213
const settings = PythonSettings.getInstance();
1314
export class InterpreterDisplay implements Disposable {
14-
constructor(private statusBar: StatusBarItem, private interpreterProvoder: IInterpreterProvider, private virtualEnvMgr: VirtualEnvironmentManager) {
15+
constructor(private statusBar: StatusBarItem,
16+
private interpreterLocator: IInterpreterLocatorService,
17+
private virtualEnvMgr: VirtualEnvironmentManager,
18+
private versionProvider: IInterpreterVersionService) {
1519
this.statusBar.command = 'python.setInterpreter';
1620
}
1721
public dispose() {
@@ -21,7 +25,7 @@ export class InterpreterDisplay implements Disposable {
2125
await this.updateDisplay(pythonPath);
2226
}
2327
private getInterpreters() {
24-
return this.interpreterProvoder.getInterpreters();
28+
return this.interpreterLocator.getInterpreters();
2529
}
2630
private async updateDisplay(pythonPath: string) {
2731
const interpreters = await this.getInterpreters();
@@ -39,7 +43,7 @@ export class InterpreterDisplay implements Disposable {
3943
else {
4044
const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`;
4145
const interpreterExists = utils.fsExistsAsync(pythonPath);
42-
const displayName = utils.getInterpreterDisplayName(pythonPath).catch(() => defaultDisplayName);
46+
const displayName = this.versionProvider.getVersion(pythonPath, defaultDisplayName);
4347
const virtualEnvName = this.getVirtualEnvironmentName(pythonPath);
4448
await Promise.all([interpreterExists, displayName, virtualEnvName])
4549
.then(([interpreterExists, displayName, virtualEnvName]) => {

‎src/client/interpreter/helpers.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function getFirstNonEmptyLineFromMultilineString(stdout: string) {
2+
if (!stdout) {
3+
return '';
4+
}
5+
const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0);
6+
return lines.length > 0 ? lines[0] : '';
7+
}

‎src/client/interpreter/index.ts‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
'use strict';
2+
import { InterpreterVersionService } from './interpreterVersion';
23
import { VirtualEnv } from './virtualEnvs/virtualEnv';
34
import { VEnv } from './virtualEnvs/venv';
45
import { Disposable, window, StatusBarAlignment, workspace } from 'vscode';
56
import { PythonSettings } from '../common/configSettings';
67
import { InterpreterDisplay } from './display';
7-
import { PythonInterpreterProvider } from './sources';
8+
import { PythonInterpreterLocatorService } from './locators';
89
import { VirtualEnvironmentManager } from './virtualEnvs/index';
910
import { IS_WINDOWS } from '../common/utils';
1011
import * as path from 'path';
11-
export * from './sources';
1212

1313
const settings = PythonSettings.getInstance();
1414

1515
export class InterpreterManager implements Disposable {
1616
private disposables: Disposable[] = [];
1717
private display: InterpreterDisplay | null | undefined;
18-
private interpreterProvider: PythonInterpreterProvider;
18+
private interpreterProvider: PythonInterpreterLocatorService;
1919
constructor() {
2020
const virtualEnvMgr = new VirtualEnvironmentManager([new VEnv(), new VirtualEnv()]);
2121
const statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
22-
this.interpreterProvider = new PythonInterpreterProvider(virtualEnvMgr);
23-
this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr);
22+
this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr);
23+
const versionService = new InterpreterVersionService();
24+
this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService);
2425
settings.addListener('change', this.onConfigChanged.bind(this));
2526
this.display.refresh();
2627

@@ -41,9 +42,9 @@ export class InterpreterManager implements Disposable {
4142
return;
4243
}
4344

44-
// Ensure this new environment is at the same level as the current workspace
45-
// In windows the interpreter is under scripts/python.exe on linux it is under bin/python
46-
// Meaning the sub directory must be either scripts, bin or other (but only one level deep)
45+
// Ensure this new environment is at the same level as the current workspace.
46+
// In windows the interpreter is under scripts/python.exe on linux it is under bin/python.
47+
// Meaning the sub directory must be either scripts, bin or other (but only one level deep).
4748
const pythonPath = interpretersInWorkspace[0].path;
4849
const relativePath = path.dirname(pythonPath).substring(workspace.rootPath!.length);
4950
if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { getInterpreterDisplayName } from '../common/utils';
2+
3+
export interface IInterpreterVersionService {
4+
getVersion(pythonPath: string, defaultValue: string): Promise<string>;
5+
}
6+
7+
export class InterpreterVersionService implements IInterpreterVersionService {
8+
getVersion(pythonPath: string, defaultValue: string): Promise<string> {
9+
return getInterpreterDisplayName(pythonPath)
10+
.catch(() => defaultValue);
11+
}
12+
}
13+

src/client/interpreter/sources/helpers.ts renamed to src/client/interpreter/locators/helpers.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PythonInterpreter } from "../index";
1+
import { PythonInterpreter } from "../contracts";
22
import { IS_WINDOWS, fsReaddirAsync } from "../../common/utils";
33
import * as path from 'path';
44
import { getArchitectureDislayName } from "../../common/registry";
@@ -19,17 +19,8 @@ export function fixInterpreterDisplayName(item: PythonInterpreter) {
1919
return item;
2020
}
2121
export function fixInterpreterPath(item: PythonInterpreter) {
22-
// For some reason anaconda seems to use \\ in the registry path
22+
// For some reason anaconda seems to use \\ in the registry path.
2323
item.path = IS_WINDOWS ? item.path.replace(/\\\\/g, "\\") : item.path;
24-
// Also ensure paths have back slashes instead of forward
2524
item.path = IS_WINDOWS ? item.path.replace(/\//g, "\\") : item.path;
2625
return item;
2726
}
28-
29-
export function getFirstNonEmptyLineFromMultilineString(stdout: string) {
30-
if (stdout.length === 0) {
31-
return '';
32-
}
33-
const lines = stdout.split(/\r?\n/g).filter(line => line.trim().length > 0);
34-
return lines.length > 0 ? lines[0] : '';
35-
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use strict";
2+
import * as _ from 'lodash';
3+
import { fixInterpreterPath, fixInterpreterDisplayName } from './helpers';
4+
import { IInterpreterLocatorService, PythonInterpreter } from '../contracts';
5+
import { InterpreterVersionService } from '../interpreterVersion';
6+
import { IS_WINDOWS, Is_64Bit, arePathsSame, areBasePathsSame } from '../../common/utils';
7+
import { RegistryImplementation } from '../../common/registry';
8+
import { CondaEnvService } from './services/condaEnvService';
9+
import { VirtualEnvService, getKnownSearchPathsForVirtualEnvs } from './services/virtualEnvService';
10+
import { KnownPathsService, getKnownSearchPathsForInterpreters } from './services/KnownPathsService';
11+
import { CurrentPathService } from './services/currentPathService';
12+
import { WindowsRegistryService } from './services/windowsRegistryService';
13+
import { VirtualEnvironmentManager } from '../virtualEnvs';
14+
import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService';
15+
16+
export class PythonInterpreterLocatorService implements IInterpreterLocatorService {
17+
private interpreters: PythonInterpreter[] = [];
18+
private locators: IInterpreterLocatorService[] = [];
19+
constructor(private virtualEnvMgr: VirtualEnvironmentManager) {
20+
const versionService = new InterpreterVersionService();
21+
// The order of the services is important.
22+
if (IS_WINDOWS) {
23+
const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit);
24+
this.locators.push(windowsRegistryProvider);
25+
this.locators.push(new CondaEnvService(windowsRegistryProvider));
26+
}
27+
else {
28+
this.locators.push(new CondaEnvService());
29+
}
30+
// Supplements the above list of conda environments.
31+
this.locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService));
32+
this.locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr, versionService));
33+
34+
if (!IS_WINDOWS) {
35+
// This must be last, it is possible we have paths returned here that are already returned
36+
// in one of the above lists.
37+
this.locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService));
38+
}
39+
// This must be last, it is possible we have paths returned here that are already returned
40+
// in one of the above lists.
41+
this.locators.push(new CurrentPathService(this.virtualEnvMgr, versionService));
42+
}
43+
public async getInterpreters() {
44+
if (this.interpreters.length > 0) {
45+
return this.interpreters;
46+
}
47+
const promises = this.locators.map(provider => provider.getInterpreters());
48+
return Promise.all(promises)
49+
.then(interpreters => _.flatten(interpreters))
50+
.then(items => items.map(fixInterpreterDisplayName))
51+
.then(items => items.map(fixInterpreterPath))
52+
.then(items => items.reduce<PythonInterpreter[]>((accumulator, current) => {
53+
if (accumulator.findIndex(item => arePathsSame(item.path, current.path)) === -1 &&
54+
accumulator.findIndex(item => areBasePathsSame(item.path, current.path)) === -1) {
55+
accumulator.push(current);
56+
}
57+
return accumulator;
58+
}, []))
59+
.then(interpreters => this.interpreters = interpreters);
60+
}
61+
}

src/client/interpreter/sources/providers/KnownPathsProvider.ts renamed to src/client/interpreter/locators/services/KnownPathsService.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"use strict";
22
import * as path from 'path';
33
import * as _ from 'lodash';
4-
import { IInterpreterProvider } from '../contracts';
5-
import { fsExistsAsync, getInterpreterDisplayName, IS_WINDOWS } from '../../../common/utils';
4+
import { IInterpreterLocatorService } from '../../contracts';
5+
import { IInterpreterVersionService } from '../../interpreterVersion';
6+
import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils';
67
import { lookForInterpretersInDirectory } from '../helpers';
78
const untildify = require('untildify');
89

9-
export class KnownPathsProvider implements IInterpreterProvider {
10-
public constructor(private knownSearchPaths: string[]) { }
10+
export class KnownPathsService implements IInterpreterLocatorService {
11+
public constructor(private knownSearchPaths: string[],
12+
private versionProvider: IInterpreterVersionService) { }
1113
public getInterpreters() {
1214
return this.suggestionsFromKnownPaths();
1315
}
@@ -20,7 +22,7 @@ export class KnownPathsProvider implements IInterpreterProvider {
2022
.then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter))));
2123
}
2224
private getInterpreterDetails(interpreter: string) {
23-
return getInterpreterDisplayName(interpreter).catch(() => path.basename(interpreter))
25+
return this.versionProvider.getVersion(interpreter, path.basename(interpreter))
2426
.then(displayName => {
2527
return {
2628
displayName,
@@ -42,7 +44,7 @@ export function getKnownSearchPathsForInterpreters(): string[] {
4244
paths.forEach(p => {
4345
paths.push(untildify('~' + p));
4446
});
45-
// Add support for paths such as /Users/xxx/anaconda/bin
47+
// Add support for paths such as /Users/xxx/anaconda/bin.
4648
if (process.env['HOME']) {
4749
paths.push(path.join(process.env['HOME'], 'anaconda', 'bin'));
4850
paths.push(path.join(process.env['HOME'], 'python', 'bin'));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { IS_WINDOWS } from "../../../common/utils";
2+
3+
// where to find the Python binary within a conda env.
4+
export const CONDA_RELATIVE_PY_PATH = IS_WINDOWS ? ['python.exe'] : ['bin', 'python'];
5+
export const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.'];
6+
export const AnacondaCompanyName = 'Anaconda, Inc.';
7+
export const AnacondaDisplayName = 'Anaconda';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use strict";
2+
import * as fs from 'fs-extra';
3+
import * as path from 'path';
4+
import { IS_WINDOWS } from '../../../common/configSettings';
5+
import { IInterpreterVersionService } from '../../interpreterVersion';
6+
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
7+
import { AnacondaDisplayName, AnacondaCompanyName, AnacondaCompanyNames, CONDA_RELATIVE_PY_PATH } from './conda';
8+
9+
export class CondaEnvFileService implements IInterpreterLocatorService {
10+
constructor(private condaEnvironmentFile: string,
11+
private versionService: IInterpreterVersionService) {
12+
}
13+
public getInterpreters() {
14+
return this.getSuggestionsFromConda();
15+
}
16+
17+
private getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
18+
return fs.pathExists(this.condaEnvironmentFile)
19+
.then(exists => exists ? this.getEnvironmentsFromFile(this.condaEnvironmentFile) : Promise.resolve([]));
20+
}
21+
private getEnvironmentsFromFile(envFile: string) {
22+
return fs.readFile(envFile)
23+
.then(buffer => buffer.toString().split(/\r?\n/g))
24+
.then(lines => lines.map(line => line.trim()))
25+
.then(lines => lines.map(line => path.join(line, ...CONDA_RELATIVE_PY_PATH)))
26+
.then(interpreterPaths => interpreterPaths.map(item => fs.pathExists(item).then(exists => exists ? item : '')))
27+
.then(promises => Promise.all(promises))
28+
.then(interpreterPaths => interpreterPaths.filter(item => item.length > 0))
29+
.then(interpreterPaths => interpreterPaths.map(item => this.getInterpreterDetails(item)))
30+
.then(promises => Promise.all(promises));
31+
}
32+
private getInterpreterDetails(interpreter: string) {
33+
return this.versionService.getVersion(interpreter, path.basename(interpreter))
34+
.then(version => {
35+
version = this.stripCompanyName(version);
36+
const envName = this.getEnvironmentRootDirectory(interpreter);
37+
const info: PythonInterpreter = {
38+
displayName: `${AnacondaDisplayName} ${version} (${envName})`,
39+
path: interpreter,
40+
companyDisplayName: AnacondaCompanyName,
41+
version: version
42+
};
43+
return info;
44+
});
45+
}
46+
private stripCompanyName(content: string) {
47+
// Strip company name from version.
48+
const startOfCompanyName = AnacondaCompanyNames.reduce((index, companyName) => {
49+
if (index > 0) {
50+
return index;
51+
}
52+
return content.indexOf(`:: ${AnacondaCompanyName}`);
53+
}, -1);
54+
55+
return startOfCompanyName > 0 ? content.substring(0, startOfCompanyName).trim() : content;
56+
}
57+
private getEnvironmentRootDirectory(interpreter: string) {
58+
const envDir = interpreter.substring(0, interpreter.length - path.join(...CONDA_RELATIVE_PY_PATH).length);
59+
return path.basename(envDir);
60+
}
61+
}
62+
63+
export function getEnvironmentsFile() {
64+
const homeDir = IS_WINDOWS ? process.env.USERPROFILE : (process.env.HOME || process.env.HOMEPATH);
65+
return homeDir ? path.join(homeDir, '.conda', 'environments.txt') : '';
66+
}

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL