Skip to content

feat: fall back to system Python when pre-built binaries unavailable#1289

Open
gounthar wants to merge 3 commits intoactions:mainfrom
gounthar:feat/system-python-fallback
Open

feat: fall back to system Python when pre-built binaries unavailable#1289
gounthar wants to merge 3 commits intoactions:mainfrom
gounthar:feat/system-python-fallback

Conversation

@gounthar
Copy link

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-python to work on architectures like riscv64 that don't yet have pre-built binaries in actions/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 system python3 exists and satisfies the requested version spec. If so, use it and emit a warning.

Behavior

  • No change for architectures with pre-built binaries (x86_64, arm64, etc.)
  • On unsupported architectures (riscv64, etc.): uses system Python if version matches, warns user
  • Still fails if system Python doesn't satisfy the version spec

Test plan

  • Builds successfully (npm run build)
  • Verify on riscv64 self-hosted runner with system Python 3.12

Fixes #1288

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>
@gounthar gounthar requested a review from a team as a code owner March 18, 2026 14:48
Copilot AI review requested due to automatic review settings March 18, 2026 14:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 python3 fallback resolution in useCpythonVersion when toolcache + manifest lookup fails.
  • Emit a warning when falling back to system Python.
  • Update the bundled dist output 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.

Comment on lines +125 to +151
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
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — tests will be added in a follow-up. The structural fixes in this push are the priority.

Comment on lines +125 to +146
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}.`
);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added !freethreaded guard. The fallback is now skipped entirely for free-threaded builds.

Comment on lines +130 to +144
'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}.`
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Comment on lines +125 to +151
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
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@gounthar
Copy link
Author

gounthar commented Mar 19, 2026

Pushed a v2 addressing all review feedback:

  1. Path parsing fix: The fallback now captures sys.executable, version, prefix, and bin dir directly from the system Python, no longer relies on versionFromPath() which expects toolcache layout. Returns early with correct outputs.

  2. Scope narrowing: The fallback only triggers when the manifest has no entries at all for the current architecture. If the manifest has entries for the arch (e.g., x86_64) but the specific version isn't found, it fails normally. This prevents silent fallback on supported platforms.

  3. Free-threaded skip: Added !freethreaded guard; the fallback is skipped entirely for free-threaded builds since we can't verify the system Python is a free-threaded build.

  4. Tests: will add in a follow-up if this approach is acceptable to the maintainers.

Signed-off-by: Bruno Verachten <gounthar@gmail.com>
@gounthar
Copy link
Author

gounthar commented Mar 19, 2026

Tests added in cb5cf22: 5 new test cases covering the system Python fallback:

  1. Happy path: falls back to system Python on unsupported architecture (riscv64)
  2. Scope guard: does NOT fall back on supported architecture (x64) with missing version
  3. Version mismatch: does NOT fall back when system Python doesn't satisfy the version spec
  4. Free-threaded guard: does NOT fall back for free-threaded builds
  5. Missing python3: handles gracefully when system Python is not available

All 13 tests pass (8 existing + 5 new).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add riscv64 architecture support

2 participants