From b79caba6bdcea7e93f864e1dd2a11f29e3d23203 Mon Sep 17 00:00:00 2001 From: mahabaleshwars <147705296+mahabaleshwars@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:50:42 +0530 Subject: [PATCH] add support for .tool-versions file --- __tests__/utils.test.ts | 79 ++++++++++++++++++++++++++++++++++++++++- dist/setup/index.js | 41 +++++++++++++++++++-- docs/advanced-usage.md | 13 +++++-- src/utils.ts | 41 ++++++++++++++++++++- 4 files changed, 168 insertions(+), 6 deletions(-) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index eac39ab6..c398d13b 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -15,7 +15,8 @@ import { getNextPageUrl, isGhes, IS_WINDOWS, - getDownloadFileName + getDownloadFileName, + getVersionInputFromToolVersions } from '../src/utils'; jest.mock('@actions/cache'); @@ -139,6 +140,82 @@ describe('Version from file test', () => { expect(_fn(pythonVersionFilePath)).toEqual([]); } ); + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = 'python 3.9.10\nnodejs 16'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with comment', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = '# python 3.8\npython 3.9'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.9']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with whitespace', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = ' python 3.10 '; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.10']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with v prefix', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = 'python v3.9.10'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with v prefix', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = 'python pypy3.10-7.3.14'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['pypy3.10-7.3.14']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with v prefix', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = 'python 3.14.0a5t'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.14.0a5t']); + } + ); + + it.each([getVersionInputFromToolVersions])( + 'Version from .tool-versions with v prefix', + async _fn => { + const toolVersionFileName = '.tool-versions'; + const toolVersionFilePath = path.join(tempDir, toolVersionFileName); + const toolVersionContent = 'python 3.14t-dev'; + fs.writeFileSync(toolVersionFilePath, toolVersionContent); + expect(_fn(toolVersionFilePath)).toEqual(['3.14t-dev']); + } + ); }); describe('getNextPageUrl', () => { diff --git a/dist/setup/index.js b/dist/setup/index.js index 53196f67..088addf5 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -100453,7 +100453,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = 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.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; /* eslint no-unsafe-finally: "off" */ const cache = __importStar(__nccwpck_require__(5116)); const core = __importStar(__nccwpck_require__(7484)); @@ -100677,12 +100677,49 @@ function getVersionInputFromPlainFile(versionFile) { } exports.getVersionInputFromPlainFile = getVersionInputFromPlainFile; /** - * Python version extracted from a plain or TOML file. + * Python version extracted from a .tool-versions file. + */ +function getVersionInputFromToolVersions(versionFile) { + var _a; + if (!fs_1.default.existsSync(versionFile)) { + core.warning(`File ${versionFile} does not exist.`); + return []; + } + try { + const fileContents = fs_1.default.readFileSync(versionFile, 'utf8'); + const lines = fileContents.split('\n'); + const versions = []; + for (const line of lines) { + // Skip commented lines + if (line.trim().startsWith('#')) { + continue; + } + const match = line.match(/^\s*python\s*v?(?[^\s]+(?:\s*[-<>=!]+[^\s]+)*)\s*(-\s([^\s].*))?\s*$/); + if (match) { + return [((_a = match.groups) === null || _a === void 0 ? void 0 : _a.version.trim()) || '']; + } + } + if (versions.length === 0) { + core.warning(`No Python version found in ${versionFile}`); + } + return versions; + } + catch (error) { + core.error(`Error reading ${versionFile}: ${error.message}`); + return []; + } +} +exports.getVersionInputFromToolVersions = getVersionInputFromToolVersions; +/** + * Python version extracted from a plain, .tool-versions or TOML file. */ function getVersionInputFromFile(versionFile) { if (versionFile.endsWith('.toml')) { return getVersionInputFromTomlFile(versionFile); } + else if (versionFile.match('.tool-versions')) { + return getVersionInputFromToolVersions(versionFile); + } else { return getVersionInputFromPlainFile(versionFile); } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 3fe6296b..b2bc755a 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -253,9 +253,9 @@ jobs: ## Using the `python-version-file` input -`setup-python` action can read the Python or PyPy version from a version file. `python-version-file` input is used to specify the path to the version file. If the file that was supplied to `python-version-file` input doesn't exist, the action will fail with an error. +`setup-python` action can read Python or PyPy version from a version file. `python-version-file` input is used for specifying the path to the version file. If the file that was supplied to `python-version-file` input doesn't exist, the action will fail with error. ->In case both `python-version` and `python-version-file` inputs are supplied, the `python-version-file` input will be ignored due to its lower priority. +>In case both `python-version` and `python-version-file` inputs are supplied, the `python-version-file` input will be ignored due to its lower priority. The .tool-versions file supports version specifications in accordance with asdf standards, adhering to Semantic Versioning ([semver](https://semver.org)). ```yaml steps: @@ -275,6 +275,15 @@ steps: - run: python my_script.py ``` +```yaml +steps: +- uses: actions/checkout@v4 +- uses: actions/setup-python@v5 + with: + python-version-file: '.tool-versions' # Read python version from a file .tool-versions +- run: python my_script.py +``` + ## Check latest version The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability and if you want to ensure a specific `Python or PyPy` version is always used. diff --git a/src/utils.ts b/src/utils.ts index a6dab63e..1019e582 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -279,11 +279,50 @@ export function getVersionInputFromPlainFile(versionFile: string): string[] { } /** - * Python version extracted from a plain or TOML file. + * Python version extracted from a .tool-versions file. + */ +export function getVersionInputFromToolVersions(versionFile: string): string[] { + if (!fs.existsSync(versionFile)) { + core.warning(`File ${versionFile} does not exist.`); + return []; + } + + try { + const fileContents = fs.readFileSync(versionFile, 'utf8'); + const lines = fileContents.split('\n'); + const versions: string[] = []; + + for (const line of lines) { + // Skip commented lines + if (line.trim().startsWith('#')) { + continue; + } + const match = line.match( + /^\s*python\s*v?(?[^\s]+(?:\s*[-<>=!]+[^\s]+)*)\s*(-\s([^\s].*))?\s*$/ + ); + if (match) { + return [match.groups?.version.trim() || '']; + } + } + + if (versions.length === 0) { + core.warning(`No Python version found in ${versionFile}`); + } + + return versions; + } catch (error) { + core.error(`Error reading ${versionFile}: ${(error as Error).message}`); + return []; + } +} +/** + * Python version extracted from a plain, .tool-versions or TOML file. */ export function getVersionInputFromFile(versionFile: string): string[] { if (versionFile.endsWith('.toml')) { return getVersionInputFromTomlFile(versionFile); + } else if (versionFile.match('.tool-versions')) { + return getVersionInputFromToolVersions(versionFile); } else { return getVersionInputFromPlainFile(versionFile); }