Skip to content

Commit 23e7774

Browse files
authored
Support for Jupyter to pass additional install args (microsoft#16133)
1 parent 305b021 commit 23e7774

12 files changed

Lines changed: 93 additions & 41 deletions

File tree

news/3 Code Health/16131.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ability for Jupyter extension to pass addtional installer arguments.

src/client/common/installer/condaInstaller.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { inDiscoveryExperiment } from '../experiments/helpers';
99
import { ExecutionInfo, IConfigurationService, IExperimentService } from '../types';
1010
import { isResource } from '../utils/misc';
1111
import { ModuleInstaller } from './moduleInstaller';
12-
import { InterpreterUri } from './types';
12+
import { InterpreterUri, ModuleInstallFlags } from './types';
1313

1414
/**
1515
* A Python module installer for a conda environment.
@@ -64,7 +64,7 @@ export class CondaInstaller extends ModuleInstaller {
6464
protected async getExecutionInfo(
6565
moduleName: string,
6666
resource?: InterpreterUri,
67-
isUpgrade?: boolean,
67+
flags: ModuleInstallFlags = 0,
6868
): Promise<ExecutionInfo> {
6969
const condaService = this.serviceContainer.get<ICondaService>(ICondaService);
7070
const condaFile = await condaService.getCondaFile();
@@ -77,7 +77,7 @@ export class CondaInstaller extends ModuleInstaller {
7777
? this.serviceContainer.get<IComponentAdapter>(IComponentAdapter)
7878
: this.serviceContainer.get<ICondaLocatorService>(ICondaLocatorService);
7979
const info = await condaLocatorService.getCondaEnvironment(pythonPath);
80-
const args = [isUpgrade ? 'update' : 'install'];
80+
const args = [flags & ModuleInstallFlags.upgrade ? 'update' : 'install'];
8181

8282
// Temporarily ensure tensorboard is installed from the conda-forge
8383
// channel since 2.4.1 is not yet available in the default index
@@ -93,6 +93,12 @@ export class CondaInstaller extends ModuleInstaller {
9393
args.push('--prefix');
9494
args.push(info.path.fileToCommandArgument());
9595
}
96+
if (flags & ModuleInstallFlags.updateDependencies) {
97+
args.push('--update-deps');
98+
}
99+
if (flags & ModuleInstallFlags.reInstall) {
100+
args.push('--force-reinstall');
101+
}
96102
args.push(moduleName);
97103
args.push('-y');
98104
return {

src/client/common/installer/moduleInstaller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { ExecutionInfo, IConfigurationService, IOutputChannel, ModuleNamePurpose
1919
import { Products } from '../utils/localize';
2020
import { isResource } from '../utils/misc';
2121
import { ProductNames } from './productNames';
22-
import { IModuleInstaller, InterpreterUri } from './types';
22+
import { IModuleInstaller, InterpreterUri, ModuleInstallFlags } from './types';
2323

2424
@injectable()
2525
export abstract class ModuleInstaller implements IModuleInstaller {
@@ -33,7 +33,7 @@ export abstract class ModuleInstaller implements IModuleInstaller {
3333
productOrModuleName: Product | string,
3434
resource?: InterpreterUri,
3535
cancel?: CancellationToken,
36-
isUpgrade?: boolean,
36+
flags?: ModuleInstallFlags,
3737
): Promise<void> {
3838
const name =
3939
typeof productOrModuleName == 'string'
@@ -48,7 +48,7 @@ export abstract class ModuleInstaller implements IModuleInstaller {
4848
} else {
4949
options.interpreter = resource;
5050
}
51-
const executionInfo = await this.getExecutionInfo(name, resource, isUpgrade);
51+
const executionInfo = await this.getExecutionInfo(name, resource, flags);
5252
const terminalService = this.serviceContainer
5353
.get<ITerminalServiceFactory>(ITerminalServiceFactory)
5454
.getTerminalService(options);
@@ -132,7 +132,7 @@ export abstract class ModuleInstaller implements IModuleInstaller {
132132
protected abstract getExecutionInfo(
133133
moduleName: string,
134134
resource?: InterpreterUri,
135-
isUpgrade?: boolean,
135+
flags?: ModuleInstallFlags,
136136
): Promise<ExecutionInfo>;
137137
private async processInstallArgs(args: string[], resource?: InterpreterUri): Promise<string[]> {
138138
const indexOfPylint = args.findIndex((arg) => arg.toUpperCase() === 'PYLINT');

src/client/common/installer/pipEnvInstaller.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { inDiscoveryExperiment } from '../experiments/helpers';
1111
import { ExecutionInfo, IExperimentService } from '../types';
1212
import { isResource } from '../utils/misc';
1313
import { ModuleInstaller } from './moduleInstaller';
14-
import { InterpreterUri } from './types';
14+
import { InterpreterUri, ModuleInstallFlags } from './types';
1515

1616
export const pipenvName = 'pipenv';
1717

@@ -61,9 +61,14 @@ export class PipEnvInstaller extends ModuleInstaller {
6161
protected async getExecutionInfo(
6262
moduleName: string,
6363
_resource?: InterpreterUri,
64-
isUpgrade?: boolean,
64+
flags: ModuleInstallFlags = 0,
6565
): Promise<ExecutionInfo> {
66-
const args = [isUpgrade ? 'update' : 'install', moduleName, '--dev'];
66+
// In pipenv the only way to update/upgrade or re-install is update (apart from a complete uninstall and re-install).
67+
const update =
68+
flags & ModuleInstallFlags.reInstall ||
69+
flags & ModuleInstallFlags.updateDependencies ||
70+
flags & ModuleInstallFlags.upgrade;
71+
const args = [update ? 'update' : 'install', moduleName, '--dev'];
6772
if (moduleName === 'black') {
6873
args.push('--pre');
6974
}

src/client/common/installer/pipInstaller.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IPythonExecutionFactory } from '../process/types';
88
import { ExecutionInfo } from '../types';
99
import { isResource } from '../utils/misc';
1010
import { ModuleInstaller } from './moduleInstaller';
11-
import { InterpreterUri } from './types';
11+
import { InterpreterUri, ModuleInstallFlags } from './types';
1212

1313
@injectable()
1414
export class PipInstaller extends ModuleInstaller {
@@ -28,16 +28,24 @@ export class PipInstaller extends ModuleInstaller {
2828
public isSupported(resource?: InterpreterUri): Promise<boolean> {
2929
return this.isPipAvailable(resource);
3030
}
31-
protected async getExecutionInfo(moduleName: string, _resource?: InterpreterUri): Promise<ExecutionInfo> {
32-
const proxyArgs: string[] = [];
31+
protected async getExecutionInfo(
32+
moduleName: string,
33+
_resource?: InterpreterUri,
34+
flags: ModuleInstallFlags = 0,
35+
): Promise<ExecutionInfo> {
36+
const args: string[] = [];
3337
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
3438
const proxy = workspaceService.getConfiguration('http').get('proxy', '');
3539
if (proxy.length > 0) {
36-
proxyArgs.push('--proxy');
37-
proxyArgs.push(proxy);
40+
args.push('--proxy');
41+
args.push(proxy);
42+
}
43+
args.push(...['install', '-U']);
44+
if (flags & ModuleInstallFlags.reInstall) {
45+
args.push('--force-reinstall');
3846
}
3947
return {
40-
args: [...proxyArgs, 'install', '-U', moduleName],
48+
args: [...args, moduleName],
4149
moduleName: 'pip',
4250
};
4351
}

src/client/common/installer/productInstaller.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
InterpreterUri,
3737
IProductPathService,
3838
IProductService,
39+
ModuleInstallFlags,
3940
} from './types';
4041

4142
export { Product } from '../types';
@@ -72,7 +73,7 @@ abstract class BaseInstaller {
7273
product: Product,
7374
resource?: InterpreterUri,
7475
cancel?: CancellationToken,
75-
isUpgrade?: boolean,
76+
flags?: ModuleInstallFlags,
7677
): Promise<InstallerResponse> {
7778
// If this method gets called twice, while previous promise has not been resolved, then return that same promise.
7879
// E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying
@@ -83,7 +84,7 @@ abstract class BaseInstaller {
8384
if (BaseInstaller.PromptPromises.has(key)) {
8485
return BaseInstaller.PromptPromises.get(key)!;
8586
}
86-
const promise = this.promptToInstallImplementation(product, resource, cancel, isUpgrade);
87+
const promise = this.promptToInstallImplementation(product, resource, cancel, flags);
8788
BaseInstaller.PromptPromises.set(key, promise);
8889
promise.then(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors();
8990
promise.catch(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors();
@@ -95,7 +96,7 @@ abstract class BaseInstaller {
9596
product: Product,
9697
resource?: InterpreterUri,
9798
cancel?: CancellationToken,
98-
isUpgrade?: boolean,
99+
flags?: ModuleInstallFlags,
99100
): Promise<InstallerResponse> {
100101
if (product === Product.unittest) {
101102
return InstallerResponse.Installed;
@@ -112,7 +113,7 @@ abstract class BaseInstaller {
112113
}
113114

114115
await installer
115-
.installModule(product, resource, cancel, isUpgrade)
116+
.installModule(product, resource, cancel, flags)
116117
.catch((ex) => traceError(`Error in installing the product '${ProductNames.get(product)}', ${ex}`));
117118

118119
return this.isInstalled(product, resource).then((isInstalled) => {
@@ -207,7 +208,7 @@ abstract class BaseInstaller {
207208
product: Product,
208209
resource?: InterpreterUri,
209210
cancel?: CancellationToken,
210-
isUpgrade?: boolean,
211+
flags?: ModuleInstallFlags,
211212
): Promise<InstallerResponse>;
212213

213214
protected getExecutableNameFromSettings(product: Product, resource?: Uri): string {
@@ -349,7 +350,7 @@ class DataScienceInstaller extends BaseInstaller {
349350
product: Product,
350351
interpreterUri?: InterpreterUri,
351352
cancel?: CancellationToken,
352-
isUpgrade?: boolean,
353+
flags?: ModuleInstallFlags,
353354
): Promise<InstallerResponse> {
354355
// Precondition
355356
if (isResource(interpreterUri)) {
@@ -390,7 +391,7 @@ class DataScienceInstaller extends BaseInstaller {
390391
}
391392

392393
await installerModule
393-
.installModule(product, interpreter, cancel, isUpgrade)
394+
.installModule(product, interpreter, cancel, flags)
394395
.catch((ex) => traceError(`Error in installing the module '${moduleName}', ${ex}`));
395396

396397
return this.isInstalled(product, interpreter).then((isInstalled) => {
@@ -447,15 +448,15 @@ export class ProductInstaller implements IInstaller {
447448
product: Product,
448449
resource?: InterpreterUri,
449450
cancel?: CancellationToken,
450-
isUpgrade?: boolean,
451+
flags?: ModuleInstallFlags,
451452
): Promise<InstallerResponse> {
452453
const currentInterpreter = isResource(resource)
453454
? await this.interpreterService.getActiveInterpreter(resource)
454455
: resource;
455456
if (!currentInterpreter) {
456457
return InstallerResponse.Ignore;
457458
}
458-
return this.createInstaller(product).promptToInstall(product, resource, cancel, isUpgrade);
459+
return this.createInstaller(product).promptToInstall(product, resource, cancel, flags);
459460
}
460461

461462
public async isProductVersionCompatible(
@@ -470,9 +471,9 @@ export class ProductInstaller implements IInstaller {
470471
product: Product,
471472
resource?: InterpreterUri,
472473
cancel?: CancellationToken,
473-
isUpgrade?: boolean,
474+
flags?: ModuleInstallFlags,
474475
): Promise<InstallerResponse> {
475-
return this.createInstaller(product).install(product, resource, cancel, isUpgrade);
476+
return this.createInstaller(product).install(product, resource, cancel, flags);
476477
}
477478

478479
public async isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean> {

src/client/common/installer/types.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface IModuleInstaller {
2727
product: string,
2828
resource?: InterpreterUri,
2929
cancel?: CancellationToken,
30-
isUpgrade?: boolean,
30+
flags?: ModuleInstallFlags,
3131
): Promise<void>;
3232
/**
3333
* Installs a Product
@@ -44,7 +44,7 @@ export interface IModuleInstaller {
4444
product: Product,
4545
resource?: InterpreterUri,
4646
cancel?: CancellationToken,
47-
isUpgrade?: boolean,
47+
flags?: ModuleInstallFlags,
4848
): Promise<void>;
4949
isSupported(resource?: InterpreterUri): Promise<boolean>;
5050
}
@@ -76,3 +76,9 @@ export const IExtensionBuildInstaller = Symbol('IExtensionBuildInstaller');
7676
export interface IExtensionBuildInstaller {
7777
install(): Promise<void>;
7878
}
79+
80+
export enum ModuleInstallFlags {
81+
upgrade = 1,
82+
updateDependencies = 2,
83+
reInstall = 4,
84+
}

src/client/common/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import { LanguageServerType } from '../activation/types';
2222
import { LogLevel } from '../logging/levels';
2323
import type { ExtensionChannels } from './insidersBuild/types';
24-
import type { InterpreterUri } from './installer/types';
24+
import type { InterpreterUri, ModuleInstallFlags } from './installer/types';
2525
import { EnvironmentVariables } from './variables/types';
2626
import { ITestingSettings } from '../testing/configuration/types';
2727

@@ -122,13 +122,13 @@ export interface IInstaller {
122122
product: Product,
123123
resource?: InterpreterUri,
124124
cancel?: CancellationToken,
125-
isUpgrade?: boolean,
125+
flags?: ModuleInstallFlags,
126126
): Promise<InstallerResponse>;
127127
install(
128128
product: Product,
129129
resource?: InterpreterUri,
130130
cancel?: CancellationToken,
131-
isUpgrade?: boolean,
131+
flags?: ModuleInstallFlags,
132132
): Promise<InstallerResponse>;
133133
isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean>;
134134
isProductVersionCompatible(

src/client/jupyter/jupyterIntegration.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CancellationToken, Disposable, Event, Extension, Memento, Uri } from 'v
1010
import * as lsp from 'vscode-languageserver-protocol';
1111
import { ILanguageServerCache, ILanguageServerConnection } from '../activation/types';
1212
import { JUPYTER_EXTENSION_ID } from '../common/constants';
13-
import { InterpreterUri } from '../common/installer/types';
13+
import { InterpreterUri, ModuleInstallFlags } from '../common/installer/types';
1414
import {
1515
GLOBAL_MEMENTO,
1616
IExperimentService,
@@ -179,7 +179,16 @@ export class JupyterExtensionIntegration {
179179
product: JupyterProductToInstall,
180180
resource?: InterpreterUri,
181181
cancel?: CancellationToken,
182-
): Promise<InstallerResponse> => this.installer.install(ProductMapping[product], resource, cancel),
182+
reInstallAndUpdate?: boolean,
183+
): Promise<InstallerResponse> =>
184+
this.installer.install(
185+
ProductMapping[product],
186+
resource,
187+
cancel,
188+
reInstallAndUpdate === true
189+
? ModuleInstallFlags.updateDependencies | ModuleInstallFlags.reInstall
190+
: undefined,
191+
),
183192
getDebuggerPath: async () => dirname(getDebugpyPackagePath()),
184193
getInterpreterPathSelectedForJupyterServer: () =>
185194
this.globalState.get<string | undefined>('INTERPRETER_PATH_SELECTED_FOR_JUPYTER_SERVER'),

src/client/tensorBoard/tensorBoardSession.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { EventName } from '../telemetry/constants';
4545
import { ImportTracker } from '../telemetry/importTracker';
4646
import { TensorBoardPromptSelection, TensorBoardSessionStartResult } from './constants';
4747
import { IMultiStepInputFactory } from '../common/utils/multiStepInput';
48+
import { ModuleInstallFlags } from '../common/installer/types';
4849

4950
enum Messages {
5051
JumpToSource = 'jump_to_source',
@@ -236,7 +237,9 @@ export class TensorBoardSession {
236237
Product.tensorboard,
237238
interpreter,
238239
installerToken,
239-
tensorboardInstallStatus === ProductInstallStatus.NeedsUpgrade,
240+
tensorboardInstallStatus === ProductInstallStatus.NeedsUpgrade
241+
? ModuleInstallFlags.upgrade
242+
: undefined,
240243
),
241244
);
242245
}

0 commit comments

Comments
 (0)