| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 53b954f commit 321e204
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -925,6 +925,12 @@ | |||
| 925 | 925 | "description": "Whether to check if Python is installed.", | |
| 926 | 926 | "scope": "resource" | |
| 927 | 927 | }, | |
| 928 | + "python.globalModuleInstallation": { | ||
| 929 | + "type": "boolean", | ||
| 930 | + "default": false, | ||
| 931 | + "description": "Whether to install Python modules globally.", | ||
| 932 | + "scope": "resource" | ||
| 933 | + }, | ||
| 928 | 934 | "python.linting.enabled": { | |
| 929 | 935 | "type": "boolean", | |
| 930 | 936 | "default": true, | |
@@ -1547,6 +1553,7 @@ | |||
| 1547 | 1553 | "reflect-metadata": "^0.1.10", | |
| 1548 | 1554 | "rxjs": "^5.5.2", | |
| 1549 | 1555 | "semver": "^5.4.1", | |
| 1556 | + "sudo-prompt": "^8.0.0", | ||
| 1550 | 1557 | "tmp": "0.0.29", | |
| 1551 | 1558 | "tree-kill": "^1.1.0", | |
| 1552 | 1559 | "typescript-char": "^0.0.0", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -198,7 +198,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''): | |||
| 198 | 198 | # we pass 'text' here only for fuzzy matcher | |
| 199 | 199 | if value: | |
| 200 | 200 | _completion['snippet'] = '%s=${1:%s}$0' % (name, value) | |
| 201 | - _completion['text'] = '%s=%s' % (name, value) | ||
| 201 | + _completion['text'] = '%s=' % (name) | ||
| 202 | 202 | else: | |
| 203 | 203 | _completion['snippet'] = '%s=$1$0' % name | |
| 204 | 204 | _completion['text'] = name | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,6 +27,7 @@ export interface IPythonSettings { | |||
| 27 | 27 | envFile: string; | |
| 28 | 28 | disablePromptForFeatures: string[]; | |
| 29 | 29 | disableInstallationChecks: boolean; | |
| 30 | + globalModuleInstallation: boolean; | ||
| 30 | 31 | } | |
| 31 | 32 | export interface ISortImportSettings { | |
| 32 | 33 | path: string; | |
@@ -147,6 +148,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { | |||
| 147 | 148 | public sortImports: ISortImportSettings; | |
| 148 | 149 | public workspaceSymbols: IWorkspaceSymbolSettings; | |
| 149 | 150 | public disableInstallationChecks: boolean; | |
| 151 | + public globalModuleInstallation: boolean; | ||
| 150 | 152 | ||
| 151 | 153 | private workspaceRoot: vscode.Uri; | |
| 152 | 154 | private disposables: vscode.Disposable[] = []; | |
@@ -224,7 +226,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { | |||
| 224 | 226 | } else { | |
| 225 | 227 | this.linting = lintingSettings; | |
| 226 | 228 | } | |
| 229 | + | ||
| 227 | 230 | this.disableInstallationChecks = pythonSettings.get<boolean>('disableInstallationCheck') === true; | |
| 231 | + this.globalModuleInstallation = pythonSettings.get<boolean>('globalModuleInstallation') === true; | ||
| 232 | + | ||
| 228 | 233 | // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion | |
| 229 | 234 | const sortImportSettings = systemVariables.resolveAny(pythonSettings.get<ISortImportSettings>('sortImports'))!; | |
| 230 | 235 | if (this.sortImports) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,7 +13,7 @@ export function isNotInstalledError(error: Error): boolean { | |||
| 13 | 13 | return true; | |
| 14 | 14 | } | |
| 15 | 15 | ||
| 16 | - const isModuleNoInstalledError = errorObj.code === 1 && error.message.indexOf('No module named') >= 0; | ||
| 16 | + const isModuleNoInstalledError = error.message.indexOf('No module named') >= 0; | ||
| 17 | 17 | return errorObj.code === 'ENOENT' || errorObj.code === 127 || isModuleNoInstalledError; | |
| 18 | 18 | } | |
| 19 | 19 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,27 +1,97 @@ | |||
| 1 | 1 | // Copyright (c) Microsoft Corporation. All rights reserved. | |
| 2 | 2 | // Licensed under the MIT License. | |
| 3 | 3 | ||
| 4 | + // tslint:disable-next-line:no-require-imports no-var-requires | ||
| 5 | + const sudo = require('sudo-prompt'); | ||
| 6 | + | ||
| 7 | + import * as fs from 'fs'; | ||
| 4 | 8 | import { injectable } from 'inversify'; | |
| 5 | - import { Uri } from 'vscode'; | ||
| 9 | + import * as path from 'path'; | ||
| 10 | + import * as vscode from 'vscode'; | ||
| 11 | + import { IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE, InterpreterType } from '../../interpreter/contracts'; | ||
| 6 | 12 | import { IServiceContainer } from '../../ioc/types'; | |
| 7 | 13 | import { PythonSettings } from '../configSettings'; | |
| 14 | + import { STANDARD_OUTPUT_CHANNEL } from '../constants'; | ||
| 15 | + import { IFileSystem } from '../platform/types'; | ||
| 8 | 16 | import { ITerminalService } from '../terminal/types'; | |
| 9 | - import { ExecutionInfo } from '../types'; | ||
| 17 | + import { ExecutionInfo, IOutputChannel } from '../types'; | ||
| 10 | 18 | ||
| 11 | 19 | @injectable() | |
| 12 | 20 | export abstract class ModuleInstaller { | |
| 13 | 21 | constructor(protected serviceContainer: IServiceContainer) { } | |
| 14 | - public async installModule(name: string, resource?: Uri): Promise<void> { | ||
| 22 | + public async installModule(name: string, resource?: vscode.Uri): Promise<void> { | ||
| 15 | 23 | const executionInfo = await this.getExecutionInfo(name, resource); | |
| 16 | 24 | const terminalService = this.serviceContainer.get<ITerminalService>(ITerminalService); | |
| 17 | 25 | ||
| 18 | 26 | if (executionInfo.moduleName) { | |
| 19 | - const pythonPath = PythonSettings.getInstance(resource).pythonPath; | ||
| 20 | - await terminalService.sendCommand(pythonPath, ['-m', 'pip'].concat(executionInfo.args)); | ||
| 27 | + const settings = PythonSettings.getInstance(resource); | ||
| 28 | + const args = ['-m', 'pip'].concat(executionInfo.args); | ||
| 29 | + const pythonPath = settings.pythonPath; | ||
| 30 | + | ||
| 31 | + const locator = this.serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE); | ||
| 32 | + const fileSystem = this.serviceContainer.get<IFileSystem>(IFileSystem); | ||
| 33 | + const interpreters = await locator.getInterpreters(resource); | ||
| 34 | + | ||
| 35 | + const currentInterpreter = interpreters.length > 1 | ||
| 36 | + ? interpreters.filter(x => fileSystem.arePathsSame(x.path, pythonPath))[0] | ||
| 37 | + : interpreters[0]; | ||
| 38 | + | ||
| 39 | + if (!currentInterpreter || currentInterpreter.type !== InterpreterType.Unknown) { | ||
| 40 | + await terminalService.sendCommand(pythonPath, args); | ||
| 41 | + } else if (settings.globalModuleInstallation) { | ||
| 42 | + if (await this.isPathWritableAsync(path.dirname(pythonPath))) { | ||
| 43 | + await terminalService.sendCommand(pythonPath, args); | ||
| 44 | + } else { | ||
| 45 | + this.elevatedInstall(pythonPath, args); | ||
| 46 | + } | ||
| 47 | + } else { | ||
| 48 | + await terminalService.sendCommand(pythonPath, args.concat(['--user'])); | ||
| 49 | + } | ||
| 21 | 50 | } else { | |
| 22 | 51 | await terminalService.sendCommand(executionInfo.execPath!, executionInfo.args); | |
| 23 | 52 | } | |
| 24 | 53 | } | |
| 25 | - public abstract isSupported(resource?: Uri): Promise<boolean>; | ||
| 26 | - protected abstract getExecutionInfo(moduleName: string, resource?: Uri): Promise<ExecutionInfo>; | ||
| 54 | + public abstract isSupported(resource?: vscode.Uri): Promise<boolean>; | ||
| 55 | + protected abstract getExecutionInfo(moduleName: string, resource?: vscode.Uri): Promise<ExecutionInfo>; | ||
| 56 | + | ||
| 57 | + private async isPathWritableAsync(directoryPath: string): Promise<boolean> { | ||
| 58 | + const filePath = `${directoryPath}${path.sep}___vscpTest___`; | ||
| 59 | + return new Promise<boolean>(resolve => { | ||
| 60 | + fs.open(filePath, fs.constants.O_CREAT | fs.constants.O_RDWR, (error, fd) => { | ||
| 61 | + if (!error) { | ||
| 62 | + fs.close(fd, (e) => { | ||
| 63 | + fs.unlink(filePath); | ||
| 64 | + }); | ||
| 65 | + } | ||
| 66 | + return resolve(!error); | ||
| 67 | + }); | ||
| 68 | + }); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + private elevatedInstall(execPath: string, args: string[]) { | ||
| 72 | + const options = { | ||
| 73 | + name: 'VS Code Python' | ||
| 74 | + }; | ||
| 75 | + const outputChannel = this.serviceContainer.get<vscode.OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL); | ||
| 76 | + const command = `"${execPath.replace(/\\/g, '/')}" ${args.join(' ')}`; | ||
| 77 | + | ||
| 78 | + outputChannel.appendLine(''); | ||
| 79 | + outputChannel.appendLine(`[Elevated] ${command}`); | ||
| 80 | + | ||
| 81 | + sudo.exec(command, options, (error, stdout, stderr) => { | ||
| 82 | + if (error) { | ||
| 83 | + vscode.window.showErrorMessage(error); | ||
| 84 | + } else { | ||
| 85 | + outputChannel.show(); | ||
| 86 | + if (stdout) { | ||
| 87 | + outputChannel.appendLine(''); | ||
| 88 | + outputChannel.append(stdout); | ||
| 89 | + } | ||
| 90 | + if (stderr) { | ||
| 91 | + outputChannel.appendLine(''); | ||
| 92 | + outputChannel.append(`Warning: ${stderr}`); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + }); | ||
| 96 | + } | ||
| 27 | 97 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,10 @@ import { Installer } from './installer/installer'; | |||
| 8 | 8 | import { Logger } from './logger'; | |
| 9 | 9 | import { PersistentStateFactory } from './persistentState'; | |
| 10 | 10 | import { IS_64_BIT, IS_WINDOWS } from './platform/constants'; | |
| 11 | + import { FileSystem } from './platform/fileSystem'; | ||
| 11 | 12 | import { PathUtils } from './platform/pathUtils'; | |
| 13 | + import { PlatformService } from './platform/platformService'; | ||
| 14 | + import { IFileSystem, IPlatformService } from './platform/types'; | ||
| 12 | 15 | import { CurrentProcess } from './process/currentProcess'; | |
| 13 | 16 | import { TerminalService } from './terminal/service'; | |
| 14 | 17 | import { ITerminalService } from './terminal/types'; | |
@@ -25,4 +28,5 @@ export function registerTypes(serviceManager: IServiceManager) { | |||
| 25 | 28 | serviceManager.addSingleton<IApplicationShell>(IApplicationShell, ApplicationShell); | |
| 26 | 29 | serviceManager.addSingleton<ICurrentProcess>(ICurrentProcess, CurrentProcess); | |
| 27 | 30 | serviceManager.addSingleton<IInstaller>(IInstaller, Installer); | |
| 31 | + serviceManager.addSingleton<IFileSystem>(IFileSystem, FileSystem); | ||
| 28 | 32 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,7 @@ | |||
| 1 | 1 | // Copyright (c) Microsoft Corporation. All rights reserved. | |
| 2 | 2 | // Licensed under the MIT License. | |
| 3 | 3 | ||
| 4 | - export const ITerminalService = Symbol('ITerminalCommandService'); | ||
| 4 | + export const ITerminalService = Symbol('ITerminalService'); | ||
| 5 | 5 | ||
| 6 | 6 | export interface ITerminalService { | |
| 7 | 7 | sendCommand(command: string, args: string[]): Promise<void>; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -88,7 +88,7 @@ export abstract class BaseFormatter { | |||
| 88 | 88 | if (isNotInstalledError(error)) { | |
| 89 | 89 | const installer = this.serviceContainer.get<IInstaller>(IInstaller); | |
| 90 | 90 | const isInstalled = await installer.isInstalled(this.product, resource); | |
| 91 | - if (isInstalled) { | ||
| 91 | + if (!isInstalled) { | ||
| 92 | 92 | customError += `\nYou could either install the '${this.Id}' formatter, turn it off or use another formatter.`; | |
| 93 | 93 | installer.promptToInstall(this.product, resource).catch(ex => console.error('Python Extension: promptToInstall', ex)); | |
| 94 | 94 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,13 +8,16 @@ import { PipInstaller } from '../../client/common/installer/pipInstaller'; | |||
| 8 | 8 | import { IModuleInstaller } from '../../client/common/installer/types'; | |
| 9 | 9 | import { Logger } from '../../client/common/logger'; | |
| 10 | 10 | import { PersistentStateFactory } from '../../client/common/persistentState'; | |
| 11 | + import { FileSystem } from '../../client/common/platform/fileSystem'; | ||
| 11 | 12 | import { PathUtils } from '../../client/common/platform/pathUtils'; | |
| 12 | - import { Architecture } from '../../client/common/platform/types'; | ||
| 13 | + import { PlatformService } from '../../client/common/platform/platformService'; | ||
| 14 | + import { Architecture, IFileSystem, IPlatformService } from '../../client/common/platform/types'; | ||
| 13 | 15 | import { CurrentProcess } from '../../client/common/process/currentProcess'; | |
| 14 | 16 | import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; | |
| 15 | 17 | import { ITerminalService } from '../../client/common/terminal/types'; | |
| 16 | 18 | import { ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IsWindows } from '../../client/common/types'; | |
| 17 | 19 | import { ICondaLocatorService, IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE, InterpreterType } from '../../client/interpreter/contracts'; | |
| 20 | + import { PythonInterpreterLocatorService } from '../../client/interpreter/locators/index'; | ||
| 18 | 21 | import { updateSetting } from '../common'; | |
| 19 | 22 | import { rootWorkspaceUri } from '../common'; | |
| 20 | 23 | import { MockProvider } from '../interpreters/mocks'; | |
@@ -60,6 +63,8 @@ suite('Module Installer', () => { | |||
| 60 | 63 | ioc.serviceManager.addSingleton<ICondaLocatorService>(ICondaLocatorService, MockCondaLocator); | |
| 61 | 64 | ioc.serviceManager.addSingleton<IPathUtils>(IPathUtils, PathUtils); | |
| 62 | 65 | ioc.serviceManager.addSingleton<ICurrentProcess>(ICurrentProcess, CurrentProcess); | |
| 66 | + ioc.serviceManager.addSingleton<IFileSystem>(IFileSystem, FileSystem); | ||
| 67 | + ioc.serviceManager.addSingleton<IPlatformService>(IPlatformService, PlatformService); | ||
| 63 | 68 | ||
| 64 | 69 | ioc.registerMockProcessTypes(); | |
| 65 | 70 | ioc.serviceManager.addSingleton<ITerminalService>(ITerminalService, MockTerminalService); | |
@@ -136,6 +141,9 @@ suite('Module Installer', () => { | |||
| 136 | 141 | }); | |
| 137 | 142 | ||
| 138 | 143 | test('Validate pip install arguments', async () => { | |
| 144 | + const mockInterpreterLocator = new MockProvider([{ path: await getCurrentPythonPath(), type: InterpreterType.Unknown }]); | ||
| 145 | + ioc.serviceManager.addSingletonInstance<IInterpreterLocatorService>(IInterpreterLocatorService, mockInterpreterLocator, INTERPRETER_LOCATOR_SERVICE); | ||
| 146 | + | ||
| 139 | 147 | const moduleName = 'xyz'; | |
| 140 | 148 | const terminalService = ioc.serviceContainer.get<MockTerminalService>(ITerminalService); | |
| 141 | 149 | ||
@@ -148,10 +156,13 @@ suite('Module Installer', () => { | |||
| 148 | 156 | const commandSent = await terminalService.commandSent; | |
| 149 | 157 | const commandParts = commandSent.split(' '); | |
| 150 | 158 | commandParts.shift(); | |
| 151 | - expect(commandParts.join(' ')).equal(`-m pip install -U ${moduleName}`, 'Invalid command sent to terminal for installation.'); | ||
| 159 | + expect(commandParts.join(' ')).equal(`-m pip install -U ${moduleName} --user`, 'Invalid command sent to terminal for installation.'); | ||
| 152 | 160 | }); | |
| 153 | 161 | ||
| 154 | 162 | test('Validate Conda install arguments', async () => { | |
| 163 | + const mockInterpreterLocator = new MockProvider([{ path: await getCurrentPythonPath(), type: InterpreterType.Conda }]); | ||
| 164 | + ioc.serviceManager.addSingletonInstance<IInterpreterLocatorService>(IInterpreterLocatorService, mockInterpreterLocator, INTERPRETER_LOCATOR_SERVICE); | ||
| 165 | + | ||
| 155 | 166 | const moduleName = 'xyz'; | |
| 156 | 167 | const terminalService = ioc.serviceContainer.get<MockTerminalService>(ITerminalService); | |
| 157 | 168 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments