Skip to content

Commit 38a7647

Browse files
authored
Install pip if necessary when installing ipykernel (microsoft#17967)
1 parent b8e702b commit 38a7647

12 files changed

Lines changed: 27595 additions & 312 deletions

File tree

‎cgmanifest.json‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@
109109
"Type": "pip"
110110
},
111111
"DevelopmentDependency": false
112+
},
113+
{
114+
"Component": {
115+
"Other": {
116+
"Name": "get-pip.py",
117+
"Version": "21.3.1",
118+
"DownloadUrl": "https://github.com/pypa/get-pip/blob/main/public/get-pip.py"
119+
},
120+
"Type": "other"
121+
},
122+
"DevelopmentDependency": false
112123
}
113124
]
114125
}

‎news/1 Enhancements/17975.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the ability to install `pip` if missing, when installing missing pacakges from the `Jupyter Extension`.

‎pythonFiles/get-pip.py‎

Lines changed: 27086 additions & 0 deletions
Large diffs are not rendered by default.

‎src/client/common/installer/moduleInstaller.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ export abstract class ModuleInstaller implements IModuleInstaller {
7474
} else {
7575
await terminalService.sendCommand(pythonPath, args, token);
7676
}
77+
} else if (name === translateProductToModule(Product.pip)) {
78+
// Pip should always be installed into the specified environment.
79+
await terminalService.sendCommand(pythonPath, args, token);
7780
} else {
7881
await terminalService.sendCommand(pythonPath, args.concat(['--user']), token);
7982
}
@@ -199,6 +202,10 @@ export function translateProductToModule(product: Product): string {
199202
return 'torch-tb-profiler';
200203
case Product.torchProfilerImportName:
201204
return 'torch_tb_profiler';
205+
case Product.pip:
206+
return 'pip';
207+
case Product.ensurepip:
208+
return 'ensurepip';
202209
default: {
203210
throw new Error(`Product ${product} cannot be installed as a Python Module.`);
204211
}

‎src/client/common/installer/pipInstaller.ts‎

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import { IServiceContainer } from '../../ioc/types';
66
import { ModuleInstallerType } from '../../pythonEnvironments/info';
77
import { IWorkspaceService } from '../application/types';
88
import { IPythonExecutionFactory } from '../process/types';
9-
import { ExecutionInfo } from '../types';
9+
import { ExecutionInfo, IInstaller, Product } from '../types';
1010
import { isResource } from '../utils/misc';
11-
import { ModuleInstaller } from './moduleInstaller';
11+
import { ModuleInstaller, translateProductToModule } from './moduleInstaller';
1212
import { InterpreterUri, ModuleInstallFlags } from './types';
13+
import * as path from 'path';
14+
import { _SCRIPTS_DIR } from '../process/internal/scripts/constants';
15+
import { ProductNames } from './productNames';
16+
import { sendTelemetryEvent } from '../../telemetry';
17+
import { EventName } from '../../telemetry/constants';
18+
import { IInterpreterService } from '../../interpreter/contracts';
1319

1420
@injectable()
1521
export class PipInstaller extends ModuleInstaller {
@@ -35,9 +41,51 @@ export class PipInstaller extends ModuleInstaller {
3541
}
3642
protected async getExecutionInfo(
3743
moduleName: string,
38-
_resource?: InterpreterUri,
44+
resource?: InterpreterUri,
3945
flags: ModuleInstallFlags = 0,
4046
): Promise<ExecutionInfo> {
47+
if (moduleName === translateProductToModule(Product.pip)) {
48+
const version = isResource(resource)
49+
? ''
50+
: `${resource.version?.major || ''}.${resource.version?.minor || ''}.${resource.version?.patch || ''}`;
51+
const envType = isResource(resource) ? undefined : resource.envType;
52+
53+
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
54+
installer: 'unavailable',
55+
requiredInstaller: ModuleInstallerType.Pip,
56+
productName: ProductNames.get(Product.pip),
57+
version,
58+
envType,
59+
});
60+
61+
// If `ensurepip` is available, if not, then install pip using the script file.
62+
const installer = this.serviceContainer.get<IInstaller>(IInstaller);
63+
if (await installer.isInstalled(Product.ensurepip, resource)) {
64+
return {
65+
args: [],
66+
moduleName: 'ensurepip',
67+
};
68+
}
69+
70+
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
71+
installer: 'unavailable',
72+
requiredInstaller: ModuleInstallerType.Pip,
73+
productName: ProductNames.get(Product.ensurepip),
74+
version,
75+
envType,
76+
});
77+
78+
// Return script to install pip.
79+
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
80+
const interpreter = isResource(resource)
81+
? await interpreterService.getActiveInterpreter(resource)
82+
: resource;
83+
return {
84+
execPath: interpreter ? interpreter.path : 'python',
85+
args: [path.join(_SCRIPTS_DIR, 'get-pip.py')],
86+
};
87+
}
88+
4189
const args: string[] = [];
4290
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
4391
const proxy = workspaceService.getConfiguration('http').get('proxy', '');

‎src/client/common/installer/productInstaller.ts‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,64 @@ export class DataScienceInstaller extends BaseInstaller {
418418
const interpreter = interpreterUri as PythonEnvironment;
419419

420420
// Get a list of known installation channels, pip, conda, etc.
421-
const channels: IModuleInstaller[] = await this.serviceContainer
421+
let channels: IModuleInstaller[] = await this.serviceContainer
422422
.get<IInstallationChannelManager>(IInstallationChannelManager)
423423
.getInstallationChannels(interpreter);
424424

425425
// Pick an installerModule based on whether the interpreter is conda or not. Default is pip.
426426
const moduleName = translateProductToModule(product);
427+
const version = `${interpreter.version?.major || ''}.${interpreter.version?.minor || ''}.${
428+
interpreter.version?.patch || ''
429+
}`;
430+
431+
// If this is a non-conda environment & pip isn't installed, we need to install pip.
432+
// The prompt would have been disabled prior to this point, so we can assume that.
433+
if (
434+
flags &&
435+
flags & ModuleInstallFlags.installPipIfRequired &&
436+
interpreter.envType !== EnvironmentType.Conda &&
437+
!channels.some((channel) => channel.type === ModuleInstallerType.Pip)
438+
) {
439+
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
440+
const pipInstaller = installers.find((installer) => installer.type === ModuleInstallerType.Pip);
441+
if (pipInstaller) {
442+
traceInfo(`Installing pip as its not available to install ${moduleName}.`);
443+
await pipInstaller
444+
.installModule(Product.pip, interpreter, cancel)
445+
.catch((ex) =>
446+
traceError(
447+
`Error in installing the module '${moduleName} as Pip could not be installed', ${ex}`,
448+
),
449+
);
450+
451+
await this.isInstalled(Product.pip, interpreter)
452+
.then((isInstalled) => {
453+
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
454+
installer: pipInstaller.displayName,
455+
requiredInstaller: ModuleInstallerType.Pip,
456+
version,
457+
envType: interpreter.envType,
458+
isInstalled,
459+
productName: ProductNames.get(Product.pip),
460+
});
461+
})
462+
.catch(noop);
463+
464+
// Refresh the list of channels (pip may be avaialble now).
465+
channels = await this.serviceContainer
466+
.get<IInstallationChannelManager>(IInstallationChannelManager)
467+
.getInstallationChannels(interpreter);
468+
} else {
469+
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
470+
installer: 'unavailable',
471+
requiredInstaller: ModuleInstallerType.Pip,
472+
productName: ProductNames.get(Product.pip),
473+
version,
474+
envType: interpreter.envType,
475+
});
476+
traceError(`Unable to install pip when its required.`);
477+
}
478+
}
427479

428480
const isAvailableThroughConda = !UnsupportedChannelsForProduct.get(product)?.has(EnvironmentType.Conda);
429481
let requiredInstaller = ModuleInstallerType.Unknown;
@@ -449,10 +501,6 @@ export class DataScienceInstaller extends BaseInstaller {
449501
}
450502
const installerModule: IModuleInstaller | undefined = channels.find((v) => v.type === requiredInstaller);
451503

452-
const version = `${interpreter.version?.major || ''}.${interpreter.version?.minor || ''}.${
453-
interpreter.version?.patch || ''
454-
}`;
455-
456504
if (!installerModule) {
457505
this.appShell.showErrorMessage(Installer.couldNotInstallLibrary().format(moduleName)).then(noop, noop);
458506
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
@@ -471,7 +519,7 @@ export class DataScienceInstaller extends BaseInstaller {
471519

472520
return this.isInstalled(product, interpreter).then((isInstalled) => {
473521
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
474-
installer: installerModule?.displayName || '',
522+
installer: installerModule.displayName || '',
475523
requiredInstaller,
476524
version,
477525
envType: interpreter.envType,

‎src/client/common/installer/productNames.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ ProductNames.set(Product.ipykernel, 'ipykernel');
2525
ProductNames.set(Product.nbconvert, 'nbconvert');
2626
ProductNames.set(Product.kernelspec, 'kernelspec');
2727
ProductNames.set(Product.pandas, 'pandas');
28+
ProductNames.set(Product.pip, 'pip');
29+
ProductNames.set(Product.ensurepip, 'ensurepip');

‎src/client/common/installer/productService.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class ProductService implements IProductService {
3434
this.ProductTypes.set(Product.tensorboard, ProductType.DataScience);
3535
this.ProductTypes.set(Product.torchProfilerInstallName, ProductType.DataScience);
3636
this.ProductTypes.set(Product.torchProfilerImportName, ProductType.DataScience);
37+
this.ProductTypes.set(Product.pip, ProductType.DataScience);
38+
this.ProductTypes.set(Product.ensurepip, ProductType.DataScience);
3739
}
3840
public getProductType(product: Product): ProductType {
3941
return this.ProductTypes.get(product)!;

‎src/client/common/installer/types.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,5 @@ export enum ModuleInstallFlags {
8282
upgrade = 1,
8383
updateDependencies = 2,
8484
reInstall = 4,
85+
installPipIfRequired = 8,
8586
}

‎src/client/common/types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export enum Product {
103103
tensorboard = 24,
104104
torchProfilerInstallName = 25,
105105
torchProfilerImportName = 26,
106+
pip = 27,
107+
ensurepip = 28,
106108
}
107109

108110
export const IInstaller = Symbol('IInstaller');

0 commit comments

Comments
 (0)