Add support for .tool-versions file in setup-python (#1043) · maybtdevops/setup-python@19e4675 · GitHub
Skip to content

Commit 19e4675

Browse files
Add support for .tool-versions file in setup-python (actions#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
1 parent 6fd11e1 commit 19e4675

5 files changed

Lines changed: 193 additions & 6 deletions

File tree

‎.github/workflows/test-python.yml‎

Lines changed: 33 additions & 0 deletions

‎__tests__/utils.test.ts‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
getNextPageUrl,
1616
isGhes,
1717
IS_WINDOWS,
18-
getDownloadFileName
18+
getDownloadFileName,
19+
getVersionInputFromToolVersions
1920
} from '../src/utils';
2021

2122
jest.mock('@actions/cache');
@@ -139,6 +140,82 @@ describe('Version from file test', () => {
139140
expect(_fn(pythonVersionFilePath)).toEqual([]);
140141
}
141142
);
143+
it.each([getVersionInputFromToolVersions])(
144+
'Version from .tool-versions',
145+
async _fn => {
146+
const toolVersionFileName = '.tool-versions';
147+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
148+
const toolVersionContent = 'python 3.9.10\nnodejs 16';
149+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
150+
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']);
151+
}
152+
);
153+
154+
it.each([getVersionInputFromToolVersions])(
155+
'Version from .tool-versions with comment',
156+
async _fn => {
157+
const toolVersionFileName = '.tool-versions';
158+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
159+
const toolVersionContent = '# python 3.8\npython 3.9';
160+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
161+
expect(_fn(toolVersionFilePath)).toEqual(['3.9']);
162+
}
163+
);
164+
165+
it.each([getVersionInputFromToolVersions])(
166+
'Version from .tool-versions with whitespace',
167+
async _fn => {
168+
const toolVersionFileName = '.tool-versions';
169+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
170+
const toolVersionContent = ' python 3.10 ';
171+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
172+
expect(_fn(toolVersionFilePath)).toEqual(['3.10']);
173+
}
174+
);
175+
176+
it.each([getVersionInputFromToolVersions])(
177+
'Version from .tool-versions with v prefix',
178+
async _fn => {
179+
const toolVersionFileName = '.tool-versions';
180+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
181+
const toolVersionContent = 'python v3.9.10';
182+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
183+
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']);
184+
}
185+
);
186+
187+
it.each([getVersionInputFromToolVersions])(
188+
'Version from .tool-versions with pypy version',
189+
async _fn => {
190+
const toolVersionFileName = '.tool-versions';
191+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
192+
const toolVersionContent = 'python pypy3.10-7.3.14';
193+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
194+
expect(_fn(toolVersionFilePath)).toEqual(['pypy3.10-7.3.14']);
195+
}
196+
);
197+
198+
it.each([getVersionInputFromToolVersions])(
199+
'Version from .tool-versions with alpha Releases',
200+
async _fn => {
201+
const toolVersionFileName = '.tool-versions';
202+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
203+
const toolVersionContent = 'python 3.14.0a5t';
204+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
205+
expect(_fn(toolVersionFilePath)).toEqual(['3.14.0a5t']);
206+
}
207+
);
208+
209+
it.each([getVersionInputFromToolVersions])(
210+
'Version from .tool-versions with dev suffix',
211+
async _fn => {
212+
const toolVersionFileName = '.tool-versions';
213+
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
214+
const toolVersionContent = 'python 3.14t-dev';
215+
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
216+
expect(_fn(toolVersionFilePath)).toEqual(['3.14t-dev']);
217+
}
218+
);
142219
});
143220

144221
describe('getNextPageUrl', () => {

‎dist/setup/index.js‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100535,7 +100535,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
100535100535
return (mod && mod.__esModule) ? mod : { "default": mod };
100536100536
};
100537100537
Object.defineProperty(exports, "__esModule", ({ value: true }));
100538-
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;
100538+
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;
100539100539
/* eslint no-unsafe-finally: "off" */
100540100540
const cache = __importStar(__nccwpck_require__(5116));
100541100541
const core = __importStar(__nccwpck_require__(7484));
@@ -100759,12 +100759,46 @@ function getVersionInputFromPlainFile(versionFile) {
100759100759
}
100760100760
exports.getVersionInputFromPlainFile = getVersionInputFromPlainFile;
100761100761
/**
100762-
* Python version extracted from a plain or TOML file.
100762+
* Python version extracted from a .tool-versions file.
100763+
*/
100764+
function getVersionInputFromToolVersions(versionFile) {
100765+
var _a;
100766+
if (!fs_1.default.existsSync(versionFile)) {
100767+
core.warning(`File ${versionFile} does not exist.`);
100768+
return [];
100769+
}
100770+
try {
100771+
const fileContents = fs_1.default.readFileSync(versionFile, 'utf8');
100772+
const lines = fileContents.split('\n');
100773+
for (const line of lines) {
100774+
// Skip commented lines
100775+
if (line.trim().startsWith('#')) {
100776+
continue;
100777+
}
100778+
const match = line.match(/^\s*python\s*v?\s*(?<version>[^\s]+)\s*$/);
100779+
if (match) {
100780+
return [((_a = match.groups) === null || _a === void 0 ? void 0 : _a.version.trim()) || ''];
100781+
}
100782+
}
100783+
core.warning(`No Python version found in ${versionFile}`);
100784+
return [];
100785+
}
100786+
catch (error) {
100787+
core.error(`Error reading ${versionFile}: ${error.message}`);
100788+
return [];
100789+
}
100790+
}
100791+
exports.getVersionInputFromToolVersions = getVersionInputFromToolVersions;
100792+
/**
100793+
* Python version extracted from a plain, .tool-versions or TOML file.
100763100794
*/
100764100795
function getVersionInputFromFile(versionFile) {
100765100796
if (versionFile.endsWith('.toml')) {
100766100797
return getVersionInputFromTomlFile(versionFile);
100767100798
}
100799+
else if (versionFile.match('.tool-versions')) {
100800+
return getVersionInputFromToolVersions(versionFile);
100801+
}
100768100802
else {
100769100803
return getVersionInputFromPlainFile(versionFile);
100770100804
}

‎docs/advanced-usage.md‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@ jobs:
278278
279279
## Using the `python-version-file` input
280280

281-
`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.
281+
`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.
282282

283-
>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.
283+
>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)).
284284

285285
```yaml
286286
steps:
@@ -300,6 +300,15 @@ steps:
300300
- run: python my_script.py
301301
```
302302

303+
```yaml
304+
steps:
305+
- uses: actions/checkout@v4
306+
- uses: actions/setup-python@v5
307+
with:
308+
python-version-file: '.tool-versions' # Read python version from a file .tool-versions
309+
- run: python my_script.py
310+
```
311+
303312
## Check latest version
304313

305314
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.

‎src/utils.ts‎

Lines changed: 35 additions & 1 deletion

0 commit comments

Comments
 (0)