Skip to content

Commit 401418a

Browse files
author
Kartik Raj
authored
Ensure IDs of Conda environments without python does not change after python is installed (#20609)
Fixes #20176 As we're changing IDs we've to ensure the same environment in cache with older ID is migrated to use the new one, this is already ensured here: https://github.com/microsoft/vscode-python/blob/32f55109c976e66bf39e8da6aae0c9b6f5115df2/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts#L109
1 parent 92d2d85 commit 401418a

File tree

6 files changed

+37
-13
lines changed

6 files changed

+37
-13
lines changed

src/client/common/installer/condaInstaller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { inject, injectable } from 'inversify';
66
import { ICondaService, IComponentAdapter } from '../../interpreter/contracts';
77
import { IServiceContainer } from '../../ioc/types';
8+
import { getEnvPath } from '../../pythonEnvironments/base/info/env';
89
import { ModuleInstallerType } from '../../pythonEnvironments/info';
910
import { ExecutionInfo, IConfigurationService, Product } from '../types';
1011
import { isResource } from '../utils/misc';
@@ -79,7 +80,7 @@ export class CondaInstaller extends ModuleInstaller {
7980

8081
const pythonPath = isResource(resource)
8182
? this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource).pythonPath
82-
: resource.id ?? '';
83+
: getEnvPath(resource.path, resource.envPath).path ?? '';
8384
const condaLocatorService = this.serviceContainer.get<IComponentAdapter>(IComponentAdapter);
8485
const info = await condaLocatorService.getCondaEnvironment(pythonPath);
8586
const args = [flags & ModuleInstallFlags.upgrade ? 'update' : 'install'];
@@ -132,7 +133,7 @@ export class CondaInstaller extends ModuleInstaller {
132133
const condaService = this.serviceContainer.get<IComponentAdapter>(IComponentAdapter);
133134
const pythonPath = isResource(resource)
134135
? this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource).pythonPath
135-
: resource.id ?? '';
136+
: getEnvPath(resource.path, resource.envPath).path ?? '';
136137
return condaService.isCondaEnvironment(pythonPath);
137138
}
138139
}

src/client/proposedApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ export function convertCompleteEnvInfo(env: PythonEnvInfo): ResolvedEnvironment
304304
const { path } = getEnvPath(env.executable.filename, env.location);
305305
const resolvedEnv: ResolvedEnvironment = {
306306
path,
307-
id: getEnvID(path),
307+
id: env.id!,
308308
executable: {
309309
uri: env.executable.filename === 'python' ? undefined : Uri.file(env.executable.filename),
310310
bitness: convertBitness(env.arch),

src/client/pythonEnvironments/base/info/env.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ function getMinimalPartialInfo(env: string | PythonEnvInfo | BasicEnvInfo): Part
198198
return undefined;
199199
}
200200
return {
201+
id: '',
201202
executable: {
202203
filename: env,
203204
sysPrefix: '',
@@ -208,6 +209,7 @@ function getMinimalPartialInfo(env: string | PythonEnvInfo | BasicEnvInfo): Part
208209
}
209210
if ('executablePath' in env) {
210211
return {
212+
id: '',
211213
executable: {
212214
filename: env.executablePath,
213215
sysPrefix: '',
@@ -235,7 +237,7 @@ export function getEnvPath(interpreterPath: string, envFolderPath?: string): Env
235237
}
236238

237239
/**
238-
* Gets unique identifier for an environment.
240+
* Gets general unique identifier for most environments.
239241
*/
240242
export function getEnvID(interpreterPath: string, envFolderPath?: string): string {
241243
return normCasePath(getEnvPath(interpreterPath, envFolderPath).path);
@@ -266,7 +268,15 @@ export function areSameEnv(
266268
const leftFilename = leftInfo.executable!.filename;
267269
const rightFilename = rightInfo.executable!.filename;
268270

271+
if (leftInfo.id && leftInfo.id === rightInfo.id) {
272+
// In case IDs are available, use it.
273+
return true;
274+
}
275+
269276
if (getEnvID(leftFilename, leftInfo.location) === getEnvID(rightFilename, rightInfo.location)) {
277+
// Otherwise use ID function to get the ID. Note ID returned by function may itself change if executable of
278+
// an environment changes, for eg. when conda installs python into the env. So only use it as a fallback if
279+
// ID is not available.
270280
return true;
271281
}
272282

src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import {
1515
import { buildEnvInfo, comparePythonVersionSpecificity, setEnvDisplayString, getEnvID } from '../../info/env';
1616
import { getEnvironmentDirFromPath, getPythonVersionFromPath } from '../../../common/commonUtils';
1717
import { arePathsSame, getFileInfo, isParentPath } from '../../../common/externalDependencies';
18-
import { AnacondaCompanyName, Conda, isCondaEnvironment } from '../../../common/environmentManagers/conda';
18+
import {
19+
AnacondaCompanyName,
20+
Conda,
21+
getCondaInterpreterPath,
22+
isCondaEnvironment,
23+
} from '../../../common/environmentManagers/conda';
1924
import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environmentManagers/pyenv';
2025
import { Architecture, getOSType, OSType } from '../../../../common/utils/platform';
2126
import { getPythonVersionFromPath as parsePythonVersionFromPath, parseVersion } from '../../info/pythonVersion';
@@ -57,7 +62,6 @@ export async function resolveBasicEnv(env: BasicEnvInfo): Promise<PythonEnvInfo>
5762
await updateEnvUsingRegistry(resolvedEnv);
5863
}
5964
setEnvDisplayString(resolvedEnv);
60-
resolvedEnv.id = getEnvID(resolvedEnv.executable.filename, resolvedEnv.location);
6165
const { ctime, mtime } = await getFileInfo(resolvedEnv.executable.filename);
6266
resolvedEnv.executable.ctime = ctime;
6367
resolvedEnv.executable.mtime = mtime;
@@ -189,6 +193,13 @@ async function resolveCondaEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
189193
if (name) {
190194
info.name = name;
191195
}
196+
if (env.envPath && path.basename(executable) === executable) {
197+
// For environments without python, set ID using the predicted executable path after python is installed.
198+
// Another alternative could've been to set ID of all conda environments to the environment path, as that
199+
// remains constant even after python installation.
200+
const predictedExecutable = getCondaInterpreterPath(env.envPath);
201+
info.id = getEnvID(predictedExecutable, env.envPath);
202+
}
192203
return info;
193204
}
194205

src/client/pythonEnvironments/common/environmentManagers/conda.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,11 @@ export async function getPythonVersionFromConda(interpreterPath: string): Promis
226226
/**
227227
* Return the interpreter's filename for the given environment.
228228
*/
229-
async function getInterpreterPath(condaEnvironmentPath: string): Promise<string | undefined> {
229+
export function getCondaInterpreterPath(condaEnvironmentPath: string): string {
230230
// where to find the Python binary within a conda env.
231231
const relativePath = getOSType() === OSType.Windows ? 'python.exe' : path.join('bin', 'python');
232232
const filePath = path.join(condaEnvironmentPath, relativePath);
233-
if (await pathExists(filePath)) {
234-
return filePath;
235-
}
236-
return undefined;
233+
return filePath;
237234
}
238235

239236
// Minimum version number of conda required to be able to use 'conda run' with '--no-capture-output' flag.
@@ -494,8 +491,8 @@ export class Conda {
494491
*/
495492
// eslint-disable-next-line class-methods-use-this
496493
public async getInterpreterPathForEnvironment(condaEnv: CondaEnvInfo | { prefix: string }): Promise<string> {
497-
const executablePath = await getInterpreterPath(condaEnv.prefix);
498-
if (executablePath) {
494+
const executablePath = getCondaInterpreterPath(condaEnv.prefix);
495+
if (await pathExists(executablePath)) {
499496
traceVerbose('Found executable within conda env', JSON.stringify(condaEnv));
500497
return executablePath;
501498
}

src/test/proposedApi.unit.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ suite('Proposed Extension API', () => {
252252
test('environments: python found', async () => {
253253
const expectedEnvs = [
254254
{
255+
id: normCasePath('this/is/a/test/python/path1'),
255256
executable: {
256257
filename: 'this/is/a/test/python/path1',
257258
ctime: 1,
@@ -273,6 +274,7 @@ suite('Proposed Extension API', () => {
273274
},
274275
},
275276
{
277+
id: normCasePath('this/is/a/test/python/path2'),
276278
executable: {
277279
filename: 'this/is/a/test/python/path2',
278280
ctime: 1,
@@ -297,6 +299,7 @@ suite('Proposed Extension API', () => {
297299
const envs = [
298300
...expectedEnvs,
299301
{
302+
id: normCasePath('this/is/a/test/python/path3'),
300303
executable: {
301304
filename: 'this/is/a/test/python/path3',
302305
ctime: 1,
@@ -343,6 +346,7 @@ suite('Proposed Extension API', () => {
343346
searchLocation: Uri.file(workspacePath),
344347
}),
345348
{
349+
id: normCasePath('this/is/a/test/python/path1'),
346350
executable: {
347351
filename: 'this/is/a/test/python/path1',
348352
ctime: 1,
@@ -364,6 +368,7 @@ suite('Proposed Extension API', () => {
364368
},
365369
},
366370
{
371+
id: normCasePath('this/is/a/test/python/path2'),
367372
executable: {
368373
filename: 'this/is/a/test/python/path2',
369374
ctime: 1,

0 commit comments

Comments
 (0)