FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Synchronous module installer in terminal by DonJayamanne · Pull Request #9032 · microsoft/vscode-python · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .json  (1) .md  (1) .py  (1) .ts  (19) All 4 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
1 change: 1 addition & 0 deletions news/3 Code Health/8952.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ability to wait for completion of the installation of modules.
3 changes: 2 additions & 1 deletion package.nls.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -390,5 +390,6 @@
"DataScience.fallbackToUseActiveInterpeterAsKernel": "Couldn't find kernel '{0}' that the notebook was created with. Using the current interpreter.",
"DataScience.fallBackToRegisterAndUseActiveInterpeterAsKernel": "Couldn't find kernel '{0}' that the notebook was created with. Registering a new kernel using the current interpreter.",
"DataScience.fallBackToPromptToUseActiveInterpreterOrSelectAKernel": "Couldn't find kernel '{0}' that the notebook was created with.",
"DataScience.kernelDescriptionForKernelPicker": "(kernel)"
"DataScience.kernelDescriptionForKernelPicker": "(kernel)",
"products.installingModule": "Installing {0}"
}
36 changes: 36 additions & 0 deletions pythonFiles/shell_exec.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os
import sys
import subprocess

# This is a simple solution to waiting for completion of commands sent to terminal.
# 1. Intercept commands send to a terminal
# 2. Send commands to our script file with an additional argument
# 3. In here create a file that'll log the progress.
# 4. Calling code monitors the contents of the file to determine state of execution.

# Last argument is a file that's used for synchronizing the actions in the terminal with the calling code in extension.
lock_file = sys.argv[-1]
shell_args = sys.argv[1:-1]

print('Executing command in shell >> ' + ' '.join(shell_args))

with open(lock_file, 'w') as fp:
try:
# Signal start of execution.
fp.write('START\n')
fp.flush()

subprocess.check_call(shell_args, stdout=sys.stdout, stderr=sys.stderr)

# Signal start of execution.
fp.write('END\n')
fp.flush()
except Exception:
import traceback
print(traceback.format_exc())
# Signal end of execution with failure state.
fp.write('FAIL\n')
fp.flush()
4 changes: 2 additions & 2 deletions src/client/common/installer/condaInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { IServiceContainer } from '../../ioc/types';
import { ExecutionInfo, IConfigurationService } from '../types';
import { isResource } from '../utils/misc';
import { ModuleInstaller } from './moduleInstaller';
import { IModuleInstaller, InterpreterUri } from './types';
import { InterpreterUri } from './types';

/**
* A Python module installer for a conda environment.
*/
@injectable()
export class CondaInstaller extends ModuleInstaller implements IModuleInstaller {
export class CondaInstaller extends ModuleInstaller {
private isCondaAvailable: boolean | undefined;

constructor(
Expand Down
62 changes: 41 additions & 21 deletions src/client/common/installer/moduleInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,70 @@
import * as fs from 'fs';
import { injectable } from 'inversify';
import * as path from 'path';
import { OutputChannel, window } from 'vscode';
import { CancellationToken, OutputChannel, ProgressLocation, ProgressOptions, window } from 'vscode';
import { IInterpreterService, InterpreterType } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { IApplicationShell } from '../application/types';
import { wrapCancellationTokens } from '../cancellation';
import { STANDARD_OUTPUT_CHANNEL } from '../constants';
import { ITerminalServiceFactory } from '../terminal/types';
import { ExecutionInfo, IConfigurationService, IOutputChannel } from '../types';
import { Products } from '../utils/localize';
import { isResource, noop } from '../utils/misc';
import { InterpreterUri } from './types';
import { IModuleInstaller, InterpreterUri } from './types';

@injectable()
export abstract class ModuleInstaller {
export abstract class ModuleInstaller implements IModuleInstaller {
public abstract get priority(): number;
public abstract get name(): string;
public abstract get displayName(): string
constructor(protected serviceContainer: IServiceContainer) { }
public async installModule(name: string, resource?: InterpreterUri): Promise<void> {
public async installModule(name: string, resource?: InterpreterUri, cancel?: CancellationToken): Promise<void> {
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { installer: this.displayName });
const uri = isResource(resource) ? resource : undefined;
const executionInfo = await this.getExecutionInfo(name, resource);
const terminalService = this.serviceContainer.get<ITerminalServiceFactory>(ITerminalServiceFactory).getTerminalService(uri);
const install = async (token?: CancellationToken) => {
const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource);
if (executionInfo.moduleName) {
const configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
const settings = configService.getSettings(uri);
const args = ['-m', executionInfo.moduleName].concat(executionInfoArgs);

const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource);
if (executionInfo.moduleName) {
const configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
const settings = configService.getSettings(uri);
const args = ['-m', executionInfo.moduleName].concat(executionInfoArgs);

const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
const interpreter = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource;
const pythonPath = isResource(resource) ? settings.pythonPath : resource.path;
if (!interpreter || interpreter.type !== InterpreterType.Unknown) {
await terminalService.sendCommand(pythonPath, args);
} else if (settings.globalModuleInstallation) {
if (await this.isPathWritableAsync(path.dirname(pythonPath))) {
await terminalService.sendCommand(pythonPath, args);
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
const interpreter = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource;
const pythonPath = isResource(resource) ? settings.pythonPath : resource.path;
if (!interpreter || interpreter.type !== InterpreterType.Unknown) {
await terminalService.sendCommand(pythonPath, args, token);
} else if (settings.globalModuleInstallation) {
if (await this.isPathWritableAsync(path.dirname(pythonPath))) {
await terminalService.sendCommand(pythonPath, args, token);
} else {
this.elevatedInstall(pythonPath, args);
}
} else {
this.elevatedInstall(pythonPath, args);
await terminalService.sendCommand(pythonPath, args.concat(['--user']), token);
}
} else {
await terminalService.sendCommand(pythonPath, args.concat(['--user']));
await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs, token);
}
};

// Display progress indicator if we have ability to cancel this operation from calling code.
// This is required as its possible the installation can take a long time.
// (i.e. if installation takes a long time in terminal or like, a progress indicator is necessary to let user know what is being waited on).
if (cancel) {
const shell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
const options: ProgressOptions = {
location: ProgressLocation.Notification,
cancellable: true,
title: Products.installingModule().format(name)
};
await shell.withProgress(options, async (_, token: CancellationToken) => install(wrapCancellationTokens(token, cancel)));
} else {
await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs);
await install(cancel);
}
}
public abstract isSupported(resource?: InterpreterUri): Promise<boolean>;
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/installer/pipEnvInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { IServiceContainer } from '../../ioc/types';
import { ExecutionInfo } from '../types';
import { isResource } from '../utils/misc';
import { ModuleInstaller } from './moduleInstaller';
import { IModuleInstaller, InterpreterUri } from './types';
import { InterpreterUri } from './types';

export const pipenvName = 'pipenv';

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

public get name(): string {
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/installer/pipInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { IPythonExecutionFactory } from '../process/types';
import { ExecutionInfo } from '../types';
import { isResource } from '../utils/misc';
import { ModuleInstaller } from './moduleInstaller';
import { IModuleInstaller, InterpreterUri } from './types';
import { InterpreterUri } from './types';

@injectable()
export class PipInstaller extends ModuleInstaller implements IModuleInstaller {
export class PipInstaller extends ModuleInstaller {
public get name(): string {
return 'Pip';
}
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/installer/poetryInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import { IProcessServiceFactory } from '../process/types';
import { ExecutionInfo, IConfigurationService } from '../types';
import { isResource } from '../utils/misc';
import { ModuleInstaller } from './moduleInstaller';
import { IModuleInstaller, InterpreterUri } from './types';
import { InterpreterUri } from './types';
export const poetryName = 'poetry';
const poetryFile = 'poetry.lock';

@injectable()
export class PoetryInstaller extends ModuleInstaller implements IModuleInstaller {
export class PoetryInstaller extends ModuleInstaller {

public get name(): string {
return 'poetry';
Expand Down
44 changes: 22 additions & 22 deletions src/client/common/installer/productInstaller.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { inject, injectable, named } from 'inversify';
import * as os from 'os';
import { OutputChannel, Uri } from 'vscode';
import { CancellationToken, OutputChannel, Uri } from 'vscode';
import '../../common/extensions';
import * as localize from '../../common/utils/localize';
import { IServiceContainer } from '../../ioc/types';
Expand Down Expand Up @@ -40,7 +40,7 @@ export abstract class BaseInstaller {
this.productService = serviceContainer.get<IProductService>(IProductService);
}

public promptToInstall(product: Product, resource?: InterpreterUri): Promise<InstallerResponse> {
public promptToInstall(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
// If this method gets called twice, while previous promise has not been resolved, then return that same promise.
// E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying
// another message.
Expand All @@ -49,15 +49,15 @@ export abstract class BaseInstaller {
if (BaseInstaller.PromptPromises.has(key)) {
return BaseInstaller.PromptPromises.get(key)!;
}
const promise = this.promptToInstallImplementation(product, resource);
const promise = this.promptToInstallImplementation(product, resource, cancel);
BaseInstaller.PromptPromises.set(key, promise);
promise.then(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors();
promise.catch(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors();

return promise;
}

public async install(product: Product, resource?: InterpreterUri): Promise<InstallerResponse> {
public async install(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
if (product === Product.unittest) {
return InstallerResponse.Installed;
}
Expand All @@ -70,7 +70,7 @@ export abstract class BaseInstaller {

const moduleName = translateProductToModule(product, ModuleNamePurpose.install);
const logger = this.serviceContainer.get<ILogger>(ILogger);
await installer.installModule(moduleName, resource)
await installer.installModule(moduleName, resource, cancel)
.catch(logger.logError.bind(logger, `Error in installing the module '${moduleName}'`));

return this.isInstalled(product, resource)
Expand Down Expand Up @@ -98,7 +98,7 @@ export abstract class BaseInstaller {
}
}

protected abstract promptToInstallImplementation(product: Product, resource?: InterpreterUri): Promise<InstallerResponse>;
protected abstract promptToInstallImplementation(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse>;
protected getExecutableNameFromSettings(product: Product, resource?: Uri): string {
const productType = this.productService.getProductType(product);
const productPathService = this.serviceContainer.get<IProductPathService>(IProductPathService, productType);
Expand Down Expand Up @@ -132,14 +132,14 @@ export class CTagsInstaller extends BaseInstaller {
}
return InstallerResponse.Ignore;
}
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: Uri, _cancel?: CancellationToken): Promise<InstallerResponse> {
const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No');
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
}
}

export class FormatterInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: Uri, cancel?: CancellationToken): Promise<InstallerResponse> {
// Hard-coded on purpose because the UI won't necessarily work having
// another formatter.
const formatters = [Product.autopep8, Product.black, Product.yapf];
Expand All @@ -160,14 +160,14 @@ export class FormatterInstaller extends BaseInstaller {

const item = await this.appShell.showErrorMessage(message, ...options);
if (item === yesChoice) {
return this.install(product, resource);
return this.install(product, resource, cancel);
} else if (typeof item === 'string') {
for (const formatter of formatters) {
const formatterName = ProductNames.get(formatter)!;

if (item.endsWith(formatterName)) {
await this.configService.updateSetting('formatting.provider', formatterName, resource);
return this.install(formatter, resource);
return this.install(formatter, resource, cancel);
}
}
}
Expand All @@ -177,7 +177,7 @@ export class FormatterInstaller extends BaseInstaller {
}

export class LinterInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: Uri, cancel?: CancellationToken): Promise<InstallerResponse> {
const isPylint = product === Product.pylint;

const productName = ProductNames.get(product)!;
Expand All @@ -202,7 +202,7 @@ export class LinterInstaller extends BaseInstaller {
const response = await this.appShell.showErrorMessage(message, ...options);
if (response === install) {
sendTelemetryEvent(EventName.LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'install' });
return this.install(product, resource);
return this.install(product, resource, cancel);
} else if (response === disableInstallPrompt) {
await this.setStoredResponse(disableLinterInstallPromptKey, true);
sendTelemetryEvent(EventName.LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'disablePrompt' });
Expand Down Expand Up @@ -250,7 +250,7 @@ export class LinterInstaller extends BaseInstaller {
}

export class TestFrameworkInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: Uri, cancel?: CancellationToken): Promise<InstallerResponse> {
const productName = ProductNames.get(product)!;

const options: string[] = [];
Expand All @@ -263,23 +263,23 @@ export class TestFrameworkInstaller extends BaseInstaller {
}

const item = await this.appShell.showErrorMessage(message, ...options);
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore;
}
}

export class RefactoringLibraryInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: Uri, cancel?: CancellationToken): Promise<InstallerResponse> {
const productName = ProductNames.get(product)!;
const item = await this.appShell.showErrorMessage(`Refactoring library ${productName} is not installed. Install?`, 'Yes', 'No');
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore;
}
}

export class DataScienceInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: InterpreterUri): Promise<InstallerResponse> {
protected async promptToInstallImplementation(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
const productName = ProductNames.get(product)!;
const item = await this.appShell.showErrorMessage(localize.DataScience.libraryNotInstalled().format(productName), 'Yes', 'No');
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore;
}
}

Expand All @@ -294,11 +294,11 @@ export class ProductInstaller implements IInstaller {

// tslint:disable-next-line:no-empty
public dispose() { }
public async promptToInstall(product: Product, resource?: InterpreterUri): Promise<InstallerResponse> {
return this.createInstaller(product).promptToInstall(product, resource);
public async promptToInstall(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
return this.createInstaller(product).promptToInstall(product, resource, cancel);
}
public async install(product: Product, resource?: InterpreterUri): Promise<InstallerResponse> {
return this.createInstaller(product).install(product, resource);
public async install(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
return this.createInstaller(product).install(product, resource, cancel);
}
public async isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean | undefined> {
return this.createInstaller(product).isInstalled(product, resource);
Expand Down
Loading

Back | FazBrowse Home | New Git URL