This commit is contained in:
Ludovic Henry 2026-08-20 11:47:21 -05:00 • committed by GitHub
commit 0fc1ab83c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 870 additions and 53 deletions

175
dist/setup/index.js vendored
View file

@ -98843,12 +98843,96 @@ function _unique(values) {
const TOKEN = getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const DEFAULT_REPO_OWNER = 'actions';
const DEFAULT_REPO_NAME = 'python-versions';
const DEFAULT_REPO_BRANCH = 'main';
const DEFAULT_MIRROR = `https://raw.githubusercontent.com/${DEFAULT_REPO_OWNER}/${DEFAULT_REPO_NAME}/${DEFAULT_REPO_BRANCH}`;
// Matches https://raw.githubusercontent.com/{owner}/{repo}/{branch}
const REPO_COORDS_RE = /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/?$/;
function getToken() {
return getInput('token');
}
function getMirrorToken() {
return getInput('mirror-token');
}
// Memoized per raw input value so the mirror is validated once per run rather
// than on every call. `getManifestUrl()` is also used to build the "version not
// found" message in find-python.ts, where re-validating would replace the real
// cause with an invalid-mirror error.
const mirrorCache = new Map();
function getMirror() {
const input = getInput('mirror') || DEFAULT_MIRROR;
let resolved = mirrorCache.get(input);
if (!resolved) {
const url = input.trim().replace(/\/+$/, '');
try {
new URL(url);
resolved = { url };
}
catch {
resolved = { error: new Error(`Invalid 'mirror' URL: "${url}"`) };
}
mirrorCache.set(input, resolved);
}
if ('error' in resolved)
throw resolved.error;
return resolved.url;
}
function getManifestUrl() {
return `${getMirror()}/versions-manifest.json`;
}
function getMirrorHost() {
try {
return new URL(getMirror()).host;
}
catch {
return undefined;
}
}
function isGitHubHost(host) {
return (host === 'github.com' ||
host.endsWith('.github.com') ||
host.endsWith('.githubusercontent.com'));
}
// Warned at most once per distinct mirror; resolveRepoCoords() is called from
// several paths within a single run.
const warnedMirrors = new Set();
function resolveRepoCoords() {
const mirror = getMirror();
const m = REPO_COORDS_RE.exec(mirror);
if (m)
return { owner: m[1], repo: m[2], branch: m[3] };
// A raw.githubusercontent.com URL that doesn't parse is usually a branch
// name containing a slash, which is indistinguishable from a deeper path.
// Fetching still works, just anonymously and without the API rate limit.
if (!warnedMirrors.has(mirror) &&
getMirrorHost() === 'raw.githubusercontent.com') {
warnedMirrors.add(mirror);
warning(`Could not parse owner/repo/branch out of mirror "${mirror}", so the manifest will be fetched by direct URL instead of the GitHub API. ` +
`Branch names containing '/' are not supported; use a branch without a slash to get the authenticated API rate limit.`);
}
return null;
}
// Mirror host with `mirror-token` set gets the token verbatim, so internal
// mirrors can choose their own scheme (Bearer, Basic, ...). GitHub hosts get
// `token ${token}`. Anything else is anonymous — neither credential is sent to
// a host the user didn't nominate.
function authForUrl(url) {
let host;
try {
host = new URL(url).host;
}
catch {
return undefined;
}
const mirrorToken = getMirrorToken();
if (mirrorToken && host === getMirrorHost())
return mirrorToken;
const token = getToken();
if (token && isGitHubHost(host))
return `token ${token}`;
return undefined;
}
function getLinuxOsRelease() {
try {
const content = external_fs_namespaceObject.readFileSync('/etc/os-release', 'utf8');
@ -98974,17 +99058,25 @@ async function fetchValidManifest(source, fetcher) {
throw new Error(`Failed to fetch a valid manifest from ${source} after ${attempts} attempt(s): ${lastError?.message}`);
}
async function getManifest() {
try {
return await fetchValidManifest('the GitHub API', install_python_getManifestFromRepo);
// Only GitHub repo mirrors can be fetched via the API. Checking up front
// avoids burning MANIFEST_FETCH_MAX_ATTEMPTS with backoff on a throw that
// could never succeed.
if (resolveRepoCoords()) {
try {
return await fetchValidManifest('the GitHub API', install_python_getManifestFromRepo);
}
catch (err) {
core_debug('Fetching the manifest via the API failed.');
if (err instanceof Error) {
core_debug(err.message);
}
else {
core_debug('An unexpected error occurred while fetching the manifest.');
}
}
}
catch (err) {
core_debug('Fetching the manifest via the API failed.');
if (err instanceof Error) {
core_debug(err.message);
}
else {
core_debug('An unexpected error occurred while fetching the manifest.');
}
else {
core_debug(`Mirror "${getMirror()}" is not a GitHub repo URL; fetching the manifest by URL.`);
}
try {
return await fetchValidManifest('the raw URL', getManifestFromURL);
@ -98996,15 +99088,32 @@ async function getManifest() {
}
}
function install_python_getManifestFromRepo() {
core_debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
return getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
const coords = resolveRepoCoords();
if (!coords) {
throw new Error(`Mirror "${getMirror()}" is not a GitHub repo URL; falling back to raw URL fetch.`);
}
core_debug(`Getting manifest from ${coords.owner}/${coords.repo}@${coords.branch}`);
// This only runs for GitHub repo mirrors, where `mirror-token` is the user's
// explicit intent for that repo. The target is always api.github.com, which
// requires the `token ` prefix, so the host rule in authForUrl() doesn't
// apply here.
const token = getToken();
const mirrorToken = getMirrorToken();
const auth = mirrorToken
? `token ${mirrorToken}`
: token
? `token ${token}`
: undefined;
return getManifestFromRepo(coords.owner, coords.repo, auth, coords.branch);
}
async function getManifestFromURL() {
core_debug('Falling back to fetching the manifest using raw URL.');
const manifestUrl = getManifestUrl();
const http = new lib_HttpClient('tool-cache');
const response = await http.getJson(MANIFEST_URL);
const auth = authForUrl(manifestUrl);
const response = await http.getJson(manifestUrl, auth ? { authorization: auth } : undefined);
if (!response.result) {
throw new Error(`Unable to get manifest from ${MANIFEST_URL}`);
throw new Error(`Unable to get manifest from ${manifestUrl}`);
}
return response.result;
}
@ -99047,7 +99156,7 @@ async function installCpythonFromRelease(release) {
let pythonPath = '';
try {
const fileName = getDownloadFileName(downloadUrl);
pythonPath = await downloadTool(downloadUrl, fileName, AUTH);
pythonPath = await downloadTool(downloadUrl, fileName, authForUrl(downloadUrl));
info('Extract downloaded archive');
let pythonExtractedFolder;
if (utils_IS_WINDOWS) {
@ -99165,7 +99274,7 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
if (freethreaded) {
msg.push(`Free threaded versions are only available for Python 3.13.0 and later.`);
}
msg.push(`The list of all available versions can be found here: ${MANIFEST_URL}`);
msg.push(`The list of all available versions can be found here: ${getManifestUrl()}`);
throw new Error(msg.join(external_os_.EOL));
}
const _binDir = binDir(installDir);
@ -99567,8 +99676,8 @@ function findPyPyInstallDirForWindows(pythonVersion) {
const install_graalpy_TOKEN = getInput('token');
const install_graalpy_AUTH = !install_graalpy_TOKEN ? undefined : `token ${install_graalpy_TOKEN}`;
const TOKEN = getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
async function installGraalPy(graalpyVersion, architecture, allowPreReleases, releases) {
let downloadDir;
releases = releases ?? (await getAvailableGraalPyVersions());
@ -99591,7 +99700,7 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
const downloadUrl = `${foundAsset.browser_download_url}`;
info(`Downloading GraalPy from "${downloadUrl}" ...`);
try {
const graalpyPath = await downloadTool(downloadUrl, undefined, install_graalpy_AUTH);
const graalpyPath = await downloadTool(downloadUrl, undefined, AUTH);
info('Extracting downloaded archive...');
if (utils_IS_WINDOWS) {
downloadDir = await extractZip(graalpyPath);
@ -99632,8 +99741,8 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
async function getAvailableGraalPyVersions() {
const http = new lib_HttpClient('tool-cache');
const headers = {};
if (install_graalpy_AUTH) {
headers.authorization = install_graalpy_AUTH;
if (AUTH) {
headers.authorization = AUTH;
}
/*
Get releases first.
@ -103614,6 +103723,16 @@ function isPyPyVersion(versionSpec) {
function isGraalPyVersion(versionSpec) {
return versionSpec.startsWith('graalpy');
}
// `mirror` only redirects CPython distributions. PyPy and GraalPy resolve from
// downloads.python.org and the GitHub releases API respectively, so warn rather
// than let the input look like it applied.
function warnIfMirrorUnsupported(versionSpec) {
if (!getInput('mirror')) {
return;
}
const implementation = isPyPyVersion(versionSpec) ? 'PyPy' : 'GraalPy';
warning(`The 'mirror' input only applies to CPython distributions and is ignored for ${implementation} ('${versionSpec}'), which is downloaded from its own upstream source.`);
}
async function cacheDependencies(cache, pythonVersion) {
const cacheDependencyPath = getInput('cache-dependency-path') || undefined;
const cacheDistributor = getCacheDistributor(cache, pythonVersion, cacheDependencyPath);
@ -103675,11 +103794,13 @@ async function run() {
startGroup('Installed versions');
for (const version of versions) {
if (isPyPyVersion(version)) {
warnIfMirrorUnsupported(version);
const installed = await findPyPyVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases);
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`);
}
else if (isGraalPyVersion(version)) {
warnIfMirrorUnsupported(version);
const installed = await findGraalPyVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases);
pythonVersion = `${installed}`;
info(`Successfully set up GraalPy ${installed}`);