mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Add functionality to use an existing Python installation for Notebook dependencies (#5228)
This commit is contained in:
@@ -22,6 +22,11 @@
|
||||
"default": "",
|
||||
"description": "%notebook.pythonPath.description%"
|
||||
},
|
||||
"notebook.useExistingPython": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%notebook.useExistingPython.description%"
|
||||
},
|
||||
"notebook.overrideEditorTheming": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
|
||||
"notebook.configuration.title": "Notebook configuration",
|
||||
"notebook.pythonPath.description": "Local path to python installation used by Notebooks.",
|
||||
"notebook.useExistingPython.description": "Local path to a preexisting python installation used by Notebooks.",
|
||||
"notebook.overrideEditorTheming.description": "Override editor default settings in the Notebook editor. Settings include background color, current line color and border",
|
||||
"notebook.maxTableRows.description": "Maximum number of rows returned per table in the Notebook editor",
|
||||
"notebook.command.new": "New Notebook",
|
||||
|
||||
@@ -21,6 +21,7 @@ export const pyspark3kernel = 'pyspark3kernel';
|
||||
export const python3DisplayName = 'Python 3';
|
||||
export const defaultSparkKernel = 'pyspark3kernel';
|
||||
export const pythonPathConfigKey = 'pythonPath';
|
||||
export const existingPythonConfigKey = 'useExistingPython';
|
||||
export const notebookConfigKey = 'notebook';
|
||||
|
||||
export const outputChannelName = 'dataManagement';
|
||||
@@ -49,3 +50,8 @@ export enum BuiltInCommands {
|
||||
export enum CommandContext {
|
||||
WizardServiceEnabled = 'wizardservice:enabled'
|
||||
}
|
||||
|
||||
export const pythonOfflinePipPackagesUrl = 'https://go.microsoft.com/fwlink/?linkid=2092867';
|
||||
export const pythonWindowsInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092866';
|
||||
export const pythonMacInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092865';
|
||||
export const pythonLinuxInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092864';
|
||||
@@ -14,6 +14,6 @@ export const msgNo = localize('msgNo', 'No');
|
||||
|
||||
// Jupyter Constants ///////////////////////////////////////////////////////
|
||||
export const msgManagePackagesPowershell = localize('msgManagePackagesPowershell', '<#\n--------------------------------------------------------------------------------\n\tThis is the sandboxed instance of python used by Jupyter server.\n\tTo install packages used by the python kernel use \'.\\python.exe -m pip install\'\n--------------------------------------------------------------------------------\n#>');
|
||||
export const msgManagePackagesBash = localize('msgJupyterManagePackagesBash', ': \'\n--------------------------------------------------------------------------------\n\tThis is the sandboxed instance of python used by Jupyter server.\n\tTo install packages used by the python kernel use \'./python3.6 -m pip install\'\n--------------------------------------------------------------------------------\n\'');
|
||||
export const msgManagePackagesBash = localize('msgJupyterManagePackagesBash', ': \'\n--------------------------------------------------------------------------------\n\tThis is the sandboxed instance of python used by Jupyter server.\n\tTo install packages used by the python kernel use \'./python3 -m pip install\'\n--------------------------------------------------------------------------------\n\'');
|
||||
export const msgManagePackagesCmd = localize('msgJupyterManagePackagesCmd', 'REM This is the sandboxed instance of python used by Jupyter server. To install packages used by the python kernel use \'.\\python.exe -m pip install\'');
|
||||
export const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
||||
|
||||
@@ -54,13 +54,15 @@ export function executeBufferedCommand(cmd: string, options: childProcess.ExecOp
|
||||
});
|
||||
}
|
||||
|
||||
export function executeStreamedCommand(cmd: string, outputChannel?: vscode.OutputChannel): Thenable<void> {
|
||||
export function executeStreamedCommand(cmd: string, options: childProcess.SpawnOptions, outputChannel?: vscode.OutputChannel): Thenable<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Start the command
|
||||
if (outputChannel) {
|
||||
outputChannel.appendLine(` > ${cmd}`);
|
||||
}
|
||||
let child = childProcess.spawn(cmd, [], { shell: true, detached: false });
|
||||
options.shell = true;
|
||||
options.detached = false;
|
||||
let child = childProcess.spawn(cmd, [], options);
|
||||
|
||||
// Add listeners to resolve/reject the promise on exit
|
||||
child.on('error', reject);
|
||||
|
||||
@@ -18,17 +18,20 @@ const localize = nls.loadMessageBundle();
|
||||
export class ConfigurePythonDialog {
|
||||
private dialog: azdata.window.Dialog;
|
||||
|
||||
private readonly DialogTitle = localize('configurePython.dialogName', 'Configure Python for Notebooks');
|
||||
private readonly OkButtonText = localize('configurePython.okButtonText', 'Install');
|
||||
private readonly CancelButtonText = localize('configurePython.cancelButtonText', 'Cancel');
|
||||
private readonly BrowseButtonText = localize('configurePython.browseButtonText', 'Change location');
|
||||
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', 'Notebook dependencies will be installed in this location');
|
||||
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', 'Select');
|
||||
private readonly InstallationNote = localize('configurePython.installNote', 'This installation will take some time. It is recommended to not close the application until the installation is complete.');
|
||||
private readonly InvalidLocationMsg = localize('configurePython.invalidLocationMsg', 'The specified install location is invalid.');
|
||||
private readonly DialogTitle = localize('configurePython.dialogName', "Configure Python for Notebooks");
|
||||
private readonly InstallButtonText = localize('configurePython.okButtonText', "Install");
|
||||
private readonly CancelButtonText = localize('configurePython.cancelButtonText', "Cancel");
|
||||
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Change location");
|
||||
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
|
||||
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
|
||||
private readonly InstallationNote = localize('configurePython.installNote', "This installation will take some time. It is recommended to not close the application until the installation is complete.");
|
||||
private readonly InvalidLocationMsg = localize('configurePython.invalidLocationMsg', "The specified install location is invalid.");
|
||||
private readonly PythonNotFoundMsg = localize('configurePython.pythonNotFoundMsg', "No python installation was found at the specified location.");
|
||||
|
||||
private pythonLocationTextBox: azdata.InputBoxComponent;
|
||||
private browseButton: azdata.ButtonComponent;
|
||||
private newInstallButton: azdata.RadioButtonComponent;
|
||||
private existingInstallButton: azdata.RadioButtonComponent;
|
||||
|
||||
private _setupComplete: Deferred<void>;
|
||||
|
||||
@@ -46,11 +49,11 @@ export class ConfigurePythonDialog {
|
||||
|
||||
this.initializeContent();
|
||||
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.okButton.label = this.InstallButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
this.dialog.cancelButton.onClick(() => {
|
||||
if (rejectOnCancel) {
|
||||
this._setupComplete.reject(localize('pythonInstallDeclined', 'Python installation was declined.'));
|
||||
this._setupComplete.reject(localize('configurePython.pythonInstallDeclined', "Python installation was declined."));
|
||||
} else {
|
||||
this._setupComplete.resolve();
|
||||
}
|
||||
@@ -92,23 +95,54 @@ export class ConfigurePythonDialog {
|
||||
}
|
||||
});
|
||||
|
||||
this.createInstallRadioButtons(view.modelBuilder);
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.newInstallButton,
|
||||
title: localize('configurePython.installationType', "Installation Type")
|
||||
}, {
|
||||
component: this.existingInstallButton,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pythonLocationTextBox,
|
||||
title: this.LocationTextBoxTitle
|
||||
}, {
|
||||
component: this.browseButton,
|
||||
title: undefined
|
||||
title: ''
|
||||
}, {
|
||||
component: noteWrapper,
|
||||
title: undefined
|
||||
title: ''
|
||||
}]).component();
|
||||
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private createInstallRadioButtons(modelBuilder: azdata.ModelBuilder): void {
|
||||
let useExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
||||
let buttonGroup = 'installationType';
|
||||
this.newInstallButton = modelBuilder.radioButton()
|
||||
.withProperties<azdata.RadioButtonProperties>({
|
||||
name: buttonGroup,
|
||||
label: localize('configurePython.newInstall', "New Python installation"),
|
||||
checked: !useExistingPython
|
||||
}).component();
|
||||
this.newInstallButton.onDidClick(() => {
|
||||
this.existingInstallButton.checked = false;
|
||||
});
|
||||
|
||||
this.existingInstallButton = modelBuilder.radioButton()
|
||||
.withProperties<azdata.RadioButtonProperties>({
|
||||
name: buttonGroup,
|
||||
label: localize('configurePython.existingInstall', "Use existing Python installation"),
|
||||
checked: useExistingPython
|
||||
}).component();
|
||||
this.existingInstallButton.onDidClick(() => {
|
||||
this.newInstallButton.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
private async handleInstall(): Promise<boolean> {
|
||||
let pythonLocation = this.pythonLocationTextBox.value;
|
||||
if (!pythonLocation || pythonLocation.length === 0) {
|
||||
@@ -116,24 +150,35 @@ export class ConfigurePythonDialog {
|
||||
return false;
|
||||
}
|
||||
|
||||
let useExistingPython = !!this.existingInstallButton.checked;
|
||||
try {
|
||||
let isValid = await this.isFileValid(pythonLocation);
|
||||
if (!isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useExistingPython) {
|
||||
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
|
||||
let pythonExists = fs.existsSync(exePath);
|
||||
if (!pythonExists) {
|
||||
this.showErrorMessage(this.PythonNotFoundMsg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
this.showErrorMessage(utils.getErrorMessage(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't wait on installation, since there's currently no Cancel functionality
|
||||
this.jupyterInstallation.startInstallProcess(false, pythonLocation)
|
||||
this.jupyterInstallation.startInstallProcess(false, { installPath: pythonLocation, existingPython: useExistingPython })
|
||||
.then(() => {
|
||||
this._setupComplete.resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
this._setupComplete.reject(utils.getErrorMessage(err));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterController } from '../jupyter/jupyterController';
|
||||
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
||||
import { pythonBundleVersion } from '../common/constants';
|
||||
import { executeStreamedCommand } from '../common/utils';
|
||||
|
||||
describe('Notebook Extension Python Installation', function () {
|
||||
this.timeout(600000);
|
||||
@@ -35,7 +38,7 @@ describe('Notebook Extension Python Installation', function () {
|
||||
jupyterController = notebookExtension.exports.getJupyterController() as JupyterController;
|
||||
|
||||
console.log('Start Jupyter Installation');
|
||||
await jupyterController.jupyterInstallation.startInstallProcess(false, pythonInstallDir);
|
||||
await jupyterController.jupyterInstallation.startInstallProcess(false, { installPath: pythonInstallDir, existingPython: false });
|
||||
installComplete = true;
|
||||
console.log('Jupyter Installation is done');
|
||||
});
|
||||
@@ -44,6 +47,22 @@ describe('Notebook Extension Python Installation', function () {
|
||||
should(installComplete).be.true('Python setup did not complete.');
|
||||
let jupyterPath = JupyterServerInstallation.getPythonInstallPath(jupyterController.jupyterInstallation.apiWrapper);
|
||||
console.log(`Expected python path: '${pythonInstallDir}'; actual: '${jupyterPath}'`);
|
||||
should(JupyterServerInstallation.getPythonInstallPath(jupyterController.jupyterInstallation.apiWrapper)).be.equal(pythonInstallDir);
|
||||
should(jupyterPath).be.equal(pythonInstallDir);
|
||||
});
|
||||
|
||||
it('Use Existing Python Installation', async function () {
|
||||
should(installComplete).be.true('Python setup did not complete.');
|
||||
|
||||
console.log('Uninstalling existing pip dependencies');
|
||||
let install = jupyterController.jupyterInstallation;
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(pythonInstallDir, false);
|
||||
let command = `"${pythonExe}" -m pip uninstall -y jupyter pandas sparkmagic prose-codeaccelerator`;
|
||||
await executeStreamedCommand(command, { env: install.execOptions.env }, install.outputChannel);
|
||||
console.log('Uninstalling existing pip dependencies is done');
|
||||
|
||||
console.log('Start Existing Python Installation');
|
||||
let existingPythonPath = path.join(pythonInstallDir, pythonBundleVersion);
|
||||
await install.startInstallProcess(false, { installPath: existingPythonPath, existingPython: true });
|
||||
console.log('Existing Python Installation is done');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,10 +36,6 @@ function msgDependenciesInstallationFailed(errorMessage: string): string { retur
|
||||
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); }
|
||||
|
||||
export default class JupyterServerInstallation {
|
||||
/**
|
||||
* Path to the folder where all configuration sets will be stored. Should always be:
|
||||
* %extension_path%/jupyter_config
|
||||
*/
|
||||
public apiWrapper: ApiWrapper;
|
||||
public extensionPath: string;
|
||||
public pythonBinPath: string;
|
||||
@@ -50,6 +46,8 @@ export default class JupyterServerInstallation {
|
||||
private _pythonInstallationPath: string;
|
||||
private _pythonExecutable: string;
|
||||
private _pythonPackageDir: string;
|
||||
private _usingExistingPython: boolean;
|
||||
private _usingConda: boolean;
|
||||
|
||||
// Allows dependencies to be installed even if an existing installation is already present
|
||||
private _forceInstall: boolean;
|
||||
@@ -63,14 +61,17 @@ export default class JupyterServerInstallation {
|
||||
this.apiWrapper = apiWrapper;
|
||||
this._pythonInstallationPath = pythonInstallationPath || JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
||||
this._forceInstall = false;
|
||||
this._usingConda = false;
|
||||
this._installInProgress = false;
|
||||
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
||||
|
||||
this.configurePackagePaths();
|
||||
}
|
||||
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall) {
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
|
||||
window.showInformationMessage(msgInstallPkgStart);
|
||||
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(msgInstallPkgProgress);
|
||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgInstallPkgProgress);
|
||||
@@ -79,9 +80,21 @@ export default class JupyterServerInstallation {
|
||||
this.outputChannel.appendLine(msgPythonDownloadComplete);
|
||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadComplete);
|
||||
|
||||
// Install jupyter on Windows because local python is not bundled with jupyter unlike linux and MacOS.
|
||||
await this.installJupyterProsePackage();
|
||||
await this.installSparkMagic();
|
||||
if (this._usingConda) {
|
||||
await this.installCondaPackages();
|
||||
} else if (this._usingExistingPython) {
|
||||
await this.installPipPackages();
|
||||
} else {
|
||||
await this.installOfflinePipPackages();
|
||||
}
|
||||
let doOnlineInstall = this._usingExistingPython;
|
||||
await this.installSparkMagic(doOnlineInstall);
|
||||
|
||||
fs.remove(this._pythonPackageDir, (err: Error) => {
|
||||
if (err) {
|
||||
this.outputChannel.appendLine(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.outputChannel.appendLine(msgInstallPkgFinish);
|
||||
backgroundOperation.updateStatus(azdata.TaskStatus.Succeeded, msgInstallPkgFinish);
|
||||
@@ -92,26 +105,28 @@ export default class JupyterServerInstallation {
|
||||
private installPythonPackage(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
|
||||
let bundleVersion = constants.pythonBundleVersion;
|
||||
let pythonVersion = constants.pythonVersion;
|
||||
let packageName = 'python-#pythonversion-#platform-#bundleversion.#extension';
|
||||
let platformId = utils.getOSPlatformId();
|
||||
let packageName: string;
|
||||
let pythonDownloadUrl: string;
|
||||
if (this._usingExistingPython) {
|
||||
packageName = `python-${pythonVersion}-${bundleVersion}-offlinePackages.zip`;
|
||||
pythonDownloadUrl = constants.pythonOfflinePipPackagesUrl;
|
||||
} else {
|
||||
let extension = process.platform === constants.winPlatform ? 'zip' : 'tar.gz';
|
||||
packageName = `python-${pythonVersion}-${platformId}-${bundleVersion}.${extension}`;
|
||||
|
||||
packageName = packageName.replace('#platform', platformId)
|
||||
.replace('#pythonversion', pythonVersion)
|
||||
.replace('#bundleversion', bundleVersion)
|
||||
.replace('#extension', process.platform === constants.winPlatform ? 'zip' : 'tar.gz');
|
||||
|
||||
let pythonDownloadUrl: string = undefined;
|
||||
switch (utils.getOSPlatform()) {
|
||||
case utils.Platform.Windows:
|
||||
pythonDownloadUrl = 'https://go.microsoft.com/fwlink/?linkid=2074021';
|
||||
break;
|
||||
case utils.Platform.Mac:
|
||||
pythonDownloadUrl = 'https://go.microsoft.com/fwlink/?linkid=2065976';
|
||||
break;
|
||||
default:
|
||||
// Default to linux
|
||||
pythonDownloadUrl = 'https://go.microsoft.com/fwlink/?linkid=2065975';
|
||||
break;
|
||||
switch (utils.getOSPlatform()) {
|
||||
case utils.Platform.Windows:
|
||||
pythonDownloadUrl = constants.pythonWindowsInstallUrl;
|
||||
break;
|
||||
case utils.Platform.Mac:
|
||||
pythonDownloadUrl = constants.pythonMacInstallUrl;
|
||||
break;
|
||||
default:
|
||||
// Default to linux
|
||||
pythonDownloadUrl = constants.pythonLinuxInstallUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let pythonPackagePathLocal = this._pythonInstallationPath + '/' + packageName;
|
||||
@@ -190,25 +205,39 @@ export default class JupyterServerInstallation {
|
||||
|
||||
private configurePackagePaths(): void {
|
||||
//Python source path up to bundle version
|
||||
let pythonSourcePath = path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
|
||||
let pythonSourcePath = this._usingExistingPython
|
||||
? this._pythonInstallationPath
|
||||
: path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
|
||||
|
||||
this._pythonPackageDir = path.join(pythonSourcePath, 'offlinePackages');
|
||||
|
||||
// Update python paths and properties to reference user's local python.
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath);
|
||||
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath, this._usingExistingPython);
|
||||
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
|
||||
|
||||
this._usingConda = this.checkCondaExists();
|
||||
|
||||
// Store paths to python libraries required to run jupyter.
|
||||
this.pythonEnvVarPath = process.env['PATH'];
|
||||
|
||||
let delimiter = path.delimiter;
|
||||
this.pythonEnvVarPath = this.pythonBinPath + delimiter + this.pythonEnvVarPath;
|
||||
if (process.platform === constants.winPlatform) {
|
||||
let pythonScriptsPath = path.join(pythonSourcePath, 'Scripts');
|
||||
this.pythonEnvVarPath = pythonScriptsPath + delimiter + this.pythonEnvVarPath;
|
||||
|
||||
if (this._usingConda) {
|
||||
this.pythonEnvVarPath = [
|
||||
path.join(pythonSourcePath, 'Library', 'mingw-w64', 'bin'),
|
||||
path.join(pythonSourcePath, 'Library', 'usr', 'bin'),
|
||||
path.join(pythonSourcePath, 'Library', 'bin'),
|
||||
path.join(pythonSourcePath, 'condabin'),
|
||||
this.pythonEnvVarPath
|
||||
].join(delimiter);
|
||||
}
|
||||
}
|
||||
this.pythonEnvVarPath = this.pythonBinPath + delimiter + this.pythonEnvVarPath;
|
||||
|
||||
// Delete existing Python variables in ADS to prevent conflict with other installs
|
||||
delete process.env['PYTHONPATH'];
|
||||
@@ -225,7 +254,7 @@ export default class JupyterServerInstallation {
|
||||
}
|
||||
|
||||
private isPythonRunning(pythonInstallPath: string): Promise<boolean> {
|
||||
let pythonExePath = JupyterServerInstallation.getPythonExePath(pythonInstallPath);
|
||||
let pythonExePath = JupyterServerInstallation.getPythonExePath(pythonInstallPath, this._usingExistingPython);
|
||||
return new Promise<boolean>(resolve => {
|
||||
fs.open(pythonExePath, 'r+', (err, fd) => {
|
||||
if (!err) {
|
||||
@@ -246,8 +275,8 @@ export default class JupyterServerInstallation {
|
||||
* @param installationPath Optional parameter that specifies where to install python.
|
||||
* The previous path (or the default) is used if a new path is not specified.
|
||||
*/
|
||||
public async startInstallProcess(forceInstall: boolean, installationPath?: string): Promise<void> {
|
||||
let isPythonRunning = await this.isPythonRunning(installationPath ? installationPath : this._pythonInstallationPath);
|
||||
public async startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void> {
|
||||
let isPythonRunning = await this.isPythonRunning(installSettings ? installSettings.installPath : this._pythonInstallationPath);
|
||||
if (isPythonRunning) {
|
||||
return Promise.reject(msgPythonRunningError);
|
||||
}
|
||||
@@ -258,18 +287,19 @@ export default class JupyterServerInstallation {
|
||||
this._installInProgress = true;
|
||||
|
||||
this._forceInstall = forceInstall;
|
||||
if (installationPath) {
|
||||
this._pythonInstallationPath = installationPath;
|
||||
if (installSettings) {
|
||||
this._pythonInstallationPath = installSettings.installPath;
|
||||
this._usingExistingPython = installSettings.existingPython;
|
||||
}
|
||||
this.configurePackagePaths();
|
||||
|
||||
let updateConfig = async () => {
|
||||
let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
||||
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, ConfigurationTarget.Global);
|
||||
};
|
||||
|
||||
let installReady = new Deferred<void>();
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall) {
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
|
||||
this.apiWrapper.startBackgroundOperation({
|
||||
displayName: msgTaskName,
|
||||
description: msgTaskName,
|
||||
@@ -311,35 +341,92 @@ export default class JupyterServerInstallation {
|
||||
}
|
||||
}
|
||||
|
||||
private async installJupyterProsePackage(): Promise<void> {
|
||||
private async installOfflinePipPackages(): Promise<void> {
|
||||
let installJupyterCommand: string;
|
||||
if (process.platform === constants.winPlatform) {
|
||||
let requirements = path.join(this._pythonPackageDir, 'requirements.txt');
|
||||
let installJupyterCommand = `"${this._pythonExecutable}" -m pip install --no-index -r "${requirements}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
installJupyterCommand = `"${this._pythonExecutable}" -m pip install --no-index -r "${requirements}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
}
|
||||
|
||||
if (installJupyterCommand) {
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||
await utils.executeStreamedCommand(installJupyterCommand, this.outputChannel);
|
||||
await this.executeCommand(installJupyterCommand);
|
||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
private async installSparkMagic(): Promise<void> {
|
||||
if (process.platform === constants.winPlatform) {
|
||||
private async installSparkMagic(doOnlineInstall: boolean): Promise<void> {
|
||||
let installSparkMagic: string;
|
||||
if (process.platform === constants.winPlatform || this._usingExistingPython) {
|
||||
let sparkWheel = path.join(this._pythonPackageDir, `sparkmagic-${constants.sparkMagicVersion}-py3-none-any.whl`);
|
||||
let installSparkMagic = `"${this._pythonExecutable}" -m pip install --no-index "${sparkWheel}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
if (doOnlineInstall) {
|
||||
installSparkMagic = `"${this._pythonExecutable}" -m pip install "${sparkWheel}" --no-warn-script-location`;
|
||||
} else {
|
||||
installSparkMagic = `"${this._pythonExecutable}" -m pip install --no-index "${sparkWheel}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
}
|
||||
}
|
||||
|
||||
if (installSparkMagic) {
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(localize('msgInstallingSpark', "Installing SparkMagic..."));
|
||||
await utils.executeStreamedCommand(installSparkMagic, this.outputChannel);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
await this.executeCommand(installSparkMagic);
|
||||
}
|
||||
}
|
||||
|
||||
private async installPipPackages(): Promise<void> {
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||
|
||||
let installCommand = `"${this._pythonExecutable}" -m pip install jupyter==1.0.0 pandas==0.24.2`;
|
||||
await this.executeCommand(installCommand);
|
||||
|
||||
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
||||
await this.executeCommand(installCommand);
|
||||
|
||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||
}
|
||||
|
||||
private async installCondaPackages(): Promise<void> {
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||
|
||||
let installCommand = `"${this.getCondaExePath()}" install -y jupyter==1.0.0 pandas==0.24.2`;
|
||||
if (process.platform !== constants.winPlatform) {
|
||||
installCommand = `${installCommand} pykerberos==1.2.1`;
|
||||
}
|
||||
await this.executeCommand(installCommand);
|
||||
|
||||
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
||||
await this.executeCommand(installCommand);
|
||||
|
||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||
}
|
||||
|
||||
private async executeCommand(command: string): Promise<void> {
|
||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||
}
|
||||
|
||||
public get pythonExecutable(): string {
|
||||
return this._pythonExecutable;
|
||||
}
|
||||
|
||||
private getCondaExePath(): string {
|
||||
return path.join(this._pythonInstallationPath,
|
||||
process.platform === constants.winPlatform ? 'Scripts\\conda.exe' : 'bin/conda');
|
||||
}
|
||||
|
||||
private checkCondaExists(): boolean {
|
||||
if (!this._usingExistingPython) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let condaExePath = this.getCondaExePath();
|
||||
return fs.existsSync(condaExePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a python executable exists at the "notebook.pythonPath" defined in the user's settings.
|
||||
* @param apiWrapper An ApiWrapper to use when retrieving user settings info.
|
||||
@@ -351,7 +438,8 @@ export default class JupyterServerInstallation {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting);
|
||||
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall);
|
||||
return fs.existsSync(pythonExe);
|
||||
}
|
||||
|
||||
@@ -365,6 +453,17 @@ export default class JupyterServerInstallation {
|
||||
return userPath ? userPath : JupyterServerInstallation.DefaultPythonLocation;
|
||||
}
|
||||
|
||||
public static getExistingPythonSetting(apiWrapper: ApiWrapper): boolean {
|
||||
let useExistingPython = false;
|
||||
if (apiWrapper) {
|
||||
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||
if (notebookConfig) {
|
||||
useExistingPython = !!notebookConfig[constants.existingPythonConfigKey];
|
||||
}
|
||||
}
|
||||
return useExistingPython;
|
||||
}
|
||||
|
||||
private static getPythonPathSetting(apiWrapper: ApiWrapper): string {
|
||||
let path = undefined;
|
||||
if (apiWrapper) {
|
||||
@@ -387,16 +486,18 @@ export default class JupyterServerInstallation {
|
||||
public static getPythonBinPath(apiWrapper: ApiWrapper): string {
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
|
||||
|
||||
return path.join(
|
||||
JupyterServerInstallation.getPythonInstallPath(apiWrapper),
|
||||
constants.pythonBundleVersion,
|
||||
useExistingInstall ? '' : constants.pythonBundleVersion,
|
||||
pythonBinPathSuffix);
|
||||
}
|
||||
|
||||
private static getPythonExePath(pythonInstallPath: string): string {
|
||||
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
||||
return path.join(
|
||||
pythonInstallPath,
|
||||
constants.pythonBundleVersion,
|
||||
useExistingInstall ? '' : constants.pythonBundleVersion,
|
||||
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user