@@ -7,8 +7,8 @@ import { execProcess } from '../../common/childProcess.apis';
77import { ENVS_EXTENSION_ID } from '../../common/constants' ;
88import { traceError , traceInfo } from '../../common/logging' ;
99import { getWorkspacePersistentState } from '../../common/persistentState' ;
10- import { getUserHomeDir , untildify } from '../../common/utils/pathUtils' ;
11- import { isWindows } from '../../common/utils/platformUtils' ;
10+ import { getUserHomeDir , normalizePath , untildify } from '../../common/utils/pathUtils' ;
11+ import { isMac , isWindows } from '../../common/utils/platformUtils' ;
1212import { getSettingWorkspaceScope } from '../../features/settings/settingHelpers' ;
1313import {
1414 isNativeEnvInfo ,
@@ -214,14 +214,14 @@ export async function getPoetryVirtualenvsPath(poetryExe?: string): Promise<stri
214214 if ( stdout ) {
215215 const venvPath = stdout . trim ( ) ;
216216 // Poetry might return the path with placeholders like {cache-dir}
217- // If it doesn't start with / or C:\ etc., assume it's using default
218- if ( ! path . isAbsolute ( venvPath ) || venvPath . includes ( '{' ) ) {
219- const home = getUserHomeDir ( ) ;
220- if ( home ) {
221- poetryVirtualenvsPath = path . join ( home , '.cache' , 'pypoetry' , 'virtualenvs' ) ;
222- }
223- } else {
217+ // Resolve the placeholder if present
218+ if ( venvPath . includes ( '{cache-dir}' ) ) {
219+ poetryVirtualenvsPath = await resolveVirtualenvsPath ( poetry , venvPath ) ;
220+ } else if ( path . isAbsolute ( venvPath ) ) {
224221 poetryVirtualenvsPath = venvPath ;
222+ } else {
223+ // Not an absolute path and no placeholder, use platform-specific default
224+ poetryVirtualenvsPath = getDefaultPoetryVirtualenvsPath ( ) ;
225225 }
226226
227227 if ( poetryVirtualenvsPath ) {
@@ -234,17 +234,99 @@ export async function getPoetryVirtualenvsPath(poetryExe?: string): Promise<stri
234234 }
235235 }
236236
237- // Fallback to default location
238- const home = getUserHomeDir ( ) ;
239- if ( home ) {
240- poetryVirtualenvsPath = path . join ( home , '.cache' , 'pypoetry' , 'virtualenvs' ) ;
237+ // Fallback to platform-specific default location
238+ poetryVirtualenvsPath = getDefaultPoetryVirtualenvsPath ( ) ;
239+ if ( poetryVirtualenvsPath ) {
241240 await state . set ( POETRY_VIRTUALENVS_PATH_KEY , poetryVirtualenvsPath ) ;
242241 return poetryVirtualenvsPath ;
243242 }
244243
245244 return undefined ;
246245}
247246
247+ /**
248+ * Returns the default Poetry cache directory based on the current platform.
249+ * - Windows: %LOCALAPPDATA%\pypoetry\Cache or %APPDATA%\pypoetry\Cache
250+ * - macOS: ~/Library/Caches/pypoetry
251+ * - Linux: ~/.cache/pypoetry
252+ */
253+ export function getDefaultPoetryCacheDir ( ) : string | undefined {
254+ if ( isWindows ( ) ) {
255+ const localAppData = process . env . LOCALAPPDATA ;
256+ if ( localAppData ) {
257+ return path . join ( localAppData , 'pypoetry' , 'Cache' ) ;
258+ }
259+ const appData = process . env . APPDATA ;
260+ if ( appData ) {
261+ return path . join ( appData , 'pypoetry' , 'Cache' ) ;
262+ }
263+ return undefined ;
264+ }
265+
266+ const home = getUserHomeDir ( ) ;
267+ if ( ! home ) {
268+ return undefined ;
269+ }
270+
271+ if ( isMac ( ) ) {
272+ return path . join ( home , 'Library' , 'Caches' , 'pypoetry' ) ;
273+ }
274+
275+ // Linux default
276+ return path . join ( home , '.cache' , 'pypoetry' ) ;
277+ }
278+
279+ /**
280+ * Returns the default Poetry virtualenvs path based on the current platform.
281+ * - Windows: %LOCALAPPDATA%\pypoetry\Cache\virtualenvs or %APPDATA%\pypoetry\Cache\virtualenvs
282+ * - macOS: ~/Library/Caches/pypoetry/virtualenvs
283+ * - Linux: ~/.cache/pypoetry/virtualenvs
284+ */
285+ export function getDefaultPoetryVirtualenvsPath ( ) : string | undefined {
286+ const cacheDir = getDefaultPoetryCacheDir ( ) ;
287+ if ( cacheDir ) {
288+ return path . join ( cacheDir , 'virtualenvs' ) ;
289+ }
290+ return undefined ;
291+ }
292+
293+ /**
294+ * Resolves the {cache-dir} placeholder in a Poetry virtualenvs path.
295+ * First tries to query Poetry's cache-dir config, then falls back to platform-specific default.
296+ * @param poetry Path to the poetry executable
297+ * @param virtualenvsPath The path possibly containing {cache-dir} placeholder
298+ * @returns The resolved path, or undefined if the placeholder cannot be resolved
299+ */
300+ async function resolveVirtualenvsPath ( poetry : string , virtualenvsPath : string ) : Promise < string | undefined > {
301+ if ( ! virtualenvsPath . includes ( '{cache-dir}' ) ) {
302+ return virtualenvsPath ;
303+ }
304+
305+ // Try to get the actual cache-dir from Poetry
306+ try {
307+ const { stdout } = await execProcess ( `"${ poetry } " config cache-dir` ) ;
308+ if ( stdout ) {
309+ const cacheDir = stdout . trim ( ) ;
310+ if ( cacheDir && path . isAbsolute ( cacheDir ) ) {
311+ const resolved = virtualenvsPath . replace ( '{cache-dir}' , cacheDir ) ;
312+ return path . normalize ( resolved ) ;
313+ }
314+ }
315+ } catch ( e ) {
316+ traceError ( 'Error getting Poetry cache-dir config' , e ) ;
317+ }
318+
319+ // Fall back to platform-specific default cache dir
320+ const defaultCacheDir = getDefaultPoetryCacheDir ( ) ;
321+ if ( defaultCacheDir ) {
322+ const resolved = virtualenvsPath . replace ( '{cache-dir}' , defaultCacheDir ) ;
323+ return path . normalize ( resolved ) ;
324+ }
325+
326+ // Cannot resolve the placeholder - return undefined instead of unresolved path
327+ return undefined ;
328+ }
329+
248330export async function getPoetryVersion ( poetry : string ) : Promise < string | undefined > {
249331 try {
250332 const { stdout } = await execProcess ( `"${ poetry } " --version` ) ;
@@ -274,8 +356,8 @@ export async function nativeToPythonEnv(
274356 const displayName = info . displayName || `poetry (${ sv } )` ;
275357
276358 // Check if this is a global Poetry virtualenv by checking if it's in Poetry's virtualenvs directory
277- // We need to use path.normalize () to ensure consistent path format comparison
278- const normalizedPrefix = path . normalize ( info . prefix ) ;
359+ // We use normalizePath () for case-insensitive path comparison on Windows
360+ const normalizedPrefix = normalizePath ( info . prefix ) ;
279361
280362 // Determine if the environment is in Poetry's global virtualenvs directory
281363 let isGlobalPoetryEnv = false ;
@@ -284,19 +366,17 @@ export async function nativeToPythonEnv(
284366 if ( ! isPoetryVirtualenvsInProject ( ) || ! info . project ) {
285367 const virtualenvsPath = poetryVirtualenvsPath ; // Use the cached value if available
286368 if ( virtualenvsPath ) {
287- const normalizedVirtualenvsPath = path . normalize ( virtualenvsPath ) ;
369+ const normalizedVirtualenvsPath = normalizePath ( virtualenvsPath ) ;
288370 isGlobalPoetryEnv = normalizedPrefix . startsWith ( normalizedVirtualenvsPath ) ;
289371 } else {
290- // Fall back to checking the default location if we haven't cached the path yet
291- const homeDir = getUserHomeDir ( ) ;
292- if ( homeDir ) {
293- const defaultPath = path . normalize ( path . join ( homeDir , '.cache' , 'pypoetry' , 'virtualenvs' ) ) ;
294- isGlobalPoetryEnv = normalizedPrefix . startsWith ( defaultPath ) ;
372+ // Fall back to checking the platform-specific default location if we haven't cached the path yet
373+ const defaultPath = getDefaultPoetryVirtualenvsPath ( ) ;
374+ if ( defaultPath ) {
375+ const normalizedDefaultPath = normalizePath ( defaultPath ) ;
376+ isGlobalPoetryEnv = normalizedPrefix . startsWith ( normalizedDefaultPath ) ;
295377
296378 // Try to get the actual path asynchronously for next time
297- getPoetryVirtualenvsPath ( _poetry ) . catch ( ( e ) =>
298- traceError ( `Error getting Poetry virtualenvs path: ${ e } ` ) ,
299- ) ;
379+ getPoetryVirtualenvsPath ( _poetry ) . catch ( ( e ) => traceError ( 'Error getting Poetry virtualenvs path' , e ) ) ;
300380 }
301381 }
302382 }
0 commit comments