From 60ed6ad40d4362bc325b8b8f713ed42bbc0f2f8c Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Fri, 12 Aug 2022 08:24:56 +0200 Subject: [PATCH] feat(versioning): add python versioning for buildpack (#17147) --- .../__snapshots__/artifacts.spec.ts.snap | 2 +- lib/modules/manager/pipenv/artifacts.spec.ts | 7 +- lib/modules/versioning/api.ts | 2 + lib/modules/versioning/python/index.spec.ts | 102 ++++++++++++++++++ lib/modules/versioning/python/index.ts | 57 ++++++++++ lib/modules/versioning/python/readme.md | 1 + lib/util/exec/buildpack.spec.ts | 4 +- lib/util/exec/buildpack.ts | 4 +- 8 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 lib/modules/versioning/python/index.spec.ts create mode 100644 lib/modules/versioning/python/index.ts create mode 100644 lib/modules/versioning/python/readme.md diff --git a/lib/modules/manager/pipenv/__snapshots__/artifacts.spec.ts.snap b/lib/modules/manager/pipenv/__snapshots__/artifacts.spec.ts.snap index 5137de4f93f826..5da4f90fe5a79f 100644 --- a/lib/modules/manager/pipenv/__snapshots__/artifacts.spec.ts.snap +++ b/lib/modules/manager/pipenv/__snapshots__/artifacts.spec.ts.snap @@ -111,7 +111,7 @@ Array [ }, }, Object { - "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e PIPENV_CACHE_DIR -e PIP_CACHE_DIR -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/sidecar bash -l -c \\"install-tool python 3.10.2 && pip install --user pipenv && pipenv lock\\"", + "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e PIPENV_CACHE_DIR -e PIP_CACHE_DIR -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/sidecar bash -l -c \\"install-tool python 3.7.6 && pip install --user pipenv && pipenv lock\\"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", diff --git a/lib/modules/manager/pipenv/artifacts.spec.ts b/lib/modules/manager/pipenv/artifacts.spec.ts index 2dd3caa80d12d7..155a721abd5eb0 100644 --- a/lib/modules/manager/pipenv/artifacts.spec.ts +++ b/lib/modules/manager/pipenv/artifacts.spec.ts @@ -53,6 +53,7 @@ describe('modules/manager/pipenv/artifacts', () => { // python getPkgReleases.mockResolvedValueOnce({ releases: [ + { version: '3.7.6' }, { version: '3.8.5' }, { version: '3.9.1' }, { version: '3.10.2' }, @@ -123,7 +124,7 @@ describe('modules/manager/pipenv/artifacts', () => { packageFileName: 'Pipfile', updatedDeps: [], newPackageFileContent: 'some new content', - config: { ...config, constraints: { python: '3.7' } }, + config: { ...config, constraints: { python: '== 3.8.*' } }, }) ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); @@ -155,7 +156,7 @@ describe('modules/manager/pipenv/artifacts', () => { it('supports install mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); - pipFileLock._meta.requires.python_version = '3.7'; + pipFileLock._meta.requires.python_full_version = '3.7.6'; fs.ensureCacheDir.mockResolvedValueOnce( '/tmp/renovate/cache/others/pipenv' ); @@ -175,7 +176,7 @@ describe('modules/manager/pipenv/artifacts', () => { }) ).not.toBeNull(); expect(execSnapshots).toMatchObject([ - { cmd: 'install-tool python 3.10.2' }, + { cmd: 'install-tool python 3.7.6' }, { cmd: 'pip install --user pipenv' }, { cmd: 'pipenv lock', options: { cwd: '/tmp/github/some/repo' } }, ]); diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts index 67847e90b9242a..7dfb74d21935f2 100644 --- a/lib/modules/versioning/api.ts +++ b/lib/modules/versioning/api.ts @@ -19,6 +19,7 @@ import * as nuget from './nuget'; import * as pep440 from './pep440'; import * as perl from './perl'; import * as poetry from './poetry'; +import * as python from './python'; import * as redhat from './redhat'; import * as regex from './regex'; import * as rez from './rez'; @@ -53,6 +54,7 @@ api.set(nuget.id, nuget.api); api.set(pep440.id, pep440.api); api.set(perl.id, perl.api); api.set(poetry.id, poetry.api); +api.set(python.id, python.api); api.set(redhat.id, redhat.api); api.set(regex.id, regex.api); api.set(rez.id, rez.api); diff --git a/lib/modules/versioning/python/index.spec.ts b/lib/modules/versioning/python/index.spec.ts new file mode 100644 index 00000000000000..7cd3b9edfa11fb --- /dev/null +++ b/lib/modules/versioning/python/index.spec.ts @@ -0,0 +1,102 @@ +import { partial } from '../../../../test/util'; +import type { NewValueConfig } from '../types'; +import { api as versioning } from '.'; + +describe('modules/versioning/python/index', () => { + test.each` + version | expected + ${'17.04.00'} | ${true} + ${'17.b4.0'} | ${false} + ${'1.2.3'} | ${true} + ${'1.2.3-foo'} | ${true} + ${'1.2.3foo'} | ${false} + ${'1.2.3a0'} | ${true} + ${'1.2.3b1'} | ${true} + ${'1.2.3rc23'} | ${true} + ${'*'} | ${true} + ${'~1.2.3'} | ${true} + ${'^1.2.3'} | ${true} + ${'>1.2.3'} | ${true} + ${'~=1.9'} | ${true} + ${'==1.9'} | ${true} + ${'===1.9.4'} | ${true} + ${'renovatebot/renovate'} | ${false} + ${'renovatebot/renovate#master'} | ${false} + ${'https://github.com/renovatebot/renovate.git'} | ${false} + `('isValid("$version") === $expected', ({ version, expected }) => { + expect(!!versioning.isValid(version)).toBe(expected); + }); + + test.each` + version | range | expected + ${'4.2.0'} | ${'4.2, >= 3.0, < 5.0.0'} | ${true} + ${'4.2.0'} | ${'2.0, >= 3.0, < 5.0.0'} | ${false} + ${'4.2.2'} | ${'4.2.0, < 4.2.4'} | ${false} + ${'4.2.2'} | ${'^4.2.0, < 4.2.4'} | ${true} + ${'4.2.0'} | ${'4.3.0, 3.0.0'} | ${false} + ${'4.2.0'} | ${'> 5.0.0, <= 6.0.0'} | ${false} + ${'4.2.0'} | ${'*'} | ${true} + ${'1.9.4'} | ${'==1.9'} | ${true} + ${'1.9.4'} | ${'===1.9.4'} | ${true} + ${'1.9.4'} | ${'===1.9.3'} | ${false} + ${'0.8.0a1'} | ${'^0.8.0-alpha.0'} | ${true} + ${'0.7.4'} | ${'^0.8.0-alpha.0'} | ${false} + ${'1.4'} | ${'1.4'} | ${true} + ${'1.4.5'} | ${'== 1.4.*'} | ${true} + ${'1.5.5'} | ${'== 1.4.*'} | ${false} + ${'1.4.5'} | ${'== 1.4.5'} | ${true} + ${'1.4.6'} | ${'== 1.4.5'} | ${false} + `( + 'matches("$version", "$range") === "$expected"', + ({ version, range, expected }) => { + expect(versioning.matches(version, range)).toBe(expected); + } + ); + + test.each` + version | range | expected + ${'0.9.0'} | ${'>= 1.0.0 <= 2.0.0'} | ${true} + ${'1.9.0'} | ${'>= 1.0.0 <= 2.0.0'} | ${false} + ${'1.9.0'} | ${'== 2.7.*'} | ${false} + `( + 'isLessThanRange("$version", "$range") === "$expected"', + ({ version, range, expected }) => { + expect(versioning.isLessThanRange?.(version, range)).toBe(expected); + } + ); + + test.each` + versions | range | expected + ${['0.4.0', '0.5.0', '4.2.0', '4.3.0', '5.0.0']} | ${'4.*, > 4.2'} | ${'4.3.0'} + ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'^4.0.0'} | ${'4.2.0'} + ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'^4.0.0, = 0.5.0'} | ${null} + ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'^4.0.0, > 4.1.0, <= 4.3.5'} | ${'4.2.0'} + ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'^6.2.0, 3.*'} | ${null} + ${['0.8.0a2', '0.8.0a7']} | ${'^0.8.0-alpha.0'} | ${'0.8.0-alpha.2'} + ${['1.0.0', '2.0.0']} | ${'^3.0.0'} | ${null} + ${['1.0.0', '2.0.0']} | ${'== 3.7.*'} | ${null} + `( + 'minSatisfyingVersion($versions, "$range") === $expected', + ({ versions, range, expected }) => { + expect(versioning.minSatisfyingVersion(versions, range)).toBe(expected); + } + ); + + test.each` + versions | range | expected + ${['4.2.1', '0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'4.*.0, < 4.2.5'} | ${'4.2.1'} + ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0', '5.0.3']} | ${'5.0, > 5.0.0'} | ${'5.0.3'} + ${['0.8.0a2', '0.8.0a7']} | ${'^0.8.0-alpha.0'} | ${'0.8.0-alpha.7'} + ${['1.0.0', '2.0.0']} | ${'^3.0.0'} | ${null} + ${['1.0.0', '2.0.0']} | ${'== 3.7.*'} | ${null} + `( + 'getSatisfyingVersion($versions, "$range") === $expected', + ({ versions, range, expected }) => { + expect(versioning.getSatisfyingVersion(versions, range)).toBe(expected); + } + ); + + test('getNewValue()', () => { + expect(versioning.getNewValue(partial({}))).toBeNull(); + }); +}); diff --git a/lib/modules/versioning/python/index.ts b/lib/modules/versioning/python/index.ts new file mode 100644 index 00000000000000..7128ae92d76b46 --- /dev/null +++ b/lib/modules/versioning/python/index.ts @@ -0,0 +1,57 @@ +import { api as pep440 } from '../pep440'; +import { api as poetry } from '../poetry'; +import type { NewValueConfig, VersioningApi } from '../types'; + +export const id = 'python'; +export const displayName = 'Python'; +export const urls = []; +export const supportsRanges = false; + +function isLessThanRange(version: string, range: string): boolean { + return poetry.isValid(range) + ? poetry.isLessThanRange!(version, range) + : pep440.isLessThanRange!(version, range); +} + +function isValid(input: string): boolean { + return poetry.isValid(input) || pep440.isValid(input); +} + +function matches(version: string, range: string): boolean { + return poetry.isValid(range) + ? poetry.matches(version, range) + : pep440.matches(version, range); +} + +function getSatisfyingVersion( + versions: string[], + range: string +): string | null { + return poetry.isValid(range) + ? poetry.getSatisfyingVersion(versions, range) + : pep440.getSatisfyingVersion(versions, range); +} + +function minSatisfyingVersion( + versions: string[], + range: string +): string | null { + return poetry.isValid(range) + ? poetry.minSatisfyingVersion(versions, range) + : pep440.minSatisfyingVersion(versions, range); +} + +function getNewValue(_: NewValueConfig): string | null { + return null; +} + +export const api: VersioningApi = { + ...poetry, + getNewValue, + getSatisfyingVersion, + isLessThanRange, + isValid, + matches, + minSatisfyingVersion, +}; +export default api; diff --git a/lib/modules/versioning/python/readme.md b/lib/modules/versioning/python/readme.md new file mode 100644 index 00000000000000..855c9071f5c53b --- /dev/null +++ b/lib/modules/versioning/python/readme.md @@ -0,0 +1 @@ +Python versioning is a little like a mix of PEP440 and Poetry SemVer. diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts index 53fa5406c61d86..42286dc8a2672a 100644 --- a/lib/util/exec/buildpack.spec.ts +++ b/lib/util/exec/buildpack.spec.ts @@ -132,9 +132,9 @@ describe('util/exec/buildpack', () => { ${'^3.9'} | ${'3.10.4'} ${'3.9.*'} | ${'3.9.1'} ${'>3.8,<3.10'} | ${'3.9.1'} - ${'==3.9.1'} | ${'3.10.4'} + ${'==3.9.*'} | ${'3.9.1'} `( - 'supports python ranges "$version" == "$expected"', + 'supports python ranges "$version" => "$expected"', async ({ version: constraint, expected }) => { datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts index c96e11adf8003c..da660dee0b2d2d 100644 --- a/lib/util/exec/buildpack.ts +++ b/lib/util/exec/buildpack.ts @@ -8,7 +8,7 @@ import { id as composerVersioningId } from '../../modules/versioning/composer'; import { id as nodeVersioningId } from '../../modules/versioning/node'; import { id as npmVersioningId } from '../../modules/versioning/npm'; import { id as pep440VersioningId } from '../../modules/versioning/pep440'; -import { id as poetryVersioningId } from '../../modules/versioning/poetry'; +import { id as pythonVersioningId } from '../../modules/versioning/python'; import { id as rubyVersioningId } from '../../modules/versioning/ruby'; import { id as semverVersioningId } from '../../modules/versioning/semver'; import { id as semverCoercedVersioningId } from '../../modules/versioning/semver-coerced'; @@ -94,7 +94,7 @@ const allToolConfig: Record = { python: { datasource: 'github-releases', depName: 'containerbase/python-prebuild', - versioning: poetryVersioningId, + versioning: pythonVersioningId, }, yarn: { datasource: 'npm',