Add functionality to use an existing Python installation for Notebook dependencies (#5228)

This commit is contained in:
Cory Rivera
2019-05-17 14:39:44 -07:00
committed by GitHub
parent 1fce604a11
commit a59d1d3c05
8 changed files with 248 additions and 69 deletions

View File

@@ -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,

View File

@@ -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",

View File

@@ -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';

View File

@@ -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.');

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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');
});
});

View File

@@ -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');
}
}