feat: fall back to system Python when pre-built binaries unavailable#1289
feat: fall back to system Python when pre-built binaries unavailable#1289gounthar wants to merge 3 commits intoactions:mainfrom
Conversation
When pre-built Python binaries are not available for the current architecture (e.g., riscv64), try using system Python as a fallback instead of failing with an error. This enables setup-python to work on architectures that don't yet have pre-built binaries in actions/python-versions, such as riscv64 self-hosted runners. The fallback only activates when: 1. No cached version is found 2. No manifest entry exists for the architecture 3. System python3 exists and satisfies the requested version spec A warning is emitted so users know the fallback was used. Fixes actions#1288 Signed-off-by: Bruno Verachten <gounthar@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a fallback path in actions/setup-python so that when no pre-built CPython binary can be found for the requested version/architecture, the action can optionally use a compatible system python3 instead of failing (targeting unsupported architectures like riscv64).
Changes:
- Add system
python3fallback resolution inuseCpythonVersionwhen toolcache + manifest lookup fails. - Emit a warning when falling back to system Python.
- Update the bundled
distoutput to include the same logic.
Reviewed changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/find-python.ts | Adds system python3 fallback when no cached/downloadable CPython is found. |
| dist/setup/index.js | Compiled distribution output reflecting the new fallback behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/find-python.ts
Outdated
| if (!installDir) { | ||
| // Try system Python as fallback (e.g., on architectures without pre-built binaries) | ||
| try { | ||
| const {exitCode, stdout} = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(sys.prefix)' | ||
| ]); | ||
| if (exitCode === 0) { | ||
| const systemPrefix = stdout.trim(); | ||
| const systemVersion = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")' | ||
| ]); | ||
| if ( | ||
| systemVersion.exitCode === 0 && | ||
| semver.satisfies(systemVersion.stdout.trim(), semanticVersionSpec) | ||
| ) { | ||
| installDir = systemPrefix; | ||
| core.warning( | ||
| `Pre-built Python not available for architecture '${architecture}'. Using system Python ${systemVersion.stdout.trim()} at ${systemPrefix}.` | ||
| ); | ||
| } | ||
| } | ||
| } catch { | ||
| // System Python not available, fall through to error | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed — tests will be added in a follow-up. The structural fixes in this push are the priority.
src/find-python.ts
Outdated
| if (!installDir) { | ||
| // Try system Python as fallback (e.g., on architectures without pre-built binaries) | ||
| try { | ||
| const {exitCode, stdout} = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(sys.prefix)' | ||
| ]); | ||
| if (exitCode === 0) { | ||
| const systemPrefix = stdout.trim(); | ||
| const systemVersion = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")' | ||
| ]); | ||
| if ( | ||
| systemVersion.exitCode === 0 && | ||
| semver.satisfies(systemVersion.stdout.trim(), semanticVersionSpec) | ||
| ) { | ||
| installDir = systemPrefix; | ||
| core.warning( | ||
| `Pre-built Python not available for architecture '${architecture}'. Using system Python ${systemVersion.stdout.trim()} at ${systemPrefix}.` | ||
| ); | ||
| } |
There was a problem hiding this comment.
Fixed — added !freethreaded guard. The fallback is now skipped entirely for free-threaded builds.
src/find-python.ts
Outdated
| 'import sys; print(sys.prefix)' | ||
| ]); | ||
| if (exitCode === 0) { | ||
| const systemPrefix = stdout.trim(); | ||
| const systemVersion = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")' | ||
| ]); | ||
| if ( | ||
| systemVersion.exitCode === 0 && | ||
| semver.satisfies(systemVersion.stdout.trim(), semanticVersionSpec) | ||
| ) { | ||
| installDir = systemPrefix; | ||
| core.warning( | ||
| `Pre-built Python not available for architecture '${architecture}'. Using system Python ${systemVersion.stdout.trim()} at ${systemPrefix}.` |
There was a problem hiding this comment.
Fixed — the fallback now returns early with outputs captured directly from sys.executable, sys.prefix, and os.path.dirname(sys.executable). No longer sets installDir or relies on versionFromPath()/binDir().
src/find-python.ts
Outdated
| if (!installDir) { | ||
| // Try system Python as fallback (e.g., on architectures without pre-built binaries) | ||
| try { | ||
| const {exitCode, stdout} = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(sys.prefix)' | ||
| ]); | ||
| if (exitCode === 0) { | ||
| const systemPrefix = stdout.trim(); | ||
| const systemVersion = await exec.getExecOutput('python3', [ | ||
| '-c', | ||
| 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")' | ||
| ]); | ||
| if ( | ||
| systemVersion.exitCode === 0 && | ||
| semver.satisfies(systemVersion.stdout.trim(), semanticVersionSpec) | ||
| ) { | ||
| installDir = systemPrefix; | ||
| core.warning( | ||
| `Pre-built Python not available for architecture '${architecture}'. Using system Python ${systemVersion.stdout.trim()} at ${systemPrefix}.` | ||
| ); | ||
| } | ||
| } | ||
| } catch { | ||
| // System Python not available, fall through to error | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed — the fallback now only triggers when the manifest has zero entries for the current architecture (!archHasManifestEntries). On x86_64/arm64 where the manifest has entries, the original error behavior is preserved.
- Gate fallback on architecture having no manifest entries, preventing false triggers on supported architectures with missing versions - Skip fallback for free-threaded Python builds - Return early with correct outputs (sys.executable, version, bin dir) instead of relying on toolcache path parsing which breaks with system prefix paths like /usr - Set environment variables (pythonLocation, Python_ROOT_DIR, etc.) correctly for system Python paths Signed-off-by: Bruno Verachten <gounthar@gmail.com>
|
Pushed a v2 addressing all review feedback:
|
Signed-off-by: Bruno Verachten <gounthar@gmail.com>
|
Tests added in cb5cf22: 5 new test cases covering the system Python fallback:
All 13 tests pass (8 existing + 5 new). |
Summary
When pre-built Python binaries are not available for the current architecture, fall back to system Python instead of failing with an error.
This enables
setup-pythonto work on architectures likeriscv64that don't yet have pre-built binaries inactions/python-versions. RISC-V self-hosted runners are becoming available through programs like RISE RISC-V runners, used by numpy, llama.cpp, and pytorch.Changes
src/find-python.ts: Before throwing "version not found" error, check if systempython3exists and satisfies the requested version spec. If so, use it and emit a warning.Behavior
Test plan
npm run build)Fixes #1288