fix: address review feedback on system Python fallback

- 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>
This commit is contained in:
Bruno Verachten 2026-03-19 14:31:03 +01:00
parent 156786c425
commit 02cd31ab13
No known key found for this signature in database
GPG key ID: E7EAD7209D3ECCD3
2 changed files with 80 additions and 39 deletions

43
dist/setup/index.js vendored
View file

@ -82997,23 +82997,37 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
installDir = tc.find('Python', semanticVersionSpec, architecture);
}
}
if (!installDir) {
// Try system Python as fallback (e.g., on architectures without pre-built binaries)
if (!installDir && !freethreaded) {
// Try system Python as fallback, but only for architectures that have
// no pre-built binaries in the manifest at all. This prevents the
// fallback from firing when a specific version is missing on a
// supported architecture (e.g., requesting Python 3.99 on x86_64).
const baseArchitecture = architecture.replace('-freethreaded', '');
if (!manifest) {
manifest = await installer.getManifest();
}
const archHasManifestEntries = manifest?.some(release => release.files?.some((file) => file.arch === baseArchitecture));
if (!archHasManifestEntries) {
try {
const { exitCode, stdout } = await exec.getExecOutput('python3', [
const sysInfo = await exec.getExecOutput('python3', [
'-c',
'import sys; print(sys.prefix)'
'import sys, os; print(sys.executable + "\\n" + f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + "\\n" + sys.prefix + "\\n" + os.path.dirname(sys.executable))'
]);
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}.`);
if (sysInfo.exitCode === 0) {
const [sysExecutable, sysVersion, sysPrefix, sysBinDir] = sysInfo.stdout.trim().split('\n');
if (semver.satisfies(sysVersion, semanticVersionSpec)) {
core.warning(`Pre-built Python not available for architecture '${baseArchitecture}'. Using system Python ${sysVersion} at ${sysExecutable}.`);
if (updateEnvironment) {
core.exportVariable('pythonLocation', sysPrefix);
core.exportVariable('PKG_CONFIG_PATH', sysPrefix + '/lib/pkgconfig');
core.exportVariable('Python_ROOT_DIR', sysPrefix);
core.exportVariable('Python2_ROOT_DIR', sysPrefix);
core.exportVariable('Python3_ROOT_DIR', sysPrefix);
core.addPath(sysBinDir);
}
core.setOutput('python-version', sysVersion);
core.setOutput('python-path', sysExecutable);
return { impl: 'CPython', version: sysVersion };
}
}
}
@ -83021,6 +83035,7 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
// System Python not available, fall through to error
}
}
}
if (!installDir) {
const osInfo = await (0, utils_1.getOSInfo)();
const msg = [

View file

@ -122,33 +122,59 @@ export async function useCpythonVersion(
}
}
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}.`
if (!installDir && !freethreaded) {
// Try system Python as fallback, but only for architectures that have
// no pre-built binaries in the manifest at all. This prevents the
// fallback from firing when a specific version is missing on a
// supported architecture (e.g., requesting Python 3.99 on x86_64).
const baseArchitecture = architecture.replace('-freethreaded', '');
if (!manifest) {
manifest = await installer.getManifest();
}
const archHasManifestEntries = manifest?.some(
release =>
release.files?.some(
(file: {arch: string}) => file.arch === baseArchitecture
)
);
if (!archHasManifestEntries) {
try {
const sysInfo = await exec.getExecOutput('python3', [
'-c',
'import sys, os; print(sys.executable + "\\n" + f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + "\\n" + sys.prefix + "\\n" + os.path.dirname(sys.executable))'
]);
if (sysInfo.exitCode === 0) {
const [sysExecutable, sysVersion, sysPrefix, sysBinDir] =
sysInfo.stdout.trim().split('\n');
if (semver.satisfies(sysVersion, semanticVersionSpec)) {
core.warning(
`Pre-built Python not available for architecture '${baseArchitecture}'. Using system Python ${sysVersion} at ${sysExecutable}.`
);
if (updateEnvironment) {
core.exportVariable('pythonLocation', sysPrefix);
core.exportVariable(
'PKG_CONFIG_PATH',
sysPrefix + '/lib/pkgconfig'
);
core.exportVariable('Python_ROOT_DIR', sysPrefix);
core.exportVariable('Python2_ROOT_DIR', sysPrefix);
core.exportVariable('Python3_ROOT_DIR', sysPrefix);
core.addPath(sysBinDir);
}
core.setOutput('python-version', sysVersion);
core.setOutput('python-path', sysExecutable);
return {impl: 'CPython', version: sysVersion};
}
}
} catch {
// System Python not available, fall through to error
}
}
}
if (!installDir) {
const osInfo = await getOSInfo();