mirror of
				https://kkgithub.com/actions/setup-python.git
				synced 2025-11-04 04:31:51 +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