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
50 changes: 50 additions & 0 deletions dist/cache-save/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97477,6 +97477,56 @@ function getDownloadFileName(downloadUrl) {
? path.join(tempDir, path.basename(downloadUrl))
: undefined;
}
function getVersionCacheSuffix() {
if (!utils_IS_LINUX)
return '';
// Skip only for true GitHub-hosted VMs, detected by presence of the
// pre-installed hostedtoolcache Python at the un-scoped path.
// bbq-beets / Partner runners also report RUNNER_ENVIRONMENT=github-hosted
// but do NOT have pre-installed Python, so they still need scoping.
if (process.env['RUNNER_ENVIRONMENT'] === 'github-hosted') {
const root = process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (root && fs.existsSync(path.join(root, 'Python'))) {
// Check if there's a *non-scoped* pre-installed Python (any dir under
// Python/ that is a plain semver, not already OS-suffixed).
try {
const entries = fs.readdirSync(path.join(root, 'Python'));
const hasPreinstalled = entries.some(e => /^\d+\.\d+\.\d+$/.test(e) && !e.includes('-'));
if (hasPreinstalled)
return '';
}
catch {
/* fall through */
}
}
}
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 '';
const safe = `${id}-${versionId}`.replace(/[^A-Za-z0-9-]/g, '-');
return `-${safe}`;
}
catch {
return '';
}
}

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

Expand Down
129 changes: 127 additions & 2 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97469,6 +97469,56 @@ function getDownloadFileName(downloadUrl) {
? external_path_.join(tempDir, external_path_.basename(downloadUrl))
: undefined;
}
function getVersionCacheSuffix() {
if (!IS_LINUX)
return '';
// Skip only for true GitHub-hosted VMs, detected by presence of the
// pre-installed hostedtoolcache Python at the un-scoped path.
// bbq-beets / Partner runners also report RUNNER_ENVIRONMENT=github-hosted
// but do NOT have pre-installed Python, so they still need scoping.
if (process.env['RUNNER_ENVIRONMENT'] === 'github-hosted') {
const root = process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (root && external_fs_default().existsSync(external_path_.join(root, 'Python'))) {
// Check if there's a *non-scoped* pre-installed Python (any dir under
// Python/ that is a plain semver, not already OS-suffixed).
try {
const entries = external_fs_default().readdirSync(external_path_.join(root, 'Python'));
const hasPreinstalled = entries.some(e => /^\d+\.\d+\.\d+$/.test(e) && !e.includes('-'));
if (hasPreinstalled)
return '';
}
catch {
/* fall through */
}
}
}
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 '';
const safe = `${id}-${versionId}`.replace(/[^A-Za-z0-9-]/g, '-');
return `-${safe}`;
}
catch {
return '';
}
}

;// 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 @@ -98488,6 +98538,49 @@ async function installCpythonFromRelease(release) {
}
info('Execute installation script');
await installPython(pythonExtractedFolder);
const suffix = getVersionCacheSuffix();
info(`[1087] version-suffix: suffix='${suffix}' RUNNER_ENVIRONMENT='${process.env['RUNNER_ENVIRONMENT'] || ''}'`);
if (suffix) {
const toolCache = process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (toolCache) {
const origDir = external_path_.join(toolCache, 'Python', release.version);
const newDir = external_path_.join(toolCache, 'Python', release.version + suffix);
const origMarker = origDir + '.complete';
const newMarker = newDir + '.complete';
try {
if (external_fs_namespaceObject.existsSync(newDir)) {
info(`[1087] version-suffix: removing stale ${newDir} before rename`);
external_fs_namespaceObject.rmSync(newDir, { recursive: true, force: true });
}
if (external_fs_namespaceObject.existsSync(newMarker)) {
external_fs_namespaceObject.rmSync(newMarker, { force: true });
}
if (external_fs_namespaceObject.existsSync(origDir)) {
external_fs_namespaceObject.renameSync(origDir, newDir);
info(`[1087] version-suffix: renamed ${origDir} -> ${newDir}`);
}
else {
warning(`[1087] version-suffix: expected ${origDir} to exist after install but it did not`);
}
if (external_fs_namespaceObject.existsSync(origMarker)) {
external_fs_namespaceObject.renameSync(origMarker, newMarker);
info(`[1087] version-suffix: renamed marker ${origMarker} -> ${newMarker}`);
}
else {
// setup.sh should always write the marker; if it didn't, write our own
external_fs_namespaceObject.writeFileSync(newMarker, '');
info(`[1087] version-suffix: wrote marker ${newMarker}`);
}
}
catch (e) {
warning(`[1087] version-suffix: rename failed: ${e.message}`);
}
}
else {
warning('[1087] version-suffix: no AGENT_TOOLSDIRECTORY/RUNNER_TOOL_CACHE');
}
}
}
catch (err) {
if (err instanceof HTTPError) {
Expand Down Expand Up @@ -98518,6 +98611,7 @@ async function installCpythonFromRelease(release) {




// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
// This is where pip is, along with anything that pip installs.
// There is a separate directory for `pip install --user`.
Expand Down Expand Up @@ -98550,6 +98644,37 @@ async function installPip(pythonLocation) {
await exec_exec(`${pythonLocation}/python -m pip install --upgrade pip==${pipVersion} --disable-pip-version-check --no-warn-script-location`);
}
}
function findScopedPython(semanticVersionSpec, architecture) {
const suffix = getVersionCacheSuffix();
const toolCache = process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (!suffix || !toolCache) {
return find('Python', semanticVersionSpec, architecture);
}
const pythonRoot = external_path_.join(toolCache, 'Python');
if (!external_fs_namespaceObject.existsSync(pythonRoot))
return null;
let bestVer = null;
let bestDir = null;
for (const entry of external_fs_namespaceObject.readdirSync(pythonRoot)) {
if (!entry.endsWith(suffix))
continue;
const ver = entry.slice(0, entry.length - suffix.length);
if (!node_modules_semver.valid(ver))
continue;
if (!node_modules_semver.satisfies(ver, semanticVersionSpec))
continue;
const candidate = external_path_.join(pythonRoot, entry, architecture);
const marker = external_path_.join(pythonRoot, entry + '.complete');
if (!external_fs_namespaceObject.existsSync(candidate) || !external_fs_namespaceObject.existsSync(marker))
continue;
if (!bestVer || node_modules_semver.gt(ver, bestVer)) {
bestVer = ver;
bestDir = candidate;
}
}
return bestDir;
}
async function useCpythonVersion(version, architecture, updateEnvironment, checkLatest, allowPreReleases, freethreaded) {
let manifest = null;
const { version: desugaredVersionSpec, freethreaded: versionFreethreaded } = desugarVersion(version);
Expand All @@ -98575,14 +98700,14 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
info(`Failed to resolve version ${semanticVersionSpec} from manifest`);
}
}
let installDir = find('Python', semanticVersionSpec, architecture);
let installDir = findScopedPython(semanticVersionSpec, architecture);
if (!installDir) {
info(`Version ${semanticVersionSpec} was not found in the local cache`);
const foundRelease = await findReleaseFromManifest(semanticVersionSpec, architecture, manifest);
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
info(`Version ${semanticVersionSpec} is available for downloading`);
await installCpythonFromRelease(foundRelease);
installDir = find('Python', semanticVersionSpec, architecture);
installDir = findScopedPython(semanticVersionSpec, architecture);
}
}
if (!installDir) {
Expand Down
44 changes: 40 additions & 4 deletions src/find-python.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as os from 'os';
import * as path from 'path';
import {IS_WINDOWS, IS_LINUX, getOSInfo} from './utils.js';
import * as fs from 'fs';
import {
IS_WINDOWS,
IS_LINUX,
getOSInfo,
getVersionCacheSuffix
} from './utils.js';

import * as semver from 'semver';

Expand Down Expand Up @@ -52,6 +58,37 @@ async function installPip(pythonLocation: string) {
}
}

function findScopedPython(
semanticVersionSpec: string,
architecture: string
): string | null {
const suffix = getVersionCacheSuffix();
const toolCache =
process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (!suffix || !toolCache) {
return tc.find('Python', semanticVersionSpec, architecture);
}
const pythonRoot = path.join(toolCache, 'Python');
if (!fs.existsSync(pythonRoot)) return null;
let bestVer: string | null = null;
let bestDir: string | null = null;
for (const entry of fs.readdirSync(pythonRoot)) {
if (!entry.endsWith(suffix)) continue;
const ver = entry.slice(0, entry.length - suffix.length);
if (!semver.valid(ver)) continue;
if (!semver.satisfies(ver, semanticVersionSpec)) continue;
const candidate = path.join(pythonRoot, entry, architecture);
const marker = path.join(pythonRoot, entry + '.complete');
if (!fs.existsSync(candidate) || !fs.existsSync(marker)) continue;
if (!bestVer || semver.gt(ver, bestVer)) {
bestVer = ver;
bestDir = candidate;
}
}
return bestDir;
}

export async function useCpythonVersion(
version: string,
architecture: string,
Expand Down Expand Up @@ -99,8 +136,7 @@ export async function useCpythonVersion(
}
}

let installDir: string | null = tc.find(
'Python',
let installDir: string | null = findScopedPython(
semanticVersionSpec,
architecture
);
Expand All @@ -118,7 +154,7 @@ export async function useCpythonVersion(
core.info(`Version ${semanticVersionSpec} is available for downloading`);
await installer.installCpythonFromRelease(foundRelease);

installDir = tc.find('Python', semanticVersionSpec, architecture);
installDir = findScopedPython(semanticVersionSpec, architecture);
}
}

Expand Down
40 changes: 39 additions & 1 deletion src/install-python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {ExecOptions} from '@actions/exec';
import * as httpm from '@actions/http-client';
import * as fs from 'fs';
import * as semver from 'semver';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils.js';
import {
IS_WINDOWS,
IS_LINUX,
getDownloadFileName,
getVersionCacheSuffix
} from './utils.js';
import {IToolRelease} from '@actions/tool-cache';

const TOKEN = core.getInput('token');
Expand Down Expand Up @@ -302,6 +307,39 @@ export async function installCpythonFromRelease(release: tc.IToolRelease) {

core.info('Execute installation script');
await installPython(pythonExtractedFolder);

const suffix = getVersionCacheSuffix();
if (suffix) {
const toolCache =
process.env['AGENT_TOOLSDIRECTORY']?.trim() ||
process.env['RUNNER_TOOL_CACHE'];
if (toolCache) {
const origDir = path.join(toolCache, 'Python', release.version);
const newDir = path.join(toolCache, 'Python', release.version + suffix);
const origMarker = origDir + '.complete';
const newMarker = newDir + '.complete';
try {
if (fs.existsSync(newDir)) {
fs.rmSync(newDir, {recursive: true, force: true});
}
if (fs.existsSync(newMarker)) {
fs.rmSync(newMarker, {force: true});
}
if (fs.existsSync(origDir)) {
fs.renameSync(origDir, newDir);
}
if (fs.existsSync(origMarker)) {
fs.renameSync(origMarker, newMarker);
} else {
fs.writeFileSync(newMarker, '');
}
} catch (e) {
core.warning(
`Failed to rename Python cache dir for OS scoping: ${(e as Error).message}`
);
}
}
}
} catch (err) {
if (err instanceof tc.HTTPError) {
// Rate limit?
Expand Down
Loading
Loading