[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

@@ -22,10 +22,14 @@
},
"devDependencies": {
"@types/execa": "^0.9.0",
"@types/fs-extra": "11.0.2",
"@types/jest": "27.4.1",
"@types/node": "14.18.33",
"@types/which": "3.0.0",
"@vercel/build-utils": "7.2.2",
"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 which from 'which';
interface PythonVersion {
version: string;
@@ -10,6 +11,18 @@ interface PythonVersion {
// The order must be most recent first
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',
pipPath: 'pip3.9',
@@ -34,6 +47,7 @@ function getDevPythonVersion(): PythonVersion {
runtime: 'python3',
};
}
export function getLatestPythonVersion({
isDev,
}: {
@@ -42,7 +56,16 @@ export function getLatestPythonVersion({
if (isDev) {
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({
@@ -55,10 +78,13 @@ export function getSupportedPythonVersion({
if (isDev) {
return getDevPythonVersion();
}
let selection = getLatestPythonVersion({ isDev: false });
if (typeof pipLockPythonVersion === 'string') {
const found = allOptions.find(o => o.version === pipLockPythonVersion);
const found = allOptions.find(
o => o.version === pipLockPythonVersion && isInstalled(o)
);
if (found) {
selection = found;
} else {
@@ -90,3 +116,10 @@ function isDiscontinued({ discontinueDate }: PythonVersion): boolean {
const today = Date.now();
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 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[];
const originalConsoleWarn = console.warn;
const realDateNow = Date.now.bind(global.Date);
const origPath = process.env.PATH;
beforeEach(() => {
warningMessages = [];
console.warn = m => {
@@ -13,15 +22,19 @@ beforeEach(() => {
afterEach(() => {
console.warn = originalConsoleWarn;
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 () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9' })
).toHaveProperty('runtime', 'python3.9');
it('should only match supported versions, otherwise throw an error', () => {
makeMockPython('3.9');
const result = getSupportedPythonVersion({ pipLockPythonVersion: '3.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(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9', isDev: true })
).toHaveProperty('runtime', 'python3');
@@ -34,24 +47,34 @@ it('should ignore minor version in vercel dev', async () => {
expect(warningMessages).toStrictEqual([]);
});
it('should select latest version when no Piplock detected', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: undefined })
).toHaveProperty('runtime', 'python3.9');
it('should select latest supported installed version when no Piplock detected', () => {
const result = getSupportedPythonVersion({ pipLockPythonVersion: undefined });
expect(result).toHaveProperty('runtime');
expect(result.runtime).toMatch(/^python3\.\d+$/);
expect(warningMessages).toStrictEqual([]);
});
it('should select latest version and warn when invalid Piplock detected', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '999' })
).toHaveProperty('runtime', 'python3.9');
it('should select latest supported installed version and warn when invalid Piplock detected', () => {
const result = getSupportedPythonVersion({ pipLockPythonVersion: '999' });
expect(result).toHaveProperty('runtime');
expect(result.runtime).toMatch(/^python3\.\d+$/);
expect(warningMessages).toStrictEqual([
'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();
makeMockPython('3.6');
expect(() =>
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toThrow(
@@ -60,8 +83,9 @@ it('should throw for discontinued versions', async () => {
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();
makeMockPython('3.6');
expect(
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',
]);
});
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}`;
}