Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pipenv #888

Merged
merged 57 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6546892
Fix pylint search
Feb 16, 2018
76af122
Handle quote escapes in strings
Feb 16, 2018
5d4d022
Escapes in strings
Feb 16, 2018
29edac2
CR feedback
Feb 16, 2018
0492aab
Missing pip
Feb 17, 2018
d0a449f
Test
Feb 17, 2018
55197c3
Tests
Feb 17, 2018
f6a0123
Tests
Feb 17, 2018
d07d3ef
Mac python path
Feb 17, 2018
2b0cc92
Tests
Feb 17, 2018
3867ec2
Tests
Feb 17, 2018
85fc4ef
Test
Feb 17, 2018
00887a4
"Go To Python object" doesn't work
Feb 17, 2018
f89dd96
Proper detection and type population in virtual env
Feb 17, 2018
32394e2
Test fixes
Feb 17, 2018
2e9c039
Simplify venv check
Feb 17, 2018
ec563c7
Remove duplicates
Feb 17, 2018
2ad4475
Test
Feb 18, 2018
1ee0be2
Discover pylintrc better + tests
Feb 19, 2018
44665b9
Merge branch 'master' into 804
Feb 19, 2018
29d3925
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Feb 19, 2018
04c5733
Undo change
Feb 19, 2018
37328a5
CR feedback
Feb 20, 2018
55ff4e5
Set interprereter before checking install
Feb 20, 2018
9a0cfa8
Merge master
Feb 20, 2018
023af49
Merge master
Feb 20, 2018
436e5a9
Fix typo and path compare on Windows
Feb 20, 2018
a53d815
Rename method
Feb 20, 2018
7f3e4fa
#815 - 'F' in flake8 means warning
Feb 20, 2018
da034f4
730 - same folder temp
Feb 20, 2018
71a508d
Properly resolve ~
Feb 20, 2018
4422811
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Feb 20, 2018
6d91912
Test
Feb 21, 2018
ff6f6d2
Test
Feb 21, 2018
7446afb
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Feb 21, 2018
a7b2854
Fix dot spacing
Feb 21, 2018
a9457f2
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Feb 21, 2018
c7c07de
Remove banner
Feb 21, 2018
e9eec39
Delete banner code
Feb 21, 2018
7b5d76b
Add pyenv and direnv folders
Feb 21, 2018
d62a8e0
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Feb 21, 2018
66395c5
Basic venv path search tests
Feb 22, 2018
005f65e
PYENV_ROOT resolution
Feb 22, 2018
63e195c
PYENV_ROOT test
Feb 22, 2018
b142a49
Use ICurrentProcess
Feb 22, 2018
a6e103c
Initial
Feb 22, 2018
5477454
Second
Feb 22, 2018
58f314c
Display name
Feb 22, 2018
bc0d238
Simplify and clean
Feb 22, 2018
d7c9765
Test
Feb 22, 2018
8f9ae51
Test
Feb 23, 2018
d4a739b
Tests
Feb 23, 2018
86b7749
Performance
Feb 23, 2018
9e018e4
Merge master
Feb 23, 2018
a425df4
Test fixes
Feb 23, 2018
e166a30
venv folder settings
Feb 24, 2018
ca3d637
Fix typo
Feb 26, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,19 @@
"description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).",
"scope": "resource"
},
"python.venvFolders": {
"type": "array",
"default": [
"envs",
".pyenv",
".direnv"
],
"description": "Folders to look into for virtual environments.",
"scope": "resource",
"items": {
"type": "string"
}
},
"python.envFile": {
"type": "string",
"description": "Absolute path to a file containing environment variable definitions.",
Expand Down Expand Up @@ -1860,4 +1873,4 @@
"publisherDisplayName": "Microsoft",
"publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8"
}
}
}
2 changes: 0 additions & 2 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,6 @@ export interface IWorkspaceService {
* ~~The folder that is open in the editor. `undefined` when no folder
* has been opened.~~
*
* @deprecated Use [`workspaceFolders`](#workspace.workspaceFolders) instead.
*
* @readonly
*/
readonly rootPath: string | undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/client/common/application/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class WorkspaceService implements IWorkspaceService {
return vscode.workspace.onDidChangeConfiguration;
}
public get rootPath(): string | undefined {
return vscode.workspace.rootPath;
return Array.isArray(vscode.workspace.workspaceFolders) ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined;
}
public get workspaceFolders(): vscode.WorkspaceFolder[] | undefined {
return vscode.workspace.workspaceFolders;
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/configSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
public envFile: string;
public disablePromptForFeatures: string[];
public venvPath: string;
public venvFolders: string[];
public devOptions: string[];
public linting: ILintingSettings;
public formatting: IFormattingSettings;
Expand Down Expand Up @@ -106,6 +107,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot);
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
this.venvPath = systemVariables.resolveAny(pythonSettings.get<string>('venvPath'))!;
this.venvFolders = systemVariables.resolveAny(pythonSettings.get<string[]>('venvFolders'))!;
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
this.jediPath = systemVariables.resolveAny(pythonSettings.get<string>('jediPath'))!;
if (typeof this.jediPath === 'string' && this.jediPath.length > 0) {
Expand Down
15 changes: 14 additions & 1 deletion src/client/common/installer/channelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,22 @@ export class InstallationChannelManager implements IInstallationChannelManager {
}

public async getInstallationChannels(resource?: Uri): Promise<IModuleInstaller[]> {
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
let installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
const supportedInstallers: IModuleInstaller[] = [];
if (installers.length === 0) {
return [];
}
// group by priority and pick supported from the highest priority
installers = installers.sort((a, b) => b.priority - a.priority);
let currentPri = installers[0].priority;
for (const mi of installers) {
if (mi.priority !== currentPri) {
if (supportedInstallers.length > 0) {
break; // return highest priority supported installers
}
// If none supported, try next priority group
currentPri = mi.priority;
}
if (await mi.isSupported(resource)) {
supportedInstallers.push(mi);
}
Expand Down
3 changes: 3 additions & 0 deletions src/client/common/installer/condaInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller
public get displayName() {
return 'Conda';
}
public get priority(): number {
return 0;
}
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(serviceContainer);
}
Expand Down
37 changes: 37 additions & 0 deletions src/client/common/installer/pipEnvInstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { inject, injectable } from 'inversify';
import { Uri } from 'vscode';
import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { ITerminalServiceFactory } from '../terminal/types';
import { IModuleInstaller } from './types';

const pipenvName = 'pipenv';

@injectable()
export class PipEnvInstaller implements IModuleInstaller {
private readonly pipenv: IInterpreterLocatorService;

public get displayName() {
return pipenvName;
}
public get priority(): number {
return 10;
}

constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
this.pipenv = this.serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, PIPENV_SERVICE);
}

public installModule(name: string): Promise<void> {
const terminalService = this.serviceContainer.get<ITerminalServiceFactory>(ITerminalServiceFactory).getTerminalService();
return terminalService.sendCommand(pipenvName, ['install', name]);
}

public async isSupported(resource?: Uri): Promise<boolean> {
const interpreters = await this.pipenv.getInterpreters(resource);
return interpreters && interpreters.length > 0;
}
}
7 changes: 5 additions & 2 deletions src/client/common/installer/pipInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller {
public get displayName() {
return 'Pip';
}
constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer) {
public get priority(): number {
return 0;
}
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(serviceContainer);
}
public isSupported(resource?: Uri): Promise<boolean> {
return this.isPipAvailable(resource);
}
protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise<ExecutionInfo> {
const proxyArgs = [];
const proxyArgs: string[] = [];
const proxy = workspace.getConfiguration('http').get('proxy', '');
if (proxy.length > 0) {
proxyArgs.push('--proxy');
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/installer/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import { IServiceManager } from '../../ioc/types';
import { InstallationChannelManager } from './channelManager';
import { CondaInstaller } from './condaInstaller';
import { PipEnvInstaller } from './pipEnvInstaller';
import { PipInstaller } from './pipInstaller';
import { IInstallationChannelManager, IModuleInstaller } from './types';

export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, CondaInstaller);
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, PipInstaller);
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, PipEnvInstaller);
serviceManager.addSingleton<IInstallationChannelManager>(IInstallationChannelManager, InstallationChannelManager);
}
1 change: 1 addition & 0 deletions src/client/common/installer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Product } from '../types';
export const IModuleInstaller = Symbol('IModuleInstaller');
export interface IModuleInstaller {
readonly displayName: string;
readonly priority: number;
installModule(name: string, resource?: Uri): Promise<void>;
isSupported(resource?: Uri): Promise<boolean>;
}
Expand Down
10 changes: 7 additions & 3 deletions src/client/common/platform/platformService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { IPlatformService } from './types';

@injectable()
export class PlatformService implements IPlatformService {
private _isWindows: boolean;
private _isMac: boolean;
private _isWindows: boolean;
private _isMac: boolean;

constructor() {
this._isWindows = /^win/.test(process.platform);
Expand All @@ -30,4 +30,8 @@ export class PlatformService implements IPlatformService {
}
public get pathVariableName() {
return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME;
}}
}
public get virtualEnvBinName() {
return this.isWindows ? 'scripts' : 'bin';
}
}
1 change: 1 addition & 0 deletions src/client/common/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface IPlatformService {
isLinux: boolean;
is64bit: boolean;
pathVariableName: 'Path' | 'PATH';
virtualEnvBinName: 'bin' | 'scripts';
}

export const IFileSystem = Symbol('IFileSystem');
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/process/proc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ExecutionResult, IBufferDecoder, IProcessService, ObservableExecutionRe

@injectable()
export class ProcessService implements IProcessService {
constructor( @inject(IBufferDecoder) private decoder: IBufferDecoder) { }
constructor(@inject(IBufferDecoder) private decoder: IBufferDecoder) { }
public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult<string> {
const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING;
delete options.encoding;
Expand Down Expand Up @@ -72,7 +72,7 @@ export class ProcessService implements IProcessService {

return { proc, out: output };
}
public async exec(file: string, args: string[], options: SpawnOptions = {}): Promise<ExecutionResult<string>> {
public exec(file: string, args: string[], options: SpawnOptions = {}): Promise<ExecutionResult<string>> {
const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING;
delete options.encoding;
const spawnOptions = { ...options };
Expand Down
15 changes: 8 additions & 7 deletions src/client/common/process/pythonProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { injectable } from 'inversify';
import { Uri } from 'vscode';
import { IInterpreterVersionService } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { ErrorUtils } from '../errors/errorUtils';
import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError';
Expand All @@ -13,24 +14,24 @@ import { ExecutionResult, IProcessService, IPythonExecutionService, ObservableEx

@injectable()
export class PythonExecutionService implements IPythonExecutionService {
private procService: IProcessService;
private configService: IConfigurationService;
private fileSystem: IFileSystem;
private readonly procService: IProcessService;
private readonly configService: IConfigurationService;
private readonly fileSystem: IFileSystem;

constructor(serviceContainer: IServiceContainer, private envVars: EnvironmentVariables | undefined, private resource?: Uri) {
constructor(private serviceContainer: IServiceContainer, private envVars: EnvironmentVariables | undefined, private resource?: Uri) {
this.procService = serviceContainer.get<IProcessService>(IProcessService);
this.configService = serviceContainer.get<IConfigurationService>(IConfigurationService);
this.fileSystem = serviceContainer.get<IFileSystem>(IFileSystem);
}

public async getVersion(): Promise<string> {
return this.procService.exec(this.pythonPath, ['--version'], { env: this.envVars, mergeStdOutErr: true })
.then(output => output.stdout.trim());
const versionService = this.serviceContainer.get<IInterpreterVersionService>(IInterpreterVersionService);
return versionService.getVersion(this.pythonPath, '');
}
public async getExecutablePath(): Promise<string> {
// If we've passed the python file, then return the file.
// This is because on mac if using the interpreter /usr/bin/python2.7 we can get a different value for the path
if (await this.fileSystem.fileExistsAsync(this.pythonPath)){
if (await this.fileSystem.fileExistsAsync(this.pythonPath)) {
return this.pythonPath;
}
return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { env: this.envVars, throwOnStdErr: true })
Expand Down
1 change: 0 additions & 1 deletion src/client/common/process/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export interface IPythonExecutionFactory {
export const IPythonExecutionService = Symbol('IPythonExecutionService');

export interface IPythonExecutionService {
getVersion(): Promise<string>;
getExecutablePath(): Promise<string>;
isModuleInstalled(moduleName: string): Promise<boolean>;

Expand Down
3 changes: 2 additions & 1 deletion src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import {Socket} from 'net';
import { Socket } from 'net';
import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri } from 'vscode';

import { EnvironmentVariables } from './variables/types';
Expand Down Expand Up @@ -96,6 +96,7 @@ export interface ICurrentProcess {
export interface IPythonSettings {
readonly pythonPath: string;
readonly venvPath: string;
readonly venvFolders: string[];
readonly jediPath: string;
readonly jediMemoryLimit: number;
readonly devOptions: string[];
Expand Down
1 change: 1 addition & 0 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const CURRENT_PATH_SERVICE = 'CurrentPathService';
export const KNOWN_PATH_SERVICE = 'KnownPathsService';
export const GLOBAL_VIRTUAL_ENV_SERVICE = 'VirtualEnvService';
export const WORKSPACE_VIRTUAL_ENV_SERVICE = 'WorkspaceVirtualEnvService';
export const PIPENV_SERVICE = 'PipEnvService';

export const IInterpreterVersionService = Symbol('IInterpreterVersionService');
export interface IInterpreterVersionService {
Expand Down
Loading