Expose Python runtime identity for downstream caching

This commit is contained in:
William Woodruff
2026-09-03 15:18:29 -04:00
parent 3956519c9e
commit 70e5842acc
11 changed files with 400 additions and 204 deletions
+2 -5
View File
@@ -16,7 +16,7 @@ import {
getPlatform,
type Platform,
} from "./utils/platforms";
import { getResolvedPythonVersion } from "./utils/python-version";
import { getPythonRuntimeId } from "./utils/python-runtime";
import { resolveUvVersion } from "./version/resolve";
const sourceDir = __dirname;
@@ -102,10 +102,7 @@ async function run(): Promise<void> {
const detectedPythonVersion = await getPythonVersion(inputs);
core.setOutput("python-version", detectedPythonVersion);
core.setOutput(
"python-version-resolved",
await getResolvedPythonVersion(inputs),
);
core.setOutput("python-runtime-id", await getPythonRuntimeId(inputs));
if (inputs.enableCache) {
await restoreCache(inputs, detectedPythonVersion);
+85
View File
@@ -0,0 +1,85 @@
import { join } from "node:path";
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import type { SetupInputs } from "./inputs";
const PYTHON_RUNTIME_QUERY = `
import json
import platform
import sys
import sysconfig
print(json.dumps({
"implementation": sys.implementation.name,
"implementationVersion": list(sys.implementation.version),
"pythonVersion": platform.python_version(),
"freethreaded": sysconfig.get_config_var("Py_GIL_DISABLED") == 1,
}))
`;
type ReleaseLevel = "alpha" | "beta" | "candidate" | "final";
interface PythonRuntime {
implementation: string;
implementationVersion: [number, number, number, ReleaseLevel, number];
pythonVersion: string;
freethreaded: boolean;
}
function formatRuntimeId(runtime: PythonRuntime): string {
if (
typeof runtime.implementation !== "string" ||
runtime.implementation === "" ||
typeof runtime.pythonVersion !== "string" ||
runtime.pythonVersion === "" ||
typeof runtime.freethreaded !== "boolean"
) {
throw new Error("Invalid Python runtime metadata");
}
let id = `cpython-${runtime.pythonVersion}`;
if (runtime.implementation !== "cpython") {
const [major, minor, micro, releaseLevel, serial] =
runtime.implementationVersion;
const suffixes = { alpha: "a", beta: "b", candidate: "rc", final: "" };
const suffix = suffixes[releaseLevel];
if (
suffix === undefined ||
![major, minor, micro, serial].every(
(part) => Number.isInteger(part) && part >= 0,
)
) {
throw new Error("Invalid Python implementation version");
}
const implementationVersion = `${major}.${minor}.${micro}${suffix}${suffix ? serial : ""}`;
id = `${runtime.implementation}-${implementationVersion}-python-${runtime.pythonVersion}`;
}
return runtime.freethreaded ? `${id}-freethreaded` : id;
}
export async function getPythonRuntimeId(inputs: SetupInputs): Promise<string> {
if (!inputs.activateEnvironment) {
return "";
}
const pythonPath =
process.platform === "win32"
? join(inputs.venvPath, "Scripts", "python.exe")
: join(inputs.venvPath, "bin", "python");
try {
// @actions/exec parses the executable as a command line, so quote the path.
const { stdout } = await exec.getExecOutput(
`"${pythonPath.replace(/"/g, '\\"')}"`,
["-I", "-c", PYTHON_RUNTIME_QUERY],
{ silent: !core.isDebug() },
);
return formatRuntimeId(JSON.parse(stdout));
} catch (error) {
core.debug(
`Failed to identify the activated environment's Python runtime. Error: ${error instanceof Error ? error.message : String(error)}`,
);
return "";
}
}
-32
View File
@@ -1,32 +0,0 @@
import { join } from "node:path";
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import type { SetupInputs } from "./inputs";
export async function getResolvedPythonVersion(
inputs: SetupInputs,
): Promise<string> {
if (!inputs.activateEnvironment) {
return "";
}
const pythonPath =
process.platform === "win32"
? join(inputs.venvPath, "Scripts", "python.exe")
: join(inputs.venvPath, "bin", "python");
try {
// @actions/exec parses the executable as a command line, so quote the path.
const { stdout } = await exec.getExecOutput(
`"${pythonPath.replace(/"/g, '\\"')}"`,
["-I", "-c", "import platform; print(platform.python_version())"],
{ silent: !core.isDebug() },
);
return stdout.trim();
} catch (error) {
core.debug(
`Failed to get the activated environment's Python version. Error: ${error instanceof Error ? error.message : String(error)}`,
);
return "";
}
}