Add platform-specific default paths for Poetry cache and virtualenvs#1218
Add platform-specific default paths for Poetry cache and virtualenvs#1218karthiknadig wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates Poetry environment discovery to use platform-correct default cache/virtualenvs locations and adds logic to resolve Poetry’s {cache-dir} placeholder (addressing issues #1182 and #1184).
Changes:
- Add
getDefaultPoetryCacheDir()/getDefaultPoetryVirtualenvsPath()and use them as fallbacks for Poetry virtualenv discovery. - Add
{cache-dir}placeholder resolution viapoetry config cache-dir. - Add
isMac()helper and unit tests for the new default-path helpers.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/managers/poetry/poetryUtils.ts | Implements platform-specific defaults and {cache-dir} resolution for Poetry virtualenv paths. |
| src/common/utils/platformUtils.ts | Adds isMac() platform helper. |
| src/test/managers/poetry/poetryUtils.unit.test.ts | Adds unit tests for the new default Poetry cache/virtualenv path helpers. |
Comments suppressed due to low confidence (2)
src/managers/poetry/poetryUtils.ts:210
resolveVirtualenvsPath()can return the original string containing{cache-dir}when it can’t resolve the placeholder.getPoetryVirtualenvsPath()then persists that unresolved value to workspace state, which can permanently break discovery until the state is cleared. Consider returningundefined(or falling back togetDefaultPoetryVirtualenvsPath()) when the placeholder can’t be resolved, and only caching paths that are resolved (no{…}) and absolute.
This issue also appears on line 303 of the same file.
// Poetry might return the path with placeholders like {cache-dir}
// Resolve the placeholder if present
if (venvPath.includes('{cache-dir}')) {
poetryVirtualenvsPath = await resolveVirtualenvsPath(poetry, venvPath);
} else if (path.isAbsolute(venvPath)) {
poetryVirtualenvsPath = venvPath;
} else {
// Not an absolute path and no placeholder, use platform-specific default
poetryVirtualenvsPath = getDefaultPoetryVirtualenvsPath();
}
if (poetryVirtualenvsPath) {
await state.set(POETRY_VIRTUALENVS_PATH_KEY, poetryVirtualenvsPath);
return poetryVirtualenvsPath;
}
src/managers/poetry/poetryUtils.ts:311
- The “last resort” branch returns
virtualenvsPathunchanged even though it still contains{cache-dir}and is likely unusable. Since callers treat a non-empty string as valid and may cache it, it would be safer to returnundefined/throw, or to fall back togetDefaultPoetryVirtualenvsPath()so the rest of the code doesn’t propagate an invalid path.
// Fall back to platform-specific default cache dir
const defaultCacheDir = getDefaultPoetryCacheDir();
if (defaultCacheDir) {
return virtualenvsPath.replace('{cache-dir}', defaultCacheDir);
}
// Last resort: return the original path (will likely not be valid)
return virtualenvsPath;
}
| suite('getDefaultPoetryCacheDir', () => { | ||
| let isWindowsStub: sinon.SinonStub; | ||
| let isMacStub: sinon.SinonStub; | ||
| let getUserHomeDirStub: sinon.SinonStub; | ||
| let originalLocalAppData: string | undefined; | ||
| let originalAppData: string | undefined; | ||
|
|
||
| setup(() => { | ||
| isWindowsStub = sinon.stub(platformUtils, 'isWindows'); | ||
| isMacStub = sinon.stub(platformUtils, 'isMac'); | ||
| getUserHomeDirStub = sinon.stub(pathUtils, 'getUserHomeDir'); | ||
|
|
||
| // Save original env vars | ||
| originalLocalAppData = process.env.LOCALAPPDATA; | ||
| originalAppData = process.env.APPDATA; | ||
| }); | ||
|
|
||
| teardown(() => { | ||
| sinon.restore(); | ||
| // Restore original env vars | ||
| if (originalLocalAppData === undefined) { | ||
| delete process.env.LOCALAPPDATA; | ||
| } else { | ||
| process.env.LOCALAPPDATA = originalLocalAppData; | ||
| } | ||
| if (originalAppData === undefined) { | ||
| delete process.env.APPDATA; | ||
| } else { | ||
| process.env.APPDATA = originalAppData; | ||
| } | ||
| }); | ||
|
|
||
| test('Windows: uses LOCALAPPDATA when available', () => { | ||
| isWindowsStub.returns(true); | ||
| process.env.LOCALAPPDATA = 'C:\\Users\\test\\AppData\\Local'; | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, path.join('C:\\Users\\test\\AppData\\Local', 'pypoetry', 'Cache')); | ||
| }); | ||
|
|
||
| test('Windows: falls back to APPDATA when LOCALAPPDATA is not set', () => { | ||
| isWindowsStub.returns(true); | ||
| delete process.env.LOCALAPPDATA; | ||
| process.env.APPDATA = 'C:\\Users\\test\\AppData\\Roaming'; | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, path.join('C:\\Users\\test\\AppData\\Roaming', 'pypoetry', 'Cache')); | ||
| }); | ||
|
|
||
| test('Windows: returns undefined when neither LOCALAPPDATA nor APPDATA is set', () => { | ||
| isWindowsStub.returns(true); | ||
| delete process.env.LOCALAPPDATA; | ||
| delete process.env.APPDATA; | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, undefined); | ||
| }); | ||
|
|
||
| test('macOS: uses ~/Library/Caches/pypoetry', () => { | ||
| isWindowsStub.returns(false); | ||
| isMacStub.returns(true); | ||
| getUserHomeDirStub.returns('/Users/test'); | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, path.join('/Users/test', 'Library', 'Caches', 'pypoetry')); | ||
| }); | ||
|
|
||
| test('Linux: uses ~/.cache/pypoetry', () => { | ||
| isWindowsStub.returns(false); | ||
| isMacStub.returns(false); | ||
| getUserHomeDirStub.returns('/home/test'); | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, path.join('/home/test', '.cache', 'pypoetry')); | ||
| }); | ||
|
|
||
| test('returns undefined when home directory is not available (non-Windows)', () => { | ||
| isWindowsStub.returns(false); | ||
| getUserHomeDirStub.returns(undefined); | ||
|
|
||
| const result = getDefaultPoetryCacheDir(); | ||
|
|
||
| assert.strictEqual(result, undefined); | ||
| }); | ||
| }); | ||
|
|
||
| suite('getDefaultPoetryVirtualenvsPath', () => { | ||
| let isWindowsStub: sinon.SinonStub; | ||
| let isMacStub: sinon.SinonStub; | ||
| let getUserHomeDirStub: sinon.SinonStub; | ||
| let originalLocalAppData: string | undefined; | ||
|
|
||
| setup(() => { | ||
| isWindowsStub = sinon.stub(platformUtils, 'isWindows'); | ||
| isMacStub = sinon.stub(platformUtils, 'isMac'); | ||
| getUserHomeDirStub = sinon.stub(pathUtils, 'getUserHomeDir'); | ||
| originalLocalAppData = process.env.LOCALAPPDATA; | ||
| }); | ||
|
|
||
| teardown(() => { | ||
| sinon.restore(); | ||
| if (originalLocalAppData === undefined) { | ||
| delete process.env.LOCALAPPDATA; | ||
| } else { | ||
| process.env.LOCALAPPDATA = originalLocalAppData; | ||
| } | ||
| }); | ||
|
|
||
| test('appends virtualenvs to cache directory', () => { | ||
| isWindowsStub.returns(false); | ||
| isMacStub.returns(false); | ||
| getUserHomeDirStub.returns('/home/test'); | ||
|
|
||
| const result = getDefaultPoetryVirtualenvsPath(); | ||
|
|
||
| assert.strictEqual(result, path.join('/home/test', '.cache', 'pypoetry', 'virtualenvs')); | ||
| }); | ||
|
|
||
| test('Windows: returns correct virtualenvs path', () => { | ||
| isWindowsStub.returns(true); | ||
| process.env.LOCALAPPDATA = 'C:\\Users\\test\\AppData\\Local'; | ||
|
|
||
| const result = getDefaultPoetryVirtualenvsPath(); | ||
|
|
||
| assert.strictEqual(result, path.join('C:\\Users\\test\\AppData\\Local', 'pypoetry', 'Cache', 'virtualenvs')); | ||
| }); | ||
|
|
||
| test('macOS: returns correct virtualenvs path', () => { | ||
| isWindowsStub.returns(false); | ||
| isMacStub.returns(true); | ||
| getUserHomeDirStub.returns('/Users/test'); | ||
|
|
||
| const result = getDefaultPoetryVirtualenvsPath(); | ||
|
|
||
| assert.strictEqual(result, path.join('/Users/test', 'Library', 'Caches', 'pypoetry', 'virtualenvs')); | ||
| }); | ||
|
|
||
| test('returns undefined when cache dir is not available', () => { | ||
| isWindowsStub.returns(false); | ||
| getUserHomeDirStub.returns(undefined); | ||
|
|
||
| const result = getDefaultPoetryVirtualenvsPath(); | ||
|
|
||
| assert.strictEqual(result, undefined); | ||
| }); |
There was a problem hiding this comment.
New behavior was added for resolving {cache-dir} in virtualenvs.path, but the unit tests only cover the platform default path helpers. Please add coverage for the placeholder-resolution flow (e.g., virtualenvs.path contains {cache-dir}, poetry config cache-dir succeeds/fails, and ensure the result is not persisted when unresolved).
Fixes #1182
Fixes #1184