diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1459e1..0ff6bc0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,6 +64,39 @@ jobs: fi env: UV_VERSION: ${{ steps.setup-uv.outputs.uv-version }} + test-pyproject-file-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install version 0.5.14 + id: setup-uv + uses: ./ + with: + pyproject-file: "__tests__/fixtures/pyproject-toml-project/pyproject.toml" + - name: Correct version gets installed + run: | + if [ "$UV_VERSION" != "0.5.14" ]; then + exit 1 + fi + env: + UV_VERSION: ${{ steps.setup-uv.outputs.uv-version }} + test-uv-file-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install version 0.5.15 + id: setup-uv + uses: ./ + with: + pyproject-file: "__tests__/fixtures/uv-toml-project/pyproject.toml" + uv-file: "__tests__/fixtures/uv-toml-project/uv.toml" + - name: Correct version gets installed + run: | + if [ "$UV_VERSION" != "0.5.15" ]; then + exit 1 + fi + env: + UV_VERSION: ${{ steps.setup-uv.outputs.uv-version }} test-checksum: runs-on: ${{ matrix.os }} strategy: diff --git a/__tests__/fixtures/pyproject-toml-project/.python-version b/__tests__/fixtures/pyproject-toml-project/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/__tests__/fixtures/pyproject-toml-project/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/__tests__/fixtures/pyproject-toml-project/README.md b/__tests__/fixtures/pyproject-toml-project/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/pyproject-toml-project/hello.py b/__tests__/fixtures/pyproject-toml-project/hello.py new file mode 100644 index 0000000..e11f88e --- /dev/null +++ b/__tests__/fixtures/pyproject-toml-project/hello.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from pyproject-toml-project!") + + +if __name__ == "__main__": + main() diff --git a/__tests__/fixtures/pyproject-toml-project/pyproject.toml b/__tests__/fixtures/pyproject-toml-project/pyproject.toml new file mode 100644 index 0000000..55ad9ab --- /dev/null +++ b/__tests__/fixtures/pyproject-toml-project/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "pyproject-toml-project" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [] + +[tool.uv] +required-version = "==0.5.14" diff --git a/__tests__/fixtures/uv-toml-project/.python-version b/__tests__/fixtures/uv-toml-project/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/__tests__/fixtures/uv-toml-project/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/__tests__/fixtures/uv-toml-project/README.md b/__tests__/fixtures/uv-toml-project/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/fixtures/uv-toml-project/hello.py b/__tests__/fixtures/uv-toml-project/hello.py new file mode 100644 index 0000000..5a19d79 --- /dev/null +++ b/__tests__/fixtures/uv-toml-project/hello.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from uv-toml-project!") + + +if __name__ == "__main__": + main() diff --git a/__tests__/fixtures/uv-toml-project/pyproject.toml b/__tests__/fixtures/uv-toml-project/pyproject.toml new file mode 100644 index 0000000..149c6e8 --- /dev/null +++ b/__tests__/fixtures/uv-toml-project/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "uv-toml-project" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [] + +[tool.uv] +required-version = "==0.5.14" diff --git a/__tests__/fixtures/uv-toml-project/uv.toml b/__tests__/fixtures/uv-toml-project/uv.toml new file mode 100644 index 0000000..b7e60ac --- /dev/null +++ b/__tests__/fixtures/uv-toml-project/uv.toml @@ -0,0 +1 @@ +required-version = "==0.5.15" diff --git a/action.yml b/action.yml index 9adec89..9c6893c 100644 --- a/action.yml +++ b/action.yml @@ -4,8 +4,14 @@ description: author: "astral-sh" inputs: version: - description: "The version of uv to install" - default: "latest" + description: "The version of uv to install e.g., `0.5.0` Defaults to the version in pyproject.toml or 'latest'." + default: "" + pyproject-file: + description: "Path to a pyproject.toml" + default: "" + uv-file: + description: "Path to a uv.toml" + default: "" python-version: description: "The version of Python to set UV_PYTHON to" required: false @@ -18,7 +24,7 @@ inputs: required: false default: ${{ github.token }} enable-cache: - description: "Enable caching of the uv cache" + description: "Enable uploading of the uv cache" default: "auto" cache-dependency-glob: description: diff --git a/dist/save-cache/index.js b/dist/save-cache/index.js index 29647f5..5f7403d 100644 Binary files a/dist/save-cache/index.js and b/dist/save-cache/index.js differ diff --git a/dist/setup/index.js b/dist/setup/index.js index c16e105..c3cd887 100644 Binary files a/dist/setup/index.js and b/dist/setup/index.js differ diff --git a/package-lock.json b/package-lock.json index 2ed47d5..c5455ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@actions/glob": "^0.5.0", "@actions/io": "^1.1.3", "@actions/tool-cache": "^2.0.1", + "@iarna/toml": "^2.2.5", "@octokit/rest": "^21.0.2" }, "devDependencies": { @@ -1104,6 +1105,11 @@ "node": ">=14" } }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -5918,6 +5924,11 @@ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==" }, + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", diff --git a/package.json b/package.json index bf6c5d2..4230206 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "@actions/glob": "^0.5.0", "@actions/io": "^1.1.3", "@actions/tool-cache": "^2.0.1", - "@octokit/rest": "^21.0.2" + "@octokit/rest": "^21.0.2", + "@iarna/toml": "^2.2.5" }, "devDependencies": { "@biomejs/biome": "1.9.4", diff --git a/src/setup-uv.ts b/src/setup-uv.ts index 408b005..9195c66 100644 --- a/src/setup-uv.ts +++ b/src/setup-uv.ts @@ -18,12 +18,16 @@ import { checkSum, enableCache, githubToken, + pyProjectFile, pythonVersion, toolBinDir, toolDir, - version, + uvFile, + version as versionInput, } from "./utils/inputs"; import * as exec from "@actions/exec"; +import fs from "node:fs"; +import { getUvVersionFromConfigFile } from "./utils/pyproject"; async function run(): Promise { const platform = getPlatform(); @@ -36,13 +40,7 @@ async function run(): Promise { if (arch === undefined) { throw new Error(`Unsupported architecture: ${process.arch}`); } - const setupResult = await setupUv( - platform, - arch, - version, - checkSum, - githubToken, - ); + const setupResult = await setupUv(platform, arch, checkSum, githubToken); addUvToPath(setupResult.uvDir); addToolBinToPath(); @@ -66,11 +64,10 @@ async function run(): Promise { async function setupUv( platform: Platform, arch: Architecture, - versionInput: string, checkSum: string | undefined, githubToken: string, ): Promise<{ uvDir: string; version: string }> { - const resolvedVersion = await resolveVersion(versionInput, githubToken); + const resolvedVersion = await determineVersion(); const toolCacheResult = tryGetFromToolCache(arch, resolvedVersion); if (toolCacheResult.installedPath) { core.info(`Found uv in tool-cache for ${toolCacheResult.version}`); @@ -94,6 +91,28 @@ async function setupUv( }; } +async function determineVersion(): Promise { + if (versionInput !== "") { + return await resolveVersion(versionInput, githubToken); + } + const configFile = uvFile !== "" ? uvFile : pyProjectFile; + if (configFile !== "") { + const versionFromConfigFile = getUvVersionFromConfigFile(configFile); + if (versionFromConfigFile === undefined) { + core.warning( + `Could not find required-version under [tool.uv] in ${configFile}. Falling back to latest`, + ); + } + return await resolveVersion(versionFromConfigFile || "latest", githubToken); + } + if (!fs.existsSync("uv.toml") && !fs.existsSync("pyproject.toml")) { + return await resolveVersion("latest", githubToken); + } + const versionFile = fs.existsSync("uv.toml") ? "uv.toml" : "pyproject.toml"; + const versionFromConfigFile = getUvVersionFromConfigFile(versionFile); + return await resolveVersion(versionFromConfigFile || "latest", githubToken); +} + function addUvToPath(cachedPath: string): void { core.addPath(cachedPath); core.info(`Added ${cachedPath} to the path`); diff --git a/src/utils/inputs.ts b/src/utils/inputs.ts index 470a9d9..6f72236 100644 --- a/src/utils/inputs.ts +++ b/src/utils/inputs.ts @@ -2,6 +2,8 @@ import * as core from "@actions/core"; import path from "node:path"; export const version = core.getInput("version"); +export const pyProjectFile = core.getInput("pyproject-file"); +export const uvFile = core.getInput("uv-file"); export const pythonVersion = core.getInput("python-version"); export const checkSum = core.getInput("checksum"); export const enableCache = getEnableCache(); diff --git a/src/utils/pyproject.ts b/src/utils/pyproject.ts new file mode 100644 index 0000000..c727855 --- /dev/null +++ b/src/utils/pyproject.ts @@ -0,0 +1,38 @@ +import fs from "node:fs"; +import * as core from "@actions/core"; +import * as toml from "@iarna/toml"; + +export function getUvVersionFromConfigFile( + filePath: string, +): string | undefined { + if (!fs.existsSync(filePath)) { + core.warning(`Could not find file: ${filePath}`); + return undefined; + } + let requiredVersion = getRequiredVersion(filePath); + + if (requiredVersion?.startsWith("==")) { + requiredVersion = requiredVersion.slice(2); + } + if (requiredVersion !== undefined) { + core.info( + `Found required-version for uv in ${filePath}: ${requiredVersion}`, + ); + } + return requiredVersion; +} + +function getRequiredVersion(filePath: string): string | undefined { + const fileContent = fs.readFileSync(filePath, "utf-8"); + + if (filePath.endsWith("pyproject.toml")) { + const tomlContent = toml.parse(fileContent) as { + tool?: { uv?: { "required-version"?: string } }; + }; + return tomlContent?.tool?.uv?.["required-version"]; + } + const tomlContent = toml.parse(fileContent) as { + "required-version"?: string; + }; + return tomlContent["required-version"]; +}