diff --git a/src/client/interpreter/locators/services/globalVirtualEnvService.ts b/src/client/interpreter/locators/services/globalVirtualEnvService.ts index 22a3b4937e45..c05e5647f1ab 100644 --- a/src/client/interpreter/locators/services/globalVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/globalVirtualEnvService.ts @@ -4,15 +4,14 @@ 'use strict'; import { inject, injectable, named } from 'inversify'; +import * as os from 'os'; +import * as path from 'path'; import { Uri } from 'vscode'; -import { IPlatformService } from '../../../common/platform/types'; +import { ICurrentProcess } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { IVirtualEnvironmentsSearchPathProvider } from '../../contracts'; import { BaseVirtualEnvService } from './baseVirtualEnvService'; -// tslint:disable-next-line:no-require-imports no-var-requires -const untildify = require('untildify'); - @injectable() export class GlobalVirtualEnvService extends BaseVirtualEnvService { public constructor( @@ -24,16 +23,22 @@ export class GlobalVirtualEnvService extends BaseVirtualEnvService { @injectable() export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider { - public constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + private readonly process: ICurrentProcess; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.process = serviceContainer.get(ICurrentProcess); } + public getSearchPaths(_resource?: Uri): string[] { - const platformService = this.serviceContainer.get(IPlatformService); - if (platformService.isWindows) { - return []; - } else { - return ['/Envs', '/.virtualenvs', '/.pyenv', '/.pyenv/versions'] - .map(item => untildify(`~${item}`)); - } + const homedir = os.homedir(); + const folders = ['Envs', '.virtualenvs'].map(item => path.join(homedir, item)); + + // tslint:disable-next-line:no-string-literal + let pyenvRoot = this.process.env['PYENV_ROOT']; + pyenvRoot = pyenvRoot ? pyenvRoot : path.join(homedir, '.pyenv'); + + folders.push(pyenvRoot); + folders.push(path.join(pyenvRoot, 'versions')); + return folders; } } diff --git a/src/client/interpreter/locators/services/workspaceVirtualEnvService.ts b/src/client/interpreter/locators/services/workspaceVirtualEnvService.ts index 47cb2a803847..97eb6813dd45 100644 --- a/src/client/interpreter/locators/services/workspaceVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/workspaceVirtualEnvService.ts @@ -4,6 +4,7 @@ 'use strict'; import { inject, injectable, named } from 'inversify'; +import * as path from 'path'; // tslint:disable-next-line:no-require-imports import untildify = require('untildify'); import { Uri } from 'vscode'; @@ -36,16 +37,20 @@ export class WorkspaceVirtualEnvironmentsSearchPathProvider implements IVirtualE } const workspaceService = this.serviceContainer.get(IWorkspaceService); if (Array.isArray(workspaceService.workspaceFolders) && workspaceService.workspaceFolders.length > 0) { + let wsPath: string | undefined; if (resource && workspaceService.workspaceFolders.length > 1) { const wkspaceFolder = workspaceService.getWorkspaceFolder(resource); if (wkspaceFolder) { - paths.push(wkspaceFolder.uri.fsPath); + wsPath = wkspaceFolder.uri.fsPath; } } else { - paths.push(workspaceService.workspaceFolders[0].uri.fsPath); + wsPath = workspaceService.workspaceFolders[0].uri.fsPath; + } + if (wsPath) { + paths.push(wsPath); + paths.push(path.join(wsPath, '.direnv')); } } return paths; - } } diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index bb90c3080e5c..fb74ecb0e522 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -109,7 +109,7 @@ suite('Formatting', () => { const formattedFile = path.join(targetDir, resultsName); if (!fs.pathExistsSync(targetDir)) { - fs.mkdirSync(targetDir); + fs.mkdirpSync(targetDir); } fs.copySync(path.join(sourceDir, originalName), fileToFormat, { overwrite: true }); fs.copySync(path.join(sourceDir, resultsName), formattedFile, { overwrite: true }); diff --git a/src/test/interpreters/venv.test.ts b/src/test/interpreters/venv.test.ts new file mode 100644 index 000000000000..eb1d8dd27bcd --- /dev/null +++ b/src/test/interpreters/venv.test.ts @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as os from 'os'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Uri, WorkspaceFolder } from 'vscode'; +import { IWorkspaceService } from '../../client/common/application/types'; +import { IConfigurationService, ICurrentProcess, IPythonSettings } from '../../client/common/types'; +import { EnvironmentVariables } from '../../client/common/variables/types'; +import { GlobalVirtualEnvironmentsSearchPathProvider } from '../../client/interpreter/locators/services/globalVirtualEnvService'; +import { WorkspaceVirtualEnvironmentsSearchPathProvider } from '../../client/interpreter/locators/services/workspaceVirtualEnvService'; +import { ServiceContainer } from '../../client/ioc/container'; +import { ServiceManager } from '../../client/ioc/serviceManager'; + +suite('Virtual environments', () => { + let serviceManager: ServiceManager; + let serviceContainer: ServiceContainer; + let settings: TypeMoq.IMock; + let config: TypeMoq.IMock; + let workspace: TypeMoq.IMock; + let process: TypeMoq.IMock; + + setup(async () => { + const cont = new Container(); + serviceManager = new ServiceManager(cont); + serviceContainer = new ServiceContainer(cont); + + settings = TypeMoq.Mock.ofType(); + config = TypeMoq.Mock.ofType(); + workspace = TypeMoq.Mock.ofType(); + process = TypeMoq.Mock.ofType(); + + config.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + + serviceManager.addSingletonInstance(IConfigurationService, config.object); + serviceManager.addSingletonInstance(IWorkspaceService, workspace.object); + serviceManager.addSingletonInstance(ICurrentProcess, process.object); + }); + + test('Global search paths', async () => { + const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer); + const envMap: EnvironmentVariables = {}; + + const homedir = os.homedir(); + let folders = ['Envs', '.virtualenvs', '.pyenv', path.join('.pyenv', 'versions')]; + let paths = pathProvider.getSearchPaths(); + let expected = folders.map(item => path.join(homedir, item)); + expect(paths).to.deep.equal(expected, 'Global search folder list is incorrect.'); + + process.setup(x => x.env).returns(() => envMap); + // tslint:disable-next-line:no-string-literal + envMap['PYENV_ROOT'] = path.join(homedir, 'some_folder'); + paths = pathProvider.getSearchPaths(); + + folders = ['Envs', '.virtualenvs', 'some_folder', path.join('some_folder', 'versions')]; + expected = folders.map(item => path.join(homedir, item)); + expect(paths).to.deep.equal(expected, 'PYENV_ROOT not resolved correctly.'); + }); + + test('Workspace search paths', async () => { + settings.setup(x => x.venvPath).returns(() => `~${path.sep}foo`); + + const wsRoot = TypeMoq.Mock.ofType(); + wsRoot.setup(x => x.uri).returns(() => Uri.file('root')); + + const folder1 = TypeMoq.Mock.ofType(); + folder1.setup(x => x.uri).returns(() => Uri.file('dir1')); + + workspace.setup(x => x.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => wsRoot.object); + workspace.setup(x => x.workspaceFolders).returns(() => [wsRoot.object, folder1.object]); + + const pathProvider = new WorkspaceVirtualEnvironmentsSearchPathProvider(serviceContainer); + const paths = pathProvider.getSearchPaths(Uri.file('')); + + const homedir = os.homedir(); + const expected = [path.join(homedir, 'foo'), `${path.sep}root`, `${path.sep}root${path.sep}.direnv`]; + expect(paths).to.deep.equal(expected, 'Workspace venv folder search list does not match.'); + }); +});