Skip to content

Add platform-specific default paths for Poetry cache and virtualenvs#1218

Draft
karthiknadig wants to merge 1 commit intomainfrom
delicious-coyote
Draft

Add platform-specific default paths for Poetry cache and virtualenvs#1218
karthiknadig wants to merge 1 commit intomainfrom
delicious-coyote

Conversation

@karthiknadig
Copy link
Member

Fixes #1182
Fixes #1184

@karthiknadig karthiknadig added the bug Issue identified by VS Code Team member as probable bug label Feb 13, 2026
@karthiknadig karthiknadig requested a review from Copilot February 13, 2026 18:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 via poetry 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 returning undefined (or falling back to getDefaultPoetryVirtualenvsPath()) 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 virtualenvsPath unchanged 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 return undefined/throw, or to fall back to getDefaultPoetryVirtualenvsPath() 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;
}

Comment on lines +169 to +318
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);
});
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issue identified by VS Code Team member as probable bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Poetry: {cache-dir} placeholder not properly resolved in virtualenvs.path Poetry: Incorrect default virtualenvs path on Windows and macOS

1 participant