diff --git a/lib/manager/poetry/__snapshots__/artifacts.spec.ts.snap b/lib/manager/poetry/__snapshots__/artifacts.spec.ts.snap index daeea84f5851d7..89b376fad158dd 100644 --- a/lib/manager/poetry/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/poetry/__snapshots__/artifacts.spec.ts.snap @@ -100,7 +100,7 @@ Array [ }, }, Object { - "cmd": "docker run --rm --name=renovate_python --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/python:2.7.5 bash -l -c \\"pip install 'poetry>=1.0' && poetry update --lock --no-interaction dep1\\"", + "cmd": "docker run --rm --name=renovate_python --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/python:2.7.5 bash -l -c \\"install-tool poetry 1.2.0 && poetry update --lock --no-interaction dep1\\"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", @@ -135,7 +135,7 @@ Array [ }, }, Object { - "cmd": "docker run --rm --name=renovate_python --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/python:3.4.2 bash -l -c \\"pip install 'poetry>=1.1.2' setuptools 'poetry-dynamic-versioning>1' && poetry update --lock --no-interaction dep1\\"", + "cmd": "docker run --rm --name=renovate_python --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/python:3.4.2 bash -l -c \\"install-tool poetry 1.2.0 && poetry update --lock --no-interaction dep1\\"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", diff --git a/lib/manager/poetry/__snapshots__/extract.spec.ts.snap b/lib/manager/poetry/__snapshots__/extract.spec.ts.snap index 0cef52a3009249..702718ac84a0af 100644 --- a/lib/manager/poetry/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/poetry/__snapshots__/extract.spec.ts.snap @@ -533,9 +533,7 @@ Array [ exports[`manager/poetry/extract extractPackageFile() handles multiple constraint dependencies 1`] = ` Object { - "constraints": Object { - "poetry": "poetry>=1.1.2 setuptools poetry-dynamic-versioning", - }, + "constraints": Object {}, "deps": Array [ Object { "currentValue": "", diff --git a/lib/manager/poetry/artifacts.spec.ts b/lib/manager/poetry/artifacts.spec.ts index 02440b20e92d19..07e2cc5fff31b6 100644 --- a/lib/manager/poetry/artifacts.spec.ts +++ b/lib/manager/poetry/artifacts.spec.ts @@ -12,6 +12,7 @@ import * as _hostRules from '../../util/host-rules'; import type { UpdateArtifactsConfig } from '../types'; import { updateArtifacts } from './artifacts'; +const pyproject1toml = loadFixture('pyproject.1.toml'); const pyproject10toml = loadFixture('pyproject.10.toml'); jest.mock('fs-extra'); @@ -131,9 +132,19 @@ describe('manager/poetry/artifacts', () => { }); it('returns updated poetry.lock using docker', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + // poetry.lock fs.readFile.mockResolvedValueOnce('[metadata]\n' as any); const execSnapshots = mockExecAll(exec); fs.readFile.mockReturnValueOnce('New poetry.lock' as any); + // poetry + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.2.0' }, + ], + }); + // python datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '2.7.5' }, { version: '3.4.2' }], }); @@ -142,12 +153,11 @@ describe('manager/poetry/artifacts', () => { await updateArtifacts({ packageFileName: 'pyproject.toml', updatedDeps, - newPackageFileContent: '{}', + newPackageFileContent: pyproject1toml, config: { ...config, constraints: { python: '~2.7 || ^3.4', - poetry: 'poetry>=1.1.2 setuptools poetry-dynamic-versioning>1', }, }, }) @@ -156,11 +166,21 @@ describe('manager/poetry/artifacts', () => { }); it('returns updated poetry.lock using docker (constraints)', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + // poetry.lock fs.readFile.mockResolvedValueOnce( '[metadata]\npython-versions = "~2.7 || ^3.4"' as any ); const execSnapshots = mockExecAll(exec); fs.readFile.mockReturnValueOnce('New poetry.lock' as any); + // poetry + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.2.0' }, + ], + }); + // python datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '2.7.5' }, { version: '3.3.2' }], }); @@ -169,10 +189,10 @@ describe('manager/poetry/artifacts', () => { await updateArtifacts({ packageFileName: 'pyproject.toml', updatedDeps, - newPackageFileContent: '{}', + newPackageFileContent: pyproject1toml, config: { ...config, - constraints: { poetry: 'poetry>=1.0' }, + constraints: {}, }, }) ).not.toBeNull(); diff --git a/lib/manager/poetry/artifacts.ts b/lib/manager/poetry/artifacts.ts index 69da0cce0c4cad..69ad411f812351 100644 --- a/lib/manager/poetry/artifacts.ts +++ b/lib/manager/poetry/artifacts.ts @@ -4,7 +4,7 @@ import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; import { exec } from '../../util/exec'; -import type { ExecOptions } from '../../util/exec/types'; +import type { ExecOptions, ToolConstraint } from '../../util/exec/types'; import { deleteLocalFile, getSiblingFileName, @@ -12,6 +12,8 @@ import { writeLocalFile, } from '../../util/fs'; import { find } from '../../util/host-rules'; +import { regEx } from '../../util/regex'; +import { dependencyPattern } from '../pip_requirements/extract'; import type { UpdateArtifact, UpdateArtifactsConfig, @@ -41,6 +43,39 @@ function getPythonConstraint( return undefined; } +const pkgValRegex = regEx(`^${dependencyPattern}$`); + +function getPoetryRequirement(pyProjectContent: string): string | null { + try { + const pyproject: PoetryFile = parse(pyProjectContent); + // https://python-poetry.org/docs/pyproject/#poetry-and-pep-517 + const buildBackend = pyproject['build-system']?.['build-backend']; + if ( + (buildBackend === 'poetry.masonry.api' || + buildBackend === 'poetry.core.masonry.api') && + is.nonEmptyArray(pyproject['build-system']?.requires) + ) { + for (const requirement of pyproject['build-system'].requires) { + if (is.nonEmptyString(requirement)) { + const pkgValMatch = pkgValRegex.exec(requirement); + if (pkgValMatch) { + const [, depName, , currVal] = pkgValMatch; + if ( + (depName === 'poetry' || depName === 'poetry_core') && + currVal + ) { + return currVal.trim(); + } + } + } + } + } + } catch (err) { + logger.debug({ err }, 'Error parsing pyproject.toml file'); + } + return null; +} + function getPoetrySources(content: string, fileName: string): PoetrySource[] { let pyprojectFile: PoetryFile; try { @@ -125,13 +160,16 @@ export async function updateArtifacts({ ); } const tagConstraint = getPythonConstraint(existingLockFileContent, config); - const poetryRequirement = config.constraints?.poetry || 'poetry'; - const poetryInstall = - 'pip install ' + poetryRequirement.split(' ').map(quote).join(' '); + const constraint = + config.constraints?.poetry || getPoetryRequirement(newPackageFileContent); const extraEnv = getSourceCredentialVars( newPackageFileContent, packageFileName ); + const toolConstraint: ToolConstraint = { + toolName: 'poetry', + constraint, + }; const execOptions: ExecOptions = { cwdFile: packageFileName, @@ -141,7 +179,7 @@ export async function updateArtifacts({ tagConstraint, tagScheme: 'poetry', }, - preCommands: [poetryInstall], + toolConstraints: [toolConstraint], }; await exec(cmd, execOptions); const newPoetryLockContent = await readLocalFile(lockFileName, 'utf8'); diff --git a/lib/manager/poetry/extract.spec.ts b/lib/manager/poetry/extract.spec.ts index 066f1c10e22884..e720428a79ff69 100644 --- a/lib/manager/poetry/extract.spec.ts +++ b/lib/manager/poetry/extract.spec.ts @@ -40,7 +40,6 @@ describe('manager/poetry/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(9); expect(res.constraints).toEqual({ - poetry: 'poetry>=1.0 wheel', python: '~2.7 || ^3.4', }); }); diff --git a/lib/manager/poetry/extract.ts b/lib/manager/poetry/extract.ts index b158dd53bca769..22d4299d4614a2 100644 --- a/lib/manager/poetry/extract.ts +++ b/lib/manager/poetry/extract.ts @@ -149,13 +149,6 @@ export async function extractPackageFile( const constraints: Record = {}; - // https://python-poetry.org/docs/pyproject/#poetry-and-pep-517 - if ( - pyprojectfile['build-system']?.['build-backend'] === 'poetry.masonry.api' - ) { - constraints.poetry = pyprojectfile['build-system']?.requires.join(' '); - } - if (is.nonEmptyString(pyprojectfile.tool?.poetry?.dependencies?.python)) { constraints.python = pyprojectfile.tool?.poetry?.dependencies?.python; } diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts index 27cd762fc033e6..159a90843e14e8 100644 --- a/lib/util/exec/buildpack.ts +++ b/lib/util/exec/buildpack.ts @@ -5,6 +5,7 @@ import { logger } from '../../logger'; import * as allVersioning from '../../versioning'; import { id as composerVersioningId } from '../../versioning/composer'; import { id as npmVersioningId } from '../../versioning/npm'; +import { id as pep440VersioningId } from '../../versioning/pep440'; import { id as semverVersioningId } from '../../versioning/semver'; import type { ToolConfig, ToolConstraint } from './types'; @@ -35,6 +36,11 @@ const allToolConfig: Record = { depName: 'pnpm', versioning: npmVersioningId, }, + poetry: { + datasource: 'pypi', + depName: 'poetry', + versioning: pep440VersioningId, + }, }; export function supportsDynamicInstall(toolName: string): boolean {