Skip to content

Commit 30158f7

Browse files
authored
Update python.venvFolders settings (#6035)
* Move venvFolders defaults, update unit tests * Add pipenv and virtualenvwrapper folders * Add news file * Remove pipenv venvs root from global search paths * Add support for WORKON_HOME + tests * Remove pipenv support from news * Use ICurrentProcess instead of global.process * Untildify WORKON_HOME + tests * fix paths in unit tests
1 parent cbb4c8d commit 30158f7

File tree

4 files changed

+87
-11
lines changed

4 files changed

+87
-11
lines changed

news/2 Fixes/4642.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add virtualenvwrapper default virtual environment location to the `python.venvFolders` config setting.

package.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,12 +2010,8 @@
20102010
},
20112011
"python.venvFolders": {
20122012
"type": "array",
2013-
"default": [
2014-
"envs",
2015-
".pyenv",
2016-
".direnv"
2017-
],
2018-
"description": "Folders in your home directory to look into for virtual environments.",
2013+
"default": [],
2014+
"description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).",
20192015
"scope": "resource",
20202016
"items": {
20212017
"type": "string"

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import { inject, injectable, named } from 'inversify';
77
import * as os from 'os';
88
import * as path from 'path';
99
import { Uri } from 'vscode';
10-
import { IConfigurationService } from '../../../common/types';
10+
import { IConfigurationService, ICurrentProcess } from '../../../common/types';
1111
import { IServiceContainer } from '../../../ioc/types';
1212
import { IVirtualEnvironmentsSearchPathProvider } from '../../contracts';
1313
import { IVirtualEnvironmentManager } from '../../virtualEnvs/types';
1414
import { BaseVirtualEnvService } from './baseVirtualEnvService';
1515

16+
// tslint:disable-next-line:no-require-imports no-var-requires
17+
const untildify: (value: string) => string = require('untildify');
18+
1619
@injectable()
1720
export class GlobalVirtualEnvService extends BaseVirtualEnvService {
1821
public constructor(
@@ -25,17 +28,30 @@ export class GlobalVirtualEnvService extends BaseVirtualEnvService {
2528
@injectable()
2629
export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider {
2730
private readonly config: IConfigurationService;
31+
private readonly currentProcess: ICurrentProcess;
2832
private readonly virtualEnvMgr: IVirtualEnvironmentManager;
2933

3034
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
3135
this.config = serviceContainer.get<IConfigurationService>(IConfigurationService);
3236
this.virtualEnvMgr = serviceContainer.get<IVirtualEnvironmentManager>(IVirtualEnvironmentManager);
37+
this.currentProcess = serviceContainer.get<ICurrentProcess>(ICurrentProcess);
3338
}
3439

3540
public async getSearchPaths(resource?: Uri): Promise<string[]> {
3641
const homedir = os.homedir();
37-
const venvFolders = this.config.getSettings(resource).venvFolders;
38-
const folders = venvFolders.map(item => path.join(homedir, item));
42+
const venvFolders = [
43+
'envs',
44+
'.pyenv',
45+
'.direnv',
46+
'.virtualenvs',
47+
...this.config.getSettings(resource).venvFolders];
48+
const folders = [...new Set(venvFolders.map(item => path.join(homedir, item)))];
49+
50+
// Add support for the WORKON_HOME environment variable used by pipenv and virtualenvwrapper.
51+
const workonHomePath = this.currentProcess.env.WORKON_HOME;
52+
if (workonHomePath) {
53+
folders.push(untildify(workonHomePath));
54+
}
3955

4056
// tslint:disable-next-line:no-string-literal
4157
const pyenvRoot = await this.virtualEnvMgr.getPyEnvRoot(resource);

src/test/interpreters/venv.unit.test.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { ServiceContainer } from '../../client/ioc/container';
1818
import { ServiceManager } from '../../client/ioc/serviceManager';
1919
import { MockAutoSelectionService } from '../mocks/autoSelector';
2020

21+
// tslint:disable-next-line:no-require-imports no-var-requires
22+
const untildify: (value: string) => string = require('untildify');
23+
24+
// tslint:disable-next-line: max-func-body-length
2125
suite('Virtual environments', () => {
2226
let serviceManager: ServiceManager;
2327
let serviceContainer: ServiceContainer;
@@ -52,11 +56,16 @@ suite('Virtual environments', () => {
5256
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);
5357

5458
const homedir = os.homedir();
55-
const folders = ['Envs', '.virtualenvs'];
59+
const folders = ['Envs', 'testpath'];
5660
settings.setup(x => x.venvFolders).returns(() => folders);
5761
virtualEnvMgr.setup(v => v.getPyEnvRoot(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
5862
let paths = await pathProvider.getSearchPaths();
59-
let expected = folders.map(item => path.join(homedir, item));
63+
let expected = [
64+
'envs',
65+
'.pyenv',
66+
'.direnv',
67+
'.virtualenvs',
68+
...folders].map(item => path.join(homedir, item));
6069

6170
virtualEnvMgr.verifyAll();
6271
expect(paths).to.deep.equal(expected, 'Global search folder list is incorrect.');
@@ -70,6 +79,60 @@ suite('Virtual environments', () => {
7079
expect(paths).to.deep.equal(expected, 'pyenv path not resolved correctly.');
7180
});
7281

82+
test('Global search paths with duplicates', async () => {
83+
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);
84+
85+
const folders = ['.virtualenvs', '.direnv'];
86+
settings.setup(x => x.venvFolders).returns(() => folders);
87+
const paths = await pathProvider.getSearchPaths();
88+
89+
expect([...new Set(paths)]).to.deep.equal(paths, 'Duplicates are not removed from the list of global search paths');
90+
});
91+
92+
test('Global search paths with tilde path in the WORKON_HOME environment variable', async () => {
93+
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);
94+
95+
const homedir = os.homedir();
96+
const workonFolder = path.join('~', '.workonFolder');
97+
process.setup(p => p.env).returns(() => {
98+
return { WORKON_HOME: workonFolder };
99+
});
100+
settings.setup(x => x.venvFolders).returns(() => []);
101+
102+
const paths = await pathProvider.getSearchPaths();
103+
const expected = [
104+
'envs',
105+
'.pyenv',
106+
'.direnv',
107+
'.virtualenvs'
108+
].map(item => path.join(homedir, item));
109+
expected.push(untildify(workonFolder));
110+
111+
expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.');
112+
});
113+
114+
test('Global search paths with absolute path in the WORKON_HOME environment variable', async () => {
115+
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);
116+
117+
const homedir = os.homedir();
118+
const workonFolder = path.join('path', 'to', '.workonFolder');
119+
process.setup(p => p.env).returns(() => {
120+
return { WORKON_HOME: workonFolder };
121+
});
122+
settings.setup(x => x.venvFolders).returns(() => []);
123+
124+
const paths = await pathProvider.getSearchPaths();
125+
const expected = [
126+
'envs',
127+
'.pyenv',
128+
'.direnv',
129+
'.virtualenvs'
130+
].map(item => path.join(homedir, item));
131+
expected.push(workonFolder);
132+
133+
expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.');
134+
});
135+
73136
test('Workspace search paths', async () => {
74137
settings.setup(x => x.venvPath).returns(() => path.join('~', 'foo'));
75138

0 commit comments

Comments
 (0)