mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 12:57:47 +00:00
[python] Add support for pip3.10 and pip3.11 in vc build (#10648)
This commit is contained in:
5
.changeset/polite-peaches-boil.md
Normal file
5
.changeset/polite-peaches-boil.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@vercel/python': minor
|
||||
---
|
||||
|
||||
Add support for pip3.10 and pip3.11
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }))
|
||||
);
|
||||
}
|
||||
|
||||
67
packages/python/test/unit.test.ts
vendored
67
packages/python/test/unit.test.ts
vendored
@@ -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}`;
|
||||
}
|
||||
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -1305,21 +1305,33 @@ importers:
|
||||
'@types/execa':
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.0
|
||||
'@types/fs-extra':
|
||||
specifier: 11.0.2
|
||||
version: 11.0.2
|
||||
'@types/jest':
|
||||
specifier: 27.4.1
|
||||
version: 27.4.1
|
||||
'@types/node':
|
||||
specifier: 14.18.33
|
||||
version: 14.18.33
|
||||
'@types/which':
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
'@vercel/build-utils':
|
||||
specifier: 7.2.2
|
||||
version: link:../build-utils
|
||||
execa:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
fs-extra:
|
||||
specifier: 11.1.1
|
||||
version: 11.1.1
|
||||
jest-junit:
|
||||
specifier: 16.0.0
|
||||
version: 16.0.0
|
||||
which:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
|
||||
packages/redwood:
|
||||
dependencies:
|
||||
@@ -4193,7 +4205,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/http-cache-semantics': 4.0.1
|
||||
'@types/keyv': 3.1.4
|
||||
'@types/node': 14.18.33
|
||||
'@types/node': 16.18.11
|
||||
'@types/responselike': 1.0.0
|
||||
dev: false
|
||||
|
||||
@@ -4315,6 +4327,13 @@ packages:
|
||||
'@types/node': 16.18.11
|
||||
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:
|
||||
resolution: {integrity: sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==}
|
||||
dependencies:
|
||||
@@ -4493,7 +4512,7 @@ packages:
|
||||
/@types/keyv@3.1.4:
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
dependencies:
|
||||
'@types/node': 14.18.33
|
||||
'@types/node': 16.18.11
|
||||
dev: false
|
||||
|
||||
/@types/load-json-file@2.0.7:
|
||||
@@ -4669,7 +4688,7 @@ packages:
|
||||
/@types/responselike@1.0.0:
|
||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||
dependencies:
|
||||
'@types/node': 14.18.33
|
||||
'@types/node': 16.18.11
|
||||
dev: false
|
||||
|
||||
/@types/retry@0.12.2:
|
||||
@@ -9139,6 +9158,15 @@ packages:
|
||||
jsonfile: 6.1.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:
|
||||
resolution: {integrity: sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==}
|
||||
engines: {node: '>=6 <7 || >=8'}
|
||||
|
||||
Reference in New Issue
Block a user