1
0
Fork 0
mirror of https://code.forgejo.org/actions/setup-python synced 2025-03-14 22:26:58 +01:00

Support free threaded Python versions like '3.13t' (#973)

* Support free threaded Python versions like '3.13t'

Python wheels, pyenv, and a number of other tools use 't' in the Python
version number to identify free threaded builds. For example, '3.13t',
'3.14.0a1', '3.14t-dev'.

This PR supports that syntax in `actions/setup-python`, strips the "t",
and adds "-freethreading" to the architecture to select the correct
Python version.

See #771

* Add free threading to advanced usage documentation

* Fix desugaring of `3.13.1t` and add test case.

* Add freethreaded input and fix handling of prerelease versions

* Fix lint

* Add 't' suffix to python-version output

* Use distinct cache key for free threaded Python

* Remove support for syntax like '3.14.0a1'

* Clarify use of 't' suffix

* Improve error message when trying to use free threaded Python versions before 3.13
This commit is contained in:
Sam Gross 2025-03-04 17:49:43 -05:00 committed by GitHub
parent 6ca8e8598f
commit 9e62be81b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 226 additions and 35 deletions

View file

@ -45,6 +45,16 @@ steps:
- run: python my_script.py - run: python my_script.py
``` ```
**Free threaded Python**
```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13t'
- run: python my_script.py
```
The `python-version` input is optional. If not supplied, the action will try to resolve the version from the default `.python-version` file. If the `.python-version` file doesn't exist Python or PyPy version from the PATH will be used. The default version of Python or PyPy in PATH varies between runners and can be changed unexpectedly so we recommend always setting Python version explicitly using the `python-version` or `python-version-file` inputs. The `python-version` input is optional. If not supplied, the action will try to resolve the version from the default `.python-version` file. If the `.python-version` file doesn't exist Python or PyPy version from the PATH will be used. The default version of Python or PyPy in PATH varies between runners and can be changed unexpectedly so we recommend always setting Python version explicitly using the `python-version` or `python-version-file` inputs.
The action will first check the local [tool cache](docs/advanced-usage.md#hosted-tool-cache) for a [semver](https://github.com/npm/node-semver#versions) match. If unable to find a specific version in the tool cache, the action will attempt to download a version of Python from [GitHub Releases](https://github.com/actions/python-versions/releases) and for PyPy from the official [PyPy's dist](https://downloads.python.org/pypy/). The action will first check the local [tool cache](docs/advanced-usage.md#hosted-tool-cache) for a [semver](https://github.com/npm/node-semver#versions) match. If unable to find a specific version in the tool cache, the action will attempt to download a version of Python from [GitHub Releases](https://github.com/actions/python-versions/releases) and for PyPy from the official [PyPy's dist](https://downloads.python.org/pypy/).

View file

@ -0,0 +1,43 @@
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python';
describe('desugarVersion', () => {
it.each([
['3.13', {version: '3.13', freethreaded: false}],
['3.13t', {version: '3.13', freethreaded: true}],
['3.13.1', {version: '3.13.1', freethreaded: false}],
['3.13.1t', {version: '3.13.1', freethreaded: true}],
['3.14-dev', {version: '~3.14.0-0', freethreaded: false}],
['3.14t-dev', {version: '~3.14.0-0', freethreaded: true}]
])('%s -> %s', (input, expected) => {
expect(desugarVersion(input)).toEqual(expected);
});
});
// Test the combined desugarVersion and pythonVersionToSemantic functions
describe('pythonVersions', () => {
it.each([
['3.13', {version: '3.13', freethreaded: false}],
['3.13t', {version: '3.13', freethreaded: true}],
['3.13.1', {version: '3.13.1', freethreaded: false}],
['3.13.1t', {version: '3.13.1', freethreaded: true}],
['3.14-dev', {version: '~3.14.0-0', freethreaded: false}],
['3.14t-dev', {version: '~3.14.0-0', freethreaded: true}]
])('%s -> %s', (input, expected) => {
const {version, freethreaded} = desugarVersion(input);
const semanticVersionSpec = pythonVersionToSemantic(version, false);
expect({version: semanticVersionSpec, freethreaded}).toEqual(expected);
});
it.each([
['3.13', {version: '~3.13.0-0', freethreaded: false}],
['3.13t', {version: '~3.13.0-0', freethreaded: true}],
['3.13.1', {version: '3.13.1', freethreaded: false}],
['3.13.1t', {version: '3.13.1', freethreaded: true}],
['3.14-dev', {version: '~3.14.0-0', freethreaded: false}],
['3.14t-dev', {version: '~3.14.0-0', freethreaded: true}]
])('%s (allowPreReleases=true) -> %s', (input, expected) => {
const {version, freethreaded} = desugarVersion(input);
const semanticVersionSpec = pythonVersionToSemantic(version, true);
expect({version: semanticVersionSpec, freethreaded}).toEqual(expected);
});
});

View file

@ -56,7 +56,7 @@ describe('Finder tests', () => {
await io.mkdirP(pythonDir); await io.mkdirP(pythonDir);
fs.writeFileSync(`${pythonDir}.complete`, 'hello'); fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('3.x', 'x64', true, false, false); await finder.useCpythonVersion('3.x', 'x64', true, false, false, false);
expect(spyCoreAddPath).toHaveBeenCalled(); expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith( expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation', 'pythonLocation',
@ -73,7 +73,7 @@ describe('Finder tests', () => {
await io.mkdirP(pythonDir); await io.mkdirP(pythonDir);
fs.writeFileSync(`${pythonDir}.complete`, 'hello'); fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('3.x', 'x64', false, false, false); await finder.useCpythonVersion('3.x', 'x64', false, false, false, false);
expect(spyCoreAddPath).not.toHaveBeenCalled(); expect(spyCoreAddPath).not.toHaveBeenCalled();
expect(spyCoreExportVariable).not.toHaveBeenCalled(); expect(spyCoreExportVariable).not.toHaveBeenCalled();
}); });
@ -96,7 +96,7 @@ describe('Finder tests', () => {
}); });
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await expect( await expect(
finder.useCpythonVersion('1.2.3', 'x64', true, false, false) finder.useCpythonVersion('1.2.3', 'x64', true, false, false, false)
).resolves.toEqual({ ).resolves.toEqual({
impl: 'CPython', impl: 'CPython',
version: '1.2.3' version: '1.2.3'
@ -135,7 +135,14 @@ describe('Finder tests', () => {
}); });
// This will throw if it doesn't find it in the manifest (because no such version exists) // This will throw if it doesn't find it in the manifest (because no such version exists)
await expect( await expect(
finder.useCpythonVersion('1.2.4-beta.2', 'x64', false, false, false) finder.useCpythonVersion(
'1.2.4-beta.2',
'x64',
false,
false,
false,
false
)
).resolves.toEqual({ ).resolves.toEqual({
impl: 'CPython', impl: 'CPython',
version: '1.2.4-beta.2' version: '1.2.4-beta.2'
@ -186,7 +193,7 @@ describe('Finder tests', () => {
fs.writeFileSync(`${pythonDir}.complete`, 'hello'); fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('1.2', 'x64', true, true, false); await finder.useCpythonVersion('1.2', 'x64', true, true, false, false);
expect(infoSpy).toHaveBeenCalledWith("Resolved as '1.2.3'"); expect(infoSpy).toHaveBeenCalledWith("Resolved as '1.2.3'");
expect(infoSpy).toHaveBeenCalledWith( expect(infoSpy).toHaveBeenCalledWith(
@ -197,7 +204,14 @@ describe('Finder tests', () => {
); );
expect(installSpy).toHaveBeenCalled(); expect(installSpy).toHaveBeenCalled();
expect(addPathSpy).toHaveBeenCalledWith(expPath); expect(addPathSpy).toHaveBeenCalledWith(expPath);
await finder.useCpythonVersion('1.2.4-beta.2', 'x64', false, true, false); await finder.useCpythonVersion(
'1.2.4-beta.2',
'x64',
false,
true,
false,
false
);
expect(spyCoreAddPath).toHaveBeenCalled(); expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith( expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation', 'pythonLocation',
@ -224,7 +238,7 @@ describe('Finder tests', () => {
}); });
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await expect( await expect(
finder.useCpythonVersion('1.2', 'x64', false, false, false) finder.useCpythonVersion('1.2', 'x64', false, false, false, false)
).resolves.toEqual({ ).resolves.toEqual({
impl: 'CPython', impl: 'CPython',
version: '1.2.3' version: '1.2.3'
@ -251,17 +265,17 @@ describe('Finder tests', () => {
}); });
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await expect( await expect(
finder.useCpythonVersion('1.1', 'x64', false, false, false) finder.useCpythonVersion('1.1', 'x64', false, false, false, false)
).rejects.toThrow(); ).rejects.toThrow();
await expect( await expect(
finder.useCpythonVersion('1.1', 'x64', false, false, true) finder.useCpythonVersion('1.1', 'x64', false, false, true, false)
).resolves.toEqual({ ).resolves.toEqual({
impl: 'CPython', impl: 'CPython',
version: '1.1.0-beta.2' version: '1.1.0-beta.2'
}); });
// Check 1.1.0 version specifier does not fallback to '1.1.0-beta.2' // Check 1.1.0 version specifier does not fallback to '1.1.0-beta.2'
await expect( await expect(
finder.useCpythonVersion('1.1.0', 'x64', false, false, true) finder.useCpythonVersion('1.1.0', 'x64', false, false, true, false)
).rejects.toThrow(); ).rejects.toThrow();
}); });
@ -269,7 +283,14 @@ describe('Finder tests', () => {
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
let thrown = false; let thrown = false;
try { try {
await finder.useCpythonVersion('3.300000', 'x64', true, false, false); await finder.useCpythonVersion(
'3.300000',
'x64',
true,
false,
false,
false
);
} catch { } catch {
thrown = true; thrown = true;
} }

View file

@ -26,6 +26,9 @@ inputs:
allow-prereleases: allow-prereleases:
description: "When 'true', a version range passed to 'python-version' input will match prerelease versions if no GA versions are found. Only 'x.y' version range is supported for CPython." description: "When 'true', a version range passed to 'python-version' input will match prerelease versions if no GA versions are found. Only 'x.y' version range is supported for CPython."
default: false default: false
freethreaded:
description: "When 'true', use the freethreaded version of Python."
default: false
outputs: outputs:
python-version: python-version:
description: "The installed Python or PyPy version. Useful when given a version range as input." description: "The installed Python or PyPy version. Useful when given a version range as input."

61
dist/setup/index.js vendored
View file

@ -99514,7 +99514,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.pythonVersionToSemantic = exports.useCpythonVersion = void 0; exports.pythonVersionToSemantic = exports.desugarVersion = exports.useCpythonVersion = void 0;
const os = __importStar(__nccwpck_require__(857)); const os = __importStar(__nccwpck_require__(857));
const path = __importStar(__nccwpck_require__(6928)); const path = __importStar(__nccwpck_require__(6928));
const utils_1 = __nccwpck_require__(1798); const utils_1 = __nccwpck_require__(1798);
@ -99542,13 +99542,22 @@ function binDir(installDir) {
return path.join(installDir, 'bin'); return path.join(installDir, 'bin');
} }
} }
function useCpythonVersion(version, architecture, updateEnvironment, checkLatest, allowPreReleases) { function useCpythonVersion(version, architecture, updateEnvironment, checkLatest, allowPreReleases, freethreaded) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
var _a; var _a;
let manifest = null; let manifest = null;
const desugaredVersionSpec = desugarDevVersion(version); const { version: desugaredVersionSpec, freethreaded: versionFreethreaded } = desugarVersion(version);
let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec, allowPreReleases); let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec, allowPreReleases);
if (versionFreethreaded) {
// Use the freethreaded version if it was specified in the input, e.g., 3.13t
freethreaded = true;
}
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
if (freethreaded) {
// Free threaded versions use an architecture suffix like `x64-freethreaded`
core.debug(`Using freethreaded version of ${semanticVersionSpec}`);
architecture += '-freethreaded';
}
if (checkLatest) { if (checkLatest) {
manifest = yield installer.getManifest(); manifest = yield installer.getManifest();
const resolvedVersion = (_a = (yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest))) === null || _a === void 0 ? void 0 : _a.version; const resolvedVersion = (_a = (yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest))) === null || _a === void 0 ? void 0 : _a.version;
@ -99572,12 +99581,16 @@ function useCpythonVersion(version, architecture, updateEnvironment, checkLatest
} }
if (!installDir) { if (!installDir) {
const osInfo = yield (0, utils_1.getOSInfo)(); const osInfo = yield (0, utils_1.getOSInfo)();
throw new Error([ const msg = [
`The version '${version}' with architecture '${architecture}' was not found for ${osInfo `The version '${version}' with architecture '${architecture}' was not found for ${osInfo
? `${osInfo.osName} ${osInfo.osVersion}` ? `${osInfo.osName} ${osInfo.osVersion}`
: 'this operating system'}.`, : 'this operating system'}.`
`The list of all available versions can be found here: ${installer.MANIFEST_URL}` ];
].join(os.EOL)); if (freethreaded) {
msg.push(`Free threaded versions are only available for Python 3.13.0 and later.`);
}
msg.push(`The list of all available versions can be found here: ${installer.MANIFEST_URL}`);
throw new Error(msg.join(os.EOL));
} }
const _binDir = binDir(installDir); const _binDir = binDir(installDir);
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
@ -99617,12 +99630,39 @@ function useCpythonVersion(version, architecture, updateEnvironment, checkLatest
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed. // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
} }
const installed = versionFromPath(installDir); const installed = versionFromPath(installDir);
core.setOutput('python-version', installed); let pythonVersion = installed;
if (freethreaded) {
// Add the freethreaded suffix to the version (e.g., 3.13.1t)
pythonVersion += 't';
}
core.setOutput('python-version', pythonVersion);
core.setOutput('python-path', pythonPath); core.setOutput('python-path', pythonPath);
return { impl: 'CPython', version: installed }; return { impl: 'CPython', version: pythonVersion };
}); });
} }
exports.useCpythonVersion = useCpythonVersion; exports.useCpythonVersion = useCpythonVersion;
/* Desugar free threaded and dev versions */
function desugarVersion(versionSpec) {
const { version, freethreaded } = desugarFreeThreadedVersion(versionSpec);
return { version: desugarDevVersion(version), freethreaded };
}
exports.desugarVersion = desugarVersion;
/* Identify freethreaded versions like, 3.13t, 3.13.1t, 3.13t-dev.
* Returns the version without the `t` and the architectures suffix, if freethreaded */
function desugarFreeThreadedVersion(versionSpec) {
const majorMinor = /^(\d+\.\d+(\.\d+)?)(t)$/;
if (majorMinor.test(versionSpec)) {
return { version: versionSpec.replace(majorMinor, '$1'), freethreaded: true };
}
const devVersion = /^(\d+\.\d+)(t)(-dev)$/;
if (devVersion.test(versionSpec)) {
return {
version: versionSpec.replace(devVersion, '$1$3'),
freethreaded: true
};
}
return { version: versionSpec, freethreaded: false };
}
/** Convert versions like `3.8-dev` to a version like `~3.8.0-0`. */ /** Convert versions like `3.8-dev` to a version like `~3.8.0-0`. */
function desugarDevVersion(versionSpec) { function desugarDevVersion(versionSpec) {
const devVersion = /^(\d+)\.(\d+)-dev$/; const devVersion = /^(\d+)\.(\d+)-dev$/;
@ -100365,6 +100405,7 @@ function run() {
const versions = resolveVersionInput(); const versions = resolveVersionInput();
const checkLatest = core.getBooleanInput('check-latest'); const checkLatest = core.getBooleanInput('check-latest');
const allowPreReleases = core.getBooleanInput('allow-prereleases'); const allowPreReleases = core.getBooleanInput('allow-prereleases');
const freethreaded = core.getBooleanInput('freethreaded');
if (versions.length) { if (versions.length) {
let pythonVersion = ''; let pythonVersion = '';
const arch = core.getInput('architecture') || os.arch(); const arch = core.getInput('architecture') || os.arch();
@ -100385,7 +100426,7 @@ function run() {
if (version.startsWith('2')) { if (version.startsWith('2')) {
core.warning('The support for python 2.7 was removed on June 19, 2023. Related issue: https://github.com/actions/setup-python/issues/672'); core.warning('The support for python 2.7 was removed on June 19, 2023. Related issue: https://github.com/actions/setup-python/issues/672');
} }
const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases); const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases, freethreaded);
pythonVersion = installed.version; pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
} }

View file

@ -77,6 +77,31 @@ steps:
- run: python my_script.py - run: python my_script.py
``` ```
You can specify the [free threading](https://docs.python.org/3/howto/free-threading-python.html) version of Python by setting the `freethreaded` input to `true` or by using the special **t** suffix in some cases.
You can use the **t** suffix when specifying the major and minor version (e.g., `3.13t`), with a patch version (e.g., `3.13.1t`), or with the **x.y-dev syntax** (e.g., `3.14t-dev`).
Free threaded Python is only available starting with the 3.13 release.
```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13t'
- run: python my_script.py
```
Note that the **t** suffix is not `semver` syntax. If you wish to specify a range, you must use the `freethreaded` input instead of the `t` suffix.
```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '>=3.13'
freethreaded: true
- run: python my_script.py
```
You can also use several types of ranges that are specified in [semver](https://github.com/npm/node-semver#ranges), for instance: You can also use several types of ranges that are specified in [semver](https://github.com/npm/node-semver#ranges), for instance:
- **[ranges](https://github.com/npm/node-semver#ranges)** to download and set up the latest available version of Python satisfying a range: - **[ranges](https://github.com/npm/node-semver#ranges)** to download and set up the latest available version of Python satisfying a range:

View file

@ -35,16 +35,28 @@ export async function useCpythonVersion(
architecture: string, architecture: string,
updateEnvironment: boolean, updateEnvironment: boolean,
checkLatest: boolean, checkLatest: boolean,
allowPreReleases: boolean allowPreReleases: boolean,
freethreaded: boolean
): Promise<InstalledVersion> { ): Promise<InstalledVersion> {
let manifest: tc.IToolRelease[] | null = null; let manifest: tc.IToolRelease[] | null = null;
const desugaredVersionSpec = desugarDevVersion(version); const {version: desugaredVersionSpec, freethreaded: versionFreethreaded} =
desugarVersion(version);
let semanticVersionSpec = pythonVersionToSemantic( let semanticVersionSpec = pythonVersionToSemantic(
desugaredVersionSpec, desugaredVersionSpec,
allowPreReleases allowPreReleases
); );
if (versionFreethreaded) {
// Use the freethreaded version if it was specified in the input, e.g., 3.13t
freethreaded = true;
}
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
if (freethreaded) {
// Free threaded versions use an architecture suffix like `x64-freethreaded`
core.debug(`Using freethreaded version of ${semanticVersionSpec}`);
architecture += '-freethreaded';
}
if (checkLatest) { if (checkLatest) {
manifest = await installer.getManifest(); manifest = await installer.getManifest();
const resolvedVersion = ( const resolvedVersion = (
@ -90,17 +102,23 @@ export async function useCpythonVersion(
if (!installDir) { if (!installDir) {
const osInfo = await getOSInfo(); const osInfo = await getOSInfo();
throw new Error( const msg = [
[
`The version '${version}' with architecture '${architecture}' was not found for ${ `The version '${version}' with architecture '${architecture}' was not found for ${
osInfo osInfo
? `${osInfo.osName} ${osInfo.osVersion}` ? `${osInfo.osName} ${osInfo.osVersion}`
: 'this operating system' : 'this operating system'
}.`, }.`
`The list of all available versions can be found here: ${installer.MANIFEST_URL}` ];
].join(os.EOL) if (freethreaded) {
msg.push(
`Free threaded versions are only available for Python 3.13.0 and later.`
); );
} }
msg.push(
`The list of all available versions can be found here: ${installer.MANIFEST_URL}`
);
throw new Error(msg.join(os.EOL));
}
const _binDir = binDir(installDir); const _binDir = binDir(installDir);
const binaryExtension = IS_WINDOWS ? '.exe' : ''; const binaryExtension = IS_WINDOWS ? '.exe' : '';
@ -153,10 +171,38 @@ export async function useCpythonVersion(
} }
const installed = versionFromPath(installDir); const installed = versionFromPath(installDir);
core.setOutput('python-version', installed); let pythonVersion = installed;
if (freethreaded) {
// Add the freethreaded suffix to the version (e.g., 3.13.1t)
pythonVersion += 't';
}
core.setOutput('python-version', pythonVersion);
core.setOutput('python-path', pythonPath); core.setOutput('python-path', pythonPath);
return {impl: 'CPython', version: installed}; return {impl: 'CPython', version: pythonVersion};
}
/* Desugar free threaded and dev versions */
export function desugarVersion(versionSpec: string) {
const {version, freethreaded} = desugarFreeThreadedVersion(versionSpec);
return {version: desugarDevVersion(version), freethreaded};
}
/* Identify freethreaded versions like, 3.13t, 3.13.1t, 3.13t-dev.
* Returns the version without the `t` and the architectures suffix, if freethreaded */
function desugarFreeThreadedVersion(versionSpec: string) {
const majorMinor = /^(\d+\.\d+(\.\d+)?)(t)$/;
if (majorMinor.test(versionSpec)) {
return {version: versionSpec.replace(majorMinor, '$1'), freethreaded: true};
}
const devVersion = /^(\d+\.\d+)(t)(-dev)$/;
if (devVersion.test(versionSpec)) {
return {
version: versionSpec.replace(devVersion, '$1$3'),
freethreaded: true
};
}
return {version: versionSpec, freethreaded: false};
} }
/** Convert versions like `3.8-dev` to a version like `~3.8.0-0`. */ /** Convert versions like `3.8-dev` to a version like `~3.8.0-0`. */

View file

@ -92,6 +92,7 @@ async function run() {
const versions = resolveVersionInput(); const versions = resolveVersionInput();
const checkLatest = core.getBooleanInput('check-latest'); const checkLatest = core.getBooleanInput('check-latest');
const allowPreReleases = core.getBooleanInput('allow-prereleases'); const allowPreReleases = core.getBooleanInput('allow-prereleases');
const freethreaded = core.getBooleanInput('freethreaded');
if (versions.length) { if (versions.length) {
let pythonVersion = ''; let pythonVersion = '';
@ -132,7 +133,8 @@ async function run() {
arch, arch,
updateEnvironment, updateEnvironment,
checkLatest, checkLatest,
allowPreReleases allowPreReleases,
freethreaded
); );
pythonVersion = installed.version; pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);