You've already forked setup-python
							
							
				mirror of
				https://github.com/actions/setup-python.git
				synced 2025-11-04 16:56:40 +07: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
 | 
					      - name: Run simple code
 | 
				
			||||||
        run: python -c 'import math; print(math.factorial(5))'
 | 
					        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:
 | 
					  setup-pre-release-version-from-manifest:
 | 
				
			||||||
    name: Setup 3.14.0-alpha.1 ${{ matrix.os }}
 | 
					    name: Setup 3.14.0-alpha.1 ${{ matrix.os }}
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,8 @@ import {
 | 
				
			|||||||
  getNextPageUrl,
 | 
					  getNextPageUrl,
 | 
				
			||||||
  isGhes,
 | 
					  isGhes,
 | 
				
			||||||
  IS_WINDOWS,
 | 
					  IS_WINDOWS,
 | 
				
			||||||
  getDownloadFileName
 | 
					  getDownloadFileName,
 | 
				
			||||||
 | 
					  getVersionInputFromToolVersions
 | 
				
			||||||
} from '../src/utils';
 | 
					} from '../src/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jest.mock('@actions/cache');
 | 
					jest.mock('@actions/cache');
 | 
				
			||||||
@ -139,6 +140,82 @@ describe('Version from file test', () => {
 | 
				
			|||||||
      expect(_fn(pythonVersionFilePath)).toEqual([]);
 | 
					      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', () => {
 | 
					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 };
 | 
					    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.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" */
 | 
					/* 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));
 | 
				
			||||||
@ -100759,12 +100759,46 @@ function getVersionInputFromPlainFile(versionFile) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
exports.getVersionInputFromPlainFile = getVersionInputFromPlainFile;
 | 
					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) {
 | 
					function getVersionInputFromFile(versionFile) {
 | 
				
			||||||
    if (versionFile.endsWith('.toml')) {
 | 
					    if (versionFile.endsWith('.toml')) {
 | 
				
			||||||
        return getVersionInputFromTomlFile(versionFile);
 | 
					        return getVersionInputFromTomlFile(versionFile);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    else if (versionFile.match('.tool-versions')) {
 | 
				
			||||||
 | 
					        return getVersionInputFromToolVersions(versionFile);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        return getVersionInputFromPlainFile(versionFile);
 | 
					        return getVersionInputFromPlainFile(versionFile);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -278,9 +278,9 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Using the `python-version-file` input
 | 
					## 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
 | 
					```yaml
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
@ -300,6 +300,15 @@ steps:
 | 
				
			|||||||
- run: python my_script.py
 | 
					- 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
 | 
					## 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.
 | 
					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[] {
 | 
					export function getVersionInputFromFile(versionFile: string): string[] {
 | 
				
			||||||
  if (versionFile.endsWith('.toml')) {
 | 
					  if (versionFile.endsWith('.toml')) {
 | 
				
			||||||
    return getVersionInputFromTomlFile(versionFile);
 | 
					    return getVersionInputFromTomlFile(versionFile);
 | 
				
			||||||
 | 
					  } else if (versionFile.match('.tool-versions')) {
 | 
				
			||||||
 | 
					    return getVersionInputFromToolVersions(versionFile);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return getVersionInputFromPlainFile(versionFile);
 | 
					    return getVersionInputFromPlainFile(versionFile);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user