Enhance reading from .python-version (#787)

* Enhance reading from .python-version

* Fix typos

* Fix lint

* Add built files

* Don't use EOL versions in `utils.test.ts`

* Fix Prettier

* Don't use unreleased versions in `utils.test.ts`

* Update versions in `utils.test.ts` again
This commit is contained in:
Kryštof Korb 2025-05-21 23:19:28 +02:00 committed by GitHub
parent a26af69be9
commit 5db1cf9a59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 40 deletions

View File

@ -10,7 +10,7 @@ import {
validatePythonVersionFormatForPyPy, validatePythonVersionFormatForPyPy,
isCacheFeatureAvailable, isCacheFeatureAvailable,
getVersionInputFromFile, getVersionInputFromFile,
getVersionInputFromPlainFile, getVersionsInputFromPlainFile,
getVersionInputFromTomlFile, getVersionInputFromTomlFile,
getNextPageUrl, getNextPageUrl,
isGhes, isGhes,
@ -24,10 +24,10 @@ jest.mock('@actions/core');
describe('validatePythonVersionFormatForPyPy', () => { describe('validatePythonVersionFormatForPyPy', () => {
it.each([ it.each([
['3.6', true], ['3.12', true],
['3.7', true], ['3.13', true],
['3.6.x', false], ['3.12.x', false],
['3.7.x', false], ['3.13.x', false],
['3.x', false], ['3.x', false],
['3', false] ['3', false]
])('%s -> %s', (input, expected) => { ])('%s -> %s', (input, expected) => {
@ -95,24 +95,52 @@ const tempDir = path.join(
); );
describe('Version from file test', () => { describe('Version from file test', () => {
it.each([getVersionInputFromPlainFile, getVersionInputFromFile])( it.each([getVersionsInputFromPlainFile, getVersionInputFromFile])(
'Version from plain file test', 'Version from plain file test',
async _fn => { async _fn => {
await io.mkdirP(tempDir); await io.mkdirP(tempDir);
const pythonVersionFileName = 'python-version.file'; const pythonVersionFileName = 'python-version.file';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersionFileContent = '3.7'; const pythonVersionFileContent = '3.13';
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersionFileContent]); expect(_fn(pythonVersionFilePath)).toEqual([pythonVersionFileContent]);
} }
); );
it.each([getVersionsInputFromPlainFile, getVersionInputFromFile])(
'Versions from multiline plain file test',
async _fn => {
await io.mkdirP(tempDir);
const pythonVersionFileName = 'python-version.file';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersionFileContent = '3.13\r\n3.12';
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual(['3.13', '3.12']);
}
);
it.each([getVersionsInputFromPlainFile, getVersionInputFromFile])(
'Version from complex plain file test',
async _fn => {
await io.mkdirP(tempDir);
const pythonVersionFileName = 'python-version.file';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersionFileContent =
'3.13/envs/virtualenv\r# 3.12\n3.11\r\n3.10\r\n 3.9 \r\n';
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([
'3.13',
'3.11',
'3.10',
'3.9'
]);
}
);
it.each([getVersionInputFromTomlFile, getVersionInputFromFile])( it.each([getVersionInputFromTomlFile, getVersionInputFromFile])(
'Version from standard pyproject.toml test', 'Version from standard pyproject.toml test',
async _fn => { async _fn => {
await io.mkdirP(tempDir); await io.mkdirP(tempDir);
const pythonVersionFileName = 'pyproject.toml'; const pythonVersionFileName = 'pyproject.toml';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersion = '>=3.7.0'; const pythonVersion = '>=3.13.0';
const pythonVersionFileContent = `[project]\nrequires-python = "${pythonVersion}"`; const pythonVersionFileContent = `[project]\nrequires-python = "${pythonVersion}"`;
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]); expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]);
@ -124,7 +152,7 @@ describe('Version from file test', () => {
await io.mkdirP(tempDir); await io.mkdirP(tempDir);
const pythonVersionFileName = 'pyproject.toml'; const pythonVersionFileName = 'pyproject.toml';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersion = '>=3.7.0'; const pythonVersion = '>=3.13.0';
const pythonVersionFileContent = `[tool.poetry.dependencies]\npython = "${pythonVersion}"`; const pythonVersionFileContent = `[tool.poetry.dependencies]\npython = "${pythonVersion}"`;
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]); expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]);
@ -145,9 +173,9 @@ describe('Version from file test', () => {
async _fn => { async _fn => {
const toolVersionFileName = '.tool-versions'; const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName); const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python 3.9.10\nnodejs 16'; const toolVersionContent = 'python 3.13.2\nnodejs 16';
fs.writeFileSync(toolVersionFilePath, toolVersionContent); fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']); expect(_fn(toolVersionFilePath)).toEqual(['3.13.2']);
} }
); );
@ -156,9 +184,9 @@ describe('Version from file test', () => {
async _fn => { async _fn => {
const toolVersionFileName = '.tool-versions'; const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName); const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = '# python 3.8\npython 3.9'; const toolVersionContent = '# python 3.13\npython 3.12';
fs.writeFileSync(toolVersionFilePath, toolVersionContent); fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9']); expect(_fn(toolVersionFilePath)).toEqual(['3.12']);
} }
); );
@ -167,9 +195,9 @@ describe('Version from file test', () => {
async _fn => { async _fn => {
const toolVersionFileName = '.tool-versions'; const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName); const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = ' python 3.10 '; const toolVersionContent = ' python 3.13 ';
fs.writeFileSync(toolVersionFilePath, toolVersionContent); fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.10']); expect(_fn(toolVersionFilePath)).toEqual(['3.13']);
} }
); );
@ -178,9 +206,9 @@ describe('Version from file test', () => {
async _fn => { async _fn => {
const toolVersionFileName = '.tool-versions'; const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName); const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python v3.9.10'; const toolVersionContent = 'python v3.13.2';
fs.writeFileSync(toolVersionFilePath, toolVersionContent); fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']); expect(_fn(toolVersionFilePath)).toEqual(['3.13.2']);
} }
); );
@ -189,9 +217,9 @@ describe('Version from file test', () => {
async _fn => { async _fn => {
const toolVersionFileName = '.tool-versions'; const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName); const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python pypy3.10-7.3.14'; const toolVersionContent = 'python pypy3.10-7.3.19';
fs.writeFileSync(toolVersionFilePath, toolVersionContent); fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['pypy3.10-7.3.14']); expect(_fn(toolVersionFilePath)).toEqual(['pypy3.10-7.3.19']);
} }
); );

37
dist/setup/index.js vendored
View File

@ -96929,7 +96929,7 @@ function cacheDependencies(cache, pythonVersion) {
} }
function resolveVersionInputFromDefaultFile() { function resolveVersionInputFromDefaultFile() {
const couples = [ const couples = [
['.python-version', utils_1.getVersionInputFromPlainFile] ['.python-version', utils_1.getVersionsInputFromPlainFile]
]; ];
for (const [versionFile, _fn] of couples) { for (const [versionFile, _fn] of couples) {
(0, utils_1.logWarning)(`Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '${versionFile}' file.`); (0, utils_1.logWarning)(`Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '${versionFile}' file.`);
@ -97066,7 +97066,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromToolVersions = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromToolVersions = exports.getVersionsInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
/* eslint no-unsafe-finally: "off" */ /* eslint no-unsafe-finally: "off" */
const cache = __importStar(__nccwpck_require__(5116)); const cache = __importStar(__nccwpck_require__(5116));
const core = __importStar(__nccwpck_require__(7484)); const core = __importStar(__nccwpck_require__(7484));
@ -97247,7 +97247,7 @@ function extractValue(obj, keys) {
* If none is present, returns an empty list. * If none is present, returns an empty list.
*/ */
function getVersionInputFromTomlFile(versionFile) { function getVersionInputFromTomlFile(versionFile) {
core.debug(`Trying to resolve version form ${versionFile}`); core.debug(`Trying to resolve version from ${versionFile}`);
let pyprojectFile = fs_1.default.readFileSync(versionFile, 'utf8'); let pyprojectFile = fs_1.default.readFileSync(versionFile, 'utf8');
// Normalize the line endings in the pyprojectFile // Normalize the line endings in the pyprojectFile
pyprojectFile = pyprojectFile.replace(/\r\n/g, '\n'); pyprojectFile = pyprojectFile.replace(/\r\n/g, '\n');
@ -97280,15 +97280,30 @@ function getVersionInputFromTomlFile(versionFile) {
} }
exports.getVersionInputFromTomlFile = getVersionInputFromTomlFile; exports.getVersionInputFromTomlFile = getVersionInputFromTomlFile;
/** /**
* Python version extracted from a plain text file. * Python versions extracted from a plain text file.
* - Resolves multiple versions from multiple lines.
* - Handles pyenv-virtualenv pointers (e.g. `3.10/envs/virtualenv`).
* - Ignores empty lines and lines starting with `#`
* - Trims whitespace.
*/ */
function getVersionInputFromPlainFile(versionFile) { function getVersionsInputFromPlainFile(versionFile) {
core.debug(`Trying to resolve version form ${versionFile}`); core.debug(`Trying to resolve versions from ${versionFile}`);
const version = fs_1.default.readFileSync(versionFile, 'utf8').trim(); const content = fs_1.default.readFileSync(versionFile, 'utf8').trim();
core.info(`Resolved ${versionFile} as ${version}`); const lines = content.split(/\r\n|\r|\n/);
return [version]; const versions = lines
.map(line => {
if (line.startsWith('#') || line.trim() === '') {
return undefined;
}
let version = line.trim();
version = version.split('/')[0];
return version;
})
.filter(version => version !== undefined);
core.info(`Resolved ${versionFile} as ${versions.join(', ')}`);
return versions;
} }
exports.getVersionInputFromPlainFile = getVersionInputFromPlainFile; exports.getVersionsInputFromPlainFile = getVersionsInputFromPlainFile;
/** /**
* Python version extracted from a .tool-versions file. * Python version extracted from a .tool-versions file.
*/ */
@ -97331,7 +97346,7 @@ function getVersionInputFromFile(versionFile) {
return getVersionInputFromToolVersions(versionFile); return getVersionInputFromToolVersions(versionFile);
} }
else { else {
return getVersionInputFromPlainFile(versionFile); return getVersionsInputFromPlainFile(versionFile);
} }
} }
exports.getVersionInputFromFile = getVersionInputFromFile; exports.getVersionInputFromFile = getVersionInputFromFile;

View File

@ -11,7 +11,7 @@ import {
logWarning, logWarning,
IS_MAC, IS_MAC,
getVersionInputFromFile, getVersionInputFromFile,
getVersionInputFromPlainFile getVersionsInputFromPlainFile
} from './utils'; } from './utils';
function isPyPyVersion(versionSpec: string) { function isPyPyVersion(versionSpec: string) {
@ -35,7 +35,7 @@ async function cacheDependencies(cache: string, pythonVersion: string) {
function resolveVersionInputFromDefaultFile(): string[] { function resolveVersionInputFromDefaultFile(): string[] {
const couples: [string, (versionFile: string) => string[]][] = [ const couples: [string, (versionFile: string) => string[]][] = [
['.python-version', getVersionInputFromPlainFile] ['.python-version', getVersionsInputFromPlainFile]
]; ];
for (const [versionFile, _fn] of couples) { for (const [versionFile, _fn] of couples) {
logWarning( logWarning(

View File

@ -228,7 +228,7 @@ function extractValue(obj: any, keys: string[]): string | undefined {
* If none is present, returns an empty list. * If none is present, returns an empty list.
*/ */
export function getVersionInputFromTomlFile(versionFile: string): string[] { export function getVersionInputFromTomlFile(versionFile: string): string[] {
core.debug(`Trying to resolve version form ${versionFile}`); core.debug(`Trying to resolve version from ${versionFile}`);
let pyprojectFile = fs.readFileSync(versionFile, 'utf8'); let pyprojectFile = fs.readFileSync(versionFile, 'utf8');
// Normalize the line endings in the pyprojectFile // Normalize the line endings in the pyprojectFile
@ -269,13 +269,28 @@ export function getVersionInputFromTomlFile(versionFile: string): string[] {
} }
/** /**
* Python version extracted from a plain text file. * Python versions extracted from a plain text file.
* - Resolves multiple versions from multiple lines.
* - Handles pyenv-virtualenv pointers (e.g. `3.10/envs/virtualenv`).
* - Ignores empty lines and lines starting with `#`
* - Trims whitespace.
*/ */
export function getVersionInputFromPlainFile(versionFile: string): string[] { export function getVersionsInputFromPlainFile(versionFile: string): string[] {
core.debug(`Trying to resolve version form ${versionFile}`); core.debug(`Trying to resolve versions from ${versionFile}`);
const version = fs.readFileSync(versionFile, 'utf8').trim(); const content = fs.readFileSync(versionFile, 'utf8').trim();
core.info(`Resolved ${versionFile} as ${version}`); const lines = content.split(/\r\n|\r|\n/);
return [version]; const versions = lines
.map(line => {
if (line.startsWith('#') || line.trim() === '') {
return undefined;
}
let version: string = line.trim();
version = version.split('/')[0];
return version;
})
.filter(version => version !== undefined) as string[];
core.info(`Resolved ${versionFile} as ${versions.join(', ')}`);
return versions;
} }
/** /**
@ -319,7 +334,7 @@ export function getVersionInputFromFile(versionFile: string): string[] {
} else if (versionFile.match('.tool-versions')) { } else if (versionFile.match('.tool-versions')) {
return getVersionInputFromToolVersions(versionFile); return getVersionInputFromToolVersions(versionFile);
} else { } else {
return getVersionInputFromPlainFile(versionFile); return getVersionsInputFromPlainFile(versionFile);
} }
} }