From 7cc9e96834da716a5d5736edc2b338de3dd2c8e6 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 30 Sep 2026 11:26:40 -0500 Subject: [PATCH] Add a Python architecture input --- README.md | 23 + __tests__/cache/restore-cache.test.ts | 66 +- __tests__/helpers/setup-inputs.ts | 1 + __tests__/utils/inputs.test.ts | 34 +- __tests__/utils/python-arch.test.ts | 152 ++ action-types.yml | 2 + action.yml | 3 + dist/save-cache/index.cjs | 20 +- dist/setup/index.cjs | 2251 +++++++++++++------------ docs/caching.md | 1 + docs/environment-and-tools.md | 2 + src/cache/restore-cache.ts | 8 +- src/setup-uv.ts | 6 + src/utils/inputs.ts | 7 + src/utils/python-arch.ts | 76 + 15 files changed, 1544 insertions(+), 1108 deletions(-) create mode 100644 __tests__/utils/python-arch.test.ts create mode 100644 src/utils/python-arch.ts diff --git a/README.md b/README.md index 7e9e522..8235c4b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs - [Inputs](#inputs) - [Outputs](#outputs) - [Python version](#python-version) + - [Python architecture](#python-architecture) - [Working directory](#working-directory) - [Advanced Configuration](#advanced-configuration) - [How it works](#how-it-works) @@ -56,6 +57,9 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed # The version of Python to set UV_PYTHON to (overrides the Python version from .tool-versions) python-version: "" + # The Python architecture to set UV_PYTHON_ARCH to, e.g., x86_64 or aarch64 + python-arch: "" + # Use uv venv to activate a venv ready to be used by later steps activate-environment: "false" @@ -193,6 +197,25 @@ jobs: run: uv run --frozen pytest ``` +### Python architecture + +Use `python-arch` to set `UV_PYTHON_ARCH` for the rest of the job. This selects the Python +architecture independently of the version, including versions requested by `.python-version`. +An explicit architecture or interpreter path in a Python request takes precedence. + +```yaml +- uses: astral-sh/setup-uv@main + with: + python-version: "3.14" + python-arch: x86_64 +- run: uv run --frozen pytest +``` + +The input overrides an existing `UV_PYTHON_ARCH` value. When it is omitted, the action respects +`UV_PYTHON_ARCH` from the environment. Both forms require a uv version that supports the variable; +the action reports an error if the installed version does not support it. The selected architecture +is included in the [cache key](docs/caching.md#cache-key). + ### Working directory You can set the working directory with the `working-directory` input. diff --git a/__tests__/cache/restore-cache.test.ts b/__tests__/cache/restore-cache.test.ts index bb97348..3df679b 100644 --- a/__tests__/cache/restore-cache.test.ts +++ b/__tests__/cache/restore-cache.test.ts @@ -1,9 +1,20 @@ -import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from "@jest/globals"; import { createSetupInputs } from "../helpers/setup-inputs"; const mockRestoreCache = jest.fn(); const mockSaveState = jest.fn(); const mockSetOutput = jest.fn(); +const mockGetArch = jest.fn(() => "x86_64"); +const mockGetOSNameVersion = jest.fn(() => "ubuntu-24.04"); +const mockGetPlatform = jest.fn(async () => "unknown-linux-gnu"); +const ORIGINAL_UV_PYTHON_ARCH = process.env.UV_PYTHON_ARCH; jest.unstable_mockModule("@actions/cache", () => ({ restoreCache: mockRestoreCache, @@ -24,9 +35,9 @@ jest.unstable_mockModule("../../src/utils/logging", () => ({ })); jest.unstable_mockModule("../../src/utils/platforms", () => ({ - getArch: jest.fn(() => "x86_64"), - getOSNameVersion: jest.fn(() => "ubuntu-24.04"), - getPlatform: jest.fn(async () => "unknown-linux-gnu"), + getArch: mockGetArch, + getOSNameVersion: mockGetOSNameVersion, + getPlatform: mockGetPlatform, })); const { restoreCache } = await import("../../src/cache/restore-cache"); @@ -38,7 +49,19 @@ function cacheKeyOutput(): string { } beforeEach(() => { + delete process.env.UV_PYTHON_ARCH; jest.clearAllMocks(); + mockGetArch.mockReturnValue("x86_64"); + mockGetOSNameVersion.mockReturnValue("ubuntu-24.04"); + 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", () => { @@ -71,4 +94,39 @@ describe("restoreCache", () => { "setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.11-dependencyhash-tests-3.11", ); }); + + it("includes an inherited Python architecture in the cache key", async () => { + process.env.UV_PYTHON_ARCH = "aarch64"; + + await restoreCache(createSetupInputs(), "3.14"); + + expect(cacheKeyOutput()).toContain("-3.14-python-aarch64-"); + }); + + it.each(["aarch64", "x86_64"])( + "separates %s Python caches on a Windows ARM64 runner", + async (pythonArch) => { + mockGetArch.mockReturnValue("aarch64"); + mockGetOSNameVersion.mockReturnValue("windows-11"); + mockGetPlatform.mockResolvedValue("pc-windows-msvc"); + const inputs = createSetupInputs({ + cachePython: true, + pythonArch, + restoreCache: true, + }); + + await restoreCache(inputs, "3.14"); + + const cacheKey = `setup-uv-2-aarch64-pc-windows-msvc-windows-11-3.14-python-${pythonArch}-py-dependencyhash`; + expect(cacheKeyOutput()).toBe(cacheKey); + expect(mockRestoreCache).toHaveBeenCalledWith( + [inputs.cacheLocalPath?.path], + cacheKey, + ); + expect(mockRestoreCache).toHaveBeenCalledWith( + [inputs.pythonDir], + `${cacheKey}-python`, + ); + }, + ); }); diff --git a/__tests__/helpers/setup-inputs.ts b/__tests__/helpers/setup-inputs.ts index 0a6a625..5c3ac71 100644 --- a/__tests__/helpers/setup-inputs.ts +++ b/__tests__/helpers/setup-inputs.ts @@ -21,6 +21,7 @@ export function createSetupInputs( ignoreNothingToCache: false, noProject: false, pruneCache: false, + pythonArch: "", pythonDir: "/tmp/uv-python-dir", pythonVersion: "", quiet: false, diff --git a/__tests__/utils/inputs.test.ts b/__tests__/utils/inputs.test.ts index f93a41b..ab6403c 100644 --- a/__tests__/utils/inputs.test.ts +++ b/__tests__/utils/inputs.test.ts @@ -19,6 +19,7 @@ const ORIGINAL_RUNNER_ENVIRONMENT = process.env.RUNNER_ENVIRONMENT; const ORIGINAL_RUNNER_TEMP = process.env.RUNNER_TEMP; const ORIGINAL_UV_CACHE_DIR = process.env.UV_CACHE_DIR; const ORIGINAL_UV_PYTHON = process.env.UV_PYTHON; +const ORIGINAL_UV_PYTHON_ARCH = process.env.UV_PYTHON_ARCH; const ORIGINAL_UV_PYTHON_INSTALL_DIR = process.env.UV_PYTHON_INSTALL_DIR; const mockDebug = jest.fn(); @@ -37,7 +38,9 @@ jest.unstable_mockModule("@actions/core", () => ({ warning: mockWarning, })); -const { CacheLocalSource, loadInputs } = await import("../../src/utils/inputs"); +const { CacheLocalSource, loadInputs, resolvePythonArch } = await import( + "../../src/utils/inputs" +); function createTempProject(files: Record = {}): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "setup-uv-inputs-test-")); @@ -62,6 +65,7 @@ function resetEnvironment(): void { delete process.env.RUNNER_TEMP; delete process.env.UV_CACHE_DIR; delete process.env.UV_PYTHON; + delete process.env.UV_PYTHON_ARCH; delete process.env.UV_PYTHON_INSTALL_DIR; } @@ -77,6 +81,11 @@ function restoreEnvironment(): void { process.env.RUNNER_TEMP = ORIGINAL_RUNNER_TEMP; process.env.UV_CACHE_DIR = ORIGINAL_UV_CACHE_DIR; process.env.UV_PYTHON = ORIGINAL_UV_PYTHON; + if (ORIGINAL_UV_PYTHON_ARCH === undefined) { + delete process.env.UV_PYTHON_ARCH; + } else { + process.env.UV_PYTHON_ARCH = ORIGINAL_UV_PYTHON_ARCH; + } process.env.UV_PYTHON_INSTALL_DIR = ORIGINAL_UV_PYTHON_INSTALL_DIR; } @@ -98,6 +107,7 @@ describe("loadInputs", () => { source: CacheLocalSource.Default, }); expect(inputs.pythonDir).toBe("/runner-temp/uv-python-dir"); + expect(inputs.pythonArch).toBe(""); expect(inputs.venvPath).toBe("/workspace/.venv"); expect(inputs.manifestFile).toBeUndefined(); expect(inputs.resolutionStrategy).toBe("highest"); @@ -149,6 +159,28 @@ describe("loadInputs", () => { expect(inputs.pythonVersion).toBe(""); }); + it("prefers the python-arch input over UV_PYTHON_ARCH", () => { + mockInputs["working-directory"] = "/workspace"; + mockInputs["python-arch"] = "x86_64"; + process.env.UV_PYTHON_ARCH = "aarch64"; + + const inputs = loadInputs(); + expect(inputs.pythonArch).toBe("x86_64"); + expect(resolvePythonArch(inputs.pythonArch)).toBe("x86_64"); + }); + + it.each(["aarch64", "x86_64_v3", ""])( + "uses UV_PYTHON_ARCH when python-arch is omitted: %s", + (pythonArch) => { + mockInputs["working-directory"] = "/workspace"; + process.env.UV_PYTHON_ARCH = pythonArch; + + const inputs = loadInputs(); + expect(inputs.pythonArch).toBe(""); + expect(resolvePythonArch(inputs.pythonArch)).toBe(pythonArch); + }, + ); + it.each(["pull_request_target", "workflow_run", "release"])( "disables automatic caching for the %s event", (eventName) => { diff --git a/__tests__/utils/python-arch.test.ts b/__tests__/utils/python-arch.test.ts new file mode 100644 index 0000000..2793cb6 --- /dev/null +++ b/__tests__/utils/python-arch.test.ts @@ -0,0 +1,152 @@ +import { promisify } from "node:util"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from "@jest/globals"; + +const ORIGINAL_UV_PYTHON_ARCH = process.env.UV_PYTHON_ARCH; + +const mockExecFile = + jest.fn< + (...args: unknown[]) => Promise<{ + stdout: string; + stderr: string; + }> + >(); +const mockExportVariable = jest.fn(); + +jest.unstable_mockModule("node:child_process", () => ({ + execFile: Object.assign(mockExecFile, { [promisify.custom]: mockExecFile }), +})); +jest.unstable_mockModule("@actions/core", () => ({ + exportVariable: mockExportVariable, +})); +jest.unstable_mockModule("../../src/utils/logging", () => ({ + info: jest.fn(), +})); + +const { setupPythonArch } = await import("../../src/utils/python-arch"); + +const supportedProbe = Object.assign(new Error("uv exited with code 2"), { + code: 2, + stderr: + "error: Failed to parse environment variable `UV_PYTHON_ARCH` with invalid value `setup-uv-probe`: Unknown architecture: setup-uv-probe\n", +}); +const success = { stderr: "", stdout: "/cache\n" }; + +beforeEach(() => { + delete process.env.UV_PYTHON_ARCH; + jest.clearAllMocks(); + mockExecFile + .mockReset() + .mockRejectedValueOnce(supportedProbe) + .mockResolvedValueOnce(success); +}); + +afterEach(() => { + if (ORIGINAL_UV_PYTHON_ARCH === undefined) { + delete process.env.UV_PYTHON_ARCH; + } else { + process.env.UV_PYTHON_ARCH = ORIGINAL_UV_PYTHON_ARCH; + } +}); + +describe("setupPythonArch", () => { + it("leaves Python selection alone without an architecture", async () => { + await setupPythonArch("/tools/uv", ""); + + expect(mockExecFile).not.toHaveBeenCalled(); + expect(mockExportVariable).not.toHaveBeenCalled(); + }); + + it("validates an inherited architecture without exporting it", async () => { + process.env.UV_PYTHON_ARCH = "aarch64"; + + await setupPythonArch("/tools/uv", ""); + + expect(mockExecFile).toHaveBeenNthCalledWith( + 2, + "/tools/uv", + ["--no-config", "cache", "dir"], + expect.objectContaining({ + env: expect.objectContaining({ UV_PYTHON_ARCH: "aarch64" }), + }), + ); + expect(mockExportVariable).not.toHaveBeenCalled(); + expect(process.env.UV_PYTHON_ARCH).toBe("aarch64"); + }); + + it.each(["/runner temp/uv", "C:\\runner temp\\uv.exe"])( + "validates and exports the architecture using the installed uv: %s", + async (uvPath) => { + await setupPythonArch(uvPath, "x86_64"); + + expect(mockExecFile).toHaveBeenNthCalledWith( + 1, + uvPath, + ["--no-config", "cache", "dir"], + expect.objectContaining({ + encoding: "utf8", + env: expect.objectContaining({ UV_PYTHON_ARCH: "setup-uv-probe" }), + }), + ); + expect(mockExecFile).toHaveBeenNthCalledWith( + 2, + uvPath, + ["--no-config", "cache", "dir"], + expect.objectContaining({ + env: expect.objectContaining({ UV_PYTHON_ARCH: "x86_64" }), + }), + ); + expect(mockExportVariable).toHaveBeenCalledWith( + "UV_PYTHON_ARCH", + "x86_64", + ); + }, + ); + + it("rejects uv versions that ignore UV_PYTHON_ARCH", async () => { + mockExecFile.mockReset().mockResolvedValue(success); + + await expect(setupPythonArch("/tools/uv", "x86_64")).rejects.toThrow( + "The installed version of uv does not support UV_PYTHON_ARCH", + ); + expect(mockExecFile).toHaveBeenCalledTimes(1); + expect(mockExportVariable).not.toHaveBeenCalled(); + }); + + it("reports unexpected probe failures", async () => { + mockExecFile.mockReset().mockRejectedValue( + Object.assign(new Error("uv exited with code 2"), { + code: 2, + stderr: "error: unrelated configuration error\n", + }), + ); + + await expect(setupPythonArch("/tools/uv", "x86_64")).rejects.toThrow( + "Failed to check uv's support for UV_PYTHON_ARCH: error: unrelated configuration error", + ); + expect(mockExportVariable).not.toHaveBeenCalled(); + }); + + it("reports invalid architectures before exporting them", async () => { + mockExecFile + .mockReset() + .mockRejectedValueOnce(supportedProbe) + .mockRejectedValueOnce( + Object.assign(new Error("uv exited with code 2"), { + code: 2, + stderr: "error: Unknown architecture: invalid\n", + }), + ); + + await expect(setupPythonArch("/tools/uv", "invalid")).rejects.toThrow( + "Failed to set Python architecture to invalid: error: Unknown architecture: invalid", + ); + expect(mockExportVariable).not.toHaveBeenCalled(); + }); +}); diff --git a/action-types.yml b/action-types.yml index a2b4ac3..3746c94 100644 --- a/action-types.yml +++ b/action-types.yml @@ -7,6 +7,8 @@ inputs: type: string python-version: type: string + python-arch: + type: string activate-environment: type: boolean venv-path: diff --git a/action.yml b/action.yml index 835f8bd..a52b558 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: python-version: description: "The version of Python to set UV_PYTHON to. Overrides the Python version from .tool-versions." required: false + python-arch: + description: "The Python architecture to set UV_PYTHON_ARCH to, e.g., x86_64 or aarch64. Overrides UV_PYTHON_ARCH." + required: false activate-environment: description: "Use uv venv to activate a venv ready to be used by later steps. " default: "false" diff --git a/dist/save-cache/index.cjs b/dist/save-cache/index.cjs index a9efef0..a62f853 100644 --- a/dist/save-cache/index.cjs +++ b/dist/save-cache/index.cjs @@ -63714,15 +63714,6 @@ function info2(msg) { } 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 var import_node_fs4 = __toESM(require("node:fs"), 1); var import_node_path = __toESM(require("node:path"), 1); @@ -64447,6 +64438,7 @@ function loadInputs() { const version3 = getInput("version"); const versionFile = getVersionFile(workingDirectory); const pythonVersion = getPythonVersion(versionFile); + const pythonArch = getInput("python-arch"); const activateEnvironment = getBooleanInput("activate-environment"); const noProject = getBooleanInput("no-project"); const venvPath = getVenvPath(workingDirectory, activateEnvironment); @@ -64490,6 +64482,7 @@ function loadInputs() { manifestFile, noProject, pruneCache: pruneCache2, + pythonArch, pythonDir, pythonVersion, quiet: quiet2, @@ -64738,6 +64731,15 @@ 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 function formatUnexpectedFailure(error2) { if (error2 instanceof Error) { diff --git a/dist/setup/index.cjs b/dist/setup/index.cjs index cf863ef..fbce395 100644 --- a/dist/setup/index.cjs +++ b/dist/setup/index.cjs @@ -10833,7 +10833,7 @@ var require_mock_interceptor = __commonJS({ var require_mock_client = __commonJS({ "node_modules/@actions/http-client/node_modules/undici/lib/mock/mock-client.js"(exports2, module2) { "use strict"; - var { promisify: promisify6 } = require("node:util"); + var { promisify: promisify7 } = require("node:util"); var Client = require_client(); var { buildMockDispatch } = require_mock_utils(); var { @@ -10873,7 +10873,7 @@ var require_mock_client = __commonJS({ return new MockInterceptor(opts, this[kDispatches]); } async [kClose]() { - await promisify6(this[kOriginalClose])(); + await promisify7(this[kOriginalClose])(); this[kConnected] = 0; this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); } @@ -10886,7 +10886,7 @@ var require_mock_client = __commonJS({ var require_mock_pool = __commonJS({ "node_modules/@actions/http-client/node_modules/undici/lib/mock/mock-pool.js"(exports2, module2) { "use strict"; - var { promisify: promisify6 } = require("node:util"); + var { promisify: promisify7 } = require("node:util"); var Pool = require_pool(); var { buildMockDispatch } = require_mock_utils(); var { @@ -10926,7 +10926,7 @@ var require_mock_pool = __commonJS({ return new MockInterceptor(opts, this[kDispatches]); } async [kClose]() { - await promisify6(this[kOriginalClose])(); + await promisify7(this[kOriginalClose])(); this[kConnected] = 0; this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); } @@ -43099,7 +43099,7 @@ var require_mock_interceptor2 = __commonJS({ var require_mock_client2 = __commonJS({ "node_modules/undici/lib/mock/mock-client.js"(exports2, module2) { "use strict"; - var { promisify: promisify6 } = require("node:util"); + var { promisify: promisify7 } = require("node:util"); var Client = require_client2(); var { buildMockDispatch } = require_mock_utils2(); var { @@ -43147,7 +43147,7 @@ var require_mock_client2 = __commonJS({ this[kDispatches] = []; } async [kClose]() { - await promisify6(this[kOriginalClose])(); + await promisify7(this[kOriginalClose])(); this[kConnected] = 0; this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); } @@ -43360,7 +43360,7 @@ var require_mock_call_history = __commonJS({ var require_mock_pool2 = __commonJS({ "node_modules/undici/lib/mock/mock-pool.js"(exports2, module2) { "use strict"; - var { promisify: promisify6 } = require("node:util"); + var { promisify: promisify7 } = require("node:util"); var Pool = require_pool2(); var { buildMockDispatch } = require_mock_utils2(); var { @@ -43408,7 +43408,7 @@ var require_mock_pool2 = __commonJS({ this[kDispatches] = []; } async [kClose]() { - await promisify6(this[kOriginalClose])(); + await promisify7(this[kOriginalClose])(); this[kConnected] = 0; this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); } @@ -93839,8 +93839,1048 @@ async function hashFiles3(pattern, verbose = false) { return ""; } -// src/utils/platforms.ts +// src/utils/inputs.ts +var import_node_fs4 = __toESM(require("node:fs"), 1); +var import_node_path = __toESM(require("node:path"), 1); + +// src/version/tool-versions-file.ts var import_node_fs2 = __toESM(require("node:fs"), 1); +function getUvVersionFromToolVersions(filePath) { + const versions = getToolVersions(filePath, "uv"); + if (versions === void 0 || versions.length !== 1) { + return void 0; + } + const version3 = stripVersionPrefix(versions[0]); + if (isPath(version3)) { + warning( + `The uv version ${versions[0]} in .tool-versions is not supported. Paths are not allowed.` + ); + return void 0; + } + if (version3.startsWith("ref")) { + warning( + "The ref syntax of .tool-versions is not supported. Please use a released version instead." + ); + return void 0; + } + return version3; +} +function getPythonVersionFromToolVersions(filePath) { + const versions = getToolVersions(filePath, "python"); + if (versions === void 0 || versions.length === 0) { + return void 0; + } + if (versions.length > 1) { + warning( + "Multiple Python versions in .tool-versions are not supported. The Python entry will be ignored." + ); + return void 0; + } + const version3 = stripVersionPrefix(versions[0]); + if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:") || isPath(version3)) { + warning( + `The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.` + ); + return void 0; + } + return version3; +} +function getToolVersions(filePath, toolName) { + if (!filePath.endsWith(".tool-versions")) { + return void 0; + } + const fileContents = import_node_fs2.default.readFileSync(filePath, "utf8"); + for (const line of fileContents.split("\n")) { + const content = line.split("#", 1)[0].trim(); + if (content === "") { + continue; + } + const [tool, ...versions] = content.split(/\s+/); + if (tool === toolName) { + return versions; + } + } + return void 0; +} +function stripVersionPrefix(version3) { + return version3.startsWith("v") ? version3.slice(1) : version3; +} +function isPath(version3) { + return version3.includes("/") || version3.includes("\\"); +} + +// src/utils/config-file.ts +var import_node_fs3 = __toESM(require("node:fs"), 1); + +// node_modules/smol-toml/dist/date.js +var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i; +var TomlDate = class _TomlDate extends Date { + #hasDate = false; + #hasTime = false; + #offset = null; + constructor(date) { + let hasDate = true; + let hasTime = true; + let offset = "Z"; + if (typeof date === "string") { + let match4 = date.match(DATE_TIME_RE); + if (match4) { + if (!match4[1]) { + hasDate = false; + date = `0000-01-01T${date}`; + } + hasTime = !!match4[2]; + hasTime && date[10] === " " && (date = date.replace(" ", "T")); + if (match4[2] && +match4[2] > 23) { + date = ""; + } else { + offset = match4[3] || null; + date = date.toUpperCase(); + if (!offset && hasTime) + date += "Z"; + } + } else { + date = ""; + } + } + super(date); + if (!isNaN(this.getTime())) { + this.#hasDate = hasDate; + this.#hasTime = hasTime; + this.#offset = offset; + } + } + isDateTime() { + return this.#hasDate && this.#hasTime; + } + isLocal() { + return !this.#hasDate || !this.#hasTime || !this.#offset; + } + isDate() { + return this.#hasDate && !this.#hasTime; + } + isTime() { + return this.#hasTime && !this.#hasDate; + } + isValid() { + return this.#hasDate || this.#hasTime; + } + toISOString() { + let iso = super.toISOString(); + if (this.isDate()) + return iso.slice(0, 10); + if (this.isTime()) + return iso.slice(11, 23); + if (this.#offset === null) + return iso.slice(0, -1); + if (this.#offset === "Z") + return iso; + let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6); + offset = this.#offset[0] === "-" ? offset : -offset; + let offsetDate = new Date(this.getTime() - offset * 6e4); + return offsetDate.toISOString().slice(0, -1) + this.#offset; + } + static wrapAsOffsetDateTime(jsDate, offset = "Z") { + let date = new _TomlDate(jsDate); + date.#offset = offset; + return date; + } + static wrapAsLocalDateTime(jsDate) { + let date = new _TomlDate(jsDate); + date.#offset = null; + return date; + } + static wrapAsLocalDate(jsDate) { + let date = new _TomlDate(jsDate); + date.#hasTime = false; + date.#offset = null; + return date; + } + static wrapAsLocalTime(jsDate) { + let date = new _TomlDate(jsDate); + date.#hasDate = false; + date.#offset = null; + return date; + } +}; + +// node_modules/smol-toml/dist/error.js +function getLineColFromPtr(string, ptr) { + let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g); + return [lines.length, lines.pop().length + 1]; +} +function makeCodeBlock(string, line, column) { + let lines = string.split(/\r\n|\n|\r/g); + let codeblock = ""; + let numberLen = (Math.log10(line + 1) | 0) + 1; + for (let i = line - 1; i <= line + 1; i++) { + let l = lines[i - 1]; + if (!l) + continue; + codeblock += i.toString().padEnd(numberLen, " "); + codeblock += ": "; + codeblock += l; + codeblock += "\n"; + if (i === line) { + codeblock += " ".repeat(numberLen + column + 2); + codeblock += "^\n"; + } + } + return codeblock; +} +var TomlError = class extends Error { + line; + column; + codeblock; + constructor(message, options) { + const [line, column] = getLineColFromPtr(options.toml, options.ptr); + const codeblock = makeCodeBlock(options.toml, line, column); + super(`Invalid TOML document: ${message} + +${codeblock}`, options); + this.line = line; + this.column = column; + this.codeblock = codeblock; + } +}; + +// node_modules/smol-toml/dist/util.js +function indexOfNewline(str, start = 0) { + let idx = str.indexOf("\n", start); + if (str.charCodeAt(idx - 1) === 13) + idx--; + return idx; +} +function skipComment(ctx) { + for (; ctx.p < ctx.s.length; ctx.p++) { + let c = ctx.s.charCodeAt(ctx.p); + if (c === 10) + break; + if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) { + ctx.p++; + break; + } + if (c < 32 && c !== 9 || c === 127) { + throw new TomlError("control characters are not allowed in comments", { + toml: ctx.s, + ptr: ctx.p + }); + } + } +} +function skipVoid(ctx, banNewLines, banComments) { + let c; + while (1) { + while ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) + ctx.p++; + if (banComments || c !== 35) + break; + skipComment(ctx); + } +} +function skipUntil(ctx, sep9, end) { + let ptr = ctx.p; + if (!end) { + ptr = indexOfNewline(ctx.s, ptr); + ctx.p = ptr < 0 ? ctx.s.length : ptr; + return; + } + for (; ctx.p < ctx.s.length; ctx.p++) { + let c = ctx.s.charCodeAt(ctx.p); + if (c === 35) { + skipComment(ctx); + } else if (c === end || c === sep9) { + return; + } + } + throw new TomlError("cannot find end of structure", { + toml: ctx.s, + ptr + }); +} + +// node_modules/smol-toml/dist/primitive.js +var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/; +var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/; +var LEADING_ZERO = /^[+-]?0[0-9_]/; +function parseString(ctx) { + let start = ctx.p; + let c = ctx.s.charCodeAt(ctx.p++); + let first = c; + let isLiteral = c === 39; + let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1); + if (isMultiline) { + if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10) + ctx.p++; + else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) + ctx.p += 2; + } + let parsed = ""; + let sliceStart = ctx.p; + let state3 = 0; + for (; ctx.p < ctx.s.length; ctx.p++) { + c = ctx.s.charCodeAt(ctx.p); + if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) { + state3 = state3 && 3; + } else if (c < 32 && c !== 9 || c === 127) { + throw new TomlError("control characters are not allowed in strings", { + toml: ctx.s, + ptr: ctx.p + }); + } else if ((!state3 || state3 === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) { + if (isMultiline) { + if (ctx.s.charCodeAt(ctx.p + 3) === first) + ctx.p++; + if (ctx.s.charCodeAt(ctx.p + 3) === first) + ctx.p++; + } + if (!state3) + parsed += ctx.s.slice(sliceStart, ctx.p); + ctx.p += isMultiline ? 3 : 1; + return parsed; + } else if (!state3) { + if (!isLiteral && c === 92) { + parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p); + state3 = 1; + } + } else if (state3 === 1) { + if (c === 120 || c === 117 || c === 85) { + let value = 0; + let len = c === 120 ? 2 : c === 117 ? 4 : 8; + for (let j = 0; j < len; j++, ctx.p++) { + let hex = ctx.s.charCodeAt(ctx.p + 1); + let digit = ( + /* 0-9 */ + hex >= 48 && hex <= 57 ? hex - 48 : ( + /* A-F */ + hex >= 65 && hex <= 70 ? hex - 65 + 10 : ( + /* a-f */ + hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1 + ) + ) + ); + if (digit < 0) + throw new TomlError("invalid non-hex character in unicode escape", { toml: ctx.s, ptr: ctx.p + 1 }); + value = value << 4 | digit; + } + if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) { + throw new TomlError("invalid unicode escape", { toml: ctx.s, ptr: ctx.p }); + } + parsed += String.fromCodePoint(value); + sliceStart = ctx.p + 1; + state3 = 0; + } else if (c === 32 || c === 9) { + state3 = 2; + } else { + if (c === 98) + parsed += "\b"; + else if (c === 116) + parsed += " "; + else if (c === 110) + parsed += "\n"; + else if (c === 102) + parsed += "\f"; + else if (c === 114) + parsed += "\r"; + else if (c === 101) + parsed += "\x1B"; + else if (c === 34) + parsed += '"'; + else if (c === 92) + parsed += "\\"; + else + throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p }); + sliceStart = ctx.p + 1; + state3 = 0; + } + } else if (c !== 32 && c !== 9) { + if (state3 === 2) { + throw new TomlError("invalid escape: only line-ending whitespace may be escaped", { + toml: ctx.s, + ptr: sliceStart + }); + } + state3 = !isLiteral && c === 92 ? 1 : 0; + sliceStart = ctx.p; + } + } + throw new TomlError("unfinished string", { toml: ctx.s, ptr: start }); +} +function sliceAndTrimEndOf(ctx, start, end) { + let value = ctx.s.slice(start, end); + let commentIdx = value.indexOf("#"); + if (commentIdx > 0) { + skipComment({ s: value, p: commentIdx, d: 0 }); + value = value.slice(0, commentIdx); + } + return value.trimEnd(); +} +function parseValue2(ctx, integersAsBigInt, end) { + let ptr = ctx.p; + let err = { toml: ctx.s, ptr }; + skipUntil(ctx, 44, end); + let value = sliceAndTrimEndOf(ctx, ptr, ctx.p); + if (!value) + throw new TomlError("incomplete declaration: value expected", err); + if (value === "-inf") + return -Infinity; + if (value === "inf" || value === "+inf") + return Infinity; + if (value === "nan" || value === "+nan" || value === "-nan") + return NaN; + if (value === "-0") + return integersAsBigInt ? 0n : 0; + let isInt = INT_REGEX.test(value); + if (isInt || FLOAT_REGEX.test(value)) { + if (LEADING_ZERO.test(value)) { + throw new TomlError("leading zeroes are not allowed", err); + } + value = value.replace(/_/g, ""); + let numeric2 = +value; + if (isNaN(numeric2)) { + throw new TomlError("invalid number", err); + } + if (isInt) { + if ((isInt = !Number.isSafeInteger(numeric2)) && !integersAsBigInt) { + throw new TomlError("integer value cannot be represented losslessly", err); + } + if (isInt || integersAsBigInt === true) + numeric2 = BigInt(value); + } + return numeric2; + } + const date = new TomlDate(value); + if (!date.isValid()) + throw new TomlError("invalid value", err); + return date; +} + +// node_modules/smol-toml/dist/extract.js +function extractValue(ctx, end, integersAsBigInt) { + let ptr = ctx.p; + let c = ctx.s.charCodeAt(ptr); + if (c === 91 || c === 123) { + if (!ctx.d--) { + throw new TomlError("document contains excessively nested structures. aborting.", { + toml: ctx.s, + ptr + }); + } + let value = c === 91 ? parseArray(ctx, integersAsBigInt) : parseInlineTable(ctx, integersAsBigInt); + ctx.d++; + return value; + } + if (c === 34 || c === 39) { + return parseString(ctx); + } + if (c === 116) { + if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101) + throw new TomlError("invalid value", { toml: ctx.s, ptr }); + ctx.p++; + return true; + } + if (c === 102) { + if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101) + throw new TomlError("invalid value", { toml: ctx.s, ptr }); + ctx.p++; + return false; + } + return parseValue2(ctx, integersAsBigInt, end); +} + +// node_modules/smol-toml/dist/struct.js +var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/; +function parseKey(ctx, end = "=") { + let start = ctx.p; + let dot = start - 1; + let parsed = []; + let endPtr = ctx.s.indexOf(end, start); + if (endPtr < 0) { + throw new TomlError("incomplete key-value: cannot find end of key", { + toml: ctx.s, + ptr: start + }); + } + do { + let c = ctx.s.charCodeAt(ctx.p = ++dot); + if (c !== 32 && c !== 9) { + if (c === 34 || c === 39) { + if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2)) { + throw new TomlError("multiline strings are not allowed in keys", { + toml: ctx.s, + ptr: ctx.p + }); + } + let part = parseString(ctx); + dot = ctx.s.indexOf(".", ctx.p); + let strEnd = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot); + let newLine = indexOfNewline(strEnd); + if (newLine > -1) { + throw new TomlError("newlines are not allowed in keys", { + toml: ctx.s, + ptr: newLine + }); + } + if (strEnd.trimStart()) { + throw new TomlError("found extra tokens after the string part", { + toml: ctx.s, + ptr: ctx.p + }); + } + if (endPtr < ctx.p) { + endPtr = ctx.s.indexOf(end, ctx.p); + if (endPtr < 0) { + throw new TomlError("incomplete key-value: cannot find end of key", { + toml: ctx.s, + ptr: start + }); + } + } + parsed.push(part); + } else { + dot = ctx.s.indexOf(".", ctx.p); + let part = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot); + if (!KEY_PART_RE.test(part)) { + throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", { + toml: ctx.s, + ptr: ctx.p + }); + } + parsed.push(part.trimEnd()); + } + } + } while (dot + 1 && dot < endPtr); + ctx.p = endPtr + 1; + skipVoid(ctx, true, true); + return parsed; +} +function parseInlineTable(ctx, integersAsBigInt) { + let res = {}; + let seen = /* @__PURE__ */ new Set(); + let c; + ctx.p++; + while (ctx.p < ctx.s.length) { + skipVoid(ctx); + if ((c = ctx.s.charCodeAt(ctx.p)) === 125) { + ctx.p++; + return res; + } + let k; + let t = res; + let hasOwn = false; + let p = ctx.p; + let key = parseKey(ctx); + for (let i = 0; i < key.length; i++) { + if (i) + t = hasOwn ? t[k] : t[k] = {}; + k = key[i]; + if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) { + throw new TomlError("trying to redefine an already defined value", { + toml: ctx.s, + ptr: p + }); + } + if (!hasOwn && k === "__proto__") { + Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true }); + } + } + if (hasOwn) { + throw new TomlError("trying to redefine an already defined value", { + toml: ctx.s, + ptr: ctx.p + }); + } + let value = extractValue(ctx, 125, integersAsBigInt); + seen.add(t[k] = value); + skipVoid(ctx); + if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) { + return res; + } + if (c !== 44) { + throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 }); + } + } + throw new TomlError("unfinished table encountered", { + toml: ctx.s, + ptr: ctx.p + }); +} +function parseArray(ctx, integersAsBigInt) { + let res = []; + let c; + ctx.p++; + while (ctx.p < ctx.s.length) { + skipVoid(ctx); + if ((c = ctx.s.charCodeAt(ctx.p)) === 93) { + ctx.p++; + return res; + } + res.push(extractValue(ctx, 93, integersAsBigInt)); + skipVoid(ctx); + if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) { + return res; + } + if (c !== 44) { + throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 }); + } + } + throw new TomlError("unfinished array encountered", { + toml: ctx.s, + ptr: ctx.p + }); +} + +// node_modules/smol-toml/dist/parse.js +function peekTable(key, table, meta, type) { + let t = table; + let m = meta; + let k; + let hasOwn = false; + let state3; + for (let i = 0; i < key.length; i++) { + if (i) { + t = hasOwn ? t[k] : t[k] = {}; + m = (state3 = m[k]).c; + if (type === 0 && (state3.t === 1 || state3.t === 2)) { + return null; + } + if (state3.t === 2) { + let l = t.length - 1; + t = t[l]; + m = m[l].c; + } + } + k = key[i]; + if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) { + return null; + } + if (!hasOwn) { + if (k === "__proto__") { + Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true }); + Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true }); + } + m[k] = { + t: i < key.length - 1 && type === 2 ? 3 : type, + d: false, + i: 0, + c: {} + }; + } + } + state3 = m[k]; + if (state3.t !== type && !(type === 1 && state3.t === 3)) { + return null; + } + if (type === 2) { + if (!state3.d) { + state3.d = true; + t[k] = []; + } + t[k].push(t = {}); + state3.c[state3.i++] = state3 = { t: 1, d: false, i: 0, c: {} }; + } + if (state3.d) { + return null; + } + state3.d = true; + if (type === 1) { + t = hasOwn ? t[k] : t[k] = {}; + } else if (type === 0 && hasOwn) { + return null; + } + return [k, t, state3.c]; +} +function parse2(toml, { maxDepth = 1e3, integersAsBigInt } = {}) { + let ctx = { s: toml, p: 0, d: maxDepth }; + let res = {}; + let meta = {}; + let tmp; + let tbl = res; + let m = meta; + skipVoid(ctx); + while (ctx.p < toml.length) { + if (toml.charCodeAt(ctx.p) === 91) { + let isTableArray = toml.charCodeAt(++ctx.p) === 91; + tmp = ctx.p += +isTableArray; + let k = parseKey(ctx, "]"); + if (isTableArray) { + if (toml.charCodeAt(ctx.p - 1) !== 93) { + throw new TomlError("expected end of table declaration", { + toml, + ptr: ctx.p - 1 + }); + } + ctx.p++; + } + let p = peekTable( + k, + res, + meta, + isTableArray ? 2 : 1 + /* Type.EXPLICIT */ + ); + if (!p) { + throw new TomlError("trying to redefine an already defined table or value", { + toml, + ptr: tmp + }); + } + m = p[2]; + tbl = p[1]; + } else { + tmp = ctx.p; + let k = parseKey(ctx); + let p = peekTable( + k, + tbl, + m, + 0 + /* Type.DOTTED */ + ); + if (!p) { + throw new TomlError("trying to redefine an already defined table or value", { + toml, + ptr: tmp + }); + } + p[1][p[0]] = extractValue(ctx, void 0, integersAsBigInt); + } + skipVoid(ctx, true); + if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && tmp !== 13) { + throw new TomlError("each key-value declaration must be followed by an end-of-line", { + toml, + ptr: ctx.p + }); + } + skipVoid(ctx); + } + return res; +} + +// src/utils/config-file.ts +function getConfigValueFromTomlFile(filePath, key) { + if (!import_node_fs3.default.existsSync(filePath) || !filePath.endsWith(".toml")) { + return void 0; + } + const fileContent = import_node_fs3.default.readFileSync(filePath, "utf-8"); + return getConfigValueFromTomlContent(filePath, fileContent, key); +} +function getConfigValueFromTomlContent(filePath, fileContent, key) { + if (!filePath.endsWith(".toml")) { + return void 0; + } + if (filePath.endsWith("pyproject.toml")) { + const tomlContent2 = parse2(fileContent); + return tomlContent2?.tool?.uv?.[key]; + } + const tomlContent = parse2(fileContent); + return tomlContent[key]; +} + +// src/utils/inputs.ts +function loadInputs() { + const workingDirectory = getInput("working-directory"); + const version3 = getInput("version"); + const versionFile = getVersionFile(workingDirectory); + const pythonVersion = getPythonVersion(versionFile); + const pythonArch = getInput("python-arch"); + const activateEnvironment2 = getBooleanInput("activate-environment"); + const noProject = getBooleanInput("no-project"); + const venvPath = getVenvPath(workingDirectory, activateEnvironment2); + const checksum = getInput("checksum"); + const enableCache = getEnableCache(); + const restoreCache3 = getInput("restore-cache") === "true"; + const saveCache2 = getSaveCache(); + const cacheSuffix = getInput("cache-suffix") || ""; + const cacheLocalPath = getCacheLocalPath( + workingDirectory, + versionFile, + enableCache + ); + const cacheDependencyGlob = getCacheDependencyGlob(workingDirectory); + const pruneCache = getInput("prune-cache") === "true"; + const cachePython = getInput("cache-python") === "true"; + const ignoreNothingToCache = getInput("ignore-nothing-to-cache") === "true"; + const ignoreEmptyWorkdir = getInput("ignore-empty-workdir") === "true"; + const toolBinDir = getToolBinDir(workingDirectory); + const toolDir = getToolDir(workingDirectory); + const pythonDir = getUvPythonDir(); + const githubToken = getInput("github-token"); + const manifestFile = getManifestFile(); + const downloadFromAstralMirror = getInput("download-from-astral-mirror") === "true"; + const addProblemMatchers = getInput("add-problem-matchers") === "true"; + const quiet2 = getInput("quiet") === "true"; + const resolutionStrategy = getResolutionStrategy(); + return { + activateEnvironment: activateEnvironment2, + addProblemMatchers, + cacheDependencyGlob, + cacheLocalPath, + cachePython, + cacheSuffix, + checksum, + downloadFromAstralMirror, + enableCache, + githubToken, + ignoreEmptyWorkdir, + ignoreNothingToCache, + manifestFile, + noProject, + pruneCache, + pythonArch, + pythonDir, + pythonVersion, + quiet: quiet2, + resolutionStrategy, + restoreCache: restoreCache3, + saveCache: saveCache2, + toolBinDir, + toolDir, + venvPath, + version: version3, + versionFile, + workingDirectory + }; +} +function getVersionFile(workingDirectory) { + const versionFileInput = getInput("version-file"); + if (versionFileInput !== "") { + const tildeExpanded = expandTilde(versionFileInput); + return resolveRelativePath(workingDirectory, tildeExpanded); + } + return versionFileInput; +} +function getPythonVersion(versionFile) { + const pythonVersionInput = getInput("python-version"); + if (pythonVersionInput !== "") { + return pythonVersionInput; + } + if (process.env.UV_PYTHON !== void 0 && process.env.UV_PYTHON !== "") { + return ""; + } + if (versionFile === "" || !import_node_fs4.default.existsSync(versionFile)) { + return ""; + } + try { + return getPythonVersionFromToolVersions(versionFile) ?? ""; + } catch (err) { + warning2( + `Error while parsing Python version from ${versionFile}: ${err.message}` + ); + return ""; + } +} +function resolvePythonArch(pythonArch) { + return pythonArch || process.env.UV_PYTHON_ARCH || ""; +} +function getVenvPath(workingDirectory, activateEnvironment2) { + const venvPathInput = getInput("venv-path"); + if (venvPathInput !== "") { + if (!activateEnvironment2) { + warning2("venv-path is only used when activate-environment is true"); + } + const tildeExpanded = expandTilde(venvPathInput); + return normalizePath(resolveRelativePath(workingDirectory, tildeExpanded)); + } + return normalizePath(resolveRelativePath(workingDirectory, ".venv")); +} +function getEnableCache() { + const enableCacheInput = getInput("enable-cache"); + if (enableCacheInput === "auto") { + if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") { + return false; + } + const eventName = process.env.GITHUB_EVENT_NAME; + const isTagPush = eventName === "push" && process.env.GITHUB_REF?.startsWith("refs/tags/"); + if (isTagPush) { + info2("Caching is disabled for tag pushes"); + return false; + } + if (eventName === "pull_request_target" || eventName === "workflow_run" || eventName === "release") { + info2(`Caching is disabled for the ${eventName} event`); + return false; + } + return true; + } + return enableCacheInput === "true"; +} +function getSaveCache() { + const saveCacheInput = getInput("save-cache"); + if (saveCacheInput === "auto") { + if (process.env.GITHUB_EVENT_NAME === "merge_group") { + info2("Cache saving is disabled for the merge_group event"); + return false; + } + return true; + } + return saveCacheInput === "true"; +} +function getToolBinDir(workingDirectory) { + const toolBinDirInput = getInput("tool-bin-dir"); + if (toolBinDirInput !== "") { + const tildeExpanded = expandTilde(toolBinDirInput); + return resolveRelativePath(workingDirectory, tildeExpanded); + } + if (process.platform === "win32") { + if (process.env.RUNNER_TEMP !== void 0) { + return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-tool-bin-dir`; + } + throw Error( + "Could not determine UV_TOOL_BIN_DIR. Please make sure RUNNER_TEMP is set or provide the tool-bin-dir input" + ); + } + return void 0; +} +function getToolDir(workingDirectory) { + const toolDirInput = getInput("tool-dir"); + if (toolDirInput !== "") { + const tildeExpanded = expandTilde(toolDirInput); + return resolveRelativePath(workingDirectory, tildeExpanded); + } + if (process.platform === "win32") { + if (process.env.RUNNER_TEMP !== void 0) { + return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-tool-dir`; + } + throw Error( + "Could not determine UV_TOOL_DIR. Please make sure RUNNER_TEMP is set or provide the tool-dir input" + ); + } + return void 0; +} +function getCacheLocalPath(workingDirectory, versionFile, enableCache) { + const cacheLocalPathInput = getInput("cache-local-path"); + if (cacheLocalPathInput !== "") { + const tildeExpanded = expandTilde(cacheLocalPathInput); + return { + path: resolveRelativePath(workingDirectory, tildeExpanded), + source: 0 /* Input */ + }; + } + const cacheDirFromConfig = getCacheDirFromConfig( + workingDirectory, + versionFile + ); + if (cacheDirFromConfig !== void 0) { + return { path: cacheDirFromConfig, source: 1 /* Config */ }; + } + if (process.env.UV_CACHE_DIR !== void 0) { + info2(`UV_CACHE_DIR is already set to ${process.env.UV_CACHE_DIR}`); + return { path: process.env.UV_CACHE_DIR, source: 2 /* Env */ }; + } + if (enableCache) { + if (process.env.RUNNER_ENVIRONMENT === "github-hosted") { + if (process.env.RUNNER_TEMP !== void 0) { + return { + path: `${process.env.RUNNER_TEMP}${import_node_path.default.sep}setup-uv-cache`, + source: 3 /* Default */ + }; + } + throw Error( + "Could not determine UV_CACHE_DIR. Please make sure RUNNER_TEMP is set or provide the cache-local-path input" + ); + } + if (process.platform === "win32") { + return { + path: `${process.env.APPDATA}${import_node_path.default.sep}uv${import_node_path.default.sep}cache`, + source: 3 /* Default */ + }; + } + return { + path: `${process.env.HOME}${import_node_path.default.sep}.cache${import_node_path.default.sep}uv`, + source: 3 /* Default */ + }; + } +} +function getCacheDirFromConfig(workingDirectory, versionFile) { + for (const filePath of [versionFile, "uv.toml", "pyproject.toml"]) { + const resolvedPath = resolveRelativePath(workingDirectory, filePath); + try { + const cacheDir2 = getConfigValueFromTomlFile(resolvedPath, "cache-dir"); + if (cacheDir2 !== void 0) { + info2(`Found cache-dir in ${resolvedPath}: ${cacheDir2}`); + return cacheDir2; + } + } catch (err) { + const message = err.message; + warning2(`Error while parsing ${filePath}: ${message}`); + return void 0; + } + } + return void 0; +} +function getUvPythonDir() { + if (process.env.UV_PYTHON_INSTALL_DIR !== void 0) { + info2( + `UV_PYTHON_INSTALL_DIR is already set to ${process.env.UV_PYTHON_INSTALL_DIR}` + ); + return process.env.UV_PYTHON_INSTALL_DIR; + } + if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") { + if (process.platform === "win32") { + return `${process.env.APPDATA}${import_node_path.default.sep}uv${import_node_path.default.sep}python`; + } + return `${process.env.HOME}${import_node_path.default.sep}.local${import_node_path.default.sep}share${import_node_path.default.sep}uv${import_node_path.default.sep}python`; + } + if (process.env.RUNNER_TEMP !== void 0) { + return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-python-dir`; + } + throw Error( + "Could not determine UV_PYTHON_INSTALL_DIR. Please make sure RUNNER_TEMP is set or provide the UV_PYTHON_INSTALL_DIR environment variable" + ); +} +function getCacheDependencyGlob(workingDirectory) { + const cacheDependencyGlobInput = getInput("cache-dependency-glob"); + if (cacheDependencyGlobInput !== "") { + return cacheDependencyGlobInput.split("\n").map((part) => part.trim()).map((part) => expandTilde(part)).map((part) => resolveRelativePath(workingDirectory, part)).join("\n"); + } + return cacheDependencyGlobInput; +} +function expandTilde(input) { + if (input.startsWith("~")) { + return `${process.env.HOME}${input.substring(1)}`; + } + return input; +} +function normalizePath(inputPath) { + const normalized = import_node_path.default.normalize(inputPath); + const root = import_node_path.default.parse(normalized).root; + let trimmed = normalized; + while (trimmed.length > root.length && trimmed.endsWith(import_node_path.default.sep)) { + trimmed = trimmed.slice(0, -1); + } + return trimmed; +} +function resolveRelativePath(workingDirectory, inputPath) { + const hasNegation = inputPath.startsWith("!"); + const pathWithoutNegation = hasNegation ? inputPath.substring(1) : inputPath; + const resolvedPath = import_node_path.default.resolve(workingDirectory, pathWithoutNegation); + debug( + `Resolving relative path ${inputPath} to ${hasNegation ? "!" : ""}${resolvedPath}` + ); + return hasNegation ? `!${resolvedPath}` : resolvedPath; +} +function getManifestFile() { + const manifestFileInput = getInput("manifest-file"); + if (manifestFileInput !== "") { + return manifestFileInput; + } + return void 0; +} +function getResolutionStrategy() { + const resolutionStrategyInput = getInput("resolution-strategy"); + if (resolutionStrategyInput === "lowest") { + return "lowest"; + } + if (resolutionStrategyInput === "highest" || resolutionStrategyInput === "") { + return "highest"; + } + throw new Error( + `Invalid resolution-strategy: ${resolutionStrategyInput}. Must be 'highest' or 'lowest'.` + ); +} + +// src/utils/platforms.ts +var import_node_fs5 = __toESM(require("node:fs"), 1); var import_node_os3 = __toESM(require("node:os"), 1); function getArch() { const arch3 = process.arch; @@ -93917,7 +94957,7 @@ function getLinuxOSNameVersion() { let idWithoutVersion; for (const file of files) { try { - const content = import_node_fs2.default.readFileSync(file, "utf8"); + const content = import_node_fs5.default.readFileSync(file, "utf8"); const id = parseOsReleaseValue(content, "ID"); const versionId2 = parseOsReleaseValue(content, "VERSION_ID"); const versionCodename = parseOsReleaseValue(content, "VERSION_CODENAME"); @@ -94038,11 +95078,13 @@ async function computeKeys(inputs, pythonVersion) { } const suffix = inputs.cacheSuffix ? `-${encodeURIComponent(inputs.cacheSuffix)}` : ""; const version3 = encodeURIComponent(pythonVersion ?? "unknown"); + const pythonArch = resolvePythonArch(inputs.pythonArch); + const pythonArchKey = pythonArch ? `-python-${encodeURIComponent(pythonArch)}` : ""; const platform2 = await getPlatform(); const osNameVersion = getOSNameVersion(); const pruned = inputs.pruneCache ? "-pruned" : ""; const python = inputs.cachePython ? "-py" : ""; - return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform2}-${osNameVersion}-${version3}${pruned}${python}${cacheDependencyPathHash}${suffix}`; + return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform2}-${osNameVersion}-${version3}${pythonArchKey}${pruned}${python}${cacheDependencyPathHash}${suffix}`; } function handleMatchResult(matchedKey, primaryKey, stateKey, outputKey) { if (!matchedKey) { @@ -94056,19 +95098,19 @@ function handleMatchResult(matchedKey, primaryKey, stateKey, outputKey) { } // src/download/download-version.ts -var import_node_fs7 = require("node:fs"); -var path15 = __toESM(require("node:path"), 1); +var import_node_fs8 = require("node:fs"); +var path16 = __toESM(require("node:path"), 1); // node_modules/@actions/tool-cache/lib/tool-cache.js var crypto5 = __toESM(require("crypto"), 1); -var fs9 = __toESM(require("fs"), 1); +var fs12 = __toESM(require("fs"), 1); // node_modules/@actions/tool-cache/lib/manifest.js var semver2 = __toESM(require_semver4(), 1); // node_modules/@actions/tool-cache/lib/tool-cache.js var os9 = __toESM(require("os"), 1); -var path13 = __toESM(require("path"), 1); +var path14 = __toESM(require("path"), 1); var semver3 = __toESM(require_semver4(), 1); var stream3 = __toESM(require("stream"), 1); var util6 = __toESM(require("util"), 1); @@ -94184,8 +95226,8 @@ var IS_MAC = process.platform === "darwin"; var userAgent = "actions/tool-cache"; function downloadTool(url2, dest, auth, headers) { return __awaiter18(this, void 0, void 0, function* () { - dest = dest || path13.join(_getTempDirectory(), crypto5.randomUUID()); - yield mkdirP(path13.dirname(dest)); + dest = dest || path14.join(_getTempDirectory(), crypto5.randomUUID()); + yield mkdirP(path14.dirname(dest)); debug(`Downloading ${url2}`); debug(`Destination ${dest}`); const maxAttempts = 3; @@ -94206,7 +95248,7 @@ function downloadTool(url2, dest, auth, headers) { } function downloadToolAttempt(url2, dest, auth, headers) { return __awaiter18(this, void 0, void 0, function* () { - if (fs9.existsSync(dest)) { + if (fs12.existsSync(dest)) { throw new Error(`Destination file path ${dest} already exists`); } const http3 = new HttpClient(userAgent, [], { @@ -94230,7 +95272,7 @@ function downloadToolAttempt(url2, dest, auth, headers) { const readStream = responseMessageFactory(); let succeeded = false; try { - yield pipeline4(readStream, fs9.createWriteStream(dest)); + yield pipeline4(readStream, fs12.createWriteStream(dest)); debug("download complete"); succeeded = true; return dest; @@ -94366,12 +95408,12 @@ function cacheDir(sourceDir2, tool, version3, arch3) { arch3 = arch3 || os9.arch(); debug(`Caching tool ${tool} ${version3} ${arch3}`); debug(`source dir: ${sourceDir2}`); - if (!fs9.statSync(sourceDir2).isDirectory()) { + if (!fs12.statSync(sourceDir2).isDirectory()) { throw new Error("sourceDir is not a directory"); } const destPath = yield _createToolPath(tool, version3, arch3); - for (const itemName of fs9.readdirSync(sourceDir2)) { - const s = path13.join(sourceDir2, itemName); + for (const itemName of fs12.readdirSync(sourceDir2)) { + const s = path14.join(sourceDir2, itemName); yield cp(s, destPath, { recursive: true }); } _completeToolPath(tool, version3, arch3); @@ -94394,9 +95436,9 @@ function find(toolName, versionSpec, arch3) { let toolPath = ""; if (versionSpec) { versionSpec = semver3.clean(versionSpec) || ""; - const cachePath = path13.join(_getCacheDirectory(), toolName, versionSpec, arch3); + const cachePath = path14.join(_getCacheDirectory(), toolName, versionSpec, arch3); debug(`checking cache: ${cachePath}`); - if (fs9.existsSync(cachePath) && fs9.existsSync(`${cachePath}.complete`)) { + if (fs12.existsSync(cachePath) && fs12.existsSync(`${cachePath}.complete`)) { debug(`Found tool in cache ${toolName} ${versionSpec} ${arch3}`); toolPath = cachePath; } else { @@ -94408,13 +95450,13 @@ function find(toolName, versionSpec, arch3) { function findAllVersions(toolName, arch3) { const versions = []; arch3 = arch3 || os9.arch(); - const toolPath = path13.join(_getCacheDirectory(), toolName); - if (fs9.existsSync(toolPath)) { - const children = fs9.readdirSync(toolPath); + const toolPath = path14.join(_getCacheDirectory(), toolName); + if (fs12.existsSync(toolPath)) { + const children = fs12.readdirSync(toolPath); for (const child2 of children) { if (isExplicitVersion(child2)) { - const fullPath = path13.join(toolPath, child2, arch3 || ""); - if (fs9.existsSync(fullPath) && fs9.existsSync(`${fullPath}.complete`)) { + const fullPath = path14.join(toolPath, child2, arch3 || ""); + if (fs12.existsSync(fullPath) && fs12.existsSync(`${fullPath}.complete`)) { versions.push(child2); } } @@ -94425,7 +95467,7 @@ function findAllVersions(toolName, arch3) { function _createExtractFolder(dest) { return __awaiter18(this, void 0, void 0, function* () { if (!dest) { - dest = path13.join(_getTempDirectory(), crypto5.randomUUID()); + dest = path14.join(_getTempDirectory(), crypto5.randomUUID()); } yield mkdirP(dest); return dest; @@ -94433,7 +95475,7 @@ function _createExtractFolder(dest) { } function _createToolPath(tool, version3, arch3) { return __awaiter18(this, void 0, void 0, function* () { - const folderPath = path13.join(_getCacheDirectory(), tool, semver3.clean(version3) || version3, arch3 || ""); + const folderPath = path14.join(_getCacheDirectory(), tool, semver3.clean(version3) || version3, arch3 || ""); debug(`destination ${folderPath}`); const markerPath = `${folderPath}.complete`; yield rmRF(folderPath); @@ -94443,9 +95485,9 @@ function _createToolPath(tool, version3, arch3) { }); } function _completeToolPath(tool, version3, arch3) { - const folderPath = path13.join(_getCacheDirectory(), tool, semver3.clean(version3) || version3, arch3 || ""); + const folderPath = path14.join(_getCacheDirectory(), tool, semver3.clean(version3) || version3, arch3 || ""); const markerPath = `${folderPath}.complete`; - fs9.writeFileSync(markerPath, ""); + fs12.writeFileSync(markerPath, ""); debug("finished caching tool"); } function isExplicitVersion(versionSpec) { @@ -94504,7 +95546,7 @@ var ASTRAL_MIRROR_PREFIX = "https://releases.astral.sh/github/uv/releases/downlo // src/download/checksum/checksum.ts var crypto6 = __toESM(require("node:crypto"), 1); -var fs10 = __toESM(require("node:fs"), 1); +var fs13 = __toESM(require("node:fs"), 1); // src/download/checksum/known-checksums.json var known_checksums_default = { @@ -99972,7 +101014,7 @@ async function validateChecksum(checksum, downloadPath, arch3, platform2, versio async function validateFileCheckSum(filePath, expected) { return new Promise((resolve3, reject) => { const hash = crypto6.createHash("sha256"); - const stream4 = fs10.createReadStream(filePath); + const stream4 = fs13.createReadStream(filePath); stream4.on("error", (err) => reject(err)); stream4.on("data", (chunk) => hash.update(chunk)); stream4.on("end", () => { @@ -100301,7 +101343,7 @@ var VERSION_PATTERN = [ ].join(""); var validRegex = new RegExp("^" + VERSION_PATTERN + "$", "i"); var cleanRegex = new RegExp("^\\s*" + VERSION_PATTERN + "\\s*$", "i"); -function parse2(version3, regex) { +function parse3(version3, regex) { const { groups } = (regex || validRegex).exec(version3) || {}; if (!groups) { return null; @@ -100319,7 +101361,7 @@ function parse2(version3, regex) { }; return parsed; } -function stringify(parsed) { +function stringify2(parsed) { if (!parsed) { return null; } @@ -100377,7 +101419,7 @@ function parse_local_version(local) { return null; } function explain(version3) { - const parsed = parse2(version3); + const parsed = parse3(version3); if (!parsed) { return parsed; } @@ -100397,7 +101439,7 @@ function explain(version3) { post: post ? post[1] : post, dev: dev ? dev[1] : dev, local: local ? local.join(".") : local, - public: stringify(parsed).split("+", 1)[0], + public: stringify2(parsed).split("+", 1)[0], base_version, is_prerelease, is_devrelease, @@ -100446,8 +101488,8 @@ function arbitrary(version3, other) { return version3.toLowerCase() === other.toLowerCase(); } function compare(version3, other) { - const parsedVersion = parse2(version3); - const parsedOther = parse2(other); + const parsedVersion = parse3(version3); + const parsedOther = parse3(other); const keyVersion = calculateKey(parsedVersion); const keyOther = calculateKey(parsedOther); return pyCompare(keyVersion, keyOther); @@ -100523,7 +101565,7 @@ var RANGE_PATTERN = [ ].join(""); var isEqualityOperator = (op) => ["==", "!=", "==="].includes(op); var rangeRegex = new RegExp("^" + RANGE_PATTERN + "$", "i"); -function parse3(ranges) { +function parse4(ranges) { if (!ranges.trim()) { return []; } @@ -100575,7 +101617,7 @@ function minSatisfying(versions, range3, options) { return found.length === 0 ? null : found[0]; } function pick(versions, specifier, options) { - const parsed = parse3(specifier); + const parsed = parse4(specifier); if (!parsed) { return []; } @@ -100701,678 +101743,10 @@ function parseVersionSpecifier(specifier) { } // src/version/version-request-resolver.ts -var path14 = __toESM(require("node:path"), 1); +var path15 = __toESM(require("node:path"), 1); // src/version/file-parser.ts -var import_node_fs6 = __toESM(require("node:fs"), 1); - -// src/utils/config-file.ts -var import_node_fs3 = __toESM(require("node:fs"), 1); - -// node_modules/smol-toml/dist/date.js -var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i; -var TomlDate = class _TomlDate extends Date { - #hasDate = false; - #hasTime = false; - #offset = null; - constructor(date) { - let hasDate = true; - let hasTime = true; - let offset = "Z"; - if (typeof date === "string") { - let match4 = date.match(DATE_TIME_RE); - if (match4) { - if (!match4[1]) { - hasDate = false; - date = `0000-01-01T${date}`; - } - hasTime = !!match4[2]; - hasTime && date[10] === " " && (date = date.replace(" ", "T")); - if (match4[2] && +match4[2] > 23) { - date = ""; - } else { - offset = match4[3] || null; - date = date.toUpperCase(); - if (!offset && hasTime) - date += "Z"; - } - } else { - date = ""; - } - } - super(date); - if (!isNaN(this.getTime())) { - this.#hasDate = hasDate; - this.#hasTime = hasTime; - this.#offset = offset; - } - } - isDateTime() { - return this.#hasDate && this.#hasTime; - } - isLocal() { - return !this.#hasDate || !this.#hasTime || !this.#offset; - } - isDate() { - return this.#hasDate && !this.#hasTime; - } - isTime() { - return this.#hasTime && !this.#hasDate; - } - isValid() { - return this.#hasDate || this.#hasTime; - } - toISOString() { - let iso = super.toISOString(); - if (this.isDate()) - return iso.slice(0, 10); - if (this.isTime()) - return iso.slice(11, 23); - if (this.#offset === null) - return iso.slice(0, -1); - if (this.#offset === "Z") - return iso; - let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6); - offset = this.#offset[0] === "-" ? offset : -offset; - let offsetDate = new Date(this.getTime() - offset * 6e4); - return offsetDate.toISOString().slice(0, -1) + this.#offset; - } - static wrapAsOffsetDateTime(jsDate, offset = "Z") { - let date = new _TomlDate(jsDate); - date.#offset = offset; - return date; - } - static wrapAsLocalDateTime(jsDate) { - let date = new _TomlDate(jsDate); - date.#offset = null; - return date; - } - static wrapAsLocalDate(jsDate) { - let date = new _TomlDate(jsDate); - date.#hasTime = false; - date.#offset = null; - return date; - } - static wrapAsLocalTime(jsDate) { - let date = new _TomlDate(jsDate); - date.#hasDate = false; - date.#offset = null; - return date; - } -}; - -// node_modules/smol-toml/dist/error.js -function getLineColFromPtr(string, ptr) { - let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g); - return [lines.length, lines.pop().length + 1]; -} -function makeCodeBlock(string, line, column) { - let lines = string.split(/\r\n|\n|\r/g); - let codeblock = ""; - let numberLen = (Math.log10(line + 1) | 0) + 1; - for (let i = line - 1; i <= line + 1; i++) { - let l = lines[i - 1]; - if (!l) - continue; - codeblock += i.toString().padEnd(numberLen, " "); - codeblock += ": "; - codeblock += l; - codeblock += "\n"; - if (i === line) { - codeblock += " ".repeat(numberLen + column + 2); - codeblock += "^\n"; - } - } - return codeblock; -} -var TomlError = class extends Error { - line; - column; - codeblock; - constructor(message, options) { - const [line, column] = getLineColFromPtr(options.toml, options.ptr); - const codeblock = makeCodeBlock(options.toml, line, column); - super(`Invalid TOML document: ${message} - -${codeblock}`, options); - this.line = line; - this.column = column; - this.codeblock = codeblock; - } -}; - -// node_modules/smol-toml/dist/util.js -function indexOfNewline(str, start = 0) { - let idx = str.indexOf("\n", start); - if (str.charCodeAt(idx - 1) === 13) - idx--; - return idx; -} -function skipComment(ctx) { - for (; ctx.p < ctx.s.length; ctx.p++) { - let c = ctx.s.charCodeAt(ctx.p); - if (c === 10) - break; - if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) { - ctx.p++; - break; - } - if (c < 32 && c !== 9 || c === 127) { - throw new TomlError("control characters are not allowed in comments", { - toml: ctx.s, - ptr: ctx.p - }); - } - } -} -function skipVoid(ctx, banNewLines, banComments) { - let c; - while (1) { - while ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) - ctx.p++; - if (banComments || c !== 35) - break; - skipComment(ctx); - } -} -function skipUntil(ctx, sep9, end) { - let ptr = ctx.p; - if (!end) { - ptr = indexOfNewline(ctx.s, ptr); - ctx.p = ptr < 0 ? ctx.s.length : ptr; - return; - } - for (; ctx.p < ctx.s.length; ctx.p++) { - let c = ctx.s.charCodeAt(ctx.p); - if (c === 35) { - skipComment(ctx); - } else if (c === end || c === sep9) { - return; - } - } - throw new TomlError("cannot find end of structure", { - toml: ctx.s, - ptr - }); -} - -// node_modules/smol-toml/dist/primitive.js -var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/; -var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/; -var LEADING_ZERO = /^[+-]?0[0-9_]/; -function parseString(ctx) { - let start = ctx.p; - let c = ctx.s.charCodeAt(ctx.p++); - let first = c; - let isLiteral = c === 39; - let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1); - if (isMultiline) { - if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10) - ctx.p++; - else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) - ctx.p += 2; - } - let parsed = ""; - let sliceStart = ctx.p; - let state3 = 0; - for (; ctx.p < ctx.s.length; ctx.p++) { - c = ctx.s.charCodeAt(ctx.p); - if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) { - state3 = state3 && 3; - } else if (c < 32 && c !== 9 || c === 127) { - throw new TomlError("control characters are not allowed in strings", { - toml: ctx.s, - ptr: ctx.p - }); - } else if ((!state3 || state3 === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) { - if (isMultiline) { - if (ctx.s.charCodeAt(ctx.p + 3) === first) - ctx.p++; - if (ctx.s.charCodeAt(ctx.p + 3) === first) - ctx.p++; - } - if (!state3) - parsed += ctx.s.slice(sliceStart, ctx.p); - ctx.p += isMultiline ? 3 : 1; - return parsed; - } else if (!state3) { - if (!isLiteral && c === 92) { - parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p); - state3 = 1; - } - } else if (state3 === 1) { - if (c === 120 || c === 117 || c === 85) { - let value = 0; - let len = c === 120 ? 2 : c === 117 ? 4 : 8; - for (let j = 0; j < len; j++, ctx.p++) { - let hex = ctx.s.charCodeAt(ctx.p + 1); - let digit = ( - /* 0-9 */ - hex >= 48 && hex <= 57 ? hex - 48 : ( - /* A-F */ - hex >= 65 && hex <= 70 ? hex - 65 + 10 : ( - /* a-f */ - hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1 - ) - ) - ); - if (digit < 0) - throw new TomlError("invalid non-hex character in unicode escape", { toml: ctx.s, ptr: ctx.p + 1 }); - value = value << 4 | digit; - } - if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) { - throw new TomlError("invalid unicode escape", { toml: ctx.s, ptr: ctx.p }); - } - parsed += String.fromCodePoint(value); - sliceStart = ctx.p + 1; - state3 = 0; - } else if (c === 32 || c === 9) { - state3 = 2; - } else { - if (c === 98) - parsed += "\b"; - else if (c === 116) - parsed += " "; - else if (c === 110) - parsed += "\n"; - else if (c === 102) - parsed += "\f"; - else if (c === 114) - parsed += "\r"; - else if (c === 101) - parsed += "\x1B"; - else if (c === 34) - parsed += '"'; - else if (c === 92) - parsed += "\\"; - else - throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p }); - sliceStart = ctx.p + 1; - state3 = 0; - } - } else if (c !== 32 && c !== 9) { - if (state3 === 2) { - throw new TomlError("invalid escape: only line-ending whitespace may be escaped", { - toml: ctx.s, - ptr: sliceStart - }); - } - state3 = !isLiteral && c === 92 ? 1 : 0; - sliceStart = ctx.p; - } - } - throw new TomlError("unfinished string", { toml: ctx.s, ptr: start }); -} -function sliceAndTrimEndOf(ctx, start, end) { - let value = ctx.s.slice(start, end); - let commentIdx = value.indexOf("#"); - if (commentIdx > 0) { - skipComment({ s: value, p: commentIdx, d: 0 }); - value = value.slice(0, commentIdx); - } - return value.trimEnd(); -} -function parseValue2(ctx, integersAsBigInt, end) { - let ptr = ctx.p; - let err = { toml: ctx.s, ptr }; - skipUntil(ctx, 44, end); - let value = sliceAndTrimEndOf(ctx, ptr, ctx.p); - if (!value) - throw new TomlError("incomplete declaration: value expected", err); - if (value === "-inf") - return -Infinity; - if (value === "inf" || value === "+inf") - return Infinity; - if (value === "nan" || value === "+nan" || value === "-nan") - return NaN; - if (value === "-0") - return integersAsBigInt ? 0n : 0; - let isInt = INT_REGEX.test(value); - if (isInt || FLOAT_REGEX.test(value)) { - if (LEADING_ZERO.test(value)) { - throw new TomlError("leading zeroes are not allowed", err); - } - value = value.replace(/_/g, ""); - let numeric2 = +value; - if (isNaN(numeric2)) { - throw new TomlError("invalid number", err); - } - if (isInt) { - if ((isInt = !Number.isSafeInteger(numeric2)) && !integersAsBigInt) { - throw new TomlError("integer value cannot be represented losslessly", err); - } - if (isInt || integersAsBigInt === true) - numeric2 = BigInt(value); - } - return numeric2; - } - const date = new TomlDate(value); - if (!date.isValid()) - throw new TomlError("invalid value", err); - return date; -} - -// node_modules/smol-toml/dist/extract.js -function extractValue(ctx, end, integersAsBigInt) { - let ptr = ctx.p; - let c = ctx.s.charCodeAt(ptr); - if (c === 91 || c === 123) { - if (!ctx.d--) { - throw new TomlError("document contains excessively nested structures. aborting.", { - toml: ctx.s, - ptr - }); - } - let value = c === 91 ? parseArray(ctx, integersAsBigInt) : parseInlineTable(ctx, integersAsBigInt); - ctx.d++; - return value; - } - if (c === 34 || c === 39) { - return parseString(ctx); - } - if (c === 116) { - if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101) - throw new TomlError("invalid value", { toml: ctx.s, ptr }); - ctx.p++; - return true; - } - if (c === 102) { - if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101) - throw new TomlError("invalid value", { toml: ctx.s, ptr }); - ctx.p++; - return false; - } - return parseValue2(ctx, integersAsBigInt, end); -} - -// node_modules/smol-toml/dist/struct.js -var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/; -function parseKey(ctx, end = "=") { - let start = ctx.p; - let dot = start - 1; - let parsed = []; - let endPtr = ctx.s.indexOf(end, start); - if (endPtr < 0) { - throw new TomlError("incomplete key-value: cannot find end of key", { - toml: ctx.s, - ptr: start - }); - } - do { - let c = ctx.s.charCodeAt(ctx.p = ++dot); - if (c !== 32 && c !== 9) { - if (c === 34 || c === 39) { - if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2)) { - throw new TomlError("multiline strings are not allowed in keys", { - toml: ctx.s, - ptr: ctx.p - }); - } - let part = parseString(ctx); - dot = ctx.s.indexOf(".", ctx.p); - let strEnd = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot); - let newLine = indexOfNewline(strEnd); - if (newLine > -1) { - throw new TomlError("newlines are not allowed in keys", { - toml: ctx.s, - ptr: newLine - }); - } - if (strEnd.trimStart()) { - throw new TomlError("found extra tokens after the string part", { - toml: ctx.s, - ptr: ctx.p - }); - } - if (endPtr < ctx.p) { - endPtr = ctx.s.indexOf(end, ctx.p); - if (endPtr < 0) { - throw new TomlError("incomplete key-value: cannot find end of key", { - toml: ctx.s, - ptr: start - }); - } - } - parsed.push(part); - } else { - dot = ctx.s.indexOf(".", ctx.p); - let part = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot); - if (!KEY_PART_RE.test(part)) { - throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", { - toml: ctx.s, - ptr: ctx.p - }); - } - parsed.push(part.trimEnd()); - } - } - } while (dot + 1 && dot < endPtr); - ctx.p = endPtr + 1; - skipVoid(ctx, true, true); - return parsed; -} -function parseInlineTable(ctx, integersAsBigInt) { - let res = {}; - let seen = /* @__PURE__ */ new Set(); - let c; - ctx.p++; - while (ctx.p < ctx.s.length) { - skipVoid(ctx); - if ((c = ctx.s.charCodeAt(ctx.p)) === 125) { - ctx.p++; - return res; - } - let k; - let t = res; - let hasOwn = false; - let p = ctx.p; - let key = parseKey(ctx); - for (let i = 0; i < key.length; i++) { - if (i) - t = hasOwn ? t[k] : t[k] = {}; - k = key[i]; - if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) { - throw new TomlError("trying to redefine an already defined value", { - toml: ctx.s, - ptr: p - }); - } - if (!hasOwn && k === "__proto__") { - Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true }); - } - } - if (hasOwn) { - throw new TomlError("trying to redefine an already defined value", { - toml: ctx.s, - ptr: ctx.p - }); - } - let value = extractValue(ctx, 125, integersAsBigInt); - seen.add(t[k] = value); - skipVoid(ctx); - if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) { - return res; - } - if (c !== 44) { - throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 }); - } - } - throw new TomlError("unfinished table encountered", { - toml: ctx.s, - ptr: ctx.p - }); -} -function parseArray(ctx, integersAsBigInt) { - let res = []; - let c; - ctx.p++; - while (ctx.p < ctx.s.length) { - skipVoid(ctx); - if ((c = ctx.s.charCodeAt(ctx.p)) === 93) { - ctx.p++; - return res; - } - res.push(extractValue(ctx, 93, integersAsBigInt)); - skipVoid(ctx); - if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) { - return res; - } - if (c !== 44) { - throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 }); - } - } - throw new TomlError("unfinished array encountered", { - toml: ctx.s, - ptr: ctx.p - }); -} - -// node_modules/smol-toml/dist/parse.js -function peekTable(key, table, meta, type) { - let t = table; - let m = meta; - let k; - let hasOwn = false; - let state3; - for (let i = 0; i < key.length; i++) { - if (i) { - t = hasOwn ? t[k] : t[k] = {}; - m = (state3 = m[k]).c; - if (type === 0 && (state3.t === 1 || state3.t === 2)) { - return null; - } - if (state3.t === 2) { - let l = t.length - 1; - t = t[l]; - m = m[l].c; - } - } - k = key[i]; - if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) { - return null; - } - if (!hasOwn) { - if (k === "__proto__") { - Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true }); - Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true }); - } - m[k] = { - t: i < key.length - 1 && type === 2 ? 3 : type, - d: false, - i: 0, - c: {} - }; - } - } - state3 = m[k]; - if (state3.t !== type && !(type === 1 && state3.t === 3)) { - return null; - } - if (type === 2) { - if (!state3.d) { - state3.d = true; - t[k] = []; - } - t[k].push(t = {}); - state3.c[state3.i++] = state3 = { t: 1, d: false, i: 0, c: {} }; - } - if (state3.d) { - return null; - } - state3.d = true; - if (type === 1) { - t = hasOwn ? t[k] : t[k] = {}; - } else if (type === 0 && hasOwn) { - return null; - } - return [k, t, state3.c]; -} -function parse4(toml, { maxDepth = 1e3, integersAsBigInt } = {}) { - let ctx = { s: toml, p: 0, d: maxDepth }; - let res = {}; - let meta = {}; - let tmp; - let tbl = res; - let m = meta; - skipVoid(ctx); - while (ctx.p < toml.length) { - if (toml.charCodeAt(ctx.p) === 91) { - let isTableArray = toml.charCodeAt(++ctx.p) === 91; - tmp = ctx.p += +isTableArray; - let k = parseKey(ctx, "]"); - if (isTableArray) { - if (toml.charCodeAt(ctx.p - 1) !== 93) { - throw new TomlError("expected end of table declaration", { - toml, - ptr: ctx.p - 1 - }); - } - ctx.p++; - } - let p = peekTable( - k, - res, - meta, - isTableArray ? 2 : 1 - /* Type.EXPLICIT */ - ); - if (!p) { - throw new TomlError("trying to redefine an already defined table or value", { - toml, - ptr: tmp - }); - } - m = p[2]; - tbl = p[1]; - } else { - tmp = ctx.p; - let k = parseKey(ctx); - let p = peekTable( - k, - tbl, - m, - 0 - /* Type.DOTTED */ - ); - if (!p) { - throw new TomlError("trying to redefine an already defined table or value", { - toml, - ptr: tmp - }); - } - p[1][p[0]] = extractValue(ctx, void 0, integersAsBigInt); - } - skipVoid(ctx, true); - if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && tmp !== 13) { - throw new TomlError("each key-value declaration must be followed by an end-of-line", { - toml, - ptr: ctx.p - }); - } - skipVoid(ctx); - } - return res; -} - -// src/utils/config-file.ts -function getConfigValueFromTomlFile(filePath, key) { - if (!import_node_fs3.default.existsSync(filePath) || !filePath.endsWith(".toml")) { - return void 0; - } - const fileContent = import_node_fs3.default.readFileSync(filePath, "utf-8"); - return getConfigValueFromTomlContent(filePath, fileContent, key); -} -function getConfigValueFromTomlContent(filePath, fileContent, key) { - if (!filePath.endsWith(".toml")) { - return void 0; - } - if (filePath.endsWith("pyproject.toml")) { - const tomlContent2 = parse4(fileContent); - return tomlContent2?.tool?.uv?.[key]; - } - const tomlContent = parse4(fileContent); - return tomlContent[key]; -} +var import_node_fs7 = __toESM(require("node:fs"), 1); // src/version/requirements-file.ts function getUvVersionFromRequirementsText(fileContent) { @@ -101391,7 +101765,7 @@ function getUvVersionFromParsedPyproject(pyproject) { ); } function parsePyprojectContent(pyprojectContent) { - return parse4(pyprojectContent); + return parse2(pyprojectContent); } function getUvVersionFromAllDependencies(allDependencies) { return allDependencies.map(getUvVersionFromDependency).find((version3) => version3 !== void 0); @@ -101401,80 +101775,14 @@ function getUvVersionFromDependency(dependency) { return dependencyWithoutMarker?.match(/^uv([=<>~!]+\S*)/)?.[1].trim(); } -// src/version/tool-versions-file.ts -var import_node_fs4 = __toESM(require("node:fs"), 1); -function getUvVersionFromToolVersions(filePath) { - const versions = getToolVersions(filePath, "uv"); - if (versions === void 0 || versions.length !== 1) { - return void 0; - } - const version3 = stripVersionPrefix(versions[0]); - if (isPath(version3)) { - warning( - `The uv version ${versions[0]} in .tool-versions is not supported. Paths are not allowed.` - ); - return void 0; - } - if (version3.startsWith("ref")) { - warning( - "The ref syntax of .tool-versions is not supported. Please use a released version instead." - ); - return void 0; - } - return version3; -} -function getPythonVersionFromToolVersions(filePath) { - const versions = getToolVersions(filePath, "python"); - if (versions === void 0 || versions.length === 0) { - return void 0; - } - if (versions.length > 1) { - warning( - "Multiple Python versions in .tool-versions are not supported. The Python entry will be ignored." - ); - return void 0; - } - const version3 = stripVersionPrefix(versions[0]); - if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:") || isPath(version3)) { - warning( - `The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.` - ); - return void 0; - } - return version3; -} -function getToolVersions(filePath, toolName) { - if (!filePath.endsWith(".tool-versions")) { - return void 0; - } - const fileContents = import_node_fs4.default.readFileSync(filePath, "utf8"); - for (const line of fileContents.split("\n")) { - const content = line.split("#", 1)[0].trim(); - if (content === "") { - continue; - } - const [tool, ...versions] = content.split(/\s+/); - if (tool === toolName) { - return versions; - } - } - return void 0; -} -function stripVersionPrefix(version3) { - return version3.startsWith("v") ? version3.slice(1) : version3; -} -function isPath(version3) { - return version3.includes("/") || version3.includes("\\"); -} - // src/version/uv-lock-file.ts -var import_node_fs5 = __toESM(require("node:fs"), 1); +var import_node_fs6 = __toESM(require("node:fs"), 1); function getUvVersionFromUvLock(filePath) { - const fileContent = import_node_fs5.default.readFileSync(filePath, "utf-8"); + const fileContent = import_node_fs6.default.readFileSync(filePath, "utf-8"); return getUvVersionFromUvLockContent(fileContent); } function getUvVersionFromUvLockContent(fileContent) { - const parsed = parse4(fileContent); + const parsed = parse2(fileContent); const uvPackage = parsed.package?.find((pkg) => pkg.name === "uv"); return uvPackage?.version; } @@ -101489,7 +101797,7 @@ var VERSION_FILE_PARSERS = [ { format: "uv.toml", parse: (filePath) => { - const fileContent = import_node_fs6.default.readFileSync(filePath, "utf-8"); + const fileContent = import_node_fs7.default.readFileSync(filePath, "utf-8"); return getConfigValueFromTomlContent( filePath, fileContent, @@ -101501,7 +101809,7 @@ var VERSION_FILE_PARSERS = [ { format: "pyproject.toml", parse: (filePath) => { - const fileContent = import_node_fs6.default.readFileSync(filePath, "utf-8"); + const fileContent = import_node_fs7.default.readFileSync(filePath, "utf-8"); const pyproject = parsePyprojectContent(fileContent); const requiredVersion = pyproject.tool?.uv?.["required-version"]; if (requiredVersion !== void 0) { @@ -101519,7 +101827,7 @@ var VERSION_FILE_PARSERS = [ { format: "requirements", parse: (filePath) => { - const fileContent = import_node_fs6.default.readFileSync(filePath, "utf-8"); + const fileContent = import_node_fs7.default.readFileSync(filePath, "utf-8"); return getUvVersionFromRequirementsText(fileContent); }, supports: (filePath) => filePath.endsWith(".txt") @@ -101527,7 +101835,7 @@ var VERSION_FILE_PARSERS = [ ]; function getParsedVersionFile(filePath) { info2(`Trying to find version for uv in: ${filePath}`); - if (!import_node_fs6.default.existsSync(filePath)) { + if (!import_node_fs7.default.existsSync(filePath)) { info2(`Could not find file: ${filePath}`); return void 0; } @@ -101579,11 +101887,11 @@ var VersionRequestContext = class { return [ { source: "uv.toml", - sourcePath: path14.join(this.workingDirectory, "uv.toml") + sourcePath: path15.join(this.workingDirectory, "uv.toml") }, { source: "pyproject.toml", - sourcePath: path14.join(this.workingDirectory, "pyproject.toml") + sourcePath: path15.join(this.workingDirectory, "pyproject.toml") } ]; } @@ -101900,12 +102208,12 @@ async function downloadArtifact(downloadUrl, artifactName, platform2, arch3, ver ); const extension = getExtension(platform2); const fullPathWithExtension = `${downloadPath}${extension}`; - await import_node_fs7.promises.copyFile(downloadPath, fullPathWithExtension); + await import_node_fs8.promises.copyFile(downloadPath, fullPathWithExtension); uvDir = await extractZip(fullPathWithExtension); } } else { const extractedDir = await extractTar2(downloadPath); - uvDir = path15.join(extractedDir, artifactName); + uvDir = path16.join(extractedDir, artifactName); } const cachedToolDir = await cacheDir( uvDir, @@ -101928,315 +102236,69 @@ function getExtension(platform2) { return platform2 === "pc-windows-msvc" ? ".zip" : ".tar.gz"; } -// src/utils/inputs.ts -var import_node_fs8 = __toESM(require("node:fs"), 1); -var import_node_path = __toESM(require("node:path"), 1); -function loadInputs() { - const workingDirectory = getInput("working-directory"); - const version3 = getInput("version"); - const versionFile = getVersionFile(workingDirectory); - const pythonVersion = getPythonVersion(versionFile); - const activateEnvironment2 = getBooleanInput("activate-environment"); - const noProject = getBooleanInput("no-project"); - const venvPath = getVenvPath(workingDirectory, activateEnvironment2); - const checksum = getInput("checksum"); - const enableCache = getEnableCache(); - const restoreCache3 = getInput("restore-cache") === "true"; - const saveCache2 = getSaveCache(); - const cacheSuffix = getInput("cache-suffix") || ""; - const cacheLocalPath = getCacheLocalPath( - workingDirectory, - versionFile, - enableCache - ); - const cacheDependencyGlob = getCacheDependencyGlob(workingDirectory); - const pruneCache = getInput("prune-cache") === "true"; - const cachePython = getInput("cache-python") === "true"; - const ignoreNothingToCache = getInput("ignore-nothing-to-cache") === "true"; - const ignoreEmptyWorkdir = getInput("ignore-empty-workdir") === "true"; - const toolBinDir = getToolBinDir(workingDirectory); - const toolDir = getToolDir(workingDirectory); - const pythonDir = getUvPythonDir(); - const githubToken = getInput("github-token"); - const manifestFile = getManifestFile(); - const downloadFromAstralMirror = getInput("download-from-astral-mirror") === "true"; - const addProblemMatchers = getInput("add-problem-matchers") === "true"; - const quiet2 = getInput("quiet") === "true"; - const resolutionStrategy = getResolutionStrategy(); - return { - activateEnvironment: activateEnvironment2, - addProblemMatchers, - cacheDependencyGlob, - cacheLocalPath, - cachePython, - cacheSuffix, - checksum, - downloadFromAstralMirror, - enableCache, - githubToken, - ignoreEmptyWorkdir, - ignoreNothingToCache, - manifestFile, - noProject, - pruneCache, - pythonDir, - pythonVersion, - quiet: quiet2, - resolutionStrategy, - restoreCache: restoreCache3, - saveCache: saveCache2, - toolBinDir, - toolDir, - venvPath, - version: version3, - versionFile, - workingDirectory - }; -} -function getVersionFile(workingDirectory) { - const versionFileInput = getInput("version-file"); - if (versionFileInput !== "") { - const tildeExpanded = expandTilde(versionFileInput); - return resolveRelativePath(workingDirectory, tildeExpanded); - } - return versionFileInput; -} -function getPythonVersion(versionFile) { - const pythonVersionInput = getInput("python-version"); - if (pythonVersionInput !== "") { - return pythonVersionInput; - } - if (process.env.UV_PYTHON !== void 0 && process.env.UV_PYTHON !== "") { - return ""; - } - if (versionFile === "" || !import_node_fs8.default.existsSync(versionFile)) { - return ""; - } - try { - return getPythonVersionFromToolVersions(versionFile) ?? ""; - } catch (err) { - warning2( - `Error while parsing Python version from ${versionFile}: ${err.message}` - ); - return ""; - } -} -function getVenvPath(workingDirectory, activateEnvironment2) { - const venvPathInput = getInput("venv-path"); - if (venvPathInput !== "") { - if (!activateEnvironment2) { - warning2("venv-path is only used when activate-environment is true"); - } - const tildeExpanded = expandTilde(venvPathInput); - return normalizePath(resolveRelativePath(workingDirectory, tildeExpanded)); - } - return normalizePath(resolveRelativePath(workingDirectory, ".venv")); -} -function getEnableCache() { - const enableCacheInput = getInput("enable-cache"); - if (enableCacheInput === "auto") { - if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") { - return false; - } - const eventName = process.env.GITHUB_EVENT_NAME; - const isTagPush = eventName === "push" && process.env.GITHUB_REF?.startsWith("refs/tags/"); - if (isTagPush) { - info2("Caching is disabled for tag pushes"); - return false; - } - if (eventName === "pull_request_target" || eventName === "workflow_run" || eventName === "release") { - info2(`Caching is disabled for the ${eventName} event`); - return false; - } - return true; - } - return enableCacheInput === "true"; -} -function getSaveCache() { - const saveCacheInput = getInput("save-cache"); - if (saveCacheInput === "auto") { - if (process.env.GITHUB_EVENT_NAME === "merge_group") { - info2("Cache saving is disabled for the merge_group event"); - return false; - } - return true; - } - return saveCacheInput === "true"; -} -function getToolBinDir(workingDirectory) { - const toolBinDirInput = getInput("tool-bin-dir"); - if (toolBinDirInput !== "") { - const tildeExpanded = expandTilde(toolBinDirInput); - return resolveRelativePath(workingDirectory, tildeExpanded); - } - if (process.platform === "win32") { - if (process.env.RUNNER_TEMP !== void 0) { - return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-tool-bin-dir`; - } - throw Error( - "Could not determine UV_TOOL_BIN_DIR. Please make sure RUNNER_TEMP is set or provide the tool-bin-dir input" - ); - } - return void 0; -} -function getToolDir(workingDirectory) { - const toolDirInput = getInput("tool-dir"); - if (toolDirInput !== "") { - const tildeExpanded = expandTilde(toolDirInput); - return resolveRelativePath(workingDirectory, tildeExpanded); - } - if (process.platform === "win32") { - if (process.env.RUNNER_TEMP !== void 0) { - return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-tool-dir`; - } - throw Error( - "Could not determine UV_TOOL_DIR. Please make sure RUNNER_TEMP is set or provide the tool-dir input" - ); - } - return void 0; -} -function getCacheLocalPath(workingDirectory, versionFile, enableCache) { - const cacheLocalPathInput = getInput("cache-local-path"); - if (cacheLocalPathInput !== "") { - const tildeExpanded = expandTilde(cacheLocalPathInput); - return { - path: resolveRelativePath(workingDirectory, tildeExpanded), - source: 0 /* Input */ - }; - } - const cacheDirFromConfig = getCacheDirFromConfig( - workingDirectory, - versionFile - ); - if (cacheDirFromConfig !== void 0) { - return { path: cacheDirFromConfig, source: 1 /* Config */ }; - } - if (process.env.UV_CACHE_DIR !== void 0) { - info2(`UV_CACHE_DIR is already set to ${process.env.UV_CACHE_DIR}`); - return { path: process.env.UV_CACHE_DIR, source: 2 /* Env */ }; - } - if (enableCache) { - if (process.env.RUNNER_ENVIRONMENT === "github-hosted") { - if (process.env.RUNNER_TEMP !== void 0) { - return { - path: `${process.env.RUNNER_TEMP}${import_node_path.default.sep}setup-uv-cache`, - source: 3 /* Default */ - }; - } - throw Error( - "Could not determine UV_CACHE_DIR. Please make sure RUNNER_TEMP is set or provide the cache-local-path input" - ); - } - if (process.platform === "win32") { - return { - path: `${process.env.APPDATA}${import_node_path.default.sep}uv${import_node_path.default.sep}cache`, - source: 3 /* Default */ - }; - } - return { - path: `${process.env.HOME}${import_node_path.default.sep}.cache${import_node_path.default.sep}uv`, - source: 3 /* Default */ - }; - } -} -function getCacheDirFromConfig(workingDirectory, versionFile) { - for (const filePath of [versionFile, "uv.toml", "pyproject.toml"]) { - const resolvedPath = resolveRelativePath(workingDirectory, filePath); - try { - const cacheDir2 = getConfigValueFromTomlFile(resolvedPath, "cache-dir"); - if (cacheDir2 !== void 0) { - info2(`Found cache-dir in ${resolvedPath}: ${cacheDir2}`); - return cacheDir2; - } - } catch (err) { - const message = err.message; - warning2(`Error while parsing ${filePath}: ${message}`); - return void 0; - } - } - return void 0; -} -function getUvPythonDir() { - if (process.env.UV_PYTHON_INSTALL_DIR !== void 0) { - info2( - `UV_PYTHON_INSTALL_DIR is already set to ${process.env.UV_PYTHON_INSTALL_DIR}` - ); - return process.env.UV_PYTHON_INSTALL_DIR; - } - if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") { - if (process.platform === "win32") { - return `${process.env.APPDATA}${import_node_path.default.sep}uv${import_node_path.default.sep}python`; - } - return `${process.env.HOME}${import_node_path.default.sep}.local${import_node_path.default.sep}share${import_node_path.default.sep}uv${import_node_path.default.sep}python`; - } - if (process.env.RUNNER_TEMP !== void 0) { - return `${process.env.RUNNER_TEMP}${import_node_path.default.sep}uv-python-dir`; - } - throw Error( - "Could not determine UV_PYTHON_INSTALL_DIR. Please make sure RUNNER_TEMP is set or provide the UV_PYTHON_INSTALL_DIR environment variable" - ); -} -function getCacheDependencyGlob(workingDirectory) { - const cacheDependencyGlobInput = getInput("cache-dependency-glob"); - if (cacheDependencyGlobInput !== "") { - return cacheDependencyGlobInput.split("\n").map((part) => part.trim()).map((part) => expandTilde(part)).map((part) => resolveRelativePath(workingDirectory, part)).join("\n"); - } - return cacheDependencyGlobInput; -} -function expandTilde(input) { - if (input.startsWith("~")) { - return `${process.env.HOME}${input.substring(1)}`; - } - return input; -} -function normalizePath(inputPath) { - const normalized = import_node_path.default.normalize(inputPath); - const root = import_node_path.default.parse(normalized).root; - let trimmed = normalized; - while (trimmed.length > root.length && trimmed.endsWith(import_node_path.default.sep)) { - trimmed = trimmed.slice(0, -1); - } - return trimmed; -} -function resolveRelativePath(workingDirectory, inputPath) { - const hasNegation = inputPath.startsWith("!"); - const pathWithoutNegation = hasNegation ? inputPath.substring(1) : inputPath; - const resolvedPath = import_node_path.default.resolve(workingDirectory, pathWithoutNegation); - debug( - `Resolving relative path ${inputPath} to ${hasNegation ? "!" : ""}${resolvedPath}` - ); - return hasNegation ? `!${resolvedPath}` : resolvedPath; -} -function getManifestFile() { - const manifestFileInput = getInput("manifest-file"); - if (manifestFileInput !== "") { - return manifestFileInput; - } - return void 0; -} -function getResolutionStrategy() { - const resolutionStrategyInput = getInput("resolution-strategy"); - if (resolutionStrategyInput === "lowest") { - return "lowest"; - } - if (resolutionStrategyInput === "highest" || resolutionStrategyInput === "") { - return "highest"; - } - throw new Error( - `Invalid resolution-strategy: ${resolutionStrategyInput}. Must be 'highest' or 'lowest'.` - ); -} - -// src/utils/python-runtime.ts +// src/utils/python-arch.ts var import_node_child_process = require("node:child_process"); var import_node_util4 = require("node:util"); var execFileAsync = (0, import_node_util4.promisify)(import_node_child_process.execFile); +var PROBE_ARCH = "setup-uv-probe"; +async function setupPythonArch(uvPath, pythonArchInput) { + const pythonArch = resolvePythonArch(pythonArchInput); + if (pythonArch === "") { + return; + } + const probe = await queryPythonArch(uvPath, PROBE_ARCH); + if (probe.exitCode === 0) { + throw new Error( + "The installed version of uv does not support UV_PYTHON_ARCH. Select a newer uv version or use an architecture-qualified python-version." + ); + } + if (!probe.stderr.includes( + `environment variable \`UV_PYTHON_ARCH\` with invalid value \`${PROBE_ARCH}\`` + )) { + throw new Error( + `Failed to check uv's support for UV_PYTHON_ARCH: ${probe.stderr.trim() || `uv exited with code ${probe.exitCode}`}` + ); + } + const selected = await queryPythonArch(uvPath, pythonArch); + if (selected.exitCode !== 0) { + throw new Error( + `Failed to set Python architecture to ${pythonArch}: ${selected.stderr.trim() || `uv exited with code ${selected.exitCode}`}` + ); + } + if (pythonArchInput !== "") { + exportVariable("UV_PYTHON_ARCH", pythonArch); + info2(`Set UV_PYTHON_ARCH to ${pythonArch}`); + } +} +async function queryPythonArch(uvPath, pythonArch) { + try { + const { stderr } = await execFileAsync( + uvPath, + ["--no-config", "cache", "dir"], + { + encoding: "utf8", + env: { ...process.env, NO_COLOR: "1", UV_PYTHON_ARCH: pythonArch } + } + ); + return { exitCode: 0, stderr }; + } catch (error2) { + if (error2 instanceof Error && "code" in error2 && typeof error2.code === "number" && "stderr" in error2 && typeof error2.stderr === "string") { + return { exitCode: error2.code, stderr: error2.stderr }; + } + throw error2; + } +} + +// src/utils/python-runtime.ts +var import_node_child_process2 = require("node:child_process"); +var import_node_util5 = require("node:util"); +var execFileAsync2 = (0, import_node_util5.promisify)(import_node_child_process2.execFile); async function getPythonRuntimeId(inputs) { if (!inputs.activateEnvironment) { return ""; } try { - const { stdout } = await execFileAsync( + const { stdout } = await execFileAsync2( "uv", [ "python", @@ -102318,6 +102380,11 @@ async function run() { throw new Error(`Unsupported architecture: ${process.arch}`); } const setupResult = await setupUv(inputs, platform2, arch3); + const uvPath = path17.join( + setupResult.uvDir, + process.platform === "win32" ? "uv.exe" : "uv" + ); + await setupPythonArch(uvPath, inputs.pythonArch); addToolBinToPath(inputs); addUvToPathAndOutput(setupResult.uvDir); setToolDir(inputs); diff --git a/docs/caching.md b/docs/caching.md index c38d4b2..4d393e2 100644 --- a/docs/caching.md +++ b/docs/caching.md @@ -11,6 +11,7 @@ The cache key is automatically generated based on: `pc-windows-msvc`) - **OS version**: OS name and version (e.g., `ubuntu-22.04`, `macos-14`, `windows-2022`) - **Python version**: The Python version in use +- **Python architecture**: The architecture selected by `python-arch` or `UV_PYTHON_ARCH`, when set - **Cache options**: Whether pruning and Python caching are enabled - **Dependency hash**: Hash of files matching `cache-dependency-glob` - **Suffix**: Optional `cache-suffix` if provided diff --git a/docs/environment-and-tools.md b/docs/environment-and-tools.md index a6ccc01..58d5741 100644 --- a/docs/environment-and-tools.md +++ b/docs/environment-and-tools.md @@ -144,6 +144,7 @@ If you want to ignore this, set the `ignore-empty-workdir` input to `true`. This action sets several environment variables that influence uv's behavior and can be used by subsequent steps: - `UV_PYTHON`: Set when `python-version` is specified or the selected `.tool-versions` file contains a supported `python` entry. Controls which Python version uv uses. +- `UV_PYTHON_ARCH`: Set when `python-arch` is specified. Controls the architecture of Python requests that do not specify one. - `UV_CACHE_DIR`: Set when caching is enabled (unless already configured in uv config files). Controls where uv stores its cache. - `UV_TOOL_DIR`: Set when `tool-dir` input is specified. Controls where uv installs tool environments. - `UV_TOOL_BIN_DIR`: Set when `tool-bin-dir` input is specified. Controls where uv installs tool binaries. @@ -155,6 +156,7 @@ This action sets several environment variables that influence uv's behavior and - `UV_NO_MODIFY_PATH`: If set, prevents the action from modifying PATH. Cannot be used with `activate-environment`. - `UV_CACHE_DIR`: If already set, the action will respect it instead of setting its own cache directory. - `UV_PYTHON`: If already set and `python-version` is not specified, the action will respect it instead of using the `python` entry from `.tool-versions`. +- `UV_PYTHON_ARCH`: If already set and `python-arch` is not specified, the action will use it and include it in the cache key. Requires a uv version that supports `UV_PYTHON_ARCH`. ```yaml - name: Example using environment variables diff --git a/src/cache/restore-cache.ts b/src/cache/restore-cache.ts index b5fec8f..453c350 100644 --- a/src/cache/restore-cache.ts +++ b/src/cache/restore-cache.ts @@ -1,7 +1,7 @@ import * as cache from "@actions/cache"; import * as core from "@actions/core"; import { hashFiles } from "../hash/hash-files"; -import type { SetupInputs } from "../utils/inputs"; +import { resolvePythonArch, type SetupInputs } from "../utils/inputs"; import * as log from "../utils/logging"; import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms"; @@ -98,11 +98,15 @@ async function computeKeys( ? `-${encodeURIComponent(inputs.cacheSuffix)}` : ""; const version = encodeURIComponent(pythonVersion ?? "unknown"); + const pythonArch = resolvePythonArch(inputs.pythonArch); + const pythonArchKey = pythonArch + ? `-python-${encodeURIComponent(pythonArch)}` + : ""; const platform = await getPlatform(); const osNameVersion = getOSNameVersion(); const pruned = inputs.pruneCache ? "-pruned" : ""; const python = inputs.cachePython ? "-py" : ""; - return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`; + return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${version}${pythonArchKey}${pruned}${python}${cacheDependencyPathHash}${suffix}`; } function handleMatchResult( diff --git a/src/setup-uv.ts b/src/setup-uv.ts index f83087f..9fbaa55 100644 --- a/src/setup-uv.ts +++ b/src/setup-uv.ts @@ -16,6 +16,7 @@ import { getPlatform, type Platform, } from "./utils/platforms"; +import { setupPythonArch } from "./utils/python-arch"; import { getPythonRuntimeId } from "./utils/python-runtime"; import { resolveUvVersion } from "./version/resolve"; @@ -86,6 +87,11 @@ async function run(): Promise { throw new Error(`Unsupported architecture: ${process.arch}`); } const setupResult = await setupUv(inputs, platform, arch); + const uvPath = path.join( + setupResult.uvDir, + process.platform === "win32" ? "uv.exe" : "uv", + ); + await setupPythonArch(uvPath, inputs.pythonArch); addToolBinToPath(inputs); addUvToPathAndOutput(setupResult.uvDir); diff --git a/src/utils/inputs.ts b/src/utils/inputs.ts index 62ddfc2..69eb902 100644 --- a/src/utils/inputs.ts +++ b/src/utils/inputs.ts @@ -24,6 +24,7 @@ export interface SetupInputs { version: string; versionFile: string; pythonVersion: string; + pythonArch: string; activateEnvironment: boolean; noProject: boolean; venvPath: string; @@ -54,6 +55,7 @@ export function loadInputs(): SetupInputs { const version = core.getInput("version"); const versionFile = getVersionFile(workingDirectory); const pythonVersion = getPythonVersion(versionFile); + const pythonArch = core.getInput("python-arch"); const activateEnvironment = core.getBooleanInput("activate-environment"); const noProject = core.getBooleanInput("no-project"); const venvPath = getVenvPath(workingDirectory, activateEnvironment); @@ -100,6 +102,7 @@ export function loadInputs(): SetupInputs { manifestFile, noProject, pruneCache, + pythonArch, pythonDir, pythonVersion, quiet, @@ -146,6 +149,10 @@ function getPythonVersion(versionFile: string): string { } } +export function resolvePythonArch(pythonArch: string): string { + return pythonArch || process.env.UV_PYTHON_ARCH || ""; +} + function getVenvPath( workingDirectory: string, activateEnvironment: boolean, diff --git a/src/utils/python-arch.ts b/src/utils/python-arch.ts new file mode 100644 index 0000000..1f4f64b --- /dev/null +++ b/src/utils/python-arch.ts @@ -0,0 +1,76 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import * as core from "@actions/core"; +import { resolvePythonArch } from "./inputs"; +import * as log from "./logging"; + +const execFileAsync = promisify(execFile); +const PROBE_ARCH = "setup-uv-probe"; + +export async function setupPythonArch( + uvPath: string, + pythonArchInput: string, +): Promise { + const pythonArch = resolvePythonArch(pythonArchInput); + if (pythonArch === "") { + return; + } + + // Older uv releases ignore unknown environment variables. An invalid architecture + // confirms that uv recognizes UV_PYTHON_ARCH without finding or downloading Python. + const probe = await queryPythonArch(uvPath, PROBE_ARCH); + if (probe.exitCode === 0) { + throw new Error( + "The installed version of uv does not support UV_PYTHON_ARCH. Select a newer uv version or use an architecture-qualified python-version.", + ); + } + if ( + !probe.stderr.includes( + `environment variable \`UV_PYTHON_ARCH\` with invalid value \`${PROBE_ARCH}\``, + ) + ) { + throw new Error( + `Failed to check uv's support for UV_PYTHON_ARCH: ${probe.stderr.trim() || `uv exited with code ${probe.exitCode}`}`, + ); + } + + const selected = await queryPythonArch(uvPath, pythonArch); + if (selected.exitCode !== 0) { + throw new Error( + `Failed to set Python architecture to ${pythonArch}: ${selected.stderr.trim() || `uv exited with code ${selected.exitCode}`}`, + ); + } + + if (pythonArchInput !== "") { + core.exportVariable("UV_PYTHON_ARCH", pythonArch); + log.info(`Set UV_PYTHON_ARCH to ${pythonArch}`); + } +} + +async function queryPythonArch( + uvPath: string, + pythonArch: string, +): Promise<{ exitCode: number; stderr: string }> { + try { + const { stderr } = await execFileAsync( + uvPath, + ["--no-config", "cache", "dir"], + { + encoding: "utf8", + env: { ...process.env, NO_COLOR: "1", UV_PYTHON_ARCH: pythonArch }, + }, + ); + return { exitCode: 0, stderr }; + } catch (error) { + if ( + error instanceof Error && + "code" in error && + typeof error.code === "number" && + "stderr" in error && + typeof error.stderr === "string" + ) { + return { exitCode: error.code, stderr: error.stderr }; + } + throw error; + } +}