mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Automatically detect existing Python/Conda installs in Configure Python dialog. (#5801)
This commit is contained in:
@@ -336,6 +336,7 @@
|
|||||||
"error-ex": "^1.3.1",
|
"error-ex": "^1.3.1",
|
||||||
"figures": "^2.0.0",
|
"figures": "^2.0.0",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
|
"glob": "^7.1.1",
|
||||||
"node-fetch": "^2.3.0",
|
"node-fetch": "^2.3.0",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"temp-write": "^3.4.0",
|
"temp-write": "^3.4.0",
|
||||||
@@ -345,6 +346,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/decompress": "^4.2.3",
|
"@types/decompress": "^4.2.3",
|
||||||
"@types/fs-extra": "^5.0.0",
|
"@types/fs-extra": "^5.0.0",
|
||||||
|
"@types/glob": "^7.1.1",
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.5",
|
||||||
"@types/node": "^11.9.3",
|
"@types/node": "^11.9.3",
|
||||||
"@types/request": "^2.48.1",
|
"@types/request": "^2.48.1",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import * as utils from '../common/utils';
|
|||||||
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
||||||
import { ApiWrapper } from '../common/apiWrapper';
|
import { ApiWrapper } from '../common/apiWrapper';
|
||||||
import { Deferred } from '../common/promise';
|
import { Deferred } from '../common/promise';
|
||||||
|
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -21,22 +22,27 @@ export class ConfigurePythonDialog {
|
|||||||
private readonly DialogTitle = localize('configurePython.dialogName', "Configure Python for Notebooks");
|
private readonly DialogTitle = localize('configurePython.dialogName', "Configure Python for Notebooks");
|
||||||
private readonly InstallButtonText = localize('configurePython.okButtonText', "Install");
|
private readonly InstallButtonText = localize('configurePython.okButtonText', "Install");
|
||||||
private readonly CancelButtonText = localize('configurePython.cancelButtonText', "Cancel");
|
private readonly CancelButtonText = localize('configurePython.cancelButtonText', "Cancel");
|
||||||
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Change location");
|
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Browse");
|
||||||
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
|
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
|
||||||
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
|
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 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 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 readonly PythonNotFoundMsg = localize('configurePython.pythonNotFoundMsg', "No python installation was found at the specified location.");
|
||||||
|
|
||||||
private pythonLocationTextBox: azdata.InputBoxComponent;
|
private pythonLocationDropdown: azdata.DropDownComponent;
|
||||||
|
private pythonDropdownLoader: azdata.LoadingComponent;
|
||||||
private browseButton: azdata.ButtonComponent;
|
private browseButton: azdata.ButtonComponent;
|
||||||
private newInstallButton: azdata.RadioButtonComponent;
|
private newInstallButton: azdata.RadioButtonComponent;
|
||||||
private existingInstallButton: azdata.RadioButtonComponent;
|
private existingInstallButton: azdata.RadioButtonComponent;
|
||||||
|
|
||||||
private _setupComplete: Deferred<void>;
|
private setupComplete: Deferred<void>;
|
||||||
|
private pythonPathsPromise: Promise<PythonPathInfo[]>;
|
||||||
|
private usingCustomPath: boolean;
|
||||||
|
|
||||||
constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) {
|
constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) {
|
||||||
this._setupComplete = new Deferred<void>();
|
this.setupComplete = new Deferred<void>();
|
||||||
|
this.pythonPathsPromise = (new PythonPathLookup()).getSuggestions();
|
||||||
|
this.usingCustomPath = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,9 +59,9 @@ export class ConfigurePythonDialog {
|
|||||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||||
this.dialog.cancelButton.onClick(() => {
|
this.dialog.cancelButton.onClick(() => {
|
||||||
if (rejectOnCancel) {
|
if (rejectOnCancel) {
|
||||||
this._setupComplete.reject(localize('configurePython.pythonInstallDeclined', "Python installation was declined."));
|
this.setupComplete.reject(localize('configurePython.pythonInstallDeclined', "Python installation was declined."));
|
||||||
} else {
|
} else {
|
||||||
this._setupComplete.resolve();
|
this.setupComplete.resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,21 +69,28 @@ export class ConfigurePythonDialog {
|
|||||||
|
|
||||||
azdata.window.openDialog(this.dialog);
|
azdata.window.openDialog(this.dialog);
|
||||||
|
|
||||||
return this._setupComplete.promise;
|
return this.setupComplete.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeContent(): void {
|
private initializeContent(): void {
|
||||||
this.dialog.registerContent(async view => {
|
this.dialog.registerContent(async view => {
|
||||||
this.pythonLocationTextBox = view.modelBuilder.inputBox()
|
this.pythonLocationDropdown = view.modelBuilder.dropDown()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProperties<azdata.DropDownProperties>({
|
||||||
value: JupyterServerInstallation.getPythonInstallPath(this.apiWrapper),
|
value: undefined,
|
||||||
|
values: [],
|
||||||
width: '100%'
|
width: '100%'
|
||||||
}).component();
|
}).component();
|
||||||
|
this.pythonDropdownLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.pythonLocationDropdown)
|
||||||
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
.component();
|
||||||
|
|
||||||
this.browseButton = view.modelBuilder.button()
|
this.browseButton = view.modelBuilder.button()
|
||||||
.withProperties<azdata.ButtonProperties>({
|
.withProperties<azdata.ButtonProperties>({
|
||||||
label: this.BrowseButtonText,
|
label: this.BrowseButtonText,
|
||||||
width: '100px'
|
width: '70px'
|
||||||
}).component();
|
}).component();
|
||||||
this.browseButton.onDidClick(() => this.handleBrowse());
|
this.browseButton.onDidClick(() => this.handleBrowse());
|
||||||
|
|
||||||
@@ -95,7 +108,8 @@ export class ConfigurePythonDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.createInstallRadioButtons(view.modelBuilder);
|
let useExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
||||||
|
this.createInstallRadioButtons(view.modelBuilder, useExistingPython);
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
@@ -105,7 +119,7 @@ export class ConfigurePythonDialog {
|
|||||||
component: this.existingInstallButton,
|
component: this.existingInstallButton,
|
||||||
title: ''
|
title: ''
|
||||||
}, {
|
}, {
|
||||||
component: this.pythonLocationTextBox,
|
component: this.pythonDropdownLoader,
|
||||||
title: this.LocationTextBoxTitle
|
title: this.LocationTextBoxTitle
|
||||||
}, {
|
}, {
|
||||||
component: this.browseButton,
|
component: this.browseButton,
|
||||||
@@ -116,11 +130,50 @@ export class ConfigurePythonDialog {
|
|||||||
}]).component();
|
}]).component();
|
||||||
|
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
|
|
||||||
|
await this.updatePythonPathsDropdown(useExistingPython);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createInstallRadioButtons(modelBuilder: azdata.ModelBuilder): void {
|
private async updatePythonPathsDropdown(useExistingPython: boolean): Promise<void> {
|
||||||
let useExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
await this.pythonDropdownLoader.updateProperties({ loading: true });
|
||||||
|
try {
|
||||||
|
let pythonPaths: PythonPathInfo[];
|
||||||
|
let dropdownValues: azdata.CategoryValue[];
|
||||||
|
if (useExistingPython) {
|
||||||
|
pythonPaths = await this.pythonPathsPromise;
|
||||||
|
if (pythonPaths && pythonPaths.length > 0) {
|
||||||
|
dropdownValues = pythonPaths.map(path => {
|
||||||
|
return {
|
||||||
|
displayName: `${path.installDir} (Python ${path.version})`,
|
||||||
|
name: path.installDir
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dropdownValues = [{
|
||||||
|
displayName: 'No supported Python versions found.',
|
||||||
|
name: ''
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let defaultPath = JupyterServerInstallation.DefaultPythonLocation;
|
||||||
|
dropdownValues = [{
|
||||||
|
displayName: `${defaultPath} (Default)`,
|
||||||
|
name: defaultPath
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.usingCustomPath = false;
|
||||||
|
await this.pythonLocationDropdown.updateProperties({
|
||||||
|
value: dropdownValues[0],
|
||||||
|
values: dropdownValues
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await this.pythonDropdownLoader.updateProperties({ loading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInstallRadioButtons(modelBuilder: azdata.ModelBuilder, useExistingPython: boolean): void {
|
||||||
let buttonGroup = 'installationType';
|
let buttonGroup = 'installationType';
|
||||||
this.newInstallButton = modelBuilder.radioButton()
|
this.newInstallButton = modelBuilder.radioButton()
|
||||||
.withProperties<azdata.RadioButtonProperties>({
|
.withProperties<azdata.RadioButtonProperties>({
|
||||||
@@ -130,6 +183,10 @@ export class ConfigurePythonDialog {
|
|||||||
}).component();
|
}).component();
|
||||||
this.newInstallButton.onDidClick(() => {
|
this.newInstallButton.onDidClick(() => {
|
||||||
this.existingInstallButton.checked = false;
|
this.existingInstallButton.checked = false;
|
||||||
|
this.updatePythonPathsDropdown(false)
|
||||||
|
.catch(err => {
|
||||||
|
this.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.existingInstallButton = modelBuilder.radioButton()
|
this.existingInstallButton = modelBuilder.radioButton()
|
||||||
@@ -140,11 +197,15 @@ export class ConfigurePythonDialog {
|
|||||||
}).component();
|
}).component();
|
||||||
this.existingInstallButton.onDidClick(() => {
|
this.existingInstallButton.onDidClick(() => {
|
||||||
this.newInstallButton.checked = false;
|
this.newInstallButton.checked = false;
|
||||||
|
this.updatePythonPathsDropdown(true)
|
||||||
|
.catch(err => {
|
||||||
|
this.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleInstall(): Promise<boolean> {
|
private async handleInstall(): Promise<boolean> {
|
||||||
let pythonLocation = this.pythonLocationTextBox.value;
|
let pythonLocation = (this.pythonLocationDropdown.value as azdata.CategoryValue).name;
|
||||||
if (!pythonLocation || pythonLocation.length === 0) {
|
if (!pythonLocation || pythonLocation.length === 0) {
|
||||||
this.showErrorMessage(this.InvalidLocationMsg);
|
this.showErrorMessage(this.InvalidLocationMsg);
|
||||||
return false;
|
return false;
|
||||||
@@ -173,10 +234,10 @@ export class ConfigurePythonDialog {
|
|||||||
// Don't wait on installation, since there's currently no Cancel functionality
|
// Don't wait on installation, since there's currently no Cancel functionality
|
||||||
this.jupyterInstallation.startInstallProcess(false, { installPath: pythonLocation, existingPython: useExistingPython })
|
this.jupyterInstallation.startInstallProcess(false, { installPath: pythonLocation, existingPython: useExistingPython })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._setupComplete.resolve();
|
this.setupComplete.resolve();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this._setupComplete.reject(utils.getErrorMessage(err));
|
this.setupComplete.reject(utils.getErrorMessage(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -216,7 +277,24 @@ export class ConfigurePythonDialog {
|
|||||||
|
|
||||||
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
|
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
|
||||||
if (fileUris && fileUris[0]) {
|
if (fileUris && fileUris[0]) {
|
||||||
this.pythonLocationTextBox.value = fileUris[0].fsPath;
|
let existingValues = <azdata.CategoryValue[]>this.pythonLocationDropdown.values;
|
||||||
|
let filePath = fileUris[0].fsPath;
|
||||||
|
let newValue = {
|
||||||
|
displayName: `${filePath} (Custom)`,
|
||||||
|
name: filePath
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.usingCustomPath) {
|
||||||
|
existingValues[0] = newValue;
|
||||||
|
} else {
|
||||||
|
existingValues.unshift(newValue);
|
||||||
|
this.usingCustomPath = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.pythonLocationDropdown.updateProperties({
|
||||||
|
value: existingValues[0],
|
||||||
|
values: existingValues
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
166
extensions/notebook/src/dialog/pythonPathLookup.ts
Normal file
166
extensions/notebook/src/dialog/pythonPathLookup.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as glob from 'glob';
|
||||||
|
|
||||||
|
import * as utils from '../common/utils';
|
||||||
|
import * as constants from '../common/constants';
|
||||||
|
|
||||||
|
export interface PythonPathInfo {
|
||||||
|
installDir: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PythonPathLookup {
|
||||||
|
private condaLocations: string[];
|
||||||
|
constructor() {
|
||||||
|
if (process.platform !== constants.winPlatform) {
|
||||||
|
let userFolder = process.env['HOME'];
|
||||||
|
this.condaLocations = [
|
||||||
|
'/opt/*conda*/bin/python3',
|
||||||
|
'/usr/share/*conda*/bin/python3',
|
||||||
|
`${userFolder}/*conda*/bin/python3`
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
let userFolder = process.env['USERPROFILE'].replace('\\', '/').replace('C:', '');
|
||||||
|
this.condaLocations = [
|
||||||
|
'/ProgramData/[Mm]iniconda*/python.exe',
|
||||||
|
'/ProgramData/[Aa]naconda*/python.exe',
|
||||||
|
`${userFolder}/[Mm]iniconda*/python.exe`,
|
||||||
|
`${userFolder}/[Aa]naconda*/python.exe`,
|
||||||
|
`${userFolder}/AppData/Local/Continuum/[Mm]iniconda*/python.exe`,
|
||||||
|
`${userFolder}/AppData/Local/Continuum/[Aa]naconda*/python.exe`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSuggestions(): Promise<PythonPathInfo[]> {
|
||||||
|
let pythonSuggestions = await this.getPythonSuggestions();
|
||||||
|
let condaSuggestions = await this.getCondaSuggestions();
|
||||||
|
|
||||||
|
if (pythonSuggestions) {
|
||||||
|
if (condaSuggestions && condaSuggestions.length > 0) {
|
||||||
|
pythonSuggestions = pythonSuggestions.concat(condaSuggestions);
|
||||||
|
}
|
||||||
|
return this.getInfoForPaths(pythonSuggestions);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCondaSuggestions(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
let condaResults = await Promise.all(this.condaLocations.map(location => this.globSearch(location)));
|
||||||
|
let condaFiles = condaResults.reduce((first, second) => first.concat(second));
|
||||||
|
return condaFiles.filter(condaPath => condaPath && condaPath.length > 0);
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private globSearch(globPattern: string): Promise<string[]> {
|
||||||
|
return new Promise<string[]>((resolve, reject) => {
|
||||||
|
glob(globPattern, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(Array.isArray(files) ? files : []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPythonSuggestions(): Promise<string[]> {
|
||||||
|
let pathsToCheck = this.getPythonCommands();
|
||||||
|
|
||||||
|
let pythonPaths = await Promise.all(pathsToCheck.map(item => this.getPythonPath(item)));
|
||||||
|
let results: string[];
|
||||||
|
if (pythonPaths) {
|
||||||
|
results = pythonPaths.filter(path => path && path.length > 0);
|
||||||
|
} else {
|
||||||
|
results = [];
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPythonPath(options: { command: string; args?: string[] }): Promise<string> {
|
||||||
|
try {
|
||||||
|
let args = Array.isArray(options.args) ? options.args : [];
|
||||||
|
args = args.concat(['-c', '"import sys;print(sys.executable)"']);
|
||||||
|
const cmd = `"${options.command}" ${args.join(' ')}`;
|
||||||
|
let output = await utils.executeBufferedCommand(cmd, {});
|
||||||
|
let value = output ? output.trim() : '';
|
||||||
|
if (value.length > 0 && fs.existsSync(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore errors here, since this python version will just be excluded.
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPythonCommands(): { command: string; args?: string[] }[] {
|
||||||
|
const paths = ['python3.7', 'python3.6', 'python3', 'python']
|
||||||
|
.map(item => { return { command: item }; });
|
||||||
|
if (process.platform !== constants.winPlatform) {
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
const versions = ['3.7', '3.6', '3'];
|
||||||
|
return paths.concat(versions.map(version => {
|
||||||
|
return { command: 'py', args: [`-${version}`] };
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getInfoForPaths(pythonPaths: string[]): Promise<PythonPathInfo[]> {
|
||||||
|
let pathsInfo = await Promise.all(pythonPaths.map(path => this.getInfoForPath(path)));
|
||||||
|
|
||||||
|
// Remove duplicate paths, and entries with missing values
|
||||||
|
let pathSet = new Set<string>();
|
||||||
|
return pathsInfo.filter(path => {
|
||||||
|
if (!path || !path.installDir || !path.version || path.installDir.length === 0 || path.version.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let majorVersion = Number.parseInt(path.version.substring(0, path.version.indexOf('.')));
|
||||||
|
if (Number.isNaN(majorVersion) || majorVersion < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = `${path.installDir} ${path.version}`;
|
||||||
|
if (pathSet.has(key)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
pathSet.add(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getInfoForPath(pythonPath: string): Promise<PythonPathInfo> {
|
||||||
|
try {
|
||||||
|
// "python --version" returns nothing from executeBufferedCommand with Python 2.X,
|
||||||
|
// so use sys.version_info here instead.
|
||||||
|
let cmd = `"${pythonPath}" -c "import sys;print('.'.join(str(i) for i in sys.version_info[:3]))"`;
|
||||||
|
let output = await utils.executeBufferedCommand(cmd, {});
|
||||||
|
let pythonVersion = output ? output.trim() : '';
|
||||||
|
|
||||||
|
cmd = `"${pythonPath}" -c "import sys;print(sys.exec_prefix)"`;
|
||||||
|
output = await utils.executeBufferedCommand(cmd, {});
|
||||||
|
let pythonPrefix = output ? output.trim() : '';
|
||||||
|
|
||||||
|
if (pythonVersion.length > 0 && pythonPrefix.length > 0) {
|
||||||
|
return {
|
||||||
|
installDir: pythonPrefix,
|
||||||
|
version: pythonVersion
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore errors here, since this python version will just be excluded.
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ export default class JupyterServerInstallation {
|
|||||||
private _forceInstall: boolean;
|
private _forceInstall: boolean;
|
||||||
private _installInProgress: boolean;
|
private _installInProgress: boolean;
|
||||||
|
|
||||||
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
|
public static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
|
||||||
|
|
||||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||||
this.extensionPath = extensionPath;
|
this.extensionPath = extensionPath;
|
||||||
@@ -76,7 +76,12 @@ export default class JupyterServerInstallation {
|
|||||||
this.outputChannel.appendLine(msgInstallPkgProgress);
|
this.outputChannel.appendLine(msgInstallPkgProgress);
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgInstallPkgProgress);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgInstallPkgProgress);
|
||||||
|
|
||||||
|
try {
|
||||||
await this.installPythonPackage(backgroundOperation);
|
await this.installPythonPackage(backgroundOperation);
|
||||||
|
} catch (err) {
|
||||||
|
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
this.outputChannel.appendLine(msgPythonDownloadComplete);
|
this.outputChannel.appendLine(msgPythonDownloadComplete);
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadComplete);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadComplete);
|
||||||
|
|
||||||
@@ -135,13 +140,13 @@ export default class JupyterServerInstallation {
|
|||||||
fs.mkdirs(this._pythonInstallationPath, (err) => {
|
fs.mkdirs(this._pythonInstallationPath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDirectoryError);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDirectoryError);
|
||||||
reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalMegaBytes: number = undefined;
|
let totalMegaBytes: number = undefined;
|
||||||
let receivedBytes = 0;
|
let receivedBytes = 0;
|
||||||
let printThreshold = 0.1;
|
let printThreshold = 0.1;
|
||||||
request.get(pythonDownloadUrl, { timeout: 20000 })
|
let downloadRequest = request.get(pythonDownloadUrl, { timeout: 20000 })
|
||||||
.on('error', (downloadError) => {
|
.on('error', (downloadError) => {
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
||||||
reject(downloadError);
|
reject(downloadError);
|
||||||
@@ -149,7 +154,7 @@ export default class JupyterServerInstallation {
|
|||||||
.on('response', (response) => {
|
.on('response', (response) => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
||||||
reject(response.statusMessage);
|
return reject(response.statusMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalBytes = parseInt(response.headers['content-length']);
|
let totalBytes = parseInt(response.headers['content-length']);
|
||||||
@@ -166,18 +171,19 @@ export default class JupyterServerInstallation {
|
|||||||
printThreshold += 0.1;
|
printThreshold += 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.pipe(fs.createWriteStream(pythonPackagePathLocal))
|
|
||||||
|
downloadRequest.pipe(fs.createWriteStream(pythonPackagePathLocal))
|
||||||
.on('close', () => {
|
.on('close', () => {
|
||||||
//unpack python zip/tar file
|
//unpack python zip/tar file
|
||||||
this.outputChannel.appendLine(msgPythonUnpackPending);
|
this.outputChannel.appendLine(msgPythonUnpackPending);
|
||||||
let pythonSourcePath = path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
|
let pythonSourcePath = path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
|
||||||
if (fs.existsSync(pythonSourcePath)) {
|
if (!this._usingExistingPython && fs.existsSync(pythonSourcePath)) {
|
||||||
try {
|
try {
|
||||||
fs.removeSync(pythonSourcePath);
|
fs.removeSync(pythonSourcePath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonUnpackError);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonUnpackError);
|
||||||
reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decompress(pythonPackagePathLocal, this._pythonInstallationPath).then(files => {
|
decompress(pythonPackagePathLocal, this._pythonInstallationPath).then(files => {
|
||||||
@@ -198,6 +204,7 @@ export default class JupyterServerInstallation {
|
|||||||
.on('error', (downloadError) => {
|
.on('error', (downloadError) => {
|
||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadError);
|
||||||
reject(downloadError);
|
reject(downloadError);
|
||||||
|
downloadRequest.abort();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -314,7 +321,6 @@ export default class JupyterServerInstallation {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
||||||
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
||||||
this.apiWrapper.showErrorMessage(errorMsg);
|
|
||||||
installReady.reject(errorMsg);
|
installReady.reject(errorMsg);
|
||||||
this._installInProgress = false;
|
this._installInProgress = false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,6 +92,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/events@*":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||||
|
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||||
|
|
||||||
"@types/form-data@*":
|
"@types/form-data@*":
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"
|
resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"
|
||||||
@@ -106,6 +111,20 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/glob@^7.1.1":
|
||||||
|
version "7.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||||
|
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
|
||||||
|
dependencies:
|
||||||
|
"@types/events" "*"
|
||||||
|
"@types/minimatch" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/minimatch@*":
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
"@types/mocha@^5.2.5":
|
"@types/mocha@^5.2.5":
|
||||||
version "5.2.6"
|
version "5.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
|
||||||
|
|||||||
Reference in New Issue
Block a user