mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Fix conda virtual environment paths in notebooks (#20438)
This commit is contained in:
@@ -63,7 +63,6 @@ export interface IJupyterServerInstallation {
|
|||||||
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
|
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
|
||||||
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
|
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||||
usingConda: boolean;
|
usingConda: boolean;
|
||||||
getCondaExePath(): string;
|
|
||||||
executeBufferedCommand(command: string): Promise<string>;
|
executeBufferedCommand(command: string): Promise<string>;
|
||||||
executeStreamedCommand(command: string): Promise<void>;
|
executeStreamedCommand(command: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
@@ -74,6 +73,7 @@ export interface IJupyterServerInstallation {
|
|||||||
installPipPackages(packages: PythonPkgDetails[], useMinVersionDefault: boolean): Promise<void>;
|
installPipPackages(packages: PythonPkgDetails[], useMinVersionDefault: boolean): Promise<void>;
|
||||||
uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void>;
|
uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||||
pythonExecutable: string;
|
pythonExecutable: string;
|
||||||
|
condaExecutable: string | undefined;
|
||||||
pythonInstallationPath: string;
|
pythonInstallationPath: string;
|
||||||
installedPythonVersion: string;
|
installedPythonVersion: string;
|
||||||
}
|
}
|
||||||
@@ -113,8 +113,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
|
|
||||||
private _pythonInstallationPath: string;
|
private _pythonInstallationPath: string;
|
||||||
private _pythonExecutable: string;
|
private _pythonExecutable: string;
|
||||||
|
private _condaExecutable: string | undefined;
|
||||||
private _usingExistingPython: boolean;
|
private _usingExistingPython: boolean;
|
||||||
private _usingConda: boolean;
|
|
||||||
private _installedPythonVersion: string;
|
private _installedPythonVersion: string;
|
||||||
|
|
||||||
private _upgradeInProcess: boolean = false;
|
private _upgradeInProcess: boolean = false;
|
||||||
@@ -154,7 +154,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath();
|
this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath();
|
||||||
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting();
|
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting();
|
||||||
}
|
}
|
||||||
this._usingConda = false;
|
|
||||||
this._installInProgress = false;
|
this._installInProgress = false;
|
||||||
|
|
||||||
this._kernelSetupCache = new Map<string, boolean>();
|
this._kernelSetupCache = new Map<string, boolean>();
|
||||||
@@ -360,10 +359,9 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||||
|
|
||||||
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath);
|
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath);
|
||||||
|
this._condaExecutable = await JupyterServerInstallation.getCondaExePath(this._pythonInstallationPath);
|
||||||
this.pythonBinPath = path.join(this._pythonInstallationPath, pythonBinPathSuffix);
|
this.pythonBinPath = path.join(this._pythonInstallationPath, pythonBinPathSuffix);
|
||||||
|
|
||||||
this._usingConda = this.isCondaInstalled();
|
|
||||||
|
|
||||||
// Store paths to python libraries required to run jupyter.
|
// Store paths to python libraries required to run jupyter.
|
||||||
this.pythonEnvVarPath = process.env['PATH'];
|
this.pythonEnvVarPath = process.env['PATH'];
|
||||||
|
|
||||||
@@ -373,7 +371,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
let pythonScriptsPath = path.join(this._pythonInstallationPath, 'Scripts');
|
let pythonScriptsPath = path.join(this._pythonInstallationPath, 'Scripts');
|
||||||
this.pythonEnvVarPath = pythonScriptsPath + delimiter + this.pythonEnvVarPath;
|
this.pythonEnvVarPath = pythonScriptsPath + delimiter + this.pythonEnvVarPath;
|
||||||
|
|
||||||
if (this._usingConda) {
|
if (this.usingConda) {
|
||||||
this.pythonEnvVarPath = [
|
this.pythonEnvVarPath = [
|
||||||
path.join(this._pythonInstallationPath, 'Library', 'mingw-w64', 'bin'),
|
path.join(this._pythonInstallationPath, 'Library', 'mingw-w64', 'bin'),
|
||||||
path.join(this._pythonInstallationPath, 'Library', 'usr', 'bin'),
|
path.join(this._pythonInstallationPath, 'Library', 'usr', 'bin'),
|
||||||
@@ -650,12 +648,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
|
|
||||||
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
|
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
|
||||||
try {
|
try {
|
||||||
if (!this.isCondaInstalled()) {
|
if (!this.condaExecutable) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
let cmd = `"${this.condaExecutable}" list --json`;
|
||||||
let condaExe = this.getCondaExePath();
|
|
||||||
let cmd = `"${condaExe}" list --json`;
|
|
||||||
let packagesInfo = await this.executeBufferedCommand(cmd);
|
let packagesInfo = await this.executeBufferedCommand(cmd);
|
||||||
|
|
||||||
let packages: PythonPkgDetails[] = [];
|
let packages: PythonPkgDetails[] = [];
|
||||||
@@ -673,8 +669,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public installCondaPackages(packages: PythonPkgDetails[], useMinVersionDefault: boolean): Promise<void> {
|
public async installCondaPackages(packages: PythonPkgDetails[], useMinVersionDefault: boolean): Promise<void> {
|
||||||
if (!packages || packages.length === 0) {
|
if (!this.condaExecutable || !packages || packages.length === 0) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,12 +679,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
const pkgVersionSpecifier = pkg.installExactVersion ? '==' : versionSpecifierDefault;
|
const pkgVersionSpecifier = pkg.installExactVersion ? '==' : versionSpecifierDefault;
|
||||||
return `"${pkg.name}${pkgVersionSpecifier}${pkg.version}"`;
|
return `"${pkg.name}${pkgVersionSpecifier}${pkg.version}"`;
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
let condaExe = this.getCondaExePath();
|
|
||||||
let cmd = `"${condaExe}" install -c conda-forge -y ${packagesStr}`;
|
let cmd = `"${this.condaExecutable}" install -c conda-forge -y ${packagesStr}`;
|
||||||
return this.executeStreamedCommand(cmd);
|
await this.executeStreamedCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
|
public async uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
|
||||||
|
if (this.condaExecutable) {
|
||||||
for (let pkg of packages) {
|
for (let pkg of packages) {
|
||||||
if (this._requiredPackagesSet.has(pkg.name)) {
|
if (this._requiredPackagesSet.has(pkg.name)) {
|
||||||
this._kernelSetupCache.clear();
|
this._kernelSetupCache.clear();
|
||||||
@@ -696,10 +693,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let condaExe = this.getCondaExePath();
|
|
||||||
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
||||||
let cmd = `"${condaExe}" uninstall -y ${packagesStr}`;
|
let cmd = `"${this.condaExecutable}" uninstall -y ${packagesStr}`;
|
||||||
return this.executeStreamedCommand(cmd);
|
await this.executeStreamedCommand(cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async executeStreamedCommand(command: string): Promise<void> {
|
public async executeStreamedCommand(command: string): Promise<void> {
|
||||||
@@ -714,9 +711,29 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
return this._pythonExecutable;
|
return this._pythonExecutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCondaExePath(): string {
|
public get condaExecutable(): string | undefined {
|
||||||
return path.join(this._pythonInstallationPath,
|
return this._condaExecutable;
|
||||||
process.platform === constants.winPlatform ? 'Scripts\\conda.exe' : 'bin/conda');
|
}
|
||||||
|
|
||||||
|
public static async getCondaExePath(pythonInstallationPath: string): Promise<string> {
|
||||||
|
let exeName = process.platform === constants.winPlatform ? 'Scripts\\conda.exe' : 'bin/conda';
|
||||||
|
let condaPath = path.join(pythonInstallationPath, exeName);
|
||||||
|
let condaExists = await fs.pathExists(condaPath);
|
||||||
|
// If conda was not found, then check if we're using a virtual environment
|
||||||
|
if (!condaExists) {
|
||||||
|
let pathParts = pythonInstallationPath.split(path.sep);
|
||||||
|
if (pathParts.length > 1 && pathParts[pathParts.length - 2] === 'envs') {
|
||||||
|
// The root Anaconda folder is 2 folders above the virtual environment's folder
|
||||||
|
// Example: Anaconda3\envs\myEnv\python.exe -> Anaconda3\conda.exe
|
||||||
|
// Docs: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-with-commands
|
||||||
|
condaPath = path.join(pythonInstallationPath, '..', '..', exeName);
|
||||||
|
condaExists = await fs.pathExists(condaPath);
|
||||||
|
}
|
||||||
|
if (!condaExists) {
|
||||||
|
condaPath = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return condaPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -727,19 +744,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get usingConda(): boolean {
|
public get usingConda(): boolean {
|
||||||
return this._usingConda;
|
return !!this._condaExecutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get installedPythonVersion(): string {
|
public get installedPythonVersion(): string {
|
||||||
return this._installedPythonVersion;
|
return this._installedPythonVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isCondaInstalled(): boolean {
|
|
||||||
let condaExePath = this.getCondaExePath();
|
|
||||||
// eslint-disable-next-line no-sync
|
|
||||||
return fs.existsSync(condaExePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a python executable exists at the "notebook.pythonPath" defined in the user's settings.
|
* Checks if a python executable exists at the "notebook.pythonPath" defined in the user's settings.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async fetchCondaPackage(packageName: string): Promise<IPackageOverview> {
|
private async fetchCondaPackage(packageName: string): Promise<IPackageOverview> {
|
||||||
let condaExe = this.jupyterInstallation.getCondaExePath();
|
let condaExe = this.jupyterInstallation.condaExecutable;
|
||||||
let cmd = `"${condaExe}" search --json ${packageName}`;
|
let cmd = `"${condaExe}" search --json ${packageName}`;
|
||||||
let packageResult: string;
|
let packageResult: string;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ describe('Manage Package Providers', () => {
|
|||||||
uninstallCondaPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
uninstallCondaPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
||||||
executeBufferedCommand: (command: string) => { return Promise.resolve(''); },
|
executeBufferedCommand: (command: string) => { return Promise.resolve(''); },
|
||||||
executeStreamedCommand: (command: string) => { return Promise.resolve(); },
|
executeStreamedCommand: (command: string) => { return Promise.resolve(); },
|
||||||
getCondaExePath: () => { return ''; },
|
|
||||||
pythonExecutable: '',
|
pythonExecutable: '',
|
||||||
|
condaExecutable: undefined,
|
||||||
pythonInstallationPath: '',
|
pythonInstallationPath: '',
|
||||||
usingConda: false,
|
usingConda: false,
|
||||||
installedPythonVersion: '',
|
installedPythonVersion: '',
|
||||||
|
|||||||
@@ -121,14 +121,14 @@ describe('Jupyter Server Installation', function () {
|
|||||||
|
|
||||||
it('Get conda packages', async function () {
|
it('Get conda packages', async function () {
|
||||||
// Should return nothing if conda is not installed
|
// Should return nothing if conda is not installed
|
||||||
sinon.stub(fs, 'existsSync').returns(false);
|
sinon.stub(installation, 'condaExecutable').get(() => undefined);
|
||||||
let pkgResult = await installation.getInstalledCondaPackages();
|
let pkgResult = await installation.getInstalledCondaPackages();
|
||||||
should(pkgResult).not.be.undefined();
|
should(pkgResult).not.be.undefined();
|
||||||
should(pkgResult.length).be.equal(0);
|
should(pkgResult.length).be.equal(0);
|
||||||
|
|
||||||
// Should return nothing on error
|
// Should return nothing on error
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
sinon.stub(fs, 'existsSync').returns(true);
|
sinon.stub(installation, 'condaExecutable').get(() => 'TestCondaPath');
|
||||||
sinon.stub(utils, 'executeBufferedCommand').rejects(new Error('Expected test failure.'));
|
sinon.stub(utils, 'executeBufferedCommand').rejects(new Error('Expected test failure.'));
|
||||||
pkgResult = await installation.getInstalledCondaPackages();
|
pkgResult = await installation.getInstalledCondaPackages();
|
||||||
should(pkgResult).not.be.undefined();
|
should(pkgResult).not.be.undefined();
|
||||||
@@ -150,7 +150,7 @@ describe('Jupyter Server Installation', function () {
|
|||||||
version: '7.8.9',
|
version: '7.8.9',
|
||||||
channel: 'conda'
|
channel: 'conda'
|
||||||
}];
|
}];
|
||||||
sinon.stub(fs, 'existsSync').returns(true);
|
sinon.stub(installation, 'condaExecutable').get(() => 'TestCondaPath');
|
||||||
sinon.stub(utils, 'executeBufferedCommand').resolves(JSON.stringify(testPackages));
|
sinon.stub(utils, 'executeBufferedCommand').resolves(JSON.stringify(testPackages));
|
||||||
pkgResult = await installation.getInstalledCondaPackages();
|
pkgResult = await installation.getInstalledCondaPackages();
|
||||||
let filteredPackages = testPackages.filter(pkg => pkg.channel !== 'pypi');
|
let filteredPackages = testPackages.filter(pkg => pkg.channel !== 'pypi');
|
||||||
@@ -158,8 +158,23 @@ describe('Jupyter Server Installation', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Install conda package', async function () {
|
it('Install conda package', async function () {
|
||||||
|
const testPackages = [{
|
||||||
|
name: 'TestPkg1',
|
||||||
|
version: '1.2.3'
|
||||||
|
}, {
|
||||||
|
name: 'TestPkg2',
|
||||||
|
version: '4.5.6'
|
||||||
|
}];
|
||||||
let commandStub = sinon.stub(utils, 'executeStreamedCommand').resolves();
|
let commandStub = sinon.stub(utils, 'executeStreamedCommand').resolves();
|
||||||
|
|
||||||
|
// Should not execute any commands if conda is not installed
|
||||||
|
let condaStub = sinon.stub(installation, 'condaExecutable').get(() => undefined);
|
||||||
|
await installation.installCondaPackages(testPackages, false);
|
||||||
|
should(commandStub.called).be.false();
|
||||||
|
|
||||||
|
condaStub.restore();
|
||||||
|
sinon.stub(installation, 'condaExecutable').get(() => 'TestCondaPath');
|
||||||
|
|
||||||
// Should not execute any commands when passed an empty package list
|
// Should not execute any commands when passed an empty package list
|
||||||
await installation.installCondaPackages(undefined, false);
|
await installation.installCondaPackages(undefined, false);
|
||||||
should(commandStub.called).be.false();
|
should(commandStub.called).be.false();
|
||||||
@@ -168,13 +183,6 @@ describe('Jupyter Server Installation', function () {
|
|||||||
should(commandStub.called).be.false();
|
should(commandStub.called).be.false();
|
||||||
|
|
||||||
// Install package using exact version
|
// Install package using exact version
|
||||||
let testPackages = [{
|
|
||||||
name: 'TestPkg1',
|
|
||||||
version: '1.2.3'
|
|
||||||
}, {
|
|
||||||
name: 'TestPkg2',
|
|
||||||
version: '4.5.6'
|
|
||||||
}];
|
|
||||||
await installation.installCondaPackages(testPackages, false);
|
await installation.installCondaPackages(testPackages, false);
|
||||||
should(commandStub.calledOnce).be.true();
|
should(commandStub.calledOnce).be.true();
|
||||||
let commandStr = commandStub.args[0][0] as string;
|
let commandStr = commandStub.args[0][0] as string;
|
||||||
@@ -188,6 +196,7 @@ describe('Jupyter Server Installation', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Uninstall conda package', async function () {
|
it('Uninstall conda package', async function () {
|
||||||
|
sinon.stub(installation, 'condaExecutable').get(() => 'TestCondaPath');
|
||||||
let commandStub = sinon.stub(utils, 'executeStreamedCommand').resolves();
|
let commandStub = sinon.stub(utils, 'executeStreamedCommand').resolves();
|
||||||
|
|
||||||
let testPackages = [{
|
let testPackages = [{
|
||||||
@@ -258,7 +267,7 @@ describe('Jupyter Server Installation', function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sinon.stub(utils, 'exists').resolves(true);
|
sinon.stub(utils, 'exists').resolves(true);
|
||||||
sinon.stub(fs, 'existsSync').returns(false);
|
sinon.stub(fs, 'pathExists').resolves(false);
|
||||||
sinon.stub(utils, 'executeBufferedCommand').resolves(`${installSettings.installPath}\\site-packages`);
|
sinon.stub(utils, 'executeBufferedCommand').resolves(`${installSettings.installPath}\\site-packages`);
|
||||||
|
|
||||||
// Both of these are called from upgradePythonPackages
|
// Both of these are called from upgradePythonPackages
|
||||||
|
|||||||
Reference in New Issue
Block a user