mirror of
				https://kkgithub.com/actions/setup-python.git
				synced 2025-10-31 10:41:52 +08:00 
			
		
		
		
	Add support for .tool-versions file in setup-python (#1043)
* add support for .tool-versions file * update regex * optimize code * update test-python.yml for .tool-versions * fix format-check errors * fix formatting in test-python.yml * Fix test-python.yml error * workflow update with latest versions * update test cases * fix lint issue
This commit is contained in:
		
							
								
								
									
										33
									
								
								.github/workflows/test-python.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/test-python.yml
									
									
									
									
										vendored
									
									
								
							| @ -245,6 +245,39 @@ jobs: | ||||
|       - name: Run simple code | ||||
|         run: python -c 'import math; print(math.factorial(5))' | ||||
|  | ||||
|   setup-versions-from-tool-versions-file: | ||||
|     name: Setup ${{ matrix.python }} ${{ matrix.os }} .tool-versions file | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: | ||||
|           [ | ||||
|             macos-latest, | ||||
|             windows-latest, | ||||
|             ubuntu-20.04, | ||||
|             ubuntu-22.04, | ||||
|             macos-13, | ||||
|             ubuntu-latest | ||||
|           ] | ||||
|         python: [3.13.0, 3.14-dev, pypy3.11-7.3.18, graalpy-24.1.2] | ||||
|         exclude: | ||||
|           - os: windows-latest | ||||
|             python: graalpy-24.1.2 | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: build-tool-versions-file ${{ matrix.python }} | ||||
|         run: | | ||||
|           echo "python ${{ matrix.python }}" > .tool-versions | ||||
|  | ||||
|       - name: setup-python using .tool-versions ${{ matrix.python }} | ||||
|         id: setup-python-tool-versions | ||||
|         uses: ./ | ||||
|         with: | ||||
|           python-version-file: .tool-versions | ||||
|  | ||||
|   setup-pre-release-version-from-manifest: | ||||
|     name: Setup 3.14.0-alpha.1 ${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|  | ||||
| @ -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 pypy version', | ||||
|     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 alpha Releases', | ||||
|     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 dev suffix', | ||||
|     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', () => { | ||||
|  | ||||
							
								
								
									
										38
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							| @ -100535,7 +100535,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)); | ||||
| @ -100759,12 +100759,46 @@ 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'); | ||||
|         for (const line of lines) { | ||||
|             // Skip commented lines
 | ||||
|             if (line.trim().startsWith('#')) { | ||||
|                 continue; | ||||
|             } | ||||
|             const match = line.match(/^\s*python\s*v?\s*(?<version>[^\s]+)\s*$/); | ||||
|             if (match) { | ||||
|                 return [((_a = match.groups) === null || _a === void 0 ? void 0 : _a.version.trim()) || '']; | ||||
|             } | ||||
|         } | ||||
|         core.warning(`No Python version found in ${versionFile}`); | ||||
|         return []; | ||||
|     } | ||||
|     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); | ||||
|     } | ||||
|  | ||||
| @ -278,9 +278,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: | ||||
| @ -300,6 +300,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. | ||||
|  | ||||
							
								
								
									
										36
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								src/utils.ts
									
									
									
									
									
								
							| @ -279,11 +279,45 @@ 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'); | ||||
|  | ||||
|     for (const line of lines) { | ||||
|       // Skip commented lines | ||||
|       if (line.trim().startsWith('#')) { | ||||
|         continue; | ||||
|       } | ||||
|       const match = line.match(/^\s*python\s*v?\s*(?<version>[^\s]+)\s*$/); | ||||
|       if (match) { | ||||
|         return [match.groups?.version.trim() || '']; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     core.warning(`No Python version found in ${versionFile}`); | ||||
|  | ||||
|     return []; | ||||
|   } 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); | ||||
|   } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user