Skip to content

Commit

Permalink
Update python.venvFolders settings (#6035)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kimadeline authored Jun 18, 2019
1 parent cbb4c8d commit 30158f7
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 11 deletions.
1 change: 1 addition & 0 deletions news/2 Fixes/4642.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add virtualenvwrapper default virtual environment location to the `python.venvFolders` config setting.
8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2010,12 +2010,8 @@
},
"python.venvFolders": {
"type": "array",
"default": [
"envs",
".pyenv",
".direnv"
],
"description": "Folders in your home directory to look into for virtual environments.",
"default": [],
"description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).",
"scope": "resource",
"items": {
"type": "string"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { inject, injectable, named } from 'inversify';
import * as os from 'os';
import * as path from 'path';
import { Uri } from 'vscode';
import { IConfigurationService } from '../../../common/types';
import { IConfigurationService, ICurrentProcess } from '../../../common/types';
import { IServiceContainer } from '../../../ioc/types';
import { IVirtualEnvironmentsSearchPathProvider } from '../../contracts';
import { IVirtualEnvironmentManager } from '../../virtualEnvs/types';
import { BaseVirtualEnvService } from './baseVirtualEnvService';

// tslint:disable-next-line:no-require-imports no-var-requires
const untildify: (value: string) => string = require('untildify');

@injectable()
export class GlobalVirtualEnvService extends BaseVirtualEnvService {
public constructor(
Expand All @@ -25,17 +28,30 @@ export class GlobalVirtualEnvService extends BaseVirtualEnvService {
@injectable()
export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider {
private readonly config: IConfigurationService;
private readonly currentProcess: ICurrentProcess;
private readonly virtualEnvMgr: IVirtualEnvironmentManager;

constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
this.config = serviceContainer.get<IConfigurationService>(IConfigurationService);
this.virtualEnvMgr = serviceContainer.get<IVirtualEnvironmentManager>(IVirtualEnvironmentManager);
this.currentProcess = serviceContainer.get<ICurrentProcess>(ICurrentProcess);
}

public async getSearchPaths(resource?: Uri): Promise<string[]> {
const homedir = os.homedir();
const venvFolders = this.config.getSettings(resource).venvFolders;
const folders = venvFolders.map(item => path.join(homedir, item));
const venvFolders = [
'envs',
'.pyenv',
'.direnv',
'.virtualenvs',
...this.config.getSettings(resource).venvFolders];
const folders = [...new Set(venvFolders.map(item => path.join(homedir, item)))];

// Add support for the WORKON_HOME environment variable used by pipenv and virtualenvwrapper.
const workonHomePath = this.currentProcess.env.WORKON_HOME;
if (workonHomePath) {
folders.push(untildify(workonHomePath));
}

// tslint:disable-next-line:no-string-literal
const pyenvRoot = await this.virtualEnvMgr.getPyEnvRoot(resource);
Expand Down
67 changes: 65 additions & 2 deletions src/test/interpreters/venv.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { ServiceContainer } from '../../client/ioc/container';
import { ServiceManager } from '../../client/ioc/serviceManager';
import { MockAutoSelectionService } from '../mocks/autoSelector';

// tslint:disable-next-line:no-require-imports no-var-requires
const untildify: (value: string) => string = require('untildify');

// tslint:disable-next-line: max-func-body-length
suite('Virtual environments', () => {
let serviceManager: ServiceManager;
let serviceContainer: ServiceContainer;
Expand Down Expand Up @@ -52,11 +56,16 @@ suite('Virtual environments', () => {
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);

const homedir = os.homedir();
const folders = ['Envs', '.virtualenvs'];
const folders = ['Envs', 'testpath'];
settings.setup(x => x.venvFolders).returns(() => folders);
virtualEnvMgr.setup(v => v.getPyEnvRoot(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
let paths = await pathProvider.getSearchPaths();
let expected = folders.map(item => path.join(homedir, item));
let expected = [
'envs',
'.pyenv',
'.direnv',
'.virtualenvs',
...folders].map(item => path.join(homedir, item));

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

test('Global search paths with duplicates', async () => {
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);

const folders = ['.virtualenvs', '.direnv'];
settings.setup(x => x.venvFolders).returns(() => folders);
const paths = await pathProvider.getSearchPaths();

expect([...new Set(paths)]).to.deep.equal(paths, 'Duplicates are not removed from the list of global search paths');
});

test('Global search paths with tilde path in the WORKON_HOME environment variable', async () => {
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);

const homedir = os.homedir();
const workonFolder = path.join('~', '.workonFolder');
process.setup(p => p.env).returns(() => {
return { WORKON_HOME: workonFolder };
});
settings.setup(x => x.venvFolders).returns(() => []);

const paths = await pathProvider.getSearchPaths();
const expected = [
'envs',
'.pyenv',
'.direnv',
'.virtualenvs'
].map(item => path.join(homedir, item));
expected.push(untildify(workonFolder));

expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.');
});

test('Global search paths with absolute path in the WORKON_HOME environment variable', async () => {
const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer);

const homedir = os.homedir();
const workonFolder = path.join('path', 'to', '.workonFolder');
process.setup(p => p.env).returns(() => {
return { WORKON_HOME: workonFolder };
});
settings.setup(x => x.venvFolders).returns(() => []);

const paths = await pathProvider.getSearchPaths();
const expected = [
'envs',
'.pyenv',
'.direnv',
'.virtualenvs'
].map(item => path.join(homedir, item));
expected.push(workonFolder);

expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.');
});

test('Workspace search paths', async () => {
settings.setup(x => x.venvPath).returns(() => path.join('~', 'foo'));

Expand Down

0 comments on commit 30158f7

Please sign in to comment.