[python] Add support for pip3.10 and pip3.11 in vc build (#10648)

This commit is contained in:
Chris Barber
2023-10-11 14:53:54 -05:00
committed by GitHub
parent d595db6294
commit 913608de4d
5 changed files with 128 additions and 21 deletions

View File

@@ -0,0 +1,5 @@
---
'@vercel/python': minor
---
Add support for pip3.10 and pip3.11

View File

@@ -22,10 +22,14 @@
}, },
"devDependencies": { "devDependencies": {
"@types/execa": "^0.9.0", "@types/execa": "^0.9.0",
"@types/fs-extra": "11.0.2",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/node": "14.18.33", "@types/node": "14.18.33",
"@types/which": "3.0.0",
"@vercel/build-utils": "7.2.2", "@vercel/build-utils": "7.2.2",
"execa": "^1.0.0", "execa": "^1.0.0",
"jest-junit": "16.0.0" "fs-extra": "11.1.1",
"jest-junit": "16.0.0",
"which": "3.0.0"
} }
} }

View File

@@ -1,4 +1,5 @@
import { NowBuildError } from '@vercel/build-utils'; import { NowBuildError } from '@vercel/build-utils';
import which from 'which';
interface PythonVersion { interface PythonVersion {
version: string; version: string;
@@ -10,6 +11,18 @@ interface PythonVersion {
// The order must be most recent first // The order must be most recent first
const allOptions: PythonVersion[] = [ const allOptions: PythonVersion[] = [
{
version: '3.11',
pipPath: 'pip3.11',
pythonPath: 'python3.11',
runtime: 'python3.11',
},
{
version: '3.10',
pipPath: 'pip3.10',
pythonPath: 'python3.10',
runtime: 'python3.10',
},
{ {
version: '3.9', version: '3.9',
pipPath: 'pip3.9', pipPath: 'pip3.9',
@@ -34,6 +47,7 @@ function getDevPythonVersion(): PythonVersion {
runtime: 'python3', runtime: 'python3',
}; };
} }
export function getLatestPythonVersion({ export function getLatestPythonVersion({
isDev, isDev,
}: { }: {
@@ -42,7 +56,16 @@ export function getLatestPythonVersion({
if (isDev) { if (isDev) {
return getDevPythonVersion(); return getDevPythonVersion();
} }
return allOptions[0];
const selection = allOptions.find(isInstalled);
if (!selection) {
throw new NowBuildError({
code: 'PYTHON_NOT_FOUND',
link: 'http://vercel.link/python-version',
message: `Unable to find any supported Python versions.`,
});
}
return selection;
} }
export function getSupportedPythonVersion({ export function getSupportedPythonVersion({
@@ -55,10 +78,13 @@ export function getSupportedPythonVersion({
if (isDev) { if (isDev) {
return getDevPythonVersion(); return getDevPythonVersion();
} }
let selection = getLatestPythonVersion({ isDev: false }); let selection = getLatestPythonVersion({ isDev: false });
if (typeof pipLockPythonVersion === 'string') { if (typeof pipLockPythonVersion === 'string') {
const found = allOptions.find(o => o.version === pipLockPythonVersion); const found = allOptions.find(
o => o.version === pipLockPythonVersion && isInstalled(o)
);
if (found) { if (found) {
selection = found; selection = found;
} else { } else {
@@ -90,3 +116,10 @@ function isDiscontinued({ discontinueDate }: PythonVersion): boolean {
const today = Date.now(); const today = Date.now();
return discontinueDate !== undefined && discontinueDate.getTime() <= today; return discontinueDate !== undefined && discontinueDate.getTime() <= today;
} }
function isInstalled({ pipPath, pythonPath }: PythonVersion): boolean {
return (
Boolean(which.sync(pipPath, { nothrow: true })) &&
Boolean(which.sync(pythonPath, { nothrow: true }))
);
}

View File

@@ -1,8 +1,17 @@
import { getSupportedPythonVersion } from '../src/version'; import { getSupportedPythonVersion } from '../src/version';
import fs from 'fs-extra';
import path from 'path';
import { tmpdir } from 'os';
const tmpPythonDir = path.join(
tmpdir(),
`vc-test-python-${Math.floor(Math.random() * 1e6)}`
);
let warningMessages: string[]; let warningMessages: string[];
const originalConsoleWarn = console.warn; const originalConsoleWarn = console.warn;
const realDateNow = Date.now.bind(global.Date); const realDateNow = Date.now.bind(global.Date);
const origPath = process.env.PATH;
beforeEach(() => { beforeEach(() => {
warningMessages = []; warningMessages = [];
console.warn = m => { console.warn = m => {
@@ -13,15 +22,19 @@ beforeEach(() => {
afterEach(() => { afterEach(() => {
console.warn = originalConsoleWarn; console.warn = originalConsoleWarn;
global.Date.now = realDateNow; global.Date.now = realDateNow;
process.env.PATH = origPath;
if (fs.existsSync(tmpPythonDir)) {
fs.removeSync(tmpPythonDir);
}
}); });
it('should only match supported versions, otherwise throw an error', async () => { it('should only match supported versions, otherwise throw an error', () => {
expect( makeMockPython('3.9');
getSupportedPythonVersion({ pipLockPythonVersion: '3.9' }) const result = getSupportedPythonVersion({ pipLockPythonVersion: '3.9' });
).toHaveProperty('runtime', 'python3.9'); expect(result).toHaveProperty('runtime', 'python3.9');
}); });
it('should ignore minor version in vercel dev', async () => { it('should ignore minor version in vercel dev', () => {
expect( expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9', isDev: true }) getSupportedPythonVersion({ pipLockPythonVersion: '3.9', isDev: true })
).toHaveProperty('runtime', 'python3'); ).toHaveProperty('runtime', 'python3');
@@ -34,24 +47,34 @@ it('should ignore minor version in vercel dev', async () => {
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
}); });
it('should select latest version when no Piplock detected', async () => { it('should select latest supported installed version when no Piplock detected', () => {
expect( const result = getSupportedPythonVersion({ pipLockPythonVersion: undefined });
getSupportedPythonVersion({ pipLockPythonVersion: undefined }) expect(result).toHaveProperty('runtime');
).toHaveProperty('runtime', 'python3.9'); expect(result.runtime).toMatch(/^python3\.\d+$/);
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
}); });
it('should select latest version and warn when invalid Piplock detected', async () => { it('should select latest supported installed version and warn when invalid Piplock detected', () => {
expect( const result = getSupportedPythonVersion({ pipLockPythonVersion: '999' });
getSupportedPythonVersion({ pipLockPythonVersion: '999' }) expect(result).toHaveProperty('runtime');
).toHaveProperty('runtime', 'python3.9'); expect(result.runtime).toMatch(/^python3\.\d+$/);
expect(warningMessages).toStrictEqual([ expect(warningMessages).toStrictEqual([
'Warning: Python version "999" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version', 'Warning: Python version "999" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version',
]); ]);
}); });
it('should throw for discontinued versions', async () => { it('should throw if python not found', () => {
process.env.PATH = '.';
expect(() =>
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toThrow('Unable to find any supported Python versions.');
expect(warningMessages).toStrictEqual([]);
});
it('should throw for discontinued versions', () => {
global.Date.now = () => new Date('2022-07-31').getTime(); global.Date.now = () => new Date('2022-07-31').getTime();
makeMockPython('3.6');
expect(() => expect(() =>
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' }) getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toThrow( ).toThrow(
@@ -60,8 +83,9 @@ it('should throw for discontinued versions', async () => {
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
}); });
it('should warn for deprecated versions, soon to be discontinued', async () => { it('should warn for deprecated versions, soon to be discontinued', () => {
global.Date.now = () => new Date('2021-07-01').getTime(); global.Date.now = () => new Date('2021-07-01').getTime();
makeMockPython('3.6');
expect( expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' }) getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
@@ -70,3 +94,16 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
'Error: Python version "3.6" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build. http://vercel.link/python-version', 'Error: Python version "3.6" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build. http://vercel.link/python-version',
]); ]);
}); });
function makeMockPython(version: string) {
fs.mkdirSync(tmpPythonDir);
for (const name of ['python', 'pip']) {
const bin = path.join(
tmpPythonDir,
`${name}${version}${process.platform === 'win32' ? '.exe' : ''}`
);
fs.writeFileSync(bin, '');
fs.chmodSync(bin, 0o755);
}
process.env.PATH = `${tmpPythonDir}${path.delimiter}${process.env.PATH}`;
}

34
pnpm-lock.yaml generated
View File

@@ -1305,21 +1305,33 @@ importers:
'@types/execa': '@types/execa':
specifier: ^0.9.0 specifier: ^0.9.0
version: 0.9.0 version: 0.9.0
'@types/fs-extra':
specifier: 11.0.2
version: 11.0.2
'@types/jest': '@types/jest':
specifier: 27.4.1 specifier: 27.4.1
version: 27.4.1 version: 27.4.1
'@types/node': '@types/node':
specifier: 14.18.33 specifier: 14.18.33
version: 14.18.33 version: 14.18.33
'@types/which':
specifier: 3.0.0
version: 3.0.0
'@vercel/build-utils': '@vercel/build-utils':
specifier: 7.2.2 specifier: 7.2.2
version: link:../build-utils version: link:../build-utils
execa: execa:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
fs-extra:
specifier: 11.1.1
version: 11.1.1
jest-junit: jest-junit:
specifier: 16.0.0 specifier: 16.0.0
version: 16.0.0 version: 16.0.0
which:
specifier: 3.0.0
version: 3.0.0
packages/redwood: packages/redwood:
dependencies: dependencies:
@@ -4193,7 +4205,7 @@ packages:
dependencies: dependencies:
'@types/http-cache-semantics': 4.0.1 '@types/http-cache-semantics': 4.0.1
'@types/keyv': 3.1.4 '@types/keyv': 3.1.4
'@types/node': 14.18.33 '@types/node': 16.18.11
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: false dev: false
@@ -4315,6 +4327,13 @@ packages:
'@types/node': 16.18.11 '@types/node': 16.18.11
dev: true dev: true
/@types/fs-extra@11.0.2:
resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==}
dependencies:
'@types/jsonfile': 6.1.1
'@types/node': 16.18.11
dev: true
/@types/fs-extra@5.1.0: /@types/fs-extra@5.1.0:
resolution: {integrity: sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==} resolution: {integrity: sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==}
dependencies: dependencies:
@@ -4493,7 +4512,7 @@ packages:
/@types/keyv@3.1.4: /@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies: dependencies:
'@types/node': 14.18.33 '@types/node': 16.18.11
dev: false dev: false
/@types/load-json-file@2.0.7: /@types/load-json-file@2.0.7:
@@ -4669,7 +4688,7 @@ packages:
/@types/responselike@1.0.0: /@types/responselike@1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies: dependencies:
'@types/node': 14.18.33 '@types/node': 16.18.11
dev: false dev: false
/@types/retry@0.12.2: /@types/retry@0.12.2:
@@ -9139,6 +9158,15 @@ packages:
jsonfile: 6.1.0 jsonfile: 6.1.0
universalify: 2.0.0 universalify: 2.0.0
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
/fs-extra@7.0.0: /fs-extra@7.0.0:
resolution: {integrity: sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==} resolution: {integrity: sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==}
engines: {node: '>=6 <7 || >=8'} engines: {node: '>=6 <7 || >=8'}