diff --git a/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap b/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap index 50d900323ed719e9a9de9761ec65a9bd8df17fb4..8021a8a0beb1146bb4535872bc1cc7e11a2bc4b0 100644 --- a/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap @@ -142,3 +142,108 @@ Array [ }, ] `; +exports[`.updateArtifacts() uses pipenv version from Pipfile 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/python", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_python -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_python --label=renovate_child --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/pipenv\\":\\"/tmp/renovate/cache/others/pipenv\\" -e PIPENV_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/python bash -l -c \\"pip install --user pipenv==2020.8.13 && pipenv lock\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; +exports[`.updateArtifacts() uses pipenv version from Pipfile dev packages 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/python", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_python -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_python --label=renovate_child --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/pipenv\\":\\"/tmp/renovate/cache/others/pipenv\\" -e PIPENV_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/python bash -l -c \\"pip install --user pipenv==2020.8.13 && pipenv lock\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; +exports[`.updateArtifacts() uses pipenv version from config 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/python", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_python -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_python --label=renovate_child --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/pipenv\\":\\"/tmp/renovate/cache/others/pipenv\\" -e PIPENV_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/python bash -l -c \\"pip install --user pipenv==2020.1.1 && pipenv lock\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; diff --git a/lib/manager/pipenv/__snapshots__/extract.spec.ts.snap b/lib/manager/pipenv/__snapshots__/extract.spec.ts.snap index e13c78182bd244cb50cf6085999d93f64528981a..5148a4d98ed40161c907c1b8ee1e503242fc819f 100644 --- a/lib/manager/pipenv/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/pipenv/__snapshots__/extract.spec.ts.snap @@ -2,6 +2,9 @@ exports[`lib/manager/pipenv/extract extractPackageFile() extracts dependencies 1`] = ` Object { + "constraints": Object { + "python": "== 3.6.*", + }, "deps": Array [ Object { "currentValue": "==0.3.1", @@ -57,6 +60,9 @@ Object { exports[`lib/manager/pipenv/extract extractPackageFile() extracts example pipfile 1`] = ` Object { + "constraints": Object { + "python": "== 2.7.*", + }, "deps": Array [ Object { "depName": "requests", @@ -126,6 +132,9 @@ Object { exports[`lib/manager/pipenv/extract extractPackageFile() extracts multiple dependencies 1`] = ` Object { + "constraints": Object { + "python": "== 3.6.*", + }, "deps": Array [ Object { "currentValue": "==1", @@ -171,6 +180,7 @@ Object { exports[`lib/manager/pipenv/extract extractPackageFile() supports custom index 1`] = ` Object { + "constraints": Object {}, "deps": Array [ Object { "currentValue": "==0.21.0", diff --git a/lib/manager/pipenv/artifacts.spec.ts b/lib/manager/pipenv/artifacts.spec.ts index 041b4f21f93b86d16cecb26616bec147f7114d1d..5ba8de38afd03060fb22f6139693b10f82d07343 100644 --- a/lib/manager/pipenv/artifacts.spec.ts +++ b/lib/manager/pipenv/artifacts.spec.ts @@ -43,7 +43,11 @@ describe('.updateArtifacts()', () => { await setUtilConfig(config); docker.resetPrefetchedImages(); - pipFileLock = { _meta: { requires: {} } }; + pipFileLock = { + _meta: { requires: {} }, + default: { pipenv: {} }, + develop: { pipenv: {} }, + }; }); it('returns if no Pipfile.lock found', async () => { @@ -153,4 +157,64 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('uses pipenv version from Pipfile', async () => { + jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); + await setUtilConfig(dockerConfig); + pipFileLock.default.pipenv.version = '==2020.8.13'; + fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValue({ + modified: ['Pipfile.lock'], + } as StatusResult); + fs.readFile.mockReturnValueOnce('new lock' as any); + expect( + await pipenv.updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some new content', + config: dockerConfig, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + it('uses pipenv version from Pipfile dev packages', async () => { + jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); + await setUtilConfig(dockerConfig); + pipFileLock.develop.pipenv.version = '==2020.8.13'; + fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValue({ + modified: ['Pipfile.lock'], + } as StatusResult); + fs.readFile.mockReturnValueOnce('new lock' as any); + expect( + await pipenv.updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some new content', + config: dockerConfig, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + it('uses pipenv version from config', async () => { + jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); + await setUtilConfig(dockerConfig); + pipFileLock.default.pipenv.version = '==2020.8.13'; + fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValue({ + modified: ['Pipfile.lock'], + } as StatusResult); + fs.readFile.mockReturnValueOnce('new lock' as any); + expect( + await pipenv.updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some new content', + config: { ...dockerConfig, constraints: { pipenv: '==2020.1.1' } }, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); }); diff --git a/lib/manager/pipenv/artifacts.ts b/lib/manager/pipenv/artifacts.ts index 3359e60288b441c8f7beb78be04f8070120e668f..2aad40ef83c3cd4511fc7e87ed6c14a29d78f4a2 100644 --- a/lib/manager/pipenv/artifacts.ts +++ b/lib/manager/pipenv/artifacts.ts @@ -1,3 +1,4 @@ +import { quote } from 'shlex'; import { logger } from '../../logger'; import { ExecOptions, exec } from '../../util/exec'; import { @@ -41,6 +42,33 @@ function getPythonConstraint( return undefined; } +function getPipenvConstraint( + existingLockFileContent: string, + config: UpdateArtifactsConfig +): string | null { + const { constraints = {} } = config; + const { pipenv } = constraints; + + if (pipenv) { + logger.debug('Using pipenv constraint from config'); + return pipenv; + } + try { + const pipfileLock = JSON.parse(existingLockFileContent); + if (pipfileLock?.default?.pipenv?.version) { + const pipenvVersion: string = pipfileLock.default.pipenv.version; + return pipenvVersion; + } + if (pipfileLock?.develop?.pipenv?.version) { + const pipenvVersion: string = pipfileLock.develop.pipenv.version; + return pipenvVersion; + } + } catch (err) { + // Do nothing + } + return ''; +} + export async function updateArtifacts({ packageFileName: pipfileName, newPackageFileContent: newPipfileContent, @@ -64,6 +92,10 @@ export async function updateArtifacts({ } const cmd = 'pipenv lock'; const tagConstraint = getPythonConstraint(existingLockFileContent, config); + const pipenvConstraint = getPipenvConstraint( + existingLockFileContent, + config + ); const execOptions: ExecOptions = { extraEnv: { PIPENV_CACHE_DIR: cacheDir, @@ -72,7 +104,9 @@ export async function updateArtifacts({ image: 'renovate/python', tagConstraint, tagScheme: 'pep440', - preCommands: ['pip install --user pipenv'], + preCommands: [ + `pip install --user ${quote(`pipenv${pipenvConstraint}`)}`, + ], volumes: [cacheDir], }, }; diff --git a/lib/manager/pipenv/extract.spec.ts b/lib/manager/pipenv/extract.spec.ts index a86690765be280321e5d466264fe3412fdd67115..86879eb34e5a0b917ba5cbbec891885f7ba6dbc1 100644 --- a/lib/manager/pipenv/extract.spec.ts +++ b/lib/manager/pipenv/extract.spec.ts @@ -89,5 +89,29 @@ describe('lib/manager/pipenv/extract', () => { expect(res.deps[0].registryUrls).toBeDefined(); expect(res.deps[0].registryUrls).toHaveLength(1); }); + it('gets python constraint from python_version', () => { + const content = + '[packages]\r\nfoo = "==1.0.0"\r\n' + + '[requires]\r\npython_version = "3.8"'; + const res = extractPackageFile(content); + expect(res.constraints.python).toEqual('== 3.8.*'); + }); + it('gets python constraint from python_full_version', () => { + const content = + '[packages]\r\nfoo = "==1.0.0"\r\n' + + '[requires]\r\npython_full_version = "3.8.6"'; + const res = extractPackageFile(content); + expect(res.constraints.python).toEqual('== 3.8.6'); + }); + it('gets pipenv constraint from packages', () => { + const content = '[packages]\r\npipenv = "==2020.8.13"'; + const res = extractPackageFile(content); + expect(res.constraints.pipenv).toEqual('==2020.8.13'); + }); + it('gets pipenv constraint from dev-packages', () => { + const content = '[dev-packages]\r\npipenv = "==2020.8.13"'; + const res = extractPackageFile(content); + expect(res.constraints.pipenv).toEqual('==2020.8.13'); + }); }); }); diff --git a/lib/manager/pipenv/extract.ts b/lib/manager/pipenv/extract.ts index ff40dfd6ec2a1d26a9079968c2baa1335091a3da..3754a3888929f1b3a491f616a26d409a3adb3acf 100644 --- a/lib/manager/pipenv/extract.ts +++ b/lib/manager/pipenv/extract.ts @@ -25,6 +25,7 @@ interface PipFile { packages?: Record<string, PipRequirement>; 'dev-packages'?: Record<string, PipRequirement>; + requires?: Record<string, string>; } interface PipRequirement { @@ -136,8 +137,24 @@ export function extractPackageFile(content: string): PackageFile | null { ...extractFromSection(pipfile, 'packages'), ...extractFromSection(pipfile, 'dev-packages'), ]; - if (res.deps.length) { - return res; + if (!res.deps.length) { + return null; + } + + const constraints: Record<string, any> = {}; + + if (is.nonEmptyString(pipfile.requires?.python_version)) { + constraints.python = `== ${pipfile.requires.python_version}.*`; + } else if (is.nonEmptyString(pipfile.requires?.python_full_version)) { + constraints.python = `== ${pipfile.requires.python_full_version}`; } - return null; + + if (is.nonEmptyString(pipfile.packages?.pipenv)) { + constraints.pipenv = pipfile.packages.pipenv; + } else if (is.nonEmptyString(pipfile['dev-packages']?.pipenv)) { + constraints.pipenv = pipfile['dev-packages'].pipenv; + } + + res.constraints = constraints; + return res; }