Resolve Python architecture during input loading

This commit is contained in:
Zanie Blue
2026-10-02 13:19:07 -05:00
parent 7cc9e96834
commit db42fc9bc6
10 changed files with 1132 additions and 1152 deletions

View File

@@ -1,11 +1,4 @@
import { import { beforeEach, describe, expect, it, jest } from "@jest/globals";
afterEach,
beforeEach,
describe,
expect,
it,
jest,
} from "@jest/globals";
import { createSetupInputs } from "../helpers/setup-inputs"; import { createSetupInputs } from "../helpers/setup-inputs";
const mockRestoreCache = jest.fn(); const mockRestoreCache = jest.fn();
@@ -14,7 +7,6 @@ const mockSetOutput = jest.fn();
const mockGetArch = jest.fn(() => "x86_64"); const mockGetArch = jest.fn(() => "x86_64");
const mockGetOSNameVersion = jest.fn(() => "ubuntu-24.04"); const mockGetOSNameVersion = jest.fn(() => "ubuntu-24.04");
const mockGetPlatform = jest.fn(async () => "unknown-linux-gnu"); const mockGetPlatform = jest.fn(async () => "unknown-linux-gnu");
const ORIGINAL_UV_PYTHON_ARCH = process.env.UV_PYTHON_ARCH;
jest.unstable_mockModule("@actions/cache", () => ({ jest.unstable_mockModule("@actions/cache", () => ({
restoreCache: mockRestoreCache, restoreCache: mockRestoreCache,
@@ -49,21 +41,12 @@ function cacheKeyOutput(): string {
} }
beforeEach(() => { beforeEach(() => {
delete process.env.UV_PYTHON_ARCH;
jest.clearAllMocks(); jest.clearAllMocks();
mockGetArch.mockReturnValue("x86_64"); mockGetArch.mockReturnValue("x86_64");
mockGetOSNameVersion.mockReturnValue("ubuntu-24.04"); mockGetOSNameVersion.mockReturnValue("ubuntu-24.04");
mockGetPlatform.mockResolvedValue("unknown-linux-gnu"); mockGetPlatform.mockResolvedValue("unknown-linux-gnu");
}); });
afterEach(() => {
if (ORIGINAL_UV_PYTHON_ARCH === undefined) {
delete process.env.UV_PYTHON_ARCH;
} else {
process.env.UV_PYTHON_ARCH = ORIGINAL_UV_PYTHON_ARCH;
}
});
describe("restoreCache", () => { describe("restoreCache", () => {
it("encodes Python version ranges before adding them to the cache key", async () => { it("encodes Python version ranges before adding them to the cache key", async () => {
await restoreCache(createSetupInputs(), ">3.10.11,<3.11"); await restoreCache(createSetupInputs(), ">3.10.11,<3.11");
@@ -95,10 +78,10 @@ describe("restoreCache", () => {
); );
}); });
it("includes an inherited Python architecture in the cache key", async () => { it("includes the resolved Python architecture in the cache key", async () => {
process.env.UV_PYTHON_ARCH = "aarch64"; const inputs = createSetupInputs({ pythonArch: "aarch64" });
await restoreCache(createSetupInputs(), "3.14"); await restoreCache(inputs, "3.14");
expect(cacheKeyOutput()).toContain("-3.14-python-aarch64-"); expect(cacheKeyOutput()).toContain("-3.14-python-aarch64-");
}); });

View File

@@ -16,6 +16,7 @@ export function createSetupInputs(
checksum: "", checksum: "",
downloadFromAstralMirror: false, downloadFromAstralMirror: false,
enableCache: true, enableCache: true,
exportPythonArch: false,
githubToken: "", githubToken: "",
ignoreEmptyWorkdir: false, ignoreEmptyWorkdir: false,
ignoreNothingToCache: false, ignoreNothingToCache: false,

View File

@@ -38,9 +38,7 @@ jest.unstable_mockModule("@actions/core", () => ({
warning: mockWarning, warning: mockWarning,
})); }));
const { CacheLocalSource, loadInputs, resolvePythonArch } = await import( const { CacheLocalSource, loadInputs } = await import("../../src/utils/inputs");
"../../src/utils/inputs"
);
function createTempProject(files: Record<string, string> = {}): string { function createTempProject(files: Record<string, string> = {}): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "setup-uv-inputs-test-")); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "setup-uv-inputs-test-"));
@@ -108,6 +106,7 @@ describe("loadInputs", () => {
}); });
expect(inputs.pythonDir).toBe("/runner-temp/uv-python-dir"); expect(inputs.pythonDir).toBe("/runner-temp/uv-python-dir");
expect(inputs.pythonArch).toBe(""); expect(inputs.pythonArch).toBe("");
expect(inputs.exportPythonArch).toBe(false);
expect(inputs.venvPath).toBe("/workspace/.venv"); expect(inputs.venvPath).toBe("/workspace/.venv");
expect(inputs.manifestFile).toBeUndefined(); expect(inputs.manifestFile).toBeUndefined();
expect(inputs.resolutionStrategy).toBe("highest"); expect(inputs.resolutionStrategy).toBe("highest");
@@ -166,7 +165,7 @@ describe("loadInputs", () => {
const inputs = loadInputs(); const inputs = loadInputs();
expect(inputs.pythonArch).toBe("x86_64"); expect(inputs.pythonArch).toBe("x86_64");
expect(resolvePythonArch(inputs.pythonArch)).toBe("x86_64"); expect(inputs.exportPythonArch).toBe(true);
}); });
it.each(["aarch64", "x86_64_v3", ""])( it.each(["aarch64", "x86_64_v3", ""])(
@@ -176,8 +175,8 @@ describe("loadInputs", () => {
process.env.UV_PYTHON_ARCH = pythonArch; process.env.UV_PYTHON_ARCH = pythonArch;
const inputs = loadInputs(); const inputs = loadInputs();
expect(inputs.pythonArch).toBe(""); expect(inputs.pythonArch).toBe(pythonArch);
expect(resolvePythonArch(inputs.pythonArch)).toBe(pythonArch); expect(inputs.exportPythonArch).toBe(false);
}, },
); );

View File

@@ -57,16 +57,16 @@ afterEach(() => {
describe("setupPythonArch", () => { describe("setupPythonArch", () => {
it("leaves Python selection alone without an architecture", async () => { it("leaves Python selection alone without an architecture", async () => {
await setupPythonArch("/tools/uv", ""); await setupPythonArch("/tools/uv", "", false);
expect(mockExecFile).not.toHaveBeenCalled(); expect(mockExecFile).not.toHaveBeenCalled();
expect(mockExportVariable).not.toHaveBeenCalled(); expect(mockExportVariable).not.toHaveBeenCalled();
}); });
it("validates an inherited architecture without exporting it", async () => { it("validates a resolved environment architecture without exporting it", async () => {
process.env.UV_PYTHON_ARCH = "aarch64"; process.env.UV_PYTHON_ARCH = "aarch64";
await setupPythonArch("/tools/uv", ""); await setupPythonArch("/tools/uv", "aarch64", false);
expect(mockExecFile).toHaveBeenNthCalledWith( expect(mockExecFile).toHaveBeenNthCalledWith(
2, 2,
@@ -83,7 +83,7 @@ describe("setupPythonArch", () => {
it.each(["/runner temp/uv", "C:\\runner temp\\uv.exe"])( it.each(["/runner temp/uv", "C:\\runner temp\\uv.exe"])(
"validates and exports the architecture using the installed uv: %s", "validates and exports the architecture using the installed uv: %s",
async (uvPath) => { async (uvPath) => {
await setupPythonArch(uvPath, "x86_64"); await setupPythonArch(uvPath, "x86_64", true);
expect(mockExecFile).toHaveBeenNthCalledWith( expect(mockExecFile).toHaveBeenNthCalledWith(
1, 1,
@@ -112,7 +112,7 @@ describe("setupPythonArch", () => {
it("rejects uv versions that ignore UV_PYTHON_ARCH", async () => { it("rejects uv versions that ignore UV_PYTHON_ARCH", async () => {
mockExecFile.mockReset().mockResolvedValue(success); mockExecFile.mockReset().mockResolvedValue(success);
await expect(setupPythonArch("/tools/uv", "x86_64")).rejects.toThrow( await expect(setupPythonArch("/tools/uv", "x86_64", true)).rejects.toThrow(
"The installed version of uv does not support UV_PYTHON_ARCH", "The installed version of uv does not support UV_PYTHON_ARCH",
); );
expect(mockExecFile).toHaveBeenCalledTimes(1); expect(mockExecFile).toHaveBeenCalledTimes(1);
@@ -127,7 +127,7 @@ describe("setupPythonArch", () => {
}), }),
); );
await expect(setupPythonArch("/tools/uv", "x86_64")).rejects.toThrow( await expect(setupPythonArch("/tools/uv", "x86_64", true)).rejects.toThrow(
"Failed to check uv's support for UV_PYTHON_ARCH: error: unrelated configuration error", "Failed to check uv's support for UV_PYTHON_ARCH: error: unrelated configuration error",
); );
expect(mockExportVariable).not.toHaveBeenCalled(); expect(mockExportVariable).not.toHaveBeenCalled();
@@ -144,7 +144,7 @@ describe("setupPythonArch", () => {
}), }),
); );
await expect(setupPythonArch("/tools/uv", "invalid")).rejects.toThrow( await expect(setupPythonArch("/tools/uv", "invalid", true)).rejects.toThrow(
"Failed to set Python architecture to invalid: error: Unknown architecture: invalid", "Failed to set Python architecture to invalid: error: Unknown architecture: invalid",
); );
expect(mockExportVariable).not.toHaveBeenCalled(); expect(mockExportVariable).not.toHaveBeenCalled();

23
dist/save-cache/index.cjs generated vendored
View File

@@ -63714,6 +63714,15 @@ function info2(msg) {
} }
var warning2 = warning; var warning2 = warning;
// src/cache/restore-cache.ts
var STATE_CACHE_KEY = "cache-key";
var STATE_CACHE_MATCHED_KEY = "cache-matched-key";
var STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key";
// src/utils/constants.ts
var STATE_UV_PATH = "uv-path";
var STATE_UV_VERSION = "uv-version";
// src/utils/inputs.ts // src/utils/inputs.ts
var import_node_fs4 = __toESM(require("node:fs"), 1); var import_node_fs4 = __toESM(require("node:fs"), 1);
var import_node_path = __toESM(require("node:path"), 1); var import_node_path = __toESM(require("node:path"), 1);
@@ -64438,7 +64447,9 @@ function loadInputs() {
const version3 = getInput("version"); const version3 = getInput("version");
const versionFile = getVersionFile(workingDirectory); const versionFile = getVersionFile(workingDirectory);
const pythonVersion = getPythonVersion(versionFile); const pythonVersion = getPythonVersion(versionFile);
const pythonArch = getInput("python-arch"); const pythonArchInput = getInput("python-arch");
const pythonArch = pythonArchInput || process.env.UV_PYTHON_ARCH || "";
const exportPythonArch = pythonArchInput !== "";
const activateEnvironment = getBooleanInput("activate-environment"); const activateEnvironment = getBooleanInput("activate-environment");
const noProject = getBooleanInput("no-project"); const noProject = getBooleanInput("no-project");
const venvPath = getVenvPath(workingDirectory, activateEnvironment); const venvPath = getVenvPath(workingDirectory, activateEnvironment);
@@ -64476,6 +64487,7 @@ function loadInputs() {
checksum, checksum,
downloadFromAstralMirror, downloadFromAstralMirror,
enableCache, enableCache,
exportPythonArch,
githubToken, githubToken,
ignoreEmptyWorkdir, ignoreEmptyWorkdir,
ignoreNothingToCache, ignoreNothingToCache,
@@ -64731,15 +64743,6 @@ function getResolutionStrategy() {
); );
} }
// src/cache/restore-cache.ts
var STATE_CACHE_KEY = "cache-key";
var STATE_CACHE_MATCHED_KEY = "cache-matched-key";
var STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key";
// src/utils/constants.ts
var STATE_UV_PATH = "uv-path";
var STATE_UV_VERSION = "uv-version";
// src/save-cache.ts // src/save-cache.ts
function formatUnexpectedFailure(error2) { function formatUnexpectedFailure(error2) {
if (error2 instanceof Error) { if (error2 instanceof Error) {

2184
dist/setup/index.cjs generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import * as cache from "@actions/cache"; import * as cache from "@actions/cache";
import * as core from "@actions/core"; import * as core from "@actions/core";
import { hashFiles } from "../hash/hash-files"; import { hashFiles } from "../hash/hash-files";
import { resolvePythonArch, type SetupInputs } from "../utils/inputs"; import type { SetupInputs } from "../utils/inputs";
import * as log from "../utils/logging"; import * as log from "../utils/logging";
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms"; import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
@@ -98,9 +98,8 @@ async function computeKeys(
? `-${encodeURIComponent(inputs.cacheSuffix)}` ? `-${encodeURIComponent(inputs.cacheSuffix)}`
: ""; : "";
const version = encodeURIComponent(pythonVersion ?? "unknown"); const version = encodeURIComponent(pythonVersion ?? "unknown");
const pythonArch = resolvePythonArch(inputs.pythonArch); const pythonArchKey = inputs.pythonArch
const pythonArchKey = pythonArch ? `-python-${encodeURIComponent(inputs.pythonArch)}`
? `-python-${encodeURIComponent(pythonArch)}`
: ""; : "";
const platform = await getPlatform(); const platform = await getPlatform();
const osNameVersion = getOSNameVersion(); const osNameVersion = getOSNameVersion();

View File

@@ -91,7 +91,7 @@ async function run(): Promise<void> {
setupResult.uvDir, setupResult.uvDir,
process.platform === "win32" ? "uv.exe" : "uv", process.platform === "win32" ? "uv.exe" : "uv",
); );
await setupPythonArch(uvPath, inputs.pythonArch); await setupPythonArch(uvPath, inputs.pythonArch, inputs.exportPythonArch);
addToolBinToPath(inputs); addToolBinToPath(inputs);
addUvToPathAndOutput(setupResult.uvDir); addUvToPathAndOutput(setupResult.uvDir);

View File

@@ -25,6 +25,7 @@ export interface SetupInputs {
versionFile: string; versionFile: string;
pythonVersion: string; pythonVersion: string;
pythonArch: string; pythonArch: string;
exportPythonArch: boolean;
activateEnvironment: boolean; activateEnvironment: boolean;
noProject: boolean; noProject: boolean;
venvPath: string; venvPath: string;
@@ -55,7 +56,9 @@ export function loadInputs(): SetupInputs {
const version = core.getInput("version"); const version = core.getInput("version");
const versionFile = getVersionFile(workingDirectory); const versionFile = getVersionFile(workingDirectory);
const pythonVersion = getPythonVersion(versionFile); const pythonVersion = getPythonVersion(versionFile);
const pythonArch = core.getInput("python-arch"); const pythonArchInput = core.getInput("python-arch");
const pythonArch = pythonArchInput || process.env.UV_PYTHON_ARCH || "";
const exportPythonArch = pythonArchInput !== "";
const activateEnvironment = core.getBooleanInput("activate-environment"); const activateEnvironment = core.getBooleanInput("activate-environment");
const noProject = core.getBooleanInput("no-project"); const noProject = core.getBooleanInput("no-project");
const venvPath = getVenvPath(workingDirectory, activateEnvironment); const venvPath = getVenvPath(workingDirectory, activateEnvironment);
@@ -96,6 +99,7 @@ export function loadInputs(): SetupInputs {
checksum, checksum,
downloadFromAstralMirror, downloadFromAstralMirror,
enableCache, enableCache,
exportPythonArch,
githubToken, githubToken,
ignoreEmptyWorkdir, ignoreEmptyWorkdir,
ignoreNothingToCache, ignoreNothingToCache,
@@ -149,10 +153,6 @@ function getPythonVersion(versionFile: string): string {
} }
} }
export function resolvePythonArch(pythonArch: string): string {
return pythonArch || process.env.UV_PYTHON_ARCH || "";
}
function getVenvPath( function getVenvPath(
workingDirectory: string, workingDirectory: string,
activateEnvironment: boolean, activateEnvironment: boolean,

View File

@@ -1,7 +1,6 @@
import { execFile } from "node:child_process"; import { execFile } from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
import * as core from "@actions/core"; import * as core from "@actions/core";
import { resolvePythonArch } from "./inputs";
import * as log from "./logging"; import * as log from "./logging";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -9,9 +8,9 @@ const PROBE_ARCH = "setup-uv-probe";
export async function setupPythonArch( export async function setupPythonArch(
uvPath: string, uvPath: string,
pythonArchInput: string, pythonArch: string,
exportPythonArch: boolean,
): Promise<void> { ): Promise<void> {
const pythonArch = resolvePythonArch(pythonArchInput);
if (pythonArch === "") { if (pythonArch === "") {
return; return;
} }
@@ -41,7 +40,7 @@ export async function setupPythonArch(
); );
} }
if (pythonArchInput !== "") { if (exportPythonArch) {
core.exportVariable("UV_PYTHON_ARCH", pythonArch); core.exportVariable("UV_PYTHON_ARCH", pythonArch);
log.info(`Set UV_PYTHON_ARCH to ${pythonArch}`); log.info(`Set UV_PYTHON_ARCH to ${pythonArch}`);
} }