From 94d2dd50481b5e055b132b1bacd00e3eeb55226b Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 1 Feb 2023 14:44:29 -0800 Subject: [PATCH] Ensure editable install only when [build-system] is present pyproject.toml --- .../creation/provider/venvUtils.ts | 39 ++++++++++++------- .../creation/provider/venvUtils.unit.test.ts | 25 +++++++++--- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/client/pythonEnvironments/creation/provider/venvUtils.ts b/src/client/pythonEnvironments/creation/provider/venvUtils.ts index b677c2e40daa..234b2d1a8cb9 100644 --- a/src/client/pythonEnvironments/creation/provider/venvUtils.ts +++ b/src/client/pythonEnvironments/creation/provider/venvUtils.ts @@ -9,7 +9,7 @@ import { CancellationToken, QuickPickItem, RelativePattern, WorkspaceFolder } fr import { CreateEnv } from '../../../common/utils/localize'; import { showQuickPick } from '../../../common/vscodeApis/windowApis'; import { findFiles } from '../../../common/vscodeApis/workspaceApis'; -import { traceError, traceVerbose } from '../../../logging'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; const exclude = '**/{.venv*,.git,.nox,.tox,.conda,site-packages,__pypackages__}/**'; async function getPipRequirementsFiles( @@ -25,20 +25,27 @@ async function getPipRequirementsFiles( return files; } -async function getTomlOptionalDeps(tomlPath: string): Promise { - const content = await fs.readFile(tomlPath, 'utf-8'); - const extras: string[] = []; +function tomlParse(content: string): tomljs.JsonMap { try { - const toml = tomljs.parse(content); - if (toml.project && (toml.project as Record>)['optional-dependencies']) { - const deps = (toml.project as Record>>)['optional-dependencies']; - for (const key of Object.keys(deps)) { - extras.push(key); - } - } + return tomljs.parse(content); } catch (err) { traceError('Failed to parse `pyproject.toml`:', err); } + return {}; +} + +function tomlHasBuildSystem(toml: tomljs.JsonMap): boolean { + return toml['build-system'] !== undefined; +} + +function getTomlOptionalDeps(toml: tomljs.JsonMap): string[] { + const extras: string[] = []; + if (toml.project && (toml.project as tomljs.JsonMap)['optional-dependencies']) { + const deps = (toml.project as tomljs.JsonMap)['optional-dependencies']; + for (const key of Object.keys(deps)) { + extras.push(key); + } + } return extras; } @@ -109,12 +116,15 @@ export async function pickPackagesToInstall( let extras: string[] = []; let tomlExists = false; + let hasBuildSystem = false; if (await fs.pathExists(tomlPath)) { tomlExists = true; - extras = await getTomlOptionalDeps(tomlPath); + const toml = tomlParse(await fs.readFile(tomlPath, 'utf-8')); + extras = getTomlOptionalDeps(toml); + hasBuildSystem = tomlHasBuildSystem(toml); } - if (tomlExists) { + if (tomlExists && hasBuildSystem) { if (extras.length === 0) { return { installType: 'toml', installList: [], source: tomlPath }; } @@ -125,6 +135,9 @@ export async function pickPackagesToInstall( } return undefined; } + if (tomlExists) { + traceInfo('Create env: Found toml without optional dependencies or build system.'); + } traceVerbose('Looking for pip requirements.'); const requirementFiles = (await getPipRequirementsFiles(workspaceFolder, token))?.map((p) => diff --git a/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts index 5627feee598d..5ef001c985ad 100644 --- a/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts @@ -46,11 +46,26 @@ suite('Venv Utils test', () => { }); }); - test('Toml found with no optional deps', async () => { + test('Toml found with no build system', async () => { findFilesStub.resolves([]); pathExistsStub.resolves(true); readFileStub.resolves('[project]\nname = "spam"\nversion = "2020.0.0"\n'); + const actual = await pickPackagesToInstall(workspace1); + assert.isTrue(showQuickPickStub.notCalled); + assert.deepStrictEqual(actual, { + installType: 'none', + installList: [], + }); + }); + + test('Toml found with no optional deps', async () => { + findFilesStub.resolves([]); + pathExistsStub.resolves(true); + readFileStub.resolves( + '[project]\nname = "spam"\nversion = "2020.0.0"\n[build-system]\nrequires = ["setuptools ~= 58.0", "cython ~= 0.29.0"]', + ); + const actual = await pickPackagesToInstall(workspace1); assert.isTrue(showQuickPickStub.notCalled); assert.deepStrictEqual(actual, { @@ -64,7 +79,7 @@ suite('Venv Utils test', () => { findFilesStub.resolves([]); pathExistsStub.resolves(true); readFileStub.resolves( - '[project]\nname = "spam"\nversion = "2020.0.0"\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', + '[project]\nname = "spam"\nversion = "2020.0.0"\n[build-system]\nrequires = ["setuptools ~= 58.0", "cython ~= 0.29.0"]\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', ); showQuickPickStub.resolves(undefined); @@ -88,7 +103,7 @@ suite('Venv Utils test', () => { findFilesStub.resolves([]); pathExistsStub.resolves(true); readFileStub.resolves( - '[project]\nname = "spam"\nversion = "2020.0.0"\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', + '[project]\nname = "spam"\nversion = "2020.0.0"\n[build-system]\nrequires = ["setuptools ~= 58.0", "cython ~= 0.29.0"]\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', ); showQuickPickStub.resolves([]); @@ -116,7 +131,7 @@ suite('Venv Utils test', () => { findFilesStub.resolves([]); pathExistsStub.resolves(true); readFileStub.resolves( - '[project]\nname = "spam"\nversion = "2020.0.0"\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', + '[project]\nname = "spam"\nversion = "2020.0.0"\n[build-system]\nrequires = ["setuptools ~= 58.0", "cython ~= 0.29.0"]\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]', ); showQuickPickStub.resolves([{ label: 'doc' }]); @@ -144,7 +159,7 @@ suite('Venv Utils test', () => { findFilesStub.resolves([]); pathExistsStub.resolves(true); readFileStub.resolves( - '[project]\nname = "spam"\nversion = "2020.0.0"\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]\ncov = ["pytest-cov"]', + '[project]\nname = "spam"\nversion = "2020.0.0"\n[build-system]\nrequires = ["setuptools ~= 58.0", "cython ~= 0.29.0"]\n[project.optional-dependencies]\ntest = ["pytest"]\ndoc = ["sphinx", "furo"]\ncov = ["pytest-cov"]', ); showQuickPickStub.resolves([{ label: 'test' }, { label: 'cov' }]);