Skip to content

Commit 02c6f38

Browse files
Copilotkarthiknadig
andcommitted
fix: use platform-specific default paths for Poetry virtualenvs
On Windows, Poetry stores virtualenvs in %LOCALAPPDATA%\pypoetry\Cache\virtualenvs (or %APPDATA%\pypoetry\Cache\virtualenvs). On macOS, it uses ~/Library/Caches/pypoetry/virtualenvs. The previous code hardcoded the Linux path (~/.cache/pypoetry/virtualenvs) for all platforms. Extract getDefaultPoetryVirtualenvsPath() to return the correct platform-specific path and use it in all 3 locations that previously had the hardcoded Linux path. Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
1 parent 511c172 commit 02c6f38

File tree

3 files changed

+150
-12
lines changed

3 files changed

+150
-12
lines changed

package-lock.json

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/managers/poetry/poetryUtils.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,38 @@ async function findPoetry(): Promise<string | undefined> {
2525
}
2626
}
2727

28+
/**
29+
* Returns the platform-specific default path for Poetry's virtualenvs directory.
30+
* - Linux: ~/.cache/pypoetry/virtualenvs
31+
* - macOS: ~/Library/Caches/pypoetry/virtualenvs
32+
* - Windows: %LOCALAPPDATA%\pypoetry\Cache\virtualenvs (or %APPDATA%\pypoetry\Cache\virtualenvs)
33+
*/
34+
export function getDefaultPoetryVirtualenvsPath(): string | undefined {
35+
if (isWindows()) {
36+
const localAppData = process.env.LOCALAPPDATA;
37+
if (localAppData) {
38+
return path.join(localAppData, 'pypoetry', 'Cache', 'virtualenvs');
39+
}
40+
const appData = process.env.APPDATA;
41+
if (appData) {
42+
return path.join(appData, 'pypoetry', 'Cache', 'virtualenvs');
43+
}
44+
return undefined;
45+
}
46+
47+
const home = getUserHomeDir();
48+
if (!home) {
49+
return undefined;
50+
}
51+
52+
if (process.platform === 'darwin') {
53+
return path.join(home, 'Library', 'Caches', 'pypoetry', 'virtualenvs');
54+
}
55+
56+
// Linux default
57+
return path.join(home, '.cache', 'pypoetry', 'virtualenvs');
58+
}
59+
2860
export const POETRY_GLOBAL = 'Global';
2961

3062
export const POETRY_PATH_KEY = `${ENVS_EXTENSION_ID}:poetry:POETRY_PATH`;
@@ -182,10 +214,7 @@ export async function getPoetryVirtualenvsPath(poetryExe?: string): Promise<stri
182214
// Poetry might return the path with placeholders like {cache-dir}
183215
// If it doesn't start with / or C:\ etc., assume it's using default
184216
if (!path.isAbsolute(venvPath) || venvPath.includes('{')) {
185-
const home = getUserHomeDir();
186-
if (home) {
187-
poetryVirtualenvsPath = path.join(home, '.cache', 'pypoetry', 'virtualenvs');
188-
}
217+
poetryVirtualenvsPath = getDefaultPoetryVirtualenvsPath();
189218
} else {
190219
poetryVirtualenvsPath = venvPath;
191220
}
@@ -201,9 +230,8 @@ export async function getPoetryVirtualenvsPath(poetryExe?: string): Promise<stri
201230
}
202231

203232
// Fallback to default location
204-
const home = getUserHomeDir();
205-
if (home) {
206-
poetryVirtualenvsPath = path.join(home, '.cache', 'pypoetry', 'virtualenvs');
233+
poetryVirtualenvsPath = getDefaultPoetryVirtualenvsPath();
234+
if (poetryVirtualenvsPath) {
207235
await state.set(POETRY_VIRTUALENVS_PATH_KEY, poetryVirtualenvsPath);
208236
return poetryVirtualenvsPath;
209237
}
@@ -257,9 +285,9 @@ async function nativeToPythonEnv(
257285
isGlobalPoetryEnv = normalizedPrefix.startsWith(normalizedVirtualenvsPath);
258286
} else {
259287
// Fall back to checking the default location if we haven't cached the path yet
260-
const homeDir = getUserHomeDir();
261-
if (homeDir) {
262-
const defaultPath = path.normalize(path.join(homeDir, '.cache', 'pypoetry', 'virtualenvs'));
288+
const defaultVirtualenvsPath = getDefaultPoetryVirtualenvsPath();
289+
if (defaultVirtualenvsPath) {
290+
const defaultPath = path.normalize(defaultVirtualenvsPath);
263291
isGlobalPoetryEnv = normalizedPrefix.startsWith(defaultPath);
264292

265293
// Try to get the actual path asynchronously for next time
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import assert from 'assert';
2+
import * as path from 'path';
3+
import * as sinon from 'sinon';
4+
import * as platformUtils from '../../../common/utils/platformUtils';
5+
import * as pathUtils from '../../../common/utils/pathUtils';
6+
import { getDefaultPoetryVirtualenvsPath } from '../../../managers/poetry/poetryUtils';
7+
8+
suite('Poetry Utils - getDefaultPoetryVirtualenvsPath', () => {
9+
let isWindowsStub: sinon.SinonStub;
10+
let getUserHomeDirStub: sinon.SinonStub;
11+
let originalPlatform: PropertyDescriptor | undefined;
12+
let originalEnv: NodeJS.ProcessEnv;
13+
14+
setup(() => {
15+
isWindowsStub = sinon.stub(platformUtils, 'isWindows');
16+
getUserHomeDirStub = sinon.stub(pathUtils, 'getUserHomeDir');
17+
originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
18+
originalEnv = { ...process.env };
19+
});
20+
21+
teardown(() => {
22+
sinon.restore();
23+
if (originalPlatform) {
24+
Object.defineProperty(process, 'platform', originalPlatform);
25+
}
26+
process.env = originalEnv;
27+
});
28+
29+
test('should return Linux path on Linux', () => {
30+
isWindowsStub.returns(false);
31+
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
32+
getUserHomeDirStub.returns('/home/testuser');
33+
34+
const result = getDefaultPoetryVirtualenvsPath();
35+
36+
assert.strictEqual(result, path.join('/home/testuser', '.cache', 'pypoetry', 'virtualenvs'));
37+
});
38+
39+
test('should return macOS path on darwin', () => {
40+
isWindowsStub.returns(false);
41+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
42+
getUserHomeDirStub.returns('/Users/testuser');
43+
44+
const result = getDefaultPoetryVirtualenvsPath();
45+
46+
assert.strictEqual(result, path.join('/Users/testuser', 'Library', 'Caches', 'pypoetry', 'virtualenvs'));
47+
});
48+
49+
test('should return LOCALAPPDATA path on Windows when LOCALAPPDATA is set', () => {
50+
isWindowsStub.returns(true);
51+
process.env.LOCALAPPDATA = 'C:\\Users\\testuser\\AppData\\Local';
52+
process.env.APPDATA = 'C:\\Users\\testuser\\AppData\\Roaming';
53+
54+
const result = getDefaultPoetryVirtualenvsPath();
55+
56+
assert.strictEqual(
57+
result,
58+
path.join('C:\\Users\\testuser\\AppData\\Local', 'pypoetry', 'Cache', 'virtualenvs'),
59+
);
60+
});
61+
62+
test('should fall back to APPDATA on Windows when LOCALAPPDATA is not set', () => {
63+
isWindowsStub.returns(true);
64+
delete process.env.LOCALAPPDATA;
65+
process.env.APPDATA = 'C:\\Users\\testuser\\AppData\\Roaming';
66+
67+
const result = getDefaultPoetryVirtualenvsPath();
68+
69+
assert.strictEqual(
70+
result,
71+
path.join('C:\\Users\\testuser\\AppData\\Roaming', 'pypoetry', 'Cache', 'virtualenvs'),
72+
);
73+
});
74+
75+
test('should return undefined on Windows when neither LOCALAPPDATA nor APPDATA is set', () => {
76+
isWindowsStub.returns(true);
77+
delete process.env.LOCALAPPDATA;
78+
delete process.env.APPDATA;
79+
80+
const result = getDefaultPoetryVirtualenvsPath();
81+
82+
assert.strictEqual(result, undefined);
83+
});
84+
85+
test('should return undefined when home directory is not available on non-Windows', () => {
86+
isWindowsStub.returns(false);
87+
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
88+
getUserHomeDirStub.returns('');
89+
90+
const result = getDefaultPoetryVirtualenvsPath();
91+
92+
assert.strictEqual(result, undefined);
93+
});
94+
});

0 commit comments

Comments
 (0)