Add utils to extract python version from files

This commit is contained in:
Dario Curreri 2023-06-06 16:51:41 +02:00
parent 3e4f08959a
commit 5fe4cfd090
No known key found for this signature in database
3 changed files with 159 additions and 31 deletions

View File

@ -1,9 +1,17 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as io from '@actions/io';
import fs from 'fs';
import path from 'path';
import {
validateVersion,
validatePythonVersionFormatForPyPy,
isCacheFeatureAvailable
isCacheFeatureAvailable,
getVersionInputFromFile,
getVersionInputFromPlainFile,
getVersionInputFromTomlFile
} from '../src/utils';
jest.mock('@actions/cache');
@ -73,3 +81,48 @@ describe('isCacheFeatureAvailable', () => {
expect(isCacheFeatureAvailable()).toBe(true);
});
});
const tempDir = path.join(
__dirname,
'runner',
path.join(Math.random().toString(36).substring(7)),
'temp'
);
describe('Version from file test', () => {
it.each([getVersionInputFromPlainFile, getVersionInputFromFile])(
'Version from plain file test',
async _fn => {
await io.mkdirP(tempDir);
const pythonVersionFileName = 'python-version.file';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersionFileContent = '3.7';
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersionFileContent]);
}
);
it.each([getVersionInputFromTomlFile, getVersionInputFromFile])(
'Version from standard pyproject.toml test',
async _fn => {
await io.mkdirP(tempDir);
const pythonVersionFileName = 'pyproject.toml';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersion = '>=3.7';
const pythonVersionFileContent = `[project]\nrequires-python = "${pythonVersion}"`;
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]);
}
);
it.each([getVersionInputFromTomlFile, getVersionInputFromFile])(
'Version from poetry pyproject.toml test',
async _fn => {
await io.mkdirP(tempDir);
const pythonVersionFileName = 'pyproject.toml';
const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName);
const pythonVersion = '>=3.7';
const pythonVersionFileContent = `[tool.poetry.dependencies]\npython = "${pythonVersion}"`;
fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent);
expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]);
}
);
});

View File

@ -5,7 +5,14 @@ import * as path from 'path';
import * as os from 'os';
import fs from 'fs';
import {getCacheDistributor} from './cache-distributions/cache-factory';
import {isCacheFeatureAvailable, logWarning, IS_MAC} from './utils';
import {
isCacheFeatureAvailable,
logWarning,
IS_MAC,
getVersionInputFromFile,
getVersionInputFromPlainFile,
getVersionInputFromTomlFile
} from './utils';
function isPyPyVersion(versionSpec: string) {
return versionSpec.startsWith('pypy');
@ -22,43 +29,48 @@ async function cacheDependencies(cache: string, pythonVersion: string) {
await cacheDistributor.restoreCache();
}
function resolveVersionInput() {
const versions = core.getMultilineInput('python-version');
let versionFile = core.getInput('python-version-file');
function resolveVersionInputFromDefaultFile(): string[] {
const couples: [string, (versionFile: string) => string[]][] = [
['.python-version', getVersionInputFromPlainFile],
['pyproject.toml', getVersionInputFromTomlFile]
];
for (const [versionFile, _fn] of couples) {
logWarning(
`Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '${versionFile}' file.`
);
if (fs.existsSync(versionFile)) {
return _fn(versionFile);
} else {
logWarning(`${versionFile} doesn't exist.`);
}
}
return [];
}
if (versions.length && versionFile) {
function resolveVersionInput() {
let versions = core.getMultilineInput('python-version');
const versionFile = core.getInput('python-version-file');
if (versions.length) {
if (versionFile) {
core.warning(
'Both python-version and python-version-file inputs are specified, only python-version will be used.'
);
}
if (versions.length) {
return versions;
}
} else {
if (versionFile) {
if (!fs.existsSync(versionFile)) {
throw new Error(
`The specified python version file at: ${versionFile} doesn't exist.`
);
}
const version = fs.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return [version];
versions = getVersionInputFromFile(versionFile);
} else {
versions = resolveVersionInputFromDefaultFile();
}
}
logWarning(
"Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file."
);
versionFile = '.python-version';
if (fs.existsSync(versionFile)) {
const version = fs.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return [version];
}
logWarning(`${versionFile} doesn't exist.`);
versions = Array.from(versions, version => version.split(',').join(' '));
return versions;
}

View File

@ -4,6 +4,7 @@ import * as core from '@actions/core';
import fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import * as toml from 'toml';
import * as exec from '@actions/exec';
export const IS_WINDOWS = process.platform === 'win32';
@ -181,3 +182,65 @@ export async function getOSInfo() {
return osInfo;
}
}
/**
* Python version extracted from the TOML file.
* If the `project` key is present at the root level, the version is assumed to
* be specified according to PEP 621 in `project.requires-python`.
* Otherwise, if the `tool` key is present at the root level, the version is
* assumed to be specified using poetry under `tool.poetry.dependencies.python`.
* If none is present, returns an empty list.
*/
export function getVersionInputFromTomlFile(versionFile: string): string[] {
core.debug(`Trying to resolve version form ${versionFile}`);
const pyprojectFile = fs.readFileSync(versionFile, 'utf8');
const pyprojectConfig = toml.parse(pyprojectFile);
const versions = [];
if ('project' in pyprojectConfig) {
// standard project metadata (PEP 621)
const projectMetadata = pyprojectConfig['project'];
if ('requires-python' in projectMetadata) {
versions.push(projectMetadata['requires-python']);
}
} else {
// python poetry
if ('tool' in pyprojectConfig) {
const toolMetadata = pyprojectConfig['tool'];
if ('poetry' in toolMetadata) {
const poetryMetadata = toolMetadata['poetry'];
if ('dependencies' in poetryMetadata) {
const dependenciesMetadata = poetryMetadata['dependencies'];
if ('python' in dependenciesMetadata) {
versions.push(dependenciesMetadata['python']);
}
}
}
}
}
core.info(`Extracted ${versions} from ${versionFile}`);
return versions;
}
/**
* Python version extracted from a plain text file.
*/
export function getVersionInputFromPlainFile(versionFile: string): string[] {
core.debug(`Trying to resolve version form ${versionFile}`);
const version = fs.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return [version];
}
/**
* Python version extracted from a plain or TOML file.
*/
export function getVersionInputFromFile(versionFile: string): string[] {
if (versionFile.endsWith('.toml')) {
return getVersionInputFromTomlFile(versionFile);
} else {
return getVersionInputFromPlainFile(versionFile);
}
}