5
0
mirror of https://github.com/astral-sh/setup-uv.git synced 2026-03-29 01:49:53 +00:00

Refactor inputs (#823)

Don't load at import time and make it easier to test
This commit is contained in:
Kevin Stillhammer
2026-03-28 16:23:26 +01:00
committed by GitHub
parent 868d1f74d9
commit f82eb19c06
9 changed files with 2363 additions and 2112 deletions

View File

@@ -17,7 +17,6 @@ const {
fetchManifest, fetchManifest,
getAllVersions, getAllVersions,
getArtifact, getArtifact,
getLatestVersion,
parseManifest, parseManifest,
} = await import("../../src/download/manifest"); } = await import("../../src/download/manifest");

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { import {
afterEach, afterEach,
beforeEach, beforeEach,
@@ -7,9 +10,13 @@ import {
jest, jest,
} from "@jest/globals"; } from "@jest/globals";
// Will be mutated per test before (re-)importing the module under test
let mockInputs: Record<string, string> = {}; let mockInputs: Record<string, string> = {};
const tempDirs: string[] = [];
const ORIGINAL_HOME = process.env.HOME; const ORIGINAL_HOME = process.env.HOME;
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_INSTALL_DIR = process.env.UV_PYTHON_INSTALL_DIR;
const mockDebug = jest.fn(); const mockDebug = jest.fn();
const mockGetBooleanInput = jest.fn( const mockGetBooleanInput = jest.fn(
@@ -27,118 +34,228 @@ jest.unstable_mockModule("@actions/core", () => ({
warning: mockWarning, warning: mockWarning,
})); }));
async function importInputsModule() { const { CacheLocalSource, loadInputs } = await import("../../src/utils/inputs");
return await import("../../src/utils/inputs");
function createTempProject(files: Record<string, string>): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "setup-uv-inputs-test-"));
tempDirs.push(dir);
for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(dir, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}
return dir;
} }
function resetEnvironment(): void {
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
delete process.env.RUNNER_ENVIRONMENT;
delete process.env.RUNNER_TEMP;
delete process.env.UV_CACHE_DIR;
delete process.env.UV_PYTHON_INSTALL_DIR;
}
function restoreEnvironment(): void {
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir !== undefined) {
fs.rmSync(dir, { force: true, recursive: true });
}
}
process.env.HOME = ORIGINAL_HOME;
process.env.RUNNER_ENVIRONMENT = ORIGINAL_RUNNER_ENVIRONMENT;
process.env.RUNNER_TEMP = ORIGINAL_RUNNER_TEMP;
process.env.UV_CACHE_DIR = ORIGINAL_UV_CACHE_DIR;
process.env.UV_PYTHON_INSTALL_DIR = ORIGINAL_UV_PYTHON_INSTALL_DIR;
}
beforeEach(() => {
resetEnvironment();
});
afterEach(() => {
restoreEnvironment();
});
describe("loadInputs", () => {
it("loads defaults for a github-hosted runner", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
const inputs = loadInputs();
expect(inputs.enableCache).toBe(true);
expect(inputs.cacheLocalPath).toEqual({
path: "/runner-temp/setup-uv-cache",
source: CacheLocalSource.Default,
});
expect(inputs.pythonDir).toBe("/runner-temp/uv-python-dir");
expect(inputs.venvPath).toBe("/workspace/.venv");
expect(inputs.manifestFile).toBeUndefined();
expect(inputs.resolutionStrategy).toBe("highest");
});
it("uses cache-dir from pyproject.toml when present", () => {
mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project]
name = "uv-project"
version = "0.1.0"
[tool.uv]
cache-dir = "/tmp/pyproject-toml-defined-cache-path"
`,
});
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toEqual({
path: "/tmp/pyproject-toml-defined-cache-path",
source: CacheLocalSource.Config,
});
expect(mockInfo).toHaveBeenCalledWith(
expect.stringContaining("Found cache-dir in"),
);
});
it("uses UV_CACHE_DIR from the environment", () => {
mockInputs["working-directory"] = createTempProject({});
process.env.UV_CACHE_DIR = "/env/cache-dir";
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toEqual({
path: "/env/cache-dir",
source: CacheLocalSource.Env,
});
expect(mockInfo).toHaveBeenCalledWith(
"UV_CACHE_DIR is already set to /env/cache-dir",
);
});
it("uses UV_PYTHON_INSTALL_DIR from the environment", () => {
mockInputs["working-directory"] = "/workspace";
process.env.UV_PYTHON_INSTALL_DIR = "/env/python-dir";
const inputs = loadInputs();
expect(inputs.pythonDir).toBe("/env/python-dir");
expect(mockInfo).toHaveBeenCalledWith(
"UV_PYTHON_INSTALL_DIR is already set to /env/python-dir",
);
});
it("warns when parsing a malformed pyproject.toml for cache-dir", () => {
mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project]
name = "malformed-pyproject-toml-project"
version = "0.1.0"
[malformed-toml
`,
});
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
expect.stringContaining("Error while parsing pyproject.toml:"),
);
});
it("throws for an invalid resolution strategy", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["resolution-strategy"] = "middle";
expect(() => loadInputs()).toThrow(
"Invalid resolution-strategy: middle. Must be 'highest' or 'lowest'.",
);
});
});
describe("cacheDependencyGlob", () => { describe("cacheDependencyGlob", () => {
beforeEach(() => { it("returns empty string when input not provided", () => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("returns empty string when input not provided", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(""); const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe("");
}); });
it("resolves a single relative path", async () => { it.each([
["requirements.txt", "/workspace/requirements.txt"],
["./uv.lock", "/workspace/uv.lock"],
])("resolves %s to %s", (globInput, expected) => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "requirements.txt"; mockInputs["cache-dependency-glob"] = globInput;
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe("/workspace/requirements.txt"); const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(expected);
}); });
it("strips leading ./ from relative path", async () => { it("handles multiple lines, trimming whitespace, tilde expansion and absolute paths", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "./uv.lock";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe("/workspace/uv.lock");
});
it("handles multiple lines, trimming whitespace, tilde expansion and absolute paths", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = mockInputs["cache-dependency-glob"] =
" ~/.cache/file1\n ./rel/file2 \nfile3.txt"; " ~/.cache/file1\n ./rel/file2 \nfile3.txt";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe( const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(
[ [
"/home/testuser/.cache/file1", // expanded tilde, absolute path unchanged "/home/testuser/.cache/file1",
"/workspace/rel/file2", // ./ stripped and resolved "/workspace/rel/file2",
"/workspace/file3.txt", // relative path resolved "/workspace/file3.txt",
].join("\n"), ].join("\n"),
); );
}); });
it("keeps absolute path unchanged in multiline input", async () => { it.each([
mockInputs["working-directory"] = "/workspace"; [
mockInputs["cache-dependency-glob"] = "/abs/path.lock\nrelative.lock"; "/abs/path.lock\nrelative.lock",
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(
["/abs/path.lock", "/workspace/relative.lock"].join("\n"), ["/abs/path.lock", "/workspace/relative.lock"].join("\n"),
); ],
}); [
"!/abs/path.lock\n!relative.lock",
it("handles exclusions in relative paths correct", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "!/abs/path.lock\n!relative.lock";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(
["!/abs/path.lock", "!/workspace/relative.lock"].join("\n"), ["!/abs/path.lock", "!/workspace/relative.lock"].join("\n"),
); ],
])("normalizes multiline glob %s", (globInput, expected) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = globInput;
const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(expected);
}); });
}); });
describe("tool directories", () => { describe("tool directories", () => {
beforeEach(() => { it("expands tilde for tool-bin-dir and tool-dir", () => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("expands tilde for tool-bin-dir and tool-dir", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["tool-bin-dir"] = "~/tool-bin-dir"; mockInputs["tool-bin-dir"] = "~/tool-bin-dir";
mockInputs["tool-dir"] = "~/tool-dir"; mockInputs["tool-dir"] = "~/tool-dir";
const { toolBinDir, toolDir } = await importInputsModule(); const inputs = loadInputs();
expect(toolBinDir).toBe("/home/testuser/tool-bin-dir"); expect(inputs.toolBinDir).toBe("/home/testuser/tool-bin-dir");
expect(toolDir).toBe("/home/testuser/tool-dir"); expect(inputs.toolDir).toBe("/home/testuser/tool-dir");
}); });
}); });
describe("cacheLocalPath", () => { describe("cacheLocalPath", () => {
beforeEach(() => { it("expands tilde in cache-local-path", () => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("expands tilde in cache-local-path", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["cache-local-path"] = "~/uv-cache/cache-local-path"; mockInputs["cache-local-path"] = "~/uv-cache/cache-local-path";
const { CacheLocalSource, cacheLocalPath } = await importInputsModule(); const inputs = loadInputs();
expect(cacheLocalPath).toEqual({ expect(inputs.cacheLocalPath).toEqual({
path: "/home/testuser/uv-cache/cache-local-path", path: "/home/testuser/uv-cache/cache-local-path",
source: CacheLocalSource.Input, source: CacheLocalSource.Input,
}); });
@@ -146,63 +263,37 @@ describe("cacheLocalPath", () => {
}); });
describe("venvPath", () => { describe("venvPath", () => {
beforeEach(() => { it("defaults to .venv in the working directory", () => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("defaults to .venv in the working directory", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/.venv"); const inputs = loadInputs();
expect(inputs.venvPath).toBe("/workspace/.venv");
}); });
it("resolves a relative venv-path", async () => { it.each([
["custom-venv", "/workspace/custom-venv"],
["custom-venv/", "/workspace/custom-venv"],
["/tmp/custom-venv", "/tmp/custom-venv"],
["~/.venv", "/home/testuser/.venv"],
])("resolves venv-path %s to %s", (venvPathInput, expected) => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true"; mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "custom-venv"; mockInputs["venv-path"] = venvPathInput;
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/custom-venv"); const inputs = loadInputs();
expect(inputs.venvPath).toBe(expected);
}); });
it("normalizes venv-path with trailing slash", async () => { it("warns when venv-path is set but activate-environment is false", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "custom-venv/";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/custom-venv");
});
it("keeps an absolute venv-path unchanged", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "/tmp/custom-venv";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/tmp/custom-venv");
});
it("expands tilde in venv-path", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "~/.venv";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/home/testuser/.venv");
});
it("warns when venv-path is set but activate-environment is false", async () => {
mockInputs["working-directory"] = "/workspace"; mockInputs["working-directory"] = "/workspace";
mockInputs["venv-path"] = "custom-venv"; mockInputs["venv-path"] = "custom-venv";
const { activateEnvironment, venvPath } = await importInputsModule(); const inputs = loadInputs();
expect(activateEnvironment).toBe(false); expect(inputs.activateEnvironment).toBe(false);
expect(venvPath).toBe("/workspace/custom-venv"); expect(inputs.venvPath).toBe("/workspace/custom-venv");
expect(mockWarning).toHaveBeenCalledWith( expect(mockWarning).toHaveBeenCalledWith(
"venv-path is only used when activate-environment is true", "venv-path is only used when activate-environment is true",
); );

656
dist/save-cache/index.cjs generated vendored

File diff suppressed because it is too large Load Diff

2973
dist/setup/index.cjs generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,7 @@
import * as cache from "@actions/cache"; import * as cache from "@actions/cache";
import * as core from "@actions/core"; import * as core from "@actions/core";
import { hashFiles } from "../hash/hash-files"; import { hashFiles } from "../hash/hash-files";
import { import type { SetupInputs } from "../utils/inputs";
cacheDependencyGlob,
cacheLocalPath,
cachePython,
cacheSuffix,
pruneCache,
pythonDir,
restoreCache as shouldRestoreCache,
} from "../utils/inputs";
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms"; import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
export const STATE_CACHE_KEY = "cache-key"; export const STATE_CACHE_KEY = "cache-key";
@@ -18,18 +10,21 @@ export const STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key";
const CACHE_VERSION = "2"; const CACHE_VERSION = "2";
export async function restoreCache(pythonVersion?: string): Promise<void> { export async function restoreCache(
const cacheKey = await computeKeys(pythonVersion); inputs: SetupInputs,
pythonVersion?: string,
): Promise<void> {
const cacheKey = await computeKeys(inputs, pythonVersion);
core.saveState(STATE_CACHE_KEY, cacheKey); core.saveState(STATE_CACHE_KEY, cacheKey);
core.setOutput("cache-key", cacheKey); core.setOutput("cache-key", cacheKey);
if (!shouldRestoreCache) { if (!inputs.restoreCache) {
core.info("restore-cache is false. Skipping restore cache step."); core.info("restore-cache is false. Skipping restore cache step.");
core.setOutput("python-cache-hit", false); core.setOutput("python-cache-hit", false);
return; return;
} }
if (cacheLocalPath === undefined) { if (inputs.cacheLocalPath === undefined) {
throw new Error( throw new Error(
"cache-local-path is not set. Cannot restore cache without a valid cache path.", "cache-local-path is not set. Cannot restore cache without a valid cache path.",
); );
@@ -37,15 +32,15 @@ export async function restoreCache(pythonVersion?: string): Promise<void> {
await restoreCacheFromKey( await restoreCacheFromKey(
cacheKey, cacheKey,
cacheLocalPath.path, inputs.cacheLocalPath.path,
STATE_CACHE_MATCHED_KEY, STATE_CACHE_MATCHED_KEY,
"cache-hit", "cache-hit",
); );
if (cachePython) { if (inputs.cachePython) {
await restoreCacheFromKey( await restoreCacheFromKey(
`${cacheKey}-python`, `${cacheKey}-python`,
pythonDir, inputs.pythonDir,
STATE_PYTHON_CACHE_MATCHED_KEY, STATE_PYTHON_CACHE_MATCHED_KEY,
"python-cache-hit", "python-cache-hit",
); );
@@ -76,28 +71,34 @@ async function restoreCacheFromKey(
handleMatchResult(matchedKey, cacheKey, stateKey, outputKey); handleMatchResult(matchedKey, cacheKey, stateKey, outputKey);
} }
async function computeKeys(pythonVersion?: string): Promise<string> { async function computeKeys(
inputs: SetupInputs,
pythonVersion?: string,
): Promise<string> {
let cacheDependencyPathHash = "-"; let cacheDependencyPathHash = "-";
if (cacheDependencyGlob !== "") { if (inputs.cacheDependencyGlob !== "") {
core.info( core.info(
`Searching files using cache dependency glob: ${cacheDependencyGlob.split("\n").join(",")}`, `Searching files using cache dependency glob: ${inputs.cacheDependencyGlob.split("\n").join(",")}`,
);
cacheDependencyPathHash += await hashFiles(
inputs.cacheDependencyGlob,
true,
); );
cacheDependencyPathHash += await hashFiles(cacheDependencyGlob, true);
if (cacheDependencyPathHash === "-") { if (cacheDependencyPathHash === "-") {
core.warning( core.warning(
`No file matched to [${cacheDependencyGlob.split("\n").join(",")}]. The cache will never get invalidated. Make sure you have checked out the target repository and configured the cache-dependency-glob input correctly.`, `No file matched to [${inputs.cacheDependencyGlob.split("\n").join(",")}]. The cache will never get invalidated. Make sure you have checked out the target repository and configured the cache-dependency-glob input correctly.`,
); );
} }
} }
if (cacheDependencyPathHash === "-") { if (cacheDependencyPathHash === "-") {
cacheDependencyPathHash = "-no-dependency-glob"; cacheDependencyPathHash = "-no-dependency-glob";
} }
const suffix = cacheSuffix ? `-${cacheSuffix}` : ""; const suffix = inputs.cacheSuffix ? `-${inputs.cacheSuffix}` : "";
const version = pythonVersion ?? "unknown"; const version = pythonVersion ?? "unknown";
const platform = await getPlatform(); const platform = await getPlatform();
const osNameVersion = getOSNameVersion(); const osNameVersion = getOSNameVersion();
const pruned = pruneCache ? "-pruned" : ""; const pruned = inputs.pruneCache ? "-pruned" : "";
const python = cachePython ? "-py" : ""; 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}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
} }

View File

@@ -33,7 +33,7 @@ export async function downloadVersion(
platform: Platform, platform: Platform,
arch: Architecture, arch: Architecture,
version: string, version: string,
checkSum: string | undefined, checksum: string | undefined,
githubToken: string, githubToken: string,
manifestUrl?: string, manifestUrl?: string,
): Promise<{ version: string; cachedToolDir: string }> { ): Promise<{ version: string; cachedToolDir: string }> {
@@ -47,10 +47,10 @@ export async function downloadVersion(
// For the default astral-sh/versions source, checksum validation relies on // For the default astral-sh/versions source, checksum validation relies on
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values. // user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values.
const checksum = const resolvedChecksum =
manifestUrl === undefined manifestUrl === undefined
? checkSum ? checksum
: resolveChecksum(checkSum, artifact.checksum); : resolveChecksum(checksum, artifact.checksum);
const mirrorUrl = rewriteToMirror(artifact.downloadUrl); const mirrorUrl = rewriteToMirror(artifact.downloadUrl);
const downloadUrl = mirrorUrl ?? artifact.downloadUrl; const downloadUrl = mirrorUrl ?? artifact.downloadUrl;
@@ -64,7 +64,7 @@ export async function downloadVersion(
platform, platform,
arch, arch,
version, version,
checksum, resolvedChecksum,
downloadToken, downloadToken,
); );
} catch (err) { } catch (err) {
@@ -82,7 +82,7 @@ export async function downloadVersion(
platform, platform,
arch, arch,
version, version,
checksum, resolvedChecksum,
githubToken, githubToken,
); );
} }
@@ -161,11 +161,11 @@ function getMissingArtifactMessage(
} }
function resolveChecksum( function resolveChecksum(
checkSum: string | undefined, checksum: string | undefined,
manifestChecksum: string, manifestChecksum: string,
): string { ): string {
return checkSum !== undefined && checkSum !== "" return checksum !== undefined && checksum !== ""
? checkSum ? checksum
: manifestChecksum; : manifestChecksum;
} }

View File

@@ -9,21 +9,14 @@ import {
STATE_PYTHON_CACHE_MATCHED_KEY, STATE_PYTHON_CACHE_MATCHED_KEY,
} from "./cache/restore-cache"; } from "./cache/restore-cache";
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants"; import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
import { import { loadInputs, type SetupInputs } from "./utils/inputs";
cacheLocalPath,
cachePython,
enableCache,
ignoreNothingToCache,
pythonDir,
pruneCache as shouldPruneCache,
saveCache as shouldSaveCache,
} from "./utils/inputs";
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
if (enableCache) { const inputs = loadInputs();
if (shouldSaveCache) { if (inputs.enableCache) {
await saveCache(); if (inputs.saveCache) {
await saveCache(inputs);
} else { } else {
core.info("save-cache is false. Skipping save cache step."); core.info("save-cache is false. Skipping save cache step.");
} }
@@ -43,7 +36,7 @@ export async function run(): Promise<void> {
} }
} }
async function saveCache(): Promise<void> { async function saveCache(inputs: SetupInputs): Promise<void> {
const cacheKey = core.getState(STATE_CACHE_KEY); const cacheKey = core.getState(STATE_CACHE_KEY);
const matchedKey = core.getState(STATE_CACHE_MATCHED_KEY); const matchedKey = core.getState(STATE_CACHE_MATCHED_KEY);
@@ -54,13 +47,13 @@ async function saveCache(): Promise<void> {
if (matchedKey === cacheKey) { if (matchedKey === cacheKey) {
core.info(`Cache hit occurred on key ${cacheKey}, not saving cache.`); core.info(`Cache hit occurred on key ${cacheKey}, not saving cache.`);
} else { } else {
if (shouldPruneCache) { if (inputs.pruneCache) {
await pruneCache(); await pruneCache();
} }
const actualCachePath = getUvCachePath(); const actualCachePath = getUvCachePath(inputs);
if (!fs.existsSync(actualCachePath)) { if (!fs.existsSync(actualCachePath)) {
if (ignoreNothingToCache) { if (inputs.ignoreNothingToCache) {
core.info( core.info(
"No cacheable uv cache paths were found. Ignoring because ignore-nothing-to-cache is enabled.", "No cacheable uv cache paths were found. Ignoring because ignore-nothing-to-cache is enabled.",
); );
@@ -79,10 +72,10 @@ async function saveCache(): Promise<void> {
} }
} }
if (cachePython) { if (inputs.cachePython) {
if (!fs.existsSync(pythonDir)) { if (!fs.existsSync(inputs.pythonDir)) {
core.warning( core.warning(
`Python cache path ${pythonDir} does not exist on disk. Skipping Python cache save because no managed Python installation was found. If you want uv to install managed Python instead of using a system interpreter, set UV_PYTHON_PREFERENCE=only-managed.`, `Python cache path ${inputs.pythonDir} does not exist on disk. Skipping Python cache save because no managed Python installation was found. If you want uv to install managed Python instead of using a system interpreter, set UV_PYTHON_PREFERENCE=only-managed.`,
); );
return; return;
} }
@@ -90,7 +83,7 @@ async function saveCache(): Promise<void> {
const pythonCacheKey = `${cacheKey}-python`; const pythonCacheKey = `${cacheKey}-python`;
await saveCacheToKey( await saveCacheToKey(
pythonCacheKey, pythonCacheKey,
pythonDir, inputs.pythonDir,
STATE_PYTHON_CACHE_MATCHED_KEY, STATE_PYTHON_CACHE_MATCHED_KEY,
"Python cache", "Python cache",
); );
@@ -113,22 +106,22 @@ async function pruneCache(): Promise<void> {
await exec.exec(uvPath, execArgs, options); await exec.exec(uvPath, execArgs, options);
} }
function getUvCachePath(): string { function getUvCachePath(inputs: SetupInputs): string {
if (cacheLocalPath === undefined) { if (inputs.cacheLocalPath === undefined) {
throw new Error( throw new Error(
"cache-local-path is not set. Cannot save cache without a valid cache path.", "cache-local-path is not set. Cannot save cache without a valid cache path.",
); );
} }
if ( if (
process.env.UV_CACHE_DIR && process.env.UV_CACHE_DIR &&
process.env.UV_CACHE_DIR !== cacheLocalPath.path process.env.UV_CACHE_DIR !== inputs.cacheLocalPath.path
) { ) {
core.warning( core.warning(
`The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${cacheLocalPath.path}".`, `The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${inputs.cacheLocalPath.path}".`,
); );
return process.env.UV_CACHE_DIR; return process.env.UV_CACHE_DIR;
} }
return cacheLocalPath.path; return inputs.cacheLocalPath.path;
} }
async function saveCacheToKey( async function saveCacheToKey(

View File

@@ -9,26 +9,7 @@ import {
tryGetFromToolCache, tryGetFromToolCache,
} from "./download/download-version"; } from "./download/download-version";
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants"; import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
import { import { CacheLocalSource, loadInputs, type SetupInputs } from "./utils/inputs";
activateEnvironment as activateEnvironmentInput,
addProblemMatchers,
CacheLocalSource,
cacheLocalPath,
checkSum,
enableCache,
githubToken,
ignoreEmptyWorkdir,
manifestFile,
pythonDir,
pythonVersion,
resolutionStrategy,
toolBinDir,
toolDir,
venvPath,
versionFile as versionFileInput,
version as versionInput,
workingDirectory,
} from "./utils/inputs";
import { import {
type Architecture, type Architecture,
getArch, getArch,
@@ -39,9 +20,9 @@ import { getUvVersionFromFile } from "./version/resolve";
const sourceDir = __dirname; const sourceDir = __dirname;
async function getPythonVersion(): Promise<string> { async function getPythonVersion(inputs: SetupInputs): Promise<string> {
if (pythonVersion !== "") { if (inputs.pythonVersion !== "") {
return pythonVersion; return inputs.pythonVersion;
} }
let output = ""; let output = "";
@@ -55,7 +36,7 @@ async function getPythonVersion(): Promise<string> {
}; };
try { try {
const execArgs = ["python", "find", "--directory", workingDirectory]; const execArgs = ["python", "find", "--directory", inputs.workingDirectory];
await exec.exec("uv", execArgs, options); await exec.exec("uv", execArgs, options);
const pythonPath = output.trim(); const pythonPath = output.trim();
@@ -71,37 +52,38 @@ async function getPythonVersion(): Promise<string> {
} }
async function run(): Promise<void> { async function run(): Promise<void> {
detectEmptyWorkdir();
const platform = await getPlatform();
const arch = getArch();
try { try {
const inputs = loadInputs();
detectEmptyWorkdir(inputs);
const platform = await getPlatform();
const arch = getArch();
if (platform === undefined) { if (platform === undefined) {
throw new Error(`Unsupported platform: ${process.platform}`); throw new Error(`Unsupported platform: ${process.platform}`);
} }
if (arch === undefined) { if (arch === undefined) {
throw new Error(`Unsupported architecture: ${process.arch}`); throw new Error(`Unsupported architecture: ${process.arch}`);
} }
const setupResult = await setupUv(platform, arch, checkSum, githubToken); const setupResult = await setupUv(inputs, platform, arch);
addToolBinToPath(); addToolBinToPath(inputs);
addUvToPathAndOutput(setupResult.uvDir); addUvToPathAndOutput(setupResult.uvDir);
setToolDir(); setToolDir(inputs);
addPythonDirToPath(); addPythonDirToPath(inputs);
setupPython(); setupPython(inputs);
await activateEnvironment(); await activateEnvironment(inputs);
addMatchers(); addMatchers(inputs);
setCacheDir(); setCacheDir(inputs);
core.setOutput("uv-version", setupResult.version); core.setOutput("uv-version", setupResult.version);
core.saveState(STATE_UV_VERSION, setupResult.version); core.saveState(STATE_UV_VERSION, setupResult.version);
core.info(`Successfully installed uv version ${setupResult.version}`); core.info(`Successfully installed uv version ${setupResult.version}`);
const pythonVersion = await getPythonVersion(); const detectedPythonVersion = await getPythonVersion(inputs);
core.setOutput("python-version", pythonVersion); core.setOutput("python-version", detectedPythonVersion);
if (enableCache) { if (inputs.enableCache) {
await restoreCache(pythonVersion); await restoreCache(inputs, detectedPythonVersion);
} }
// https://github.com/nodejs/node/issues/56645#issuecomment-3077594952 // https://github.com/nodejs/node/issues/56645#issuecomment-3077594952
await new Promise((resolve) => setTimeout(resolve, 50)); await new Promise((resolve) => setTimeout(resolve, 50));
@@ -111,9 +93,9 @@ async function run(): Promise<void> {
} }
} }
function detectEmptyWorkdir(): void { function detectEmptyWorkdir(inputs: SetupInputs): void {
if (fs.readdirSync(workingDirectory).length === 0) { if (fs.readdirSync(inputs.workingDirectory).length === 0) {
if (ignoreEmptyWorkdir) { if (inputs.ignoreEmptyWorkdir) {
core.info( core.info(
"Empty workdir detected. Ignoring because ignore-empty-workdir is enabled", "Empty workdir detected. Ignoring because ignore-empty-workdir is enabled",
); );
@@ -126,12 +108,11 @@ function detectEmptyWorkdir(): void {
} }
async function setupUv( async function setupUv(
inputs: SetupInputs,
platform: Platform, platform: Platform,
arch: Architecture, arch: Architecture,
checkSum: string | undefined,
githubToken: string,
): Promise<{ uvDir: string; version: string }> { ): Promise<{ uvDir: string; version: string }> {
const resolvedVersion = await determineVersion(); const resolvedVersion = await determineVersion(inputs);
const toolCacheResult = tryGetFromToolCache(arch, resolvedVersion); const toolCacheResult = tryGetFromToolCache(arch, resolvedVersion);
if (toolCacheResult.installedPath) { if (toolCacheResult.installedPath) {
core.info(`Found uv in tool-cache for ${toolCacheResult.version}`); core.info(`Found uv in tool-cache for ${toolCacheResult.version}`);
@@ -145,9 +126,9 @@ async function setupUv(
platform, platform,
arch, arch,
resolvedVersion, resolvedVersion,
checkSum, inputs.checksum,
githubToken, inputs.githubToken,
manifestFile, inputs.manifestFile,
); );
return { return {
@@ -156,34 +137,34 @@ async function setupUv(
}; };
} }
async function determineVersion(): Promise<string> { async function determineVersion(inputs: SetupInputs): Promise<string> {
return await resolveVersion( return await resolveVersion(
getRequestedVersion(), getRequestedVersion(inputs),
manifestFile, inputs.manifestFile,
resolutionStrategy, inputs.resolutionStrategy,
); );
} }
function getRequestedVersion(): string { function getRequestedVersion(inputs: SetupInputs): string {
if (versionInput !== "") { if (inputs.version !== "") {
return versionInput; return inputs.version;
} }
if (versionFileInput !== "") { if (inputs.versionFile !== "") {
const versionFromFile = getUvVersionFromFile(versionFileInput); const versionFromFile = getUvVersionFromFile(inputs.versionFile);
if (versionFromFile === undefined) { if (versionFromFile === undefined) {
throw new Error( throw new Error(
`Could not determine uv version from file: ${versionFileInput}`, `Could not determine uv version from file: ${inputs.versionFile}`,
); );
} }
return versionFromFile; return versionFromFile;
} }
const versionFromUvToml = getUvVersionFromFile( const versionFromUvToml = getUvVersionFromFile(
`${workingDirectory}${path.sep}uv.toml`, `${inputs.workingDirectory}${path.sep}uv.toml`,
); );
const versionFromPyproject = getUvVersionFromFile( const versionFromPyproject = getUvVersionFromFile(
`${workingDirectory}${path.sep}pyproject.toml`, `${inputs.workingDirectory}${path.sep}pyproject.toml`,
); );
if (versionFromUvToml === undefined && versionFromPyproject === undefined) { if (versionFromUvToml === undefined && versionFromPyproject === undefined) {
@@ -207,15 +188,17 @@ function addUvToPathAndOutput(cachedPath: string): void {
} }
} }
function addToolBinToPath(): void { function addToolBinToPath(inputs: SetupInputs): void {
if (toolBinDir !== undefined) { if (inputs.toolBinDir !== undefined) {
core.exportVariable("UV_TOOL_BIN_DIR", toolBinDir); core.exportVariable("UV_TOOL_BIN_DIR", inputs.toolBinDir);
core.info(`Set UV_TOOL_BIN_DIR to ${toolBinDir}`); core.info(`Set UV_TOOL_BIN_DIR to ${inputs.toolBinDir}`);
if (process.env.UV_NO_MODIFY_PATH !== undefined) { if (process.env.UV_NO_MODIFY_PATH !== undefined) {
core.info(`UV_NO_MODIFY_PATH is set, not adding ${toolBinDir} to path`); core.info(
`UV_NO_MODIFY_PATH is set, not adding ${inputs.toolBinDir} to path`,
);
} else { } else {
core.addPath(toolBinDir); core.addPath(inputs.toolBinDir);
core.info(`Added ${toolBinDir} to the path`); core.info(`Added ${inputs.toolBinDir} to the path`);
} }
} else { } else {
if (process.env.UV_NO_MODIFY_PATH !== undefined) { if (process.env.UV_NO_MODIFY_PATH !== undefined) {
@@ -235,73 +218,73 @@ function addToolBinToPath(): void {
} }
} }
function setToolDir(): void { function setToolDir(inputs: SetupInputs): void {
if (toolDir !== undefined) { if (inputs.toolDir !== undefined) {
core.exportVariable("UV_TOOL_DIR", toolDir); core.exportVariable("UV_TOOL_DIR", inputs.toolDir);
core.info(`Set UV_TOOL_DIR to ${toolDir}`); core.info(`Set UV_TOOL_DIR to ${inputs.toolDir}`);
} }
} }
function addPythonDirToPath(): void { function addPythonDirToPath(inputs: SetupInputs): void {
core.exportVariable("UV_PYTHON_INSTALL_DIR", pythonDir); core.exportVariable("UV_PYTHON_INSTALL_DIR", inputs.pythonDir);
core.info(`Set UV_PYTHON_INSTALL_DIR to ${pythonDir}`); core.info(`Set UV_PYTHON_INSTALL_DIR to ${inputs.pythonDir}`);
if (process.env.UV_NO_MODIFY_PATH !== undefined) { if (process.env.UV_NO_MODIFY_PATH !== undefined) {
core.info("UV_NO_MODIFY_PATH is set, not adding python dir to path"); core.info("UV_NO_MODIFY_PATH is set, not adding python dir to path");
} else { } else {
core.addPath(pythonDir); core.addPath(inputs.pythonDir);
core.info(`Added ${pythonDir} to the path`); core.info(`Added ${inputs.pythonDir} to the path`);
} }
} }
function setupPython(): void { function setupPython(inputs: SetupInputs): void {
if (pythonVersion !== "") { if (inputs.pythonVersion !== "") {
core.exportVariable("UV_PYTHON", pythonVersion); core.exportVariable("UV_PYTHON", inputs.pythonVersion);
core.info(`Set UV_PYTHON to ${pythonVersion}`); core.info(`Set UV_PYTHON to ${inputs.pythonVersion}`);
} }
} }
async function activateEnvironment(): Promise<void> { async function activateEnvironment(inputs: SetupInputs): Promise<void> {
if (activateEnvironmentInput) { if (inputs.activateEnvironment) {
if (process.env.UV_NO_MODIFY_PATH !== undefined) { if (process.env.UV_NO_MODIFY_PATH !== undefined) {
throw new Error( throw new Error(
"UV_NO_MODIFY_PATH and activate-environment cannot be used together.", "UV_NO_MODIFY_PATH and activate-environment cannot be used together.",
); );
} }
core.info(`Creating and activating python venv at ${venvPath}...`); core.info(`Creating and activating python venv at ${inputs.venvPath}...`);
await exec.exec("uv", [ await exec.exec("uv", [
"venv", "venv",
venvPath, inputs.venvPath,
"--directory", "--directory",
workingDirectory, inputs.workingDirectory,
"--clear", "--clear",
]); ]);
let venvBinPath = `${venvPath}${path.sep}bin`; let venvBinPath = `${inputs.venvPath}${path.sep}bin`;
if (process.platform === "win32") { if (process.platform === "win32") {
venvBinPath = `${venvPath}${path.sep}Scripts`; venvBinPath = `${inputs.venvPath}${path.sep}Scripts`;
} }
core.addPath(path.resolve(venvBinPath)); core.addPath(path.resolve(venvBinPath));
core.exportVariable("VIRTUAL_ENV", venvPath); core.exportVariable("VIRTUAL_ENV", inputs.venvPath);
core.setOutput("venv", venvPath); core.setOutput("venv", inputs.venvPath);
} }
} }
function setCacheDir(): void { function setCacheDir(inputs: SetupInputs): void {
if (cacheLocalPath !== undefined) { if (inputs.cacheLocalPath !== undefined) {
if (cacheLocalPath.source === CacheLocalSource.Config) { if (inputs.cacheLocalPath.source === CacheLocalSource.Config) {
core.info( core.info(
"Using cache-dir from uv config file, not modifying UV_CACHE_DIR", "Using cache-dir from uv config file, not modifying UV_CACHE_DIR",
); );
return; return;
} }
core.exportVariable("UV_CACHE_DIR", cacheLocalPath.path); core.exportVariable("UV_CACHE_DIR", inputs.cacheLocalPath.path);
core.info(`Set UV_CACHE_DIR to ${cacheLocalPath.path}`); core.info(`Set UV_CACHE_DIR to ${inputs.cacheLocalPath.path}`);
} }
} }
function addMatchers(): void { function addMatchers(inputs: SetupInputs): void {
if (addProblemMatchers) { if (inputs.addProblemMatchers) {
const matchersPath = path.join(sourceDir, "..", "..", ".github"); const matchersPath = path.join(sourceDir, "..", "..", ".github");
core.info(`##[add-matcher]${path.join(matchersPath, "python.json")}`); core.info(`##[add-matcher]${path.join(matchersPath, "python.json")}`);
} }

View File

@@ -9,68 +9,152 @@ export enum CacheLocalSource {
Default, Default,
} }
export const workingDirectory = core.getInput("working-directory"); export interface CacheLocalPath {
export const version = core.getInput("version"); path: string;
export const versionFile = getVersionFile(); source: CacheLocalSource;
export const pythonVersion = core.getInput("python-version"); }
export const activateEnvironment = core.getBooleanInput("activate-environment");
export const venvPath = getVenvPath();
export const checkSum = core.getInput("checksum");
export const enableCache = getEnableCache();
export const restoreCache = core.getInput("restore-cache") === "true";
export const saveCache = core.getInput("save-cache") === "true";
export const cacheSuffix = core.getInput("cache-suffix") || "";
export const cacheLocalPath = getCacheLocalPath();
export const cacheDependencyGlob = getCacheDependencyGlob();
export const pruneCache = core.getInput("prune-cache") === "true";
export const cachePython = core.getInput("cache-python") === "true";
export const ignoreNothingToCache =
core.getInput("ignore-nothing-to-cache") === "true";
export const ignoreEmptyWorkdir =
core.getInput("ignore-empty-workdir") === "true";
export const toolBinDir = getToolBinDir();
export const toolDir = getToolDir();
export const pythonDir = getUvPythonDir();
export const githubToken = core.getInput("github-token");
export const manifestFile = getManifestFile();
export const addProblemMatchers =
core.getInput("add-problem-matchers") === "true";
export const resolutionStrategy = getResolutionStrategy();
function getVersionFile(): string { export interface SetupInputs {
const versionFileInput = core.getInput("version-file"); workingDirectory: string;
version: string;
versionFile: string;
pythonVersion: string;
activateEnvironment: boolean;
venvPath: string;
checksum: string;
enableCache: boolean;
restoreCache: boolean;
saveCache: boolean;
cacheSuffix: string;
cacheLocalPath?: CacheLocalPath;
cacheDependencyGlob: string;
pruneCache: boolean;
cachePython: boolean;
ignoreNothingToCache: boolean;
ignoreEmptyWorkdir: boolean;
toolBinDir?: string;
toolDir?: string;
pythonDir: string;
githubToken: string;
manifestFile?: string;
addProblemMatchers: boolean;
resolutionStrategy: "highest" | "lowest";
}
export function loadInputs(): SetupInputs {
const workingDirectory = core.getInput("working-directory");
const version = core.getInput("version");
const versionFile = getVersionFile(
workingDirectory,
core.getInput("version-file"),
);
const pythonVersion = core.getInput("python-version");
const activateEnvironment = core.getBooleanInput("activate-environment");
const venvPath = getVenvPath(
workingDirectory,
core.getInput("venv-path"),
activateEnvironment,
);
const checksum = core.getInput("checksum");
const enableCache = getEnableCache(core.getInput("enable-cache"));
const restoreCache = core.getInput("restore-cache") === "true";
const saveCache = core.getInput("save-cache") === "true";
const cacheSuffix = core.getInput("cache-suffix") || "";
const cacheLocalPath = getCacheLocalPath(
workingDirectory,
versionFile,
enableCache,
);
const cacheDependencyGlob = getCacheDependencyGlob(
workingDirectory,
core.getInput("cache-dependency-glob"),
);
const pruneCache = core.getInput("prune-cache") === "true";
const cachePython = core.getInput("cache-python") === "true";
const ignoreNothingToCache =
core.getInput("ignore-nothing-to-cache") === "true";
const ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true";
const toolBinDir = getToolBinDir(
workingDirectory,
core.getInput("tool-bin-dir"),
);
const toolDir = getToolDir(workingDirectory, core.getInput("tool-dir"));
const pythonDir = getUvPythonDir();
const githubToken = core.getInput("github-token");
const manifestFile = getManifestFile(core.getInput("manifest-file"));
const addProblemMatchers = core.getInput("add-problem-matchers") === "true";
const resolutionStrategy = getResolutionStrategy(
core.getInput("resolution-strategy"),
);
return {
activateEnvironment,
addProblemMatchers,
cacheDependencyGlob,
cacheLocalPath,
cachePython,
cacheSuffix,
checksum,
enableCache,
githubToken,
ignoreEmptyWorkdir,
ignoreNothingToCache,
manifestFile,
pruneCache,
pythonDir,
pythonVersion,
resolutionStrategy,
restoreCache,
saveCache,
toolBinDir,
toolDir,
venvPath,
version,
versionFile,
workingDirectory,
};
}
function getVersionFile(
workingDirectory: string,
versionFileInput: string,
): string {
if (versionFileInput !== "") { if (versionFileInput !== "") {
const tildeExpanded = expandTilde(versionFileInput); const tildeExpanded = expandTilde(versionFileInput);
return resolveRelativePath(tildeExpanded); return resolveRelativePath(workingDirectory, tildeExpanded);
} }
return versionFileInput; return versionFileInput;
} }
function getVenvPath(): string { function getVenvPath(
const venvPathInput = core.getInput("venv-path"); workingDirectory: string,
venvPathInput: string,
activateEnvironment: boolean,
): string {
if (venvPathInput !== "") { if (venvPathInput !== "") {
if (!activateEnvironment) { if (!activateEnvironment) {
core.warning("venv-path is only used when activate-environment is true"); core.warning("venv-path is only used when activate-environment is true");
} }
const tildeExpanded = expandTilde(venvPathInput); const tildeExpanded = expandTilde(venvPathInput);
return normalizePath(resolveRelativePath(tildeExpanded)); return normalizePath(resolveRelativePath(workingDirectory, tildeExpanded));
} }
return normalizePath(resolveRelativePath(".venv")); return normalizePath(resolveRelativePath(workingDirectory, ".venv"));
} }
function getEnableCache(): boolean { function getEnableCache(enableCacheInput: string): boolean {
const enableCacheInput = core.getInput("enable-cache");
if (enableCacheInput === "auto") { if (enableCacheInput === "auto") {
return process.env.RUNNER_ENVIRONMENT === "github-hosted"; return process.env.RUNNER_ENVIRONMENT === "github-hosted";
} }
return enableCacheInput === "true"; return enableCacheInput === "true";
} }
function getToolBinDir(): string | undefined { function getToolBinDir(
const toolBinDirInput = core.getInput("tool-bin-dir"); workingDirectory: string,
toolBinDirInput: string,
): string | undefined {
if (toolBinDirInput !== "") { if (toolBinDirInput !== "") {
const tildeExpanded = expandTilde(toolBinDirInput); const tildeExpanded = expandTilde(toolBinDirInput);
return resolveRelativePath(tildeExpanded); return resolveRelativePath(workingDirectory, tildeExpanded);
} }
if (process.platform === "win32") { if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) { if (process.env.RUNNER_TEMP !== undefined) {
@@ -83,11 +167,13 @@ function getToolBinDir(): string | undefined {
return undefined; return undefined;
} }
function getToolDir(): string | undefined { function getToolDir(
const toolDirInput = core.getInput("tool-dir"); workingDirectory: string,
toolDirInput: string,
): string | undefined {
if (toolDirInput !== "") { if (toolDirInput !== "") {
const tildeExpanded = expandTilde(toolDirInput); const tildeExpanded = expandTilde(toolDirInput);
return resolveRelativePath(tildeExpanded); return resolveRelativePath(workingDirectory, tildeExpanded);
} }
if (process.platform === "win32") { if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) { if (process.env.RUNNER_TEMP !== undefined) {
@@ -100,21 +186,23 @@ function getToolDir(): string | undefined {
return undefined; return undefined;
} }
function getCacheLocalPath(): function getCacheLocalPath(
| { workingDirectory: string,
path: string; versionFile: string,
source: CacheLocalSource; enableCache: boolean,
} ): CacheLocalPath | undefined {
| undefined {
const cacheLocalPathInput = core.getInput("cache-local-path"); const cacheLocalPathInput = core.getInput("cache-local-path");
if (cacheLocalPathInput !== "") { if (cacheLocalPathInput !== "") {
const tildeExpanded = expandTilde(cacheLocalPathInput); const tildeExpanded = expandTilde(cacheLocalPathInput);
return { return {
path: resolveRelativePath(tildeExpanded), path: resolveRelativePath(workingDirectory, tildeExpanded),
source: CacheLocalSource.Input, source: CacheLocalSource.Input,
}; };
} }
const cacheDirFromConfig = getCacheDirFromConfig(); const cacheDirFromConfig = getCacheDirFromConfig(
workingDirectory,
versionFile,
);
if (cacheDirFromConfig !== undefined) { if (cacheDirFromConfig !== undefined) {
return { path: cacheDirFromConfig, source: CacheLocalSource.Config }; return { path: cacheDirFromConfig, source: CacheLocalSource.Config };
} }
@@ -122,7 +210,7 @@ function getCacheLocalPath():
core.info(`UV_CACHE_DIR is already set to ${process.env.UV_CACHE_DIR}`); core.info(`UV_CACHE_DIR is already set to ${process.env.UV_CACHE_DIR}`);
return { path: process.env.UV_CACHE_DIR, source: CacheLocalSource.Env }; return { path: process.env.UV_CACHE_DIR, source: CacheLocalSource.Env };
} }
if (getEnableCache()) { if (enableCache) {
if (process.env.RUNNER_ENVIRONMENT === "github-hosted") { if (process.env.RUNNER_ENVIRONMENT === "github-hosted") {
if (process.env.RUNNER_TEMP !== undefined) { if (process.env.RUNNER_TEMP !== undefined) {
return { return {
@@ -147,9 +235,12 @@ function getCacheLocalPath():
} }
} }
function getCacheDirFromConfig(): string | undefined { function getCacheDirFromConfig(
workingDirectory: string,
versionFile: string,
): string | undefined {
for (const filePath of [versionFile, "uv.toml", "pyproject.toml"]) { for (const filePath of [versionFile, "uv.toml", "pyproject.toml"]) {
const resolvedPath = resolveRelativePath(filePath); const resolvedPath = resolveRelativePath(workingDirectory, filePath);
try { try {
const cacheDir = getConfigValueFromTomlFile(resolvedPath, "cache-dir"); const cacheDir = getConfigValueFromTomlFile(resolvedPath, "cache-dir");
if (cacheDir !== undefined) { if (cacheDir !== undefined) {
@@ -175,9 +266,8 @@ export function getUvPythonDir(): string {
if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") { if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
if (process.platform === "win32") { if (process.platform === "win32") {
return `${process.env.APPDATA}${path.sep}uv${path.sep}python`; return `${process.env.APPDATA}${path.sep}uv${path.sep}python`;
} else {
return `${process.env.HOME}${path.sep}.local${path.sep}share${path.sep}uv${path.sep}python`;
} }
return `${process.env.HOME}${path.sep}.local${path.sep}share${path.sep}uv${path.sep}python`;
} }
if (process.env.RUNNER_TEMP !== undefined) { if (process.env.RUNNER_TEMP !== undefined) {
return `${process.env.RUNNER_TEMP}${path.sep}uv-python-dir`; return `${process.env.RUNNER_TEMP}${path.sep}uv-python-dir`;
@@ -187,14 +277,16 @@ export function getUvPythonDir(): string {
); );
} }
function getCacheDependencyGlob(): string { function getCacheDependencyGlob(
const cacheDependencyGlobInput = core.getInput("cache-dependency-glob"); workingDirectory: string,
cacheDependencyGlobInput: string,
): string {
if (cacheDependencyGlobInput !== "") { if (cacheDependencyGlobInput !== "") {
return cacheDependencyGlobInput return cacheDependencyGlobInput
.split("\n") .split("\n")
.map((part) => part.trim()) .map((part) => part.trim())
.map((part) => expandTilde(part)) .map((part) => expandTilde(part))
.map((part) => resolveRelativePath(part)) .map((part) => resolveRelativePath(workingDirectory, part))
.join("\n"); .join("\n");
} }
return cacheDependencyGlobInput; return cacheDependencyGlobInput;
@@ -220,7 +312,10 @@ function normalizePath(inputPath: string): string {
return trimmed; return trimmed;
} }
function resolveRelativePath(inputPath: string): string { function resolveRelativePath(
workingDirectory: string,
inputPath: string,
): string {
const hasNegation = inputPath.startsWith("!"); const hasNegation = inputPath.startsWith("!");
const pathWithoutNegation = hasNegation ? inputPath.substring(1) : inputPath; const pathWithoutNegation = hasNegation ? inputPath.substring(1) : inputPath;
@@ -232,16 +327,16 @@ function resolveRelativePath(inputPath: string): string {
return hasNegation ? `!${resolvedPath}` : resolvedPath; return hasNegation ? `!${resolvedPath}` : resolvedPath;
} }
function getManifestFile(): string | undefined { function getManifestFile(manifestFileInput: string): string | undefined {
const manifestFileInput = core.getInput("manifest-file");
if (manifestFileInput !== "") { if (manifestFileInput !== "") {
return manifestFileInput; return manifestFileInput;
} }
return undefined; return undefined;
} }
function getResolutionStrategy(): "highest" | "lowest" { function getResolutionStrategy(
const resolutionStrategyInput = core.getInput("resolution-strategy"); resolutionStrategyInput: string,
): "highest" | "lowest" {
if (resolutionStrategyInput === "lowest") { if (resolutionStrategyInput === "lowest") {
return "lowest"; return "lowest";
} }