diff --git a/lib/modules/manager/pipenv/artifacts.spec.ts b/lib/modules/manager/pipenv/artifacts.spec.ts index ca5692fcfd2756..ae859276ebfe9a 100644 --- a/lib/modules/manager/pipenv/artifacts.spec.ts +++ b/lib/modules/manager/pipenv/artifacts.spec.ts @@ -73,6 +73,7 @@ describe('modules/manager/pipenv/artifacts', () => { // python getPkgReleases.mockResolvedValueOnce({ releases: [ + { version: '3.6.5' }, { version: '3.7.6' }, { version: '3.8.5' }, { version: '3.9.1' }, @@ -263,7 +264,51 @@ describe('modules/manager/pipenv/artifacts', () => { it('supports install mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); - pipFileLock._meta!.requires!.python_full_version = '3.7.6'; + pipFileLock._meta!.requires!.python_version = '3.6'; + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + // pipenv + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '2023.1.2' }], + }); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValue( + partial({ + modified: ['Pipfile.lock'], + }), + ); + fs.readLocalFile.mockResolvedValueOnce('new lock'); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some new content', + config, + }), + ).not.toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.6.5' }, + { cmd: 'install-tool pipenv 2013.6.12' }, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + PIP_CACHE_DIR: pipCacheDir, + WORKON_HOME: virtualenvsCacheDir, + }, + }, + }, + ]); + }); + + it('defaults to latest if no lock constraints', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); @@ -290,7 +335,7 @@ describe('modules/manager/pipenv/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchObject([ - { cmd: 'install-tool python 3.7.6' }, + { cmd: 'install-tool python 3.10.2' }, { cmd: 'install-tool pipenv 2013.6.12' }, { cmd: 'pipenv lock', diff --git a/lib/modules/manager/pipenv/artifacts.ts b/lib/modules/manager/pipenv/artifacts.ts index 3b33eb2987daa8..86a517ce7d5657 100644 --- a/lib/modules/manager/pipenv/artifacts.ts +++ b/lib/modules/manager/pipenv/artifacts.ts @@ -1,3 +1,5 @@ +import is from '@sindresorhus/is'; +import semver from 'semver'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import type { HostRule } from '../../../types'; @@ -38,14 +40,17 @@ export function getPythonConstraint( logger.warn({ error: result.error }, 'Invalid Pipfile.lock'); return undefined; } - if (result.data._meta?.requires?.python_version) { - const pythonVersion = result.data._meta.requires.python_version; - return `== ${pythonVersion}.*`; - } + // Exact python version has been included since 2022.10.9. It is more specific than the major.minor version + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022109-2022-10-09 if (result.data._meta?.requires?.python_full_version) { const pythonFullVersion = result.data._meta.requires.python_full_version; return `== ${pythonFullVersion}`; } + // Before 2022.10.9, only the major.minor version was included + if (result.data._meta?.requires?.python_version) { + const pythonVersion = result.data._meta.requires.python_version; + return `== ${pythonVersion}.*`; + } } catch (err) { // Do nothing } @@ -76,6 +81,32 @@ export function getPipenvConstraint( if (result.data.develop?.pipenv?.version) { return result.data.develop.pipenv.version; } + // Exact python version has been included since 2022.10.9 + const pythonFullVersion = result.data._meta?.requires?.python_full_version; + if (is.string(pythonFullVersion) && semver.valid(pythonFullVersion)) { + // python_full_version was added after 3.6 was already deprecated, so it should be impossible to have a 3.6 version + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022109-2022-10-09 + if (semver.satisfies(pythonFullVersion, '3.7.*')) { + // Python 3.7 support was dropped in pipenv 2023.10.20 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#20231020-2023-10-20 + return '< 2023.10.20'; + } + // Future deprecations will go here + } + // Before 2022.10.9, only the major.minor version was included + const pythonVersion = result.data._meta?.requires?.python_version; + if (pythonVersion) { + if (pythonVersion === '3.6') { + // Python 3.6 was deprecated in 2022.4.20 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022420-2022-04-20 + return '< 2022.4.20'; + } + if (pythonVersion === '3.7') { + // Python 3.7 was deprecated in 2023.10.20 but we shouldn't reach here unless we are < 2022.10.9 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#20231020-2023-10-20 + return '< 2022.10.9'; + } + } } catch (err) { // Do nothing }