Skip to content

Commit 1e25bbb

Browse files
authored
refactor: streamline conda environment handling and activation commands (#1177)
1 parent 3d4a402 commit 1e25bbb

File tree

2 files changed

+32
-56
lines changed

2 files changed

+32
-56
lines changed

src/managers/conda/condaEnvManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,7 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
111111
}
112112

113113
if (scope === 'global') {
114-
return this.collection.filter((env) => {
115-
env.name === 'base';
116-
});
114+
return this.collection.filter((env) => env.name === 'base');
117115
}
118116

119117
if (scope instanceof Uri) {
@@ -192,7 +190,10 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
192190
} catch (error) {
193191
this.log.error('Failed to create conda environment:', error);
194192
showErrorMessage(
195-
l10n.t('Failed to create conda environment: {0}', error instanceof Error ? error.message : String(error)),
193+
l10n.t(
194+
'Failed to create conda environment: {0}',
195+
error instanceof Error ? error.message : String(error),
196+
),
196197
);
197198
return undefined;
198199
}

src/managers/conda/condaUtils.ts

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const CONDA_GLOBAL_KEY = `${ENVS_EXTENSION_ID}:conda:GLOBAL_SELECTED`;
6666
let condaPath: string | undefined;
6767
export async function clearCondaCache(): Promise<void> {
6868
condaPath = undefined;
69+
prefixes = undefined;
6970
}
7071

7172
async function setConda(conda: string): Promise<void> {
@@ -331,26 +332,13 @@ export async function getVersion(root: string): Promise<string> {
331332
throw new Error('Python version not found');
332333
}
333334

334-
function isPrefixOf(roots: string[], e: string): boolean {
335-
if (!roots || !Array.isArray(roots)) {
336-
return false;
337-
}
338-
const t = path.normalize(e);
339-
for (let r of roots.map((r) => path.normalize(r))) {
340-
if (t.startsWith(r)) {
341-
return true;
342-
}
343-
}
344-
return false;
345-
}
346-
347335
/**
348336
* Creates a PythonEnvironmentInfo object for a named conda environment.
349337
* @param name The name of the conda environment
350338
* @param prefix The installation prefix path for the environment
351339
* @param executable The path to the Python executable
352340
* @param version The Python version string
353-
* @param _conda The path to the conda executable (TODO: currently unused)
341+
* @param conda The path to the conda executable (used for activation commands)
354342
* @param envManager The environment manager instance
355343
* @returns Promise resolving to a PythonEnvironmentInfo object
356344
*/
@@ -359,7 +347,7 @@ export async function getNamedCondaPythonInfo(
359347
prefix: string,
360348
executable: string,
361349
version: string,
362-
_conda: string, // TODO:: fix this, why is it not being used to build the info object
350+
conda: string,
363351
envManager: EnvironmentManager,
364352
): Promise<PythonEnvironmentInfo> {
365353
const { shellActivation, shellDeactivation } = await buildShellActivationMapForConda(prefix, envManager, name);
@@ -381,8 +369,8 @@ export async function getNamedCondaPythonInfo(
381369
executable: path.join(executable),
382370
args: [],
383371
},
384-
activation: [{ executable: 'conda', args: ['activate', name] }],
385-
deactivation: [{ executable: 'conda', args: ['deactivate'] }],
372+
activation: [{ executable: conda, args: ['activate', name] }],
373+
deactivation: [{ executable: conda, args: ['deactivate'] }],
386374
shellActivation,
387375
shellDeactivation,
388376
},
@@ -596,10 +584,12 @@ async function windowsExceptionGenerateConfig(
596584
traceVerbose(`PS1 hook path: ${ps1Hook ?? 'not found'}`);
597585
const activation = ps1Hook ? ps1Hook : sourceInitPath;
598586

599-
const pwshActivate = [{ executable: activation }, { executable: 'conda', args: ['activate', prefix] }];
600-
const cmdActivate = [{ executable: sourceInitPath }, { executable: 'conda', args: ['activate', prefix] }];
587+
// Quote the prefix path to handle spaces in paths (common on Windows)
588+
const quotedPrefix = quoteStringIfNecessary(prefix);
589+
const pwshActivate = [{ executable: activation }, { executable: 'conda', args: ['activate', quotedPrefix] }];
590+
const cmdActivate = [{ executable: sourceInitPath }, { executable: 'conda', args: ['activate', quotedPrefix] }];
601591

602-
const bashActivate = [{ executable: 'source', args: [sourceInitPath.replace(/\\/g, '/'), prefix] }];
592+
const bashActivate = [{ executable: 'source', args: [sourceInitPath.replace(/\\/g, '/'), quotedPrefix] }];
603593
traceVerbose(
604594
`Windows activation commands:
605595
PowerShell: ${JSON.stringify(pwshActivate)},
@@ -648,7 +638,6 @@ async function nativeToPythonEnv(
648638
manager: EnvironmentManager,
649639
log: LogOutputChannel,
650640
conda: string,
651-
condaPrefixes: string[],
652641
): Promise<PythonEnvironment | undefined> {
653642
// Defensive check: Validate NativeEnvInfo object
654643
if (!e) {
@@ -672,21 +661,24 @@ async function nativeToPythonEnv(
672661
);
673662
log.info(`Found base environment: ${e.prefix}`);
674663
return environment;
675-
} else if (!isPrefixOf(condaPrefixes, e.prefix)) {
664+
} else if (e.name) {
665+
// Server explicitly provided a name - this is a named environment (created with -n/--name)
666+
// Use name-based activation: conda activate <name>
676667
const environment = api.createPythonEnvironmentItem(
677-
await getPrefixesCondaPythonInfo(e.prefix, e.executable, e.version, conda, manager),
668+
await getNamedCondaPythonInfo(e.name, e.prefix, e.executable, e.version, conda, manager),
678669
manager,
679670
);
680-
log.info(`Found prefix environment: ${e.prefix}`);
671+
log.info(`Found named environment: ${e.name} at ${e.prefix}`);
681672
return environment;
682673
} else {
683-
const basename = path.basename(e.prefix);
684-
const name = e.name ?? basename;
674+
// Server returned undefined/null name - this is a prefix-based environment (created with -p/--prefix)
675+
// Use prefix-based activation: conda activate <full-path>
676+
// This aligns with the PR #331 fix in python-environment-tools
685677
const environment = api.createPythonEnvironmentItem(
686-
await getNamedCondaPythonInfo(name, e.prefix, e.executable, e.version, conda, manager),
678+
await getPrefixesCondaPythonInfo(e.prefix, e.executable, e.version, conda, manager),
687679
manager,
688680
);
689-
log.info(`Found named environment: ${e.prefix}`);
681+
log.info(`Found prefix environment: ${e.prefix}`);
690682
return environment;
691683
}
692684
}
@@ -704,8 +696,7 @@ export async function resolveCondaPath(
704696
return undefined;
705697
}
706698
const conda = await getConda();
707-
const condaPrefixes = await getPrefixes();
708-
return nativeToPythonEnv(e, api, manager, log, conda, condaPrefixes);
699+
return nativeToPythonEnv(e, api, manager, log, conda);
709700
} catch {
710701
return undefined;
711702
}
@@ -764,7 +755,6 @@ export async function refreshCondaEnvs(
764755
const condaPath = conda;
765756

766757
if (condaPath) {
767-
const condaPrefixes = await getPrefixes();
768758
const envs = data
769759
.filter((e) => isNativeEnvInfo(e))
770760
.map((e) => e as NativeEnvInfo)
@@ -774,7 +764,7 @@ export async function refreshCondaEnvs(
774764
await Promise.all(
775765
envs.map(async (e) => {
776766
try {
777-
const environment = await nativeToPythonEnv(e, api, manager, log, condaPath, condaPrefixes);
767+
const environment = await nativeToPythonEnv(e, api, manager, log, condaPath);
778768
if (environment) {
779769
collection.push(environment);
780770
}
@@ -1056,6 +1046,7 @@ export async function createPrefixCondaEnvironment(
10561046
export async function generateName(fsPath: string): Promise<string | undefined> {
10571047
let attempts = 0;
10581048
while (attempts < 5) {
1049+
attempts++;
10591050
const randomStr = Math.random().toString(36).substring(2);
10601051
const name = `env_${randomStr}`;
10611052
const prefix = path.join(fsPath, name);
@@ -1084,32 +1075,16 @@ export async function quickCreateConda(
10841075
},
10851076
async () => {
10861077
try {
1078+
const conda = await getConda();
10871079
await runCondaExecutable(['create', '--yes', '--prefix', prefix, 'python'], log);
10881080
if (additionalPackages && additionalPackages.length > 0) {
10891081
await runConda(['install', '--yes', '--prefix', prefix, ...additionalPackages], log);
10901082
}
10911083
const version = await getVersion(prefix);
10921084

1085+
// Use proper prefix-based activation with actual conda path
10931086
const environment = api.createPythonEnvironmentItem(
1094-
{
1095-
name: path.basename(prefix),
1096-
environmentPath: Uri.file(prefix),
1097-
displayName: `${version} (${name})`,
1098-
displayPath: prefix,
1099-
description: prefix,
1100-
version,
1101-
execInfo: {
1102-
run: { executable: execPath },
1103-
activatedRun: {
1104-
executable: execPath,
1105-
args: [],
1106-
},
1107-
activation: [{ executable: 'conda', args: ['activate', prefix] }],
1108-
deactivation: [{ executable: 'conda', args: ['deactivate'] }],
1109-
},
1110-
sysPrefix: prefix,
1111-
group: 'Prefix',
1112-
},
1087+
await getPrefixesCondaPythonInfo(prefix, execPath, version, conda, manager),
11131088
manager,
11141089
);
11151090
return environment;
@@ -1328,7 +1303,7 @@ async function installPython(
13281303
await nativeFinder.refresh(true, NativePythonEnvironmentKind.conda);
13291304
const native = await nativeFinder.resolve(environment.sysPrefix);
13301305
if (native.kind === NativePythonEnvironmentKind.conda) {
1331-
return nativeToPythonEnv(native, api, manager, log, await getConda(), await getPrefixes());
1306+
return nativeToPythonEnv(native, api, manager, log, await getConda());
13321307
}
13331308
return undefined;
13341309
}

0 commit comments

Comments
 (0)