Skip to content

Commit 7707267

Browse files
authored
Search for interpreters in all paths found in PATH variable (#2575)
* Search for interpreters in all paths found in PATH variable * Fixes * Fixed tests
1 parent a464d9c commit 7707267

File tree

7 files changed

+147
-36
lines changed

7 files changed

+147
-36
lines changed

news/1 Enhancements/2398.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Search for python interpreters in all paths found in the `PATH`/`Path` environment variable.

src/client/interpreter/contracts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ export interface IInterpreterVersionService {
1717
}
1818

1919
export const IKnownSearchPathsForInterpreters = Symbol('IKnownSearchPathsForInterpreters');
20-
20+
export interface IKnownSearchPathsForInterpreters {
21+
getSearchPaths(): string[];
22+
}
2123
export const IVirtualEnvironmentsSearchPathProvider = Symbol('IVirtualEnvironmentsSearchPathProvider');
2224
export interface IVirtualEnvironmentsSearchPathProvider {
2325
getSearchPaths(resource?: Uri): string[];

src/client/interpreter/locators/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
7878
[PIPENV_SERVICE, ''],
7979
[GLOBAL_VIRTUAL_ENV_SERVICE, ''],
8080
[WORKSPACE_VIRTUAL_ENV_SERVICE, ''],
81-
[KNOWN_PATH_SERVICE, '-win'],
81+
[KNOWN_PATH_SERVICE, ''],
8282
[CURRENT_PATH_SERVICE, '']
8383
];
8484
return getLocators(keys, this.platform, (key) => {

src/client/interpreter/locators/services/KnownPathsService.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@ import * as _ from 'lodash';
33
import * as path from 'path';
44
import { Uri } from 'vscode';
55
import { fsExistsAsync } from '../../../../utils/fs';
6-
import { IS_WINDOWS } from '../../../common/util';
6+
import { IPlatformService } from '../../../common/platform/types';
7+
import { ICurrentProcess, IPathUtils } from '../../../common/types';
78
import { IServiceContainer } from '../../../ioc/types';
89
import { IInterpreterHelper, IKnownSearchPathsForInterpreters, InterpreterType, PythonInterpreter } from '../../contracts';
910
import { lookForInterpretersInDirectory } from '../helpers';
1011
import { CacheableLocatorService } from './cacheableLocatorService';
1112

12-
// tslint:disable-next-line:no-require-imports no-var-requires
13-
const untildify = require('untildify');
14-
1513
/**
1614
* Locates "known" paths.
1715
*/
1816
@injectable()
1917
export class KnownPathsService extends CacheableLocatorService {
2018
public constructor(
21-
@inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: string[],
19+
@inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: IKnownSearchPathsForInterpreters,
2220
@inject(IInterpreterHelper) private helper: IInterpreterHelper,
2321
@inject(IServiceContainer) serviceContainer: IServiceContainer
2422
) {
@@ -46,7 +44,7 @@ export class KnownPathsService extends CacheableLocatorService {
4644
* Return the located interpreters.
4745
*/
4846
private suggestionsFromKnownPaths() {
49-
const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir));
47+
const promises = this.knownSearchPaths.getSearchPaths().map(dir => this.getInterpretersInDirectory(dir));
5048
return Promise.all<string[]>(promises)
5149
// tslint:disable-next-line:underscore-consistent-invocation
5250
.then(listOfInterpreters => _.flatten(listOfInterpreters))
@@ -79,22 +77,34 @@ export class KnownPathsService extends CacheableLocatorService {
7977
}
8078
}
8179

82-
/**
83-
* Return the paths where Python interpreters might be found.
84-
*/
85-
export function getKnownSearchPathsForInterpreters(): string[] {
86-
if (IS_WINDOWS) {
87-
return [];
88-
} else {
89-
const paths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'];
90-
paths.forEach(p => {
91-
paths.push(untildify(`~${p}`));
92-
});
93-
// Add support for paths such as /Users/xxx/anaconda/bin.
94-
if (process.env.HOME) {
95-
paths.push(path.join(process.env.HOME, 'anaconda', 'bin'));
96-
paths.push(path.join(process.env.HOME, 'python', 'bin'));
80+
@injectable()
81+
export class KnownSearchPathsForInterpreters implements IKnownSearchPathsForInterpreters {
82+
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { }
83+
/**
84+
* Return the paths where Python interpreters might be found.
85+
*/
86+
public getSearchPaths(): string[] {
87+
const currentProcess = this.serviceContainer.get<ICurrentProcess>(ICurrentProcess);
88+
const platformService = this.serviceContainer.get<IPlatformService>(IPlatformService);
89+
const pathUtils = this.serviceContainer.get<IPathUtils>(IPathUtils);
90+
91+
const searchPaths = currentProcess.env[platformService.pathVariableName]!
92+
.split(pathUtils.delimiter)
93+
.map(p => p.trim())
94+
.filter(p => p.length > 0);
95+
96+
if (!platformService.isWindows) {
97+
['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin']
98+
.forEach(p => {
99+
searchPaths.push(p);
100+
searchPaths.push(path.join(pathUtils.home, p));
101+
});
102+
// Add support for paths such as /Users/xxx/anaconda/bin.
103+
if (process.env.HOME) {
104+
searchPaths.push(path.join(pathUtils.home, 'anaconda', 'bin'));
105+
searchPaths.push(path.join(pathUtils.home, 'python', 'bin'));
106+
}
97107
}
98-
return paths;
108+
return searchPaths;
99109
}
100110
}

src/client/interpreter/serviceRegistry.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ import { CondaEnvService } from './locators/services/condaEnvService';
4242
import { CondaService } from './locators/services/condaService';
4343
import { CurrentPathService } from './locators/services/currentPathService';
4444
import { GlobalVirtualEnvironmentsSearchPathProvider, GlobalVirtualEnvService } from './locators/services/globalVirtualEnvService';
45-
import { getKnownSearchPathsForInterpreters, KnownPathsService } from './locators/services/KnownPathsService';
45+
import { KnownPathsService, KnownSearchPathsForInterpreters } from './locators/services/KnownPathsService';
4646
import { PipEnvService } from './locators/services/pipEnvService';
4747
import { WindowsRegistryService } from './locators/services/windowsRegistryService';
4848
import { WorkspaceVirtualEnvironmentsSearchPathProvider, WorkspaceVirtualEnvService } from './locators/services/workspaceVirtualEnvService';
4949
import { VirtualEnvironmentManager } from './virtualEnvs/index';
5050
import { IVirtualEnvironmentManager } from './virtualEnvs/types';
5151

5252
export function registerTypes(serviceManager: IServiceManager) {
53-
serviceManager.addSingletonInstance<string[]>(IKnownSearchPathsForInterpreters, getKnownSearchPathsForInterpreters());
53+
serviceManager.addSingleton<IKnownSearchPathsForInterpreters>(IKnownSearchPathsForInterpreters, KnownSearchPathsForInterpreters);
5454
serviceManager.addSingleton<IVirtualEnvironmentsSearchPathProvider>(IVirtualEnvironmentsSearchPathProvider, GlobalVirtualEnvironmentsSearchPathProvider, 'global');
5555
serviceManager.addSingleton<IVirtualEnvironmentsSearchPathProvider>(IVirtualEnvironmentsSearchPathProvider, WorkspaceVirtualEnvironmentsSearchPathProvider, 'workspace');
5656

@@ -70,9 +70,8 @@ export function registerTypes(serviceManager: IServiceManager) {
7070
const isWindows = serviceManager.get<boolean>(IsWindows);
7171
if (isWindows) {
7272
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE);
73-
} else {
74-
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, KnownPathsService, KNOWN_PATH_SERVICE);
7573
}
74+
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, KnownPathsService, KNOWN_PATH_SERVICE);
7675
serviceManager.addSingleton<IInterpreterService>(IInterpreterService, InterpreterService);
7776
serviceManager.addSingleton<IInterpreterDisplay>(IInterpreterDisplay, InterpreterDisplay);
7877

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable:max-func-body-length no-any
7+
8+
import { expect } from 'chai';
9+
import * as path from 'path';
10+
import * as TypeMoq from 'typemoq';
11+
import { IPlatformService } from '../../client/common/platform/types';
12+
import { ICurrentProcess, IPathUtils } from '../../client/common/types';
13+
import { IKnownSearchPathsForInterpreters } from '../../client/interpreter/contracts';
14+
import { KnownSearchPathsForInterpreters } from '../../client/interpreter/locators/services/KnownPathsService';
15+
import { IServiceContainer } from '../../client/ioc/types';
16+
17+
suite('Interpreters Known Paths', () => {
18+
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
19+
let currentProcess: TypeMoq.IMock<ICurrentProcess>;
20+
let platformService: TypeMoq.IMock<IPlatformService>;
21+
let pathUtils: TypeMoq.IMock<IPathUtils>;
22+
let knownSearchPaths: IKnownSearchPathsForInterpreters;
23+
24+
setup(async () => {
25+
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
26+
currentProcess = TypeMoq.Mock.ofType<ICurrentProcess>();
27+
platformService = TypeMoq.Mock.ofType<IPlatformService>();
28+
pathUtils = TypeMoq.Mock.ofType<IPathUtils>();
29+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICurrentProcess), TypeMoq.It.isAny())).returns(() => currentProcess.object);
30+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())).returns(() => platformService.object);
31+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPathUtils), TypeMoq.It.isAny())).returns(() => pathUtils.object);
32+
33+
knownSearchPaths = new KnownSearchPathsForInterpreters(serviceContainer.object);
34+
});
35+
36+
test('Ensure known list of paths are returned', async () => {
37+
const pathDelimiter = 'X';
38+
const pathsInPATHVar = [path.join('a', 'b', 'c'), '', path.join('1', '2'), '3'];
39+
pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter);
40+
platformService.setup(p => p.isWindows).returns(() => true);
41+
platformService.setup(p => p.pathVariableName).returns(() => 'PATH');
42+
currentProcess.setup(p => p.env).returns(() => {
43+
return { PATH: pathsInPATHVar.join(pathDelimiter) };
44+
});
45+
46+
const expectedPaths = [...pathsInPATHVar].filter(item => item.length > 0);
47+
48+
const paths = knownSearchPaths.getSearchPaths();
49+
50+
expect(paths).to.deep.equal(expectedPaths);
51+
});
52+
53+
test('Ensure known list of paths are returned on non-windows', async () => {
54+
const homeDir = '/users/peter Smith';
55+
const pathDelimiter = 'X';
56+
pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter);
57+
pathUtils.setup(p => p.home).returns(() => homeDir);
58+
platformService.setup(p => p.isWindows).returns(() => false);
59+
platformService.setup(p => p.pathVariableName).returns(() => 'PATH');
60+
currentProcess.setup(p => p.env).returns(() => {
61+
return { PATH: '' };
62+
});
63+
64+
const expectedPaths: string[] = [];
65+
['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin']
66+
.forEach(p => {
67+
expectedPaths.push(p);
68+
expectedPaths.push(path.join(homeDir, p));
69+
});
70+
71+
expectedPaths.push(path.join(homeDir, 'anaconda', 'bin'));
72+
expectedPaths.push(path.join(homeDir, 'python', 'bin'));
73+
74+
const paths = knownSearchPaths.getSearchPaths();
75+
76+
expect(paths).to.deep.equal(expectedPaths);
77+
});
78+
79+
test('Ensure PATH variable and known list of paths are merged on non-windows', async () => {
80+
const homeDir = '/users/peter Smith';
81+
const pathDelimiter = 'X';
82+
const pathsInPATHVar = [path.join('a', 'b', 'c'), '', path.join('1', '2'), '3'];
83+
pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter);
84+
pathUtils.setup(p => p.home).returns(() => homeDir);
85+
platformService.setup(p => p.isWindows).returns(() => false);
86+
platformService.setup(p => p.pathVariableName).returns(() => 'PATH');
87+
currentProcess.setup(p => p.env).returns(() => {
88+
return { PATH: pathsInPATHVar.join(pathDelimiter) };
89+
});
90+
91+
const expectedPaths = [...pathsInPATHVar].filter(item => item.length > 0);
92+
['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin']
93+
.forEach(p => {
94+
expectedPaths.push(p);
95+
expectedPaths.push(path.join(homeDir, p));
96+
});
97+
98+
expectedPaths.push(path.join(homeDir, 'anaconda', 'bin'));
99+
expectedPaths.push(path.join(homeDir, 'python', 'bin'));
100+
101+
const paths = knownSearchPaths.getSearchPaths();
102+
103+
expect(paths).to.deep.equal(expectedPaths);
104+
});
105+
});

src/test/interpreters/locators/index.unit.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,7 @@ suite('Interpreters - Locators Index', () => {
5454
locatorsTypes.push(PIPENV_SERVICE);
5555
locatorsTypes.push(GLOBAL_VIRTUAL_ENV_SERVICE);
5656
locatorsTypes.push(WORKSPACE_VIRTUAL_ENV_SERVICE);
57-
58-
if (osType.value !== OSType.Windows) {
59-
locatorsTypes.push(KNOWN_PATH_SERVICE);
60-
}
57+
locatorsTypes.push(KNOWN_PATH_SERVICE);
6158
locatorsTypes.push(CURRENT_PATH_SERVICE);
6259

6360
const locatorsWithInterpreters = locatorsTypes.map(typeName => {
@@ -115,10 +112,7 @@ suite('Interpreters - Locators Index', () => {
115112
locatorsTypes.push(PIPENV_SERVICE);
116113
locatorsTypes.push(GLOBAL_VIRTUAL_ENV_SERVICE);
117114
locatorsTypes.push(WORKSPACE_VIRTUAL_ENV_SERVICE);
118-
119-
if (osType.value !== OSType.Windows) {
120-
locatorsTypes.push(KNOWN_PATH_SERVICE);
121-
}
115+
locatorsTypes.push(KNOWN_PATH_SERVICE);
122116
locatorsTypes.push(CURRENT_PATH_SERVICE);
123117

124118
const locatorsWithInterpreters = locatorsTypes.map(typeName => {

0 commit comments

Comments
 (0)