Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions dist/cache-save/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97477,6 +97477,72 @@ function getDownloadFileName(downloadUrl) {
? path.join(tempDir, path.basename(downloadUrl))
: undefined;
}
/**
* Issue #1087: on Linux, scope the tool-cache root by OS id + version so a
* self-hosted runner that switches between different distro versions
* (e.g. Ubuntu 20.04 / 24.04) does not reuse a Python built against a
* different glibc / OpenSSL. Returns e.g. "os-ubuntu-24.04", or null when
* not applicable (non-Linux, or /etc/os-release unavailable/incomplete).
*/
function getOsScopedToolCacheSegment() {
if (!utils_IS_LINUX)
return null;
try {
const content = fs.readFileSync('/etc/os-release', 'utf8');
const map = {};
for (const line of content.split('\n')) {
const eq = line.indexOf('=');
if (eq <= 0)
continue;
const k = line.slice(0, eq).trim();
const v = line
.slice(eq + 1)
.trim()
.replace(/^"|"$/g, '');
if (k && v)
map[k] = v;
}
const id = map['ID'];
const versionId = map['VERSION_ID'];
if (!id || !versionId)
return null;
const safe = `${id}-${versionId}`.replace(/[^A-Za-z0-9._-]/g, '_');
return `os-${safe}`;
}
catch {
return null;
}
}
/**
* Issue #1087: apply {@link getOsScopedToolCacheSegment} to the tool-cache
* root env vars so all downstream consumers (`@actions/tool-cache`, and the
* `setup.sh` inside `actions/python-versions` release tarballs) transparently
* install/read under an OS-scoped root.
*
* IMPORTANT: only applied on self-hosted runners. GitHub-hosted runners ship
* with a pre-installed, fully-configured Python under the un-scoped path
* (`/opt/hostedtoolcache/Python/…`); redirecting them would cause a needless
* re-install of a less-configured Python (e.g. breaks numpy source builds on
* freethreaded 3.13). Detected via the documented `RUNNER_ENVIRONMENT` env var
* (`github-hosted` on hosted, `self-hosted` on self-hosted runners).
*
* Idempotent and a no-op off Linux or on hosted runners.
*/
function scopeToolCacheByOs() {
if (process.env['RUNNER_ENVIRONMENT'] === 'github-hosted')
return;
const seg = getOsScopedToolCacheSegment();
if (!seg)
return;
for (const varName of ['RUNNER_TOOL_CACHE', 'AGENT_TOOLSDIRECTORY']) {
const cur = process.env[varName];
if (!cur)
continue;
if (path.basename(cur) === seg)
continue; // already scoped
process.env[varName] = path.join(cur, seg);
}
}

;// CONCATENATED MODULE: ./src/cache-distributions/cache-distributor.ts

Expand Down
70 changes: 70 additions & 0 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97469,6 +97469,72 @@ function getDownloadFileName(downloadUrl) {
? external_path_.join(tempDir, external_path_.basename(downloadUrl))
: undefined;
}
/**
* Issue #1087: on Linux, scope the tool-cache root by OS id + version so a
* self-hosted runner that switches between different distro versions
* (e.g. Ubuntu 20.04 / 24.04) does not reuse a Python built against a
* different glibc / OpenSSL. Returns e.g. "os-ubuntu-24.04", or null when
* not applicable (non-Linux, or /etc/os-release unavailable/incomplete).
*/
function getOsScopedToolCacheSegment() {
if (!IS_LINUX)
return null;
try {
const content = external_fs_default().readFileSync('/etc/os-release', 'utf8');
const map = {};
for (const line of content.split('\n')) {
const eq = line.indexOf('=');
if (eq <= 0)
continue;
const k = line.slice(0, eq).trim();
const v = line
.slice(eq + 1)
.trim()
.replace(/^"|"$/g, '');
if (k && v)
map[k] = v;
}
const id = map['ID'];
const versionId = map['VERSION_ID'];
if (!id || !versionId)
return null;
const safe = `${id}-${versionId}`.replace(/[^A-Za-z0-9._-]/g, '_');
return `os-${safe}`;
}
catch {
return null;
}
}
/**
* Issue #1087: apply {@link getOsScopedToolCacheSegment} to the tool-cache
* root env vars so all downstream consumers (`@actions/tool-cache`, and the
* `setup.sh` inside `actions/python-versions` release tarballs) transparently
* install/read under an OS-scoped root.
*
* IMPORTANT: only applied on self-hosted runners. GitHub-hosted runners ship
* with a pre-installed, fully-configured Python under the un-scoped path
* (`/opt/hostedtoolcache/Python/…`); redirecting them would cause a needless
* re-install of a less-configured Python (e.g. breaks numpy source builds on
* freethreaded 3.13). Detected via the documented `RUNNER_ENVIRONMENT` env var
* (`github-hosted` on hosted, `self-hosted` on self-hosted runners).
*
* Idempotent and a no-op off Linux or on hosted runners.
*/
function scopeToolCacheByOs() {
if (process.env['RUNNER_ENVIRONMENT'] === 'github-hosted')
return;
const seg = getOsScopedToolCacheSegment();
if (!seg)
return;
for (const varName of ['RUNNER_TOOL_CACHE', 'AGENT_TOOLSDIRECTORY']) {
const cur = process.env[varName];
if (!cur)
continue;
if (external_path_.basename(cur) === seg)
continue; // already scoped
process.env[varName] = external_path_.join(cur, seg);
}
}

;// CONCATENATED MODULE: ./node_modules/@actions/tool-cache/lib/manifest.js
var manifest_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
Expand Down Expand Up @@ -103004,6 +103070,10 @@ async function run() {
if (process.env.AGENT_TOOLSDIRECTORY?.trim()) {
process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY'];
}
// Issue #1087: on self-hosted Linux, scope the tool-cache root by OS id +
// version so runners that switch between distro versions do not reuse a
// Python built against a different glibc / OpenSSL. No-op on hosted runners.
scopeToolCacheByOs();
core_debug(`Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}`);
try {
const versions = resolveVersionInput();
Expand Down
8 changes: 7 additions & 1 deletion src/setup-python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
logWarning,
IS_MAC,
getVersionInputFromFile,
getVersionsInputFromPlainFile
getVersionsInputFromPlainFile,
scopeToolCacheByOs
} from './utils.js';

function isPyPyVersion(versionSpec: string) {
Expand Down Expand Up @@ -86,6 +87,11 @@ async function run() {
process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY'];
}

// Issue #1087: on self-hosted Linux, scope the tool-cache root by OS id +
// version so runners that switch between distro versions do not reuse a
// Python built against a different glibc / OpenSSL. No-op on hosted runners.
scopeToolCacheByOs();

core.debug(
`Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}`
);
Expand Down
59 changes: 59 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,62 @@ export function getDownloadFileName(downloadUrl: string): string | undefined {
? path.join(tempDir, path.basename(downloadUrl))
: undefined;
}

/**
* Issue #1087: on Linux, scope the tool-cache root by OS id + version so a
* self-hosted runner that switches between different distro versions
* (e.g. Ubuntu 20.04 / 24.04) does not reuse a Python built against a
* different glibc / OpenSSL. Returns e.g. "os-ubuntu-24.04", or null when
* not applicable (non-Linux, or /etc/os-release unavailable/incomplete).
*/
export function getOsScopedToolCacheSegment(): string | null {
if (!IS_LINUX) return null;
try {
const content = fs.readFileSync('/etc/os-release', 'utf8');
const map: Record<string, string> = {};
for (const line of content.split('\n')) {
const eq = line.indexOf('=');
if (eq <= 0) continue;
const k = line.slice(0, eq).trim();
const v = line
.slice(eq + 1)
.trim()
.replace(/^"|"$/g, '');
if (k && v) map[k] = v;
}
const id = map['ID'];
const versionId = map['VERSION_ID'];
if (!id || !versionId) return null;
const safe = `${id}-${versionId}`.replace(/[^A-Za-z0-9._-]/g, '_');
return `os-${safe}`;
} catch {
return null;
}
}

/**
* Issue #1087: apply {@link getOsScopedToolCacheSegment} to the tool-cache
* root env vars so all downstream consumers (`@actions/tool-cache`, and the
* `setup.sh` inside `actions/python-versions` release tarballs) transparently
* install/read under an OS-scoped root.
*
* IMPORTANT: only applied on self-hosted runners. GitHub-hosted runners ship
* with a pre-installed, fully-configured Python under the un-scoped path
* (`/opt/hostedtoolcache/Python/…`); redirecting them would cause a needless
* re-install of a less-configured Python (e.g. breaks numpy source builds on
* freethreaded 3.13). Detected via the documented `RUNNER_ENVIRONMENT` env var
* (`github-hosted` on hosted, `self-hosted` on self-hosted runners).
*
* Idempotent and a no-op off Linux or on hosted runners.
*/
export function scopeToolCacheByOs(): void {
if (process.env['RUNNER_ENVIRONMENT'] === 'github-hosted') return;
const seg = getOsScopedToolCacheSegment();
if (!seg) return;
for (const varName of ['RUNNER_TOOL_CACHE', 'AGENT_TOOLSDIRECTORY']) {
const cur = process.env[varName];
if (!cur) continue;
if (path.basename(cur) === seg) continue; // already scoped
process.env[varName] = path.join(cur, seg);
}
}