mirror of
https://github.com/astral-sh/setup-uv.git
synced 2026-08-15 09:50:29 +02:00
Read Python version from .tool-versions (#996)
## Summary - read the Python version from an explicitly selected `.tool-versions` file - preserve `python-version` and existing `UV_PYTHON` precedence - add parser, input, and workflow coverage and update documentation and bundled action artifacts ## Validation - `npm run all` - `actionlint .github/workflows/test.yml` - `uvx zizmor .github/workflows/test.yml` Closes #983 Refs: pi-session 019ff01a-544c-79f3-8f73-a00132af39f5
This commit is contained in:
@@ -234,6 +234,31 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-tool-versions-python-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install versions from .tool-versions
|
||||
id: setup-uv
|
||||
uses: ./
|
||||
with:
|
||||
version-file: "__tests__/fixtures/.tool-versions"
|
||||
- name: Verify Python version from .tool-versions
|
||||
run: |
|
||||
if [ "$UV_PYTHON" != "3.13.1t" ]; then
|
||||
echo "Wrong UV_PYTHON: $UV_PYTHON"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$PYTHON_VERSION" != "3.13.1t" ]; then
|
||||
echo "Wrong python-version output: $PYTHON_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
PYTHON_VERSION: ${{ steps.setup-uv.outputs.python-version }}
|
||||
|
||||
test-malformed-pyproject-file-fallback:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -1126,6 +1151,7 @@ jobs:
|
||||
- test-from-working-directory-version
|
||||
- test-malformed-pyproject-file-fallback
|
||||
- test-version-file-version
|
||||
- test-tool-versions-python-version
|
||||
- test-checksum
|
||||
- test-with-explicit-token
|
||||
- test-uvx
|
||||
|
||||
@@ -47,13 +47,13 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
|
||||
# The version of uv to install, e.g., "0.5.0", "latest", or "latest-known" (default: searches for version in config files, then latest)
|
||||
version: ""
|
||||
|
||||
# Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock (default: searches uv.toml then pyproject.toml)
|
||||
# Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock. A selected .tool-versions file can also provide the Python version (default: searches uv.toml then pyproject.toml)
|
||||
version-file: ""
|
||||
|
||||
# Resolution strategy when resolving version ranges: 'highest' or 'lowest'
|
||||
resolution-strategy: "highest"
|
||||
|
||||
# The version of Python to set UV_PYTHON to
|
||||
# The version of Python to set UV_PYTHON to (overrides the Python version from .tool-versions)
|
||||
python-version: ""
|
||||
|
||||
# Use uv venv to activate a venv ready to be used by later steps
|
||||
@@ -144,7 +144,25 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
|
||||
|
||||
You can use the input `python-version` to set the environment variable `UV_PYTHON` for the rest of your workflow
|
||||
|
||||
This will override any python version specifications in `pyproject.toml` and `.python-version`
|
||||
This will override any python version specifications in `pyproject.toml`, `.python-version`, and
|
||||
an explicitly selected `.tool-versions` file.
|
||||
|
||||
When `version-file` points to `.tool-versions`, its `python` entry is used if neither
|
||||
`python-version` nor `UV_PYTHON` is set:
|
||||
|
||||
```text
|
||||
uv 0.12.3
|
||||
python 3.13
|
||||
```
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
with:
|
||||
version-file: ".tool-versions"
|
||||
```
|
||||
|
||||
Only a single Python version is supported. Multiple fallback versions and the asdf `ref:`, `path:`,
|
||||
and `system` forms are ignored with a warning.
|
||||
|
||||
```yaml
|
||||
- name: Install the latest version of uv and set the python version to 3.13t
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
uv 0.5.15
|
||||
python 3.13.1t
|
||||
|
||||
@@ -18,6 +18,7 @@ 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 = process.env.UV_PYTHON;
|
||||
const ORIGINAL_UV_PYTHON_INSTALL_DIR = process.env.UV_PYTHON_INSTALL_DIR;
|
||||
|
||||
const mockDebug = jest.fn();
|
||||
@@ -60,6 +61,7 @@ function resetEnvironment(): void {
|
||||
delete process.env.RUNNER_ENVIRONMENT;
|
||||
delete process.env.RUNNER_TEMP;
|
||||
delete process.env.UV_CACHE_DIR;
|
||||
delete process.env.UV_PYTHON;
|
||||
delete process.env.UV_PYTHON_INSTALL_DIR;
|
||||
}
|
||||
|
||||
@@ -74,6 +76,7 @@ function restoreEnvironment(): void {
|
||||
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 = ORIGINAL_UV_PYTHON;
|
||||
process.env.UV_PYTHON_INSTALL_DIR = ORIGINAL_UV_PYTHON_INSTALL_DIR;
|
||||
}
|
||||
|
||||
@@ -100,6 +103,52 @@ describe("loadInputs", () => {
|
||||
expect(inputs.resolutionStrategy).toBe("highest");
|
||||
});
|
||||
|
||||
it("uses the Python version from an explicitly selected .tool-versions file", () => {
|
||||
mockInputs["working-directory"] = createTempProject({
|
||||
".tool-versions": "uv 0.12.3\npython 3.13.1t\n",
|
||||
});
|
||||
mockInputs["version-file"] = ".tool-versions";
|
||||
|
||||
const inputs = loadInputs();
|
||||
|
||||
expect(inputs.pythonVersion).toBe("3.13.1t");
|
||||
});
|
||||
|
||||
it("prefers the python-version input over .tool-versions", () => {
|
||||
mockInputs["working-directory"] = createTempProject({
|
||||
".tool-versions": "uv 0.12.3\npython 3.13\n",
|
||||
});
|
||||
mockInputs["version-file"] = ".tool-versions";
|
||||
mockInputs["python-version"] = "3.12";
|
||||
|
||||
const inputs = loadInputs();
|
||||
|
||||
expect(inputs.pythonVersion).toBe("3.12");
|
||||
});
|
||||
|
||||
it("preserves UV_PYTHON instead of overriding it from .tool-versions", () => {
|
||||
mockInputs["working-directory"] = createTempProject({
|
||||
".tool-versions": "uv 0.12.3\npython 3.13\n",
|
||||
});
|
||||
mockInputs["version-file"] = ".tool-versions";
|
||||
process.env.UV_PYTHON = "3.11";
|
||||
|
||||
const inputs = loadInputs();
|
||||
|
||||
expect(inputs.pythonVersion).toBe("");
|
||||
expect(process.env.UV_PYTHON).toBe("3.11");
|
||||
});
|
||||
|
||||
it("does not discover .tool-versions from the working directory", () => {
|
||||
mockInputs["working-directory"] = createTempProject({
|
||||
".tool-versions": "uv 0.12.3\npython 3.13\n",
|
||||
});
|
||||
|
||||
const inputs = loadInputs();
|
||||
|
||||
expect(inputs.pythonVersion).toBe("");
|
||||
});
|
||||
|
||||
it.each(["pull_request_target", "workflow_run", "release"])(
|
||||
"disables automatic caching for the %s event",
|
||||
(eventName) => {
|
||||
|
||||
@@ -21,6 +21,11 @@ async function getVersionFromToolVersions(filePath: string) {
|
||||
return getUvVersionFromToolVersions(filePath);
|
||||
}
|
||||
|
||||
async function getPythonVersionFromToolVersions(filePath: string) {
|
||||
const module = await import("../../src/version/tool-versions-file");
|
||||
return module.getPythonVersionFromToolVersions(filePath);
|
||||
}
|
||||
|
||||
describe("getUvVersionFromToolVersions", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -61,8 +66,8 @@ describe("getUvVersionFromToolVersions", () => {
|
||||
expect(result).toBe("0.3.0");
|
||||
});
|
||||
|
||||
it("should skip commented lines", async () => {
|
||||
const fileContent = "# uv 0.1.0\npython 3.11.0\nuv 0.2.0";
|
||||
it("should skip comments", async () => {
|
||||
const fileContent = "# uv 0.1.0\npython 3.11.0\nuv 0.2.0 # inline comment";
|
||||
mockReadFileSync.mockReturnValue(fileContent);
|
||||
|
||||
const result = await getVersionFromToolVersions(".tool-versions");
|
||||
@@ -121,3 +126,68 @@ describe("getUvVersionFromToolVersions", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPythonVersionFromToolVersions", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should return version for a valid Python entry", async () => {
|
||||
mockReadFileSync.mockReturnValue(
|
||||
"nodejs 24.0.0\r\npython v3.13.1t # use free-threaded Python\r\nuv 0.12.3",
|
||||
);
|
||||
|
||||
const result = await getPythonVersionFromToolVersions(".tool-versions");
|
||||
|
||||
expect(result).toBe("3.13.1t");
|
||||
});
|
||||
|
||||
it("should return the first matching Python version", async () => {
|
||||
mockReadFileSync.mockReturnValue("python 3.12\npython 3.13");
|
||||
|
||||
const result = await getPythonVersionFromToolVersions(".tool-versions");
|
||||
|
||||
expect(result).toBe("3.12");
|
||||
});
|
||||
|
||||
it("should return undefined when no Python entry is found", async () => {
|
||||
mockReadFileSync.mockReturnValue("uv 0.12.3\nnodejs 24.0.0");
|
||||
|
||||
const result = await getPythonVersionFromToolVersions(".tool-versions");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should warn and return undefined for multiple Python versions", async () => {
|
||||
mockReadFileSync.mockReturnValue("python 3.13 3.12 system");
|
||||
|
||||
const result = await getPythonVersionFromToolVersions(".tool-versions");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockWarning).toHaveBeenCalledWith(
|
||||
"Multiple Python versions in .tool-versions are not supported. The Python entry will be ignored.",
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["ref:main", "path:~/src/python", "system"])(
|
||||
"should warn and return undefined for %s",
|
||||
async (version) => {
|
||||
mockReadFileSync.mockReturnValue(`python ${version}`);
|
||||
|
||||
const result = await getPythonVersionFromToolVersions(".tool-versions");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockWarning).toHaveBeenCalledWith(
|
||||
`The Python version ${version} in .tool-versions is not supported. The Python entry will be ignored.`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("should return undefined for non-.tool-versions files", async () => {
|
||||
const result = await getPythonVersionFromToolVersions(".python-version");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockReadFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@ inputs:
|
||||
description: "The version of uv to install, e.g., `0.5.0`, `latest`, or `latest-known`. Defaults to the version in pyproject.toml or `latest`."
|
||||
default: ""
|
||||
version-file:
|
||||
description: "Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock. Defaults to searching for uv.toml and if not found pyproject.toml."
|
||||
description: "Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock. A selected .tool-versions file can also provide the Python version. Defaults to searching for uv.toml and if not found pyproject.toml."
|
||||
default: ""
|
||||
python-version:
|
||||
description: "The version of Python to set UV_PYTHON to"
|
||||
description: "The version of Python to set UV_PYTHON to. Overrides the Python version from .tool-versions."
|
||||
required: false
|
||||
activate-environment:
|
||||
description: "Use uv venv to activate a venv ready to be used by later steps. "
|
||||
|
||||
+71
-7
@@ -26998,7 +26998,7 @@ __export(save_cache_exports, {
|
||||
run: () => run
|
||||
});
|
||||
module.exports = __toCommonJS(save_cache_exports);
|
||||
var fs7 = __toESM(require("node:fs"), 1);
|
||||
var fs9 = __toESM(require("node:fs"), 1);
|
||||
|
||||
// node_modules/@actions/core/lib/command.js
|
||||
var os = __toESM(require("os"), 1);
|
||||
@@ -61845,10 +61845,54 @@ 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);
|
||||
|
||||
// src/utils/config-file.ts
|
||||
// src/version/tool-versions-file.ts
|
||||
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
||||
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:")) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -62532,10 +62576,10 @@ function parse3(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
||||
|
||||
// src/utils/config-file.ts
|
||||
function getConfigValueFromTomlFile(filePath, key) {
|
||||
if (!import_node_fs2.default.existsSync(filePath) || !filePath.endsWith(".toml")) {
|
||||
if (!import_node_fs3.default.existsSync(filePath) || !filePath.endsWith(".toml")) {
|
||||
return void 0;
|
||||
}
|
||||
const fileContent = import_node_fs2.default.readFileSync(filePath, "utf-8");
|
||||
const fileContent = import_node_fs3.default.readFileSync(filePath, "utf-8");
|
||||
return getConfigValueFromTomlContent(filePath, fileContent, key);
|
||||
}
|
||||
function getConfigValueFromTomlContent(filePath, fileContent, key) {
|
||||
@@ -62555,7 +62599,7 @@ function loadInputs() {
|
||||
const workingDirectory = getInput("working-directory");
|
||||
const version3 = getInput("version");
|
||||
const versionFile = getVersionFile(workingDirectory);
|
||||
const pythonVersion = getInput("python-version");
|
||||
const pythonVersion = getPythonVersion(versionFile);
|
||||
const activateEnvironment = getBooleanInput("activate-environment");
|
||||
const noProject = getBooleanInput("no-project");
|
||||
const venvPath = getVenvPath(workingDirectory, activateEnvironment);
|
||||
@@ -62621,6 +62665,26 @@ function getVersionFile(workingDirectory) {
|
||||
}
|
||||
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 getVenvPath(workingDirectory, activateEnvironment) {
|
||||
const venvPathInput = getInput("venv-path");
|
||||
if (venvPathInput !== "") {
|
||||
@@ -62864,7 +62928,7 @@ async function saveCache3(inputs) {
|
||||
await pruneCache();
|
||||
}
|
||||
const actualCachePath = getUvCachePath(inputs);
|
||||
if (!fs7.existsSync(actualCachePath)) {
|
||||
if (!fs9.existsSync(actualCachePath)) {
|
||||
if (inputs.ignoreNothingToCache) {
|
||||
info2(
|
||||
"No cacheable uv cache paths were found. Ignoring because ignore-nothing-to-cache is enabled."
|
||||
@@ -62884,7 +62948,7 @@ async function saveCache3(inputs) {
|
||||
}
|
||||
}
|
||||
if (inputs.cachePython) {
|
||||
if (!fs7.existsSync(inputs.pythonDir)) {
|
||||
if (!fs9.existsSync(inputs.pythonDir)) {
|
||||
warning2(
|
||||
`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.`
|
||||
);
|
||||
|
||||
+69
-18
@@ -56054,7 +56054,7 @@ var require_semver5 = __commonJS({
|
||||
});
|
||||
|
||||
// src/setup-uv.ts
|
||||
var import_node_fs8 = __toESM(require("node:fs"), 1);
|
||||
var import_node_fs9 = __toESM(require("node:fs"), 1);
|
||||
var path16 = __toESM(require("node:path"), 1);
|
||||
|
||||
// node_modules/@actions/core/lib/command.js
|
||||
@@ -98258,29 +98258,59 @@ function getUvVersionFromDependency(dependency) {
|
||||
// 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 (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:")) {
|
||||
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");
|
||||
const lines = fileContents.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim().startsWith("#")) {
|
||||
for (const line of fileContents.split("\n")) {
|
||||
const content = line.split("#", 1)[0].trim();
|
||||
if (content === "") {
|
||||
continue;
|
||||
}
|
||||
const match2 = line.match(/^\s*uv\s*v?\s*(?<version>[^\s]+)\s*$/);
|
||||
if (match2) {
|
||||
const matchedVersion = match2.groups?.version.trim();
|
||||
if (matchedVersion?.startsWith("ref")) {
|
||||
warning(
|
||||
"The ref syntax of .tool-versions is not supported. Please use a released version instead."
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
return matchedVersion;
|
||||
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;
|
||||
}
|
||||
|
||||
// src/version/uv-lock-file.ts
|
||||
var import_node_fs5 = __toESM(require("node:fs"), 1);
|
||||
@@ -98734,12 +98764,13 @@ function getExtension(platform2) {
|
||||
}
|
||||
|
||||
// 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 = getInput("python-version");
|
||||
const pythonVersion = getPythonVersion(versionFile);
|
||||
const activateEnvironment2 = getBooleanInput("activate-environment");
|
||||
const noProject = getBooleanInput("no-project");
|
||||
const venvPath = getVenvPath(workingDirectory, activateEnvironment2);
|
||||
@@ -98805,6 +98836,26 @@ function getVersionFile(workingDirectory) {
|
||||
}
|
||||
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 !== "") {
|
||||
@@ -99018,7 +99069,7 @@ process.on("uncaughtException", (error2) => {
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
failUnexpectedly("Unhandled promise rejection", reason);
|
||||
});
|
||||
async function getPythonVersion(inputs) {
|
||||
async function getPythonVersion2(inputs) {
|
||||
if (inputs.pythonVersion !== "") {
|
||||
return inputs.pythonVersion;
|
||||
}
|
||||
@@ -99068,7 +99119,7 @@ async function run() {
|
||||
setOutput("uv-version", setupResult.version);
|
||||
saveState(STATE_UV_VERSION, setupResult.version);
|
||||
info2(`Successfully installed uv version ${setupResult.version}`);
|
||||
const detectedPythonVersion = await getPythonVersion(inputs);
|
||||
const detectedPythonVersion = await getPythonVersion2(inputs);
|
||||
setOutput("python-version", detectedPythonVersion);
|
||||
if (inputs.enableCache) {
|
||||
await restoreCache2(inputs, detectedPythonVersion);
|
||||
@@ -99080,7 +99131,7 @@ async function run() {
|
||||
}
|
||||
}
|
||||
function detectEmptyWorkdir(inputs) {
|
||||
if (import_node_fs8.default.readdirSync(inputs.workingDirectory).length === 0) {
|
||||
if (import_node_fs9.default.readdirSync(inputs.workingDirectory).length === 0) {
|
||||
if (inputs.ignoreEmptyWorkdir) {
|
||||
info2(
|
||||
"Empty workdir detected. Ignoring because ignore-empty-workdir is enabled"
|
||||
|
||||
@@ -85,7 +85,10 @@ You can use the `version-file` input to specify a file that contains the version
|
||||
This can either be a `pyproject.toml` or `uv.toml` file which defines a `required-version` or
|
||||
uv defined as a dependency in `pyproject.toml` or `requirements.txt`.
|
||||
|
||||
[asdf](https://asdf-vm.com/) `.tool-versions` is also supported, but without the `ref` syntax.
|
||||
[asdf](https://asdf-vm.com/) `.tool-versions` is also supported for selecting uv. If neither
|
||||
`python-version` nor `UV_PYTHON` is set, the `python` entry from the selected file is also exported
|
||||
as `UV_PYTHON`. Only a single Python version is supported; multiple fallback versions and the asdf
|
||||
`ref:`, `path:`, and `system` forms are ignored with a warning.
|
||||
|
||||
```yaml
|
||||
- name: Install uv based on the version defined in pyproject.toml
|
||||
|
||||
@@ -131,7 +131,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` input is specified. Controls which Python version uv uses.
|
||||
- `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_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.
|
||||
@@ -142,6 +142,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`.
|
||||
|
||||
```yaml
|
||||
- name: Example using environment variables
|
||||
|
||||
+25
-1
@@ -1,5 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import * as core from "@actions/core";
|
||||
import { getPythonVersionFromToolVersions } from "../version/tool-versions-file";
|
||||
import { getConfigValueFromTomlFile } from "./config-file";
|
||||
import * as log from "./logging";
|
||||
|
||||
@@ -51,7 +53,7 @@ export function loadInputs(): SetupInputs {
|
||||
const workingDirectory = core.getInput("working-directory");
|
||||
const version = core.getInput("version");
|
||||
const versionFile = getVersionFile(workingDirectory);
|
||||
const pythonVersion = core.getInput("python-version");
|
||||
const pythonVersion = getPythonVersion(versionFile);
|
||||
const activateEnvironment = core.getBooleanInput("activate-environment");
|
||||
const noProject = core.getBooleanInput("no-project");
|
||||
const venvPath = getVenvPath(workingDirectory, activateEnvironment);
|
||||
@@ -122,6 +124,28 @@ function getVersionFile(workingDirectory: string): string {
|
||||
return versionFileInput;
|
||||
}
|
||||
|
||||
function getPythonVersion(versionFile: string): string {
|
||||
const pythonVersionInput = core.getInput("python-version");
|
||||
if (pythonVersionInput !== "") {
|
||||
return pythonVersionInput;
|
||||
}
|
||||
if (process.env.UV_PYTHON !== undefined && process.env.UV_PYTHON !== "") {
|
||||
return "";
|
||||
}
|
||||
if (versionFile === "" || !fs.existsSync(versionFile)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return getPythonVersionFromToolVersions(versionFile) ?? "";
|
||||
} catch (err) {
|
||||
log.warning(
|
||||
`Error while parsing Python version from ${versionFile}: ${(err as Error).message}`,
|
||||
);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getVenvPath(
|
||||
workingDirectory: string,
|
||||
activateEnvironment: boolean,
|
||||
|
||||
@@ -4,28 +4,72 @@ import * as core from "@actions/core";
|
||||
export function getUvVersionFromToolVersions(
|
||||
filePath: string,
|
||||
): string | undefined {
|
||||
const versions = getToolVersions(filePath, "uv");
|
||||
if (versions === undefined || versions.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const version = stripVersionPrefix(versions[0]);
|
||||
if (version.startsWith("ref")) {
|
||||
core.warning(
|
||||
"The ref syntax of .tool-versions is not supported. Please use a released version instead.",
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
export function getPythonVersionFromToolVersions(
|
||||
filePath: string,
|
||||
): string | undefined {
|
||||
const versions = getToolVersions(filePath, "python");
|
||||
if (versions === undefined || versions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (versions.length > 1) {
|
||||
core.warning(
|
||||
"Multiple Python versions in .tool-versions are not supported. The Python entry will be ignored.",
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const version = stripVersionPrefix(versions[0]);
|
||||
if (
|
||||
version === "system" ||
|
||||
version.startsWith("ref:") ||
|
||||
version.startsWith("path:")
|
||||
) {
|
||||
core.warning(
|
||||
`The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
function getToolVersions(
|
||||
filePath: string,
|
||||
toolName: string,
|
||||
): string[] | undefined {
|
||||
if (!filePath.endsWith(".tool-versions")) {
|
||||
return undefined;
|
||||
}
|
||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||
const lines = fileContents.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
// Skip commented lines
|
||||
if (line.trim().startsWith("#")) {
|
||||
for (const line of fileContents.split("\n")) {
|
||||
const content = line.split("#", 1)[0].trim();
|
||||
if (content === "") {
|
||||
continue;
|
||||
}
|
||||
const match = line.match(/^\s*uv\s*v?\s*(?<version>[^\s]+)\s*$/);
|
||||
if (match) {
|
||||
const matchedVersion = match.groups?.version.trim();
|
||||
if (matchedVersion?.startsWith("ref")) {
|
||||
core.warning(
|
||||
"The ref syntax of .tool-versions is not supported. Please use a released version instead.",
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return matchedVersion;
|
||||
|
||||
const [tool, ...versions] = content.split(/\s+/);
|
||||
if (tool === toolName) {
|
||||
return versions;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function stripVersionPrefix(version: string): string {
|
||||
return version.startsWith("v") ? version.slice(1) : version;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user