mirror of
https://github.com/astral-sh/setup-uv.git
synced 2026-09-21 00:33:38 +00:00
test / test-specific-version (map[expected-version:0.3.5 version-input:0.3]) (push) Failing after 41s
test / test-specific-version (map[expected-version:0.3.0 version-input:0.3.0]) (push) Failing after 43s
test / test-latest-version (>=0.8) (push) Failing after 19s
test / test-specific-version (map[expected-version:0.4.30 version-input:>=0.4.25,<0.5]) (push) Failing after 44s
test / test-specific-version (map[expected-version:0.1.45 resolution-strategy:highest version-input:>=0.1,<0.2]) (push) Failing after 45s
test / test-latest-version (latest) (push) Failing after 19s
test / test-specific-version (map[expected-version:0.4.25 resolution-strategy:lowest version-input:>=0.4.25,<0.5]) (push) Failing after 21s
test / test-specific-version (map[expected-version:0.3.5 version-input:0.3.x]) (push) Failing after 43s
test / test-version-file-version (map[expected-version:0.6.17 version-file:__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt]) (push) Failing after 23s
test / test-from-working-directory-version (map[expected-version:0.5.15 working-directory:__tests__/fixtures/uv-toml-project]) (push) Failing after 26s
test / test-version-file-version (map[expected-version:0.5.15 version-file:__tests__/fixtures/.tool-versions]) (push) Failing after 23s
test / test-specific-version (map[expected-version:0.3.2 version-input:0.3.2]) (push) Failing after 43s
test / test-specific-version (map[expected-version:0.1.0 resolution-strategy:lowest version-input:>=0.1.0,<0.2]) (push) Failing after 45s
test / test-malformed-pyproject-file-fallback (push) Failing after 6s
test / test-specific-version (map[expected-version:0.4.25 resolution-strategy:lowest version-input:>=0.4.25]) (push) Failing after 43s
test / test-from-working-directory-version (map[expected-version:0.5.14 working-directory:__tests__/fixtures/pyproject-toml-project]) (push) Failing after 44s
test / test-activate-environment-no-project (push) Failing after 4s
test / test-cache-local-cache-disabled-but-explicit-path (push) Failing after 4s
test / test-download-from-astral-mirror-false (push) Failing after 5s
test / test-absolute-path (push) Failing after 5s
test / test-setup-cache-dependency-glob (push) Failing after 17s
test / test-restore-cache-dependency-glob (push) Skipped
test / test-setup-cache-save-cache-false (push) Failing after 18s
test / test-setup-cache-restore-cache-false (push) Failing after 18s
test / test-relative-path (push) Failing after 4s
test / test-python-install-dir (map[expected-python-dir:/home/runner/work/_temp/uv-python-dir os:ubuntu-latest]) (push) Failing after 5s
test / test-workflow-run (push) Failing after 30s
test / test-uv-no-modify-path (push) Failing after 46s
test / test-default-version (ubuntu-latest) (push) Failing after 46s
test / test-version-file-version (map[expected-version:0.8.3 version-file:__tests__/fixtures/uv-in-requirements-hash-txt-project/requirements.txt]) (push) Failing after 5s
test / test-uvx (push) Failing after 3s
test / test-tool-install (ubuntu-latest) (push) Failing after 4s
test / test-tool-versions-python-version (push) Failing after 5s
test / test-activate-environment (ubuntu-latest) (push) Failing after 4s
test / test-activate-environment-custom-path (ubuntu-latest) (push) Failing after 4s
test / test-with-explicit-token (push) Failing after 13s
test / test-setup-cache (auto, ubuntu-latest) (push) Failing after 18s
test / test-setup-cache (false, ubuntu-latest) (push) Failing after 16s
test / test-cache-key-os-version (ubuntu-22.04, ubuntu-22.04) (push) Failing after 20s
test / test-cache-local (map[expected-cache-dir:/home/runner/work/_temp/setup-uv-cache os:ubuntu-latest]) (push) Failing after 4s
test / test-checksum (map[checksum:4d9279ad5ca596b1e2d703901d508430eb07564dc4d8837de9e2fca9c90f8ecd os:ubuntu-latest]) (push) Failing after 12s
test / test-cache-python-installs (push) Failing after 16s
test / test-restore-python-installs (push) Skipped
test / test-cache-python-missing-managed-install-dir (push) Failing after 17s
test / validate-typings (push) Successful in 48s
test / test-cache-local-cache-disabled (push) Failing after 5s
test / test-python-version (ubuntu-latest) (push) Failing after 15s
test / test-musl (push) Failing after 25s
test / test-custom-manifest-file (push) Failing after 22s
test / test-cache-prune-force (push) Failing after 17s
test / test-cache-dir-from-file (push) Failing after 18s
test / test-restore-cache-save-cache-false (push) Skipped
test / test-restore-cache-restore-cache-false (push) Skipped
CodeQL / Analyze (TypeScript) (push) Failing after 2m10s
test / test-setup-cache-requirements-txt (push) Failing after 17s
test / test-setup-cache (true, ubuntu-latest) (push) Failing after 17s
test / test-restore-cache-requirements-txt (push) Skipped
test / test-debian-unstable (push) Failing after 26s
test / test-no-python-version (push) Failing after 16s
test / lint (push) Failing after 1m58s
test / test-default-version (macos-14) (push) Canceled after 0s
test / test-default-version (macos-latest) (push) Canceled after 0s
test / test-default-version (windows-latest) (push) Canceled after 0s
test / test-checksum (map[checksum:a70cbfbf3bb5c08b2f84963b4f12c94e08fbb2468ba418a3bfe1066fbe9e7218 os:macos-latest]) (push) Canceled after 0s
test / test-tool-install (macos-14) (push) Canceled after 0s
test / test-tool-install (macos-latest) (push) Canceled after 0s
test / test-tool-install (windows-latest) (push) Canceled after 0s
test / test-cache-key-os-version (windows-2025, windows-2025) (push) Canceled after 0s
test / test-setup-cache (auto, windows-latest) (push) Canceled after 0s
test / test-setup-cache (false, windows-latest) (push) Canceled after 0s
test / test-setup-cache (true, windows-latest) (push) Canceled after 0s
test / test-restore-cache (auto, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (auto, windows-latest) (push) Canceled after 0s
test / test-restore-cache (false, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (false, windows-latest) (push) Canceled after 0s
test / test-python-version (macos-latest) (push) Canceled after 0s
test / test-python-version (windows-latest) (push) Canceled after 0s
test / test-activate-environment (macos-latest) (push) Canceled after 0s
test / test-activate-environment (windows-latest) (push) Canceled after 0s
test / test-activate-environment-custom-path (macos-latest) (push) Canceled after 0s
test / test-activate-environment-custom-path (windows-latest) (push) Canceled after 0s
test / test-cache-key-os-version (macos-14, macos-14) (push) Canceled after 0s
test / test-cache-key-os-version (macos-15, macos-15) (push) Canceled after 0s
test / test-cache-key-os-version (ubuntu-24.04, ubuntu-24.04) (push) Canceled after 0s
test / test-cache-key-os-version (windows-2022, windows-2022) (push) Canceled after 0s
test / test-restore-cache (true, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (true, windows-latest) (push) Canceled after 0s
test / test-cache-local (map[expected-cache-dir:D:\a\_temp\setup-uv-cache os:windows-latest]) (push) Canceled after 0s
test / test-python-install-dir (map[expected-python-dir:D:\a\_temp\uv-python-dir os:windows-latest]) (push) Canceled after 0s
test / all-tests-passed (push) Canceled after 0s
Release Drafter / ✏️ Draft release (push) Canceled after 0s
https://github.com/pyca/cryptography/pull/15572#discussion_r3913508686 has the context for this: TL;DR our current `python-version` output mirrors the "request" version exactly, which means that it's insufficient for any downstream that needs to manage its own cache keys (since caches shouldn't be shared across release candidates, but the `uv python` request version doesn't include RC numbers). The first commit here was my attempt to fix this by exposing the runtime Python version, but this too is imprecise: the runtime version doesn't indicate the interpreter variant (e.g. freethreading), which is also important to capture in the cache identity. My solution here is to expose `python-runtime-id`, which is just the `key` of the active Python version from `uv python list --output-format=json`. --------- Signed-off-by: William Woodruff <william@yossarian.net>
288 lines
8.8 KiB
TypeScript
288 lines
8.8 KiB
TypeScript
import fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import * as core from "@actions/core";
|
|
import * as exec from "@actions/exec";
|
|
import { restoreCache } from "./cache/restore-cache";
|
|
import {
|
|
downloadVersion,
|
|
tryGetFromToolCache,
|
|
} from "./download/download-version";
|
|
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
|
|
import { CacheLocalSource, loadInputs, type SetupInputs } from "./utils/inputs";
|
|
import * as log from "./utils/logging";
|
|
import {
|
|
type Architecture,
|
|
getArch,
|
|
getPlatform,
|
|
type Platform,
|
|
} from "./utils/platforms";
|
|
import { getPythonRuntimeId } from "./utils/python-runtime";
|
|
import { resolveUvVersion } from "./version/resolve";
|
|
|
|
const sourceDir = __dirname;
|
|
|
|
function formatUnexpectedFailure(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
return error.stack ?? error.message;
|
|
}
|
|
return String(error);
|
|
}
|
|
|
|
function failUnexpectedly(event: string, error: unknown): never {
|
|
core.setFailed(`${event}: ${formatUnexpectedFailure(error)}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
process.on("uncaughtException", (error) => {
|
|
failUnexpectedly("Uncaught exception", error);
|
|
});
|
|
|
|
process.on("unhandledRejection", (reason) => {
|
|
failUnexpectedly("Unhandled promise rejection", reason);
|
|
});
|
|
|
|
async function getPythonVersion(inputs: SetupInputs): Promise<string> {
|
|
if (inputs.pythonVersion !== "") {
|
|
return inputs.pythonVersion;
|
|
}
|
|
|
|
let output = "";
|
|
const options: exec.ExecOptions = {
|
|
listeners: {
|
|
stdout: (data: Buffer) => {
|
|
output += data.toString();
|
|
},
|
|
},
|
|
silent: !core.isDebug(),
|
|
};
|
|
|
|
try {
|
|
const execArgs = ["python", "find", "--directory", inputs.workingDirectory];
|
|
await exec.exec("uv", execArgs, options);
|
|
const pythonPath = output.trim();
|
|
|
|
output = "";
|
|
await exec.exec(pythonPath, ["--version"], options);
|
|
// output is like "Python 3.8.10"
|
|
return output.split(" ")[1].trim();
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
core.debug(`Failed to get python version from uv. Error: ${err.message}`);
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
async function run(): Promise<void> {
|
|
try {
|
|
const inputs = loadInputs();
|
|
detectEmptyWorkdir(inputs);
|
|
const platform = await getPlatform();
|
|
const arch = getArch();
|
|
|
|
if (platform === undefined) {
|
|
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
}
|
|
if (arch === undefined) {
|
|
throw new Error(`Unsupported architecture: ${process.arch}`);
|
|
}
|
|
const setupResult = await setupUv(inputs, platform, arch);
|
|
|
|
addToolBinToPath(inputs);
|
|
addUvToPathAndOutput(setupResult.uvDir);
|
|
setToolDir(inputs);
|
|
addPythonDirToPath(inputs);
|
|
setupPython(inputs);
|
|
await activateEnvironment(inputs);
|
|
addMatchers(inputs);
|
|
setCacheDir(inputs);
|
|
|
|
core.setOutput("uv-version", setupResult.version);
|
|
core.saveState(STATE_UV_VERSION, setupResult.version);
|
|
log.info(`Successfully installed uv version ${setupResult.version}`);
|
|
|
|
const detectedPythonVersion = await getPythonVersion(inputs);
|
|
core.setOutput("python-version", detectedPythonVersion);
|
|
core.setOutput("python-runtime-id", await getPythonRuntimeId(inputs));
|
|
|
|
if (inputs.enableCache) {
|
|
await restoreCache(inputs, detectedPythonVersion);
|
|
}
|
|
// https://github.com/nodejs/node/issues/56645#issuecomment-3924958861
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
process.exit(0);
|
|
} catch (err) {
|
|
core.setFailed((err as Error).message);
|
|
}
|
|
}
|
|
|
|
function detectEmptyWorkdir(inputs: SetupInputs): void {
|
|
if (fs.readdirSync(inputs.workingDirectory).length === 0) {
|
|
if (inputs.ignoreEmptyWorkdir) {
|
|
log.info(
|
|
"Empty workdir detected. Ignoring because ignore-empty-workdir is enabled",
|
|
);
|
|
} else {
|
|
log.warning(
|
|
"Empty workdir detected. This may cause unexpected behavior. You can enable ignore-empty-workdir to mute this warning.",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function setupUv(
|
|
inputs: SetupInputs,
|
|
platform: Platform,
|
|
arch: Architecture,
|
|
): Promise<{ uvDir: string; version: string }> {
|
|
const resolvedVersion = await resolveUvVersion({
|
|
manifestFile: inputs.manifestFile,
|
|
resolutionStrategy: inputs.resolutionStrategy,
|
|
version: inputs.version,
|
|
versionFile: inputs.versionFile,
|
|
workingDirectory: inputs.workingDirectory,
|
|
});
|
|
const toolCacheResult = tryGetFromToolCache(arch, resolvedVersion);
|
|
if (toolCacheResult.installedPath) {
|
|
log.info(`Found uv in tool-cache for ${toolCacheResult.version}`);
|
|
return {
|
|
uvDir: toolCacheResult.installedPath,
|
|
version: toolCacheResult.version,
|
|
};
|
|
}
|
|
|
|
const downloadResult = await downloadVersion(
|
|
platform,
|
|
arch,
|
|
resolvedVersion,
|
|
inputs.checksum,
|
|
inputs.githubToken,
|
|
inputs.manifestFile,
|
|
inputs.downloadFromAstralMirror,
|
|
);
|
|
|
|
return {
|
|
uvDir: downloadResult.cachedToolDir,
|
|
version: downloadResult.version,
|
|
};
|
|
}
|
|
|
|
function addUvToPathAndOutput(cachedPath: string): void {
|
|
core.setOutput("uv-path", `${cachedPath}${path.sep}uv`);
|
|
core.saveState(STATE_UV_PATH, `${cachedPath}${path.sep}uv`);
|
|
core.setOutput("uvx-path", `${cachedPath}${path.sep}uvx`);
|
|
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
|
|
log.info("UV_NO_MODIFY_PATH is set, not modifying PATH");
|
|
} else {
|
|
core.addPath(cachedPath);
|
|
log.info(`Added ${cachedPath} to the path`);
|
|
}
|
|
}
|
|
|
|
function addToolBinToPath(inputs: SetupInputs): void {
|
|
if (inputs.toolBinDir !== undefined) {
|
|
core.exportVariable("UV_TOOL_BIN_DIR", inputs.toolBinDir);
|
|
log.info(`Set UV_TOOL_BIN_DIR to ${inputs.toolBinDir}`);
|
|
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
|
|
log.info(
|
|
`UV_NO_MODIFY_PATH is set, not adding ${inputs.toolBinDir} to path`,
|
|
);
|
|
} else {
|
|
core.addPath(inputs.toolBinDir);
|
|
log.info(`Added ${inputs.toolBinDir} to the path`);
|
|
}
|
|
} else {
|
|
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
|
|
log.info("UV_NO_MODIFY_PATH is set, not adding user local bin to path");
|
|
return;
|
|
}
|
|
if (process.env.XDG_BIN_HOME !== undefined) {
|
|
core.addPath(process.env.XDG_BIN_HOME);
|
|
log.info(`Added ${process.env.XDG_BIN_HOME} to the path`);
|
|
} else if (process.env.XDG_DATA_HOME !== undefined) {
|
|
core.addPath(`${process.env.XDG_DATA_HOME}/../bin`);
|
|
log.info(`Added ${process.env.XDG_DATA_HOME}/../bin to the path`);
|
|
} else {
|
|
core.addPath(`${process.env.HOME}/.local/bin`);
|
|
log.info(`Added ${process.env.HOME}/.local/bin to the path`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setToolDir(inputs: SetupInputs): void {
|
|
if (inputs.toolDir !== undefined) {
|
|
core.exportVariable("UV_TOOL_DIR", inputs.toolDir);
|
|
log.info(`Set UV_TOOL_DIR to ${inputs.toolDir}`);
|
|
}
|
|
}
|
|
|
|
function addPythonDirToPath(inputs: SetupInputs): void {
|
|
core.exportVariable("UV_PYTHON_INSTALL_DIR", inputs.pythonDir);
|
|
log.info(`Set UV_PYTHON_INSTALL_DIR to ${inputs.pythonDir}`);
|
|
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
|
|
log.info("UV_NO_MODIFY_PATH is set, not adding python dir to path");
|
|
} else {
|
|
core.addPath(inputs.pythonDir);
|
|
log.info(`Added ${inputs.pythonDir} to the path`);
|
|
}
|
|
}
|
|
|
|
function setupPython(inputs: SetupInputs): void {
|
|
if (inputs.pythonVersion !== "") {
|
|
core.exportVariable("UV_PYTHON", inputs.pythonVersion);
|
|
log.info(`Set UV_PYTHON to ${inputs.pythonVersion}`);
|
|
}
|
|
}
|
|
|
|
async function activateEnvironment(inputs: SetupInputs): Promise<void> {
|
|
if (inputs.activateEnvironment) {
|
|
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
|
|
throw new Error(
|
|
"UV_NO_MODIFY_PATH and activate-environment cannot be used together.",
|
|
);
|
|
}
|
|
|
|
log.info(`Creating and activating python venv at ${inputs.venvPath}...`);
|
|
const venvArgs = [
|
|
"venv",
|
|
inputs.venvPath,
|
|
"--directory",
|
|
inputs.workingDirectory,
|
|
"--clear",
|
|
];
|
|
if (inputs.noProject) {
|
|
venvArgs.push("--no-project");
|
|
}
|
|
await exec.exec("uv", venvArgs);
|
|
|
|
let venvBinPath = `${inputs.venvPath}${path.sep}bin`;
|
|
if (process.platform === "win32") {
|
|
venvBinPath = `${inputs.venvPath}${path.sep}Scripts`;
|
|
}
|
|
core.addPath(path.resolve(venvBinPath));
|
|
core.exportVariable("VIRTUAL_ENV", inputs.venvPath);
|
|
core.setOutput("venv", inputs.venvPath);
|
|
}
|
|
}
|
|
|
|
function setCacheDir(inputs: SetupInputs): void {
|
|
if (inputs.cacheLocalPath !== undefined) {
|
|
if (inputs.cacheLocalPath.source === CacheLocalSource.Config) {
|
|
log.info(
|
|
"Using cache-dir from uv config file, not modifying UV_CACHE_DIR",
|
|
);
|
|
return;
|
|
}
|
|
core.exportVariable("UV_CACHE_DIR", inputs.cacheLocalPath.path);
|
|
log.info(`Set UV_CACHE_DIR to ${inputs.cacheLocalPath.path}`);
|
|
}
|
|
}
|
|
|
|
function addMatchers(inputs: SetupInputs): void {
|
|
if (inputs.addProblemMatchers) {
|
|
const matchersPath = path.join(sourceDir, "..", "..", ".github");
|
|
core.info(`##[add-matcher]${path.join(matchersPath, "python.json")}`);
|
|
}
|
|
}
|
|
|
|
run();
|