mirror of
https://gitea.com/actions/setup-java.git
synced 2026-08-07 02:31:20 +00:00
Reuse the tool cache for floating versions the resolution cache identified (#1219)
A floating Oracle JDK or Oracle GraalVM request now skips the tool-cache short-circuit entirely, so every job re-downloads and re-extracts the JDK even when the exact bytes the mutable URL currently serves are already installed locally. The JDK resolution cache is keyed on the artifact's checksum (or, failing that, its HTTP response fingerprint), so a hit proves which concrete version the URL is serving right now. Once it has vouched for that version, an existing tool-cache installation of exactly that version is the artifact the download would have produced, and can be reused. Reuse is therefore gated on the resolution cache hit, never on the requested major: an unidentified floating artifact still downloads, as does an explicit force-download. Co-authored-by: Bruno Borges <brunoborges@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f2d9a891-a680-4b35-9644-cf2450f76f6d
This commit is contained in:
@@ -518,6 +518,106 @@ describe('setupJava', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('floating tool-cache reuse', () => {
|
||||
let toolCacheRoot: string;
|
||||
|
||||
const installVersion = (toolcacheVersion: string): string => {
|
||||
const architecturePath = path.join(
|
||||
toolCacheRoot,
|
||||
'Java_Floating_jdk',
|
||||
toolcacheVersion,
|
||||
'x64'
|
||||
);
|
||||
fs.mkdirSync(architecturePath, {recursive: true});
|
||||
fs.writeFileSync(`${architecturePath}.complete`, '');
|
||||
return architecturePath;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
toolCacheRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-java-tc-'));
|
||||
process.env['RUNNER_TOOL_CACHE'] = toolCacheRoot;
|
||||
FloatingJavaBase.actualVersion = '21.0.8+9';
|
||||
FloatingJavaBase.checksum = 'artifact-one';
|
||||
(jdkCache.restoreJdk as jest.Mock).mockResolvedValue(false);
|
||||
spyTcFindAllVersions.mockReturnValue([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(toolCacheRoot, {recursive: true, force: true});
|
||||
delete process.env['RUNNER_TOOL_CACHE'];
|
||||
});
|
||||
|
||||
const createDistribution = (forceDownload = false) =>
|
||||
new FloatingJavaBase({
|
||||
version: '21',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false,
|
||||
cacheJdk: true,
|
||||
forceDownload
|
||||
});
|
||||
|
||||
it('reuses a tool-cache installation once the resolution cache identifies the floating version', async () => {
|
||||
const installedPath = installVersion('21.0.8-9');
|
||||
(jdkResolutionCache.restoreJdkResolution as jest.Mock).mockResolvedValue({
|
||||
release: {version: '21.0.8+9'}
|
||||
});
|
||||
const distribution = createDistribution();
|
||||
const downloadTool = jest.spyOn(distribution as any, 'downloadTool');
|
||||
|
||||
await expect(distribution.setupJava()).resolves.toEqual({
|
||||
version: '21.0.8+9',
|
||||
path: installedPath
|
||||
});
|
||||
|
||||
// The artifact behind the mutable URL is already installed, so neither a
|
||||
// download nor a cache round-trip is needed.
|
||||
expect(downloadTool).not.toHaveBeenCalled();
|
||||
expect(jdkCache.restoreJdk).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ignores a tool-cache installation of a version the resolution cache did not vouch for', async () => {
|
||||
installVersion('21.0.7-6');
|
||||
(jdkResolutionCache.restoreJdkResolution as jest.Mock).mockResolvedValue({
|
||||
release: {version: '21.0.8+9'}
|
||||
});
|
||||
const distribution = createDistribution();
|
||||
const downloadTool = jest.spyOn(distribution as any, 'downloadTool');
|
||||
|
||||
await distribution.setupJava();
|
||||
|
||||
expect(downloadTool).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('never reuses the tool-cache for a floating artifact the resolution cache cannot identify', async () => {
|
||||
installVersion('21.0.8-9');
|
||||
spyTcFindAllVersions.mockReturnValue(['21.0.8-9']);
|
||||
(jdkResolutionCache.restoreJdkResolution as jest.Mock).mockResolvedValue(
|
||||
undefined
|
||||
);
|
||||
const distribution = createDistribution();
|
||||
const downloadTool = jest.spyOn(distribution as any, 'downloadTool');
|
||||
|
||||
await distribution.setupJava();
|
||||
|
||||
// Nothing ties the installed bytes to what the URL serves right now.
|
||||
expect(downloadTool).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('still downloads a resolution-cache-identified version when force-download is set', async () => {
|
||||
installVersion('21.0.8-9');
|
||||
(jdkResolutionCache.restoreJdkResolution as jest.Mock).mockResolvedValue({
|
||||
release: {version: '21.0.8+9'}
|
||||
});
|
||||
const distribution = createDistribution(true);
|
||||
const downloadTool = jest.spyOn(distribution as any, 'downloadTool');
|
||||
|
||||
await distribution.setupJava();
|
||||
|
||||
expect(downloadTool).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the concrete versions of two different floating artifacts under the same major', async () => {
|
||||
spyTcFindAllVersions.mockReturnValue(['21.0.8-9']);
|
||||
spyGetToolcachePath.mockImplementation(
|
||||
|
||||
Vendored
+30
-4
@@ -218,6 +218,12 @@ class JavaBase {
|
||||
checkLatest;
|
||||
forceDownload;
|
||||
cacheJdk;
|
||||
/**
|
||||
* Whether the concrete version of a floating release has been established
|
||||
* from the checksum-bound resolution cache. Until then the release version is
|
||||
* only the requested major and says nothing about the bytes behind the URL.
|
||||
*/
|
||||
floatingVersionVerified = false;
|
||||
setDefault;
|
||||
verifySignature;
|
||||
verifySignaturePublicKey;
|
||||
@@ -328,10 +334,16 @@ class JavaBase {
|
||||
let javaRelease = await this.resolveJavaRelease();
|
||||
core/* info */.pq(`Resolved latest version as ${javaRelease.version}`);
|
||||
if (javaRelease.floating) {
|
||||
// A tool-cache entry has no source identity. Even when its concrete
|
||||
// version matches, only the checksum-bound JDK cache can prove that
|
||||
// it contains the bytes currently served by the mutable URL.
|
||||
foundJava = null;
|
||||
// A tool-cache entry has no source identity, and until the
|
||||
// checksum-bound resolution cache maps the current artifact to a
|
||||
// concrete version the release version is still just the requested
|
||||
// major — so nothing already on the runner can be trusted. Once that
|
||||
// mapping is known, an installation of exactly that version is the
|
||||
// artifact we would otherwise download.
|
||||
foundJava =
|
||||
this.floatingVersionVerified && !this.forceDownload
|
||||
? this.findConcreteVersionInToolcache(javaRelease.version)
|
||||
: null;
|
||||
}
|
||||
if (!this.forceDownload && foundJava?.version === javaRelease.version) {
|
||||
core/* info */.pq(`Resolved Java ${foundJava.version} from tool-cache`);
|
||||
@@ -489,6 +501,7 @@ class JavaBase {
|
||||
return javaRelease;
|
||||
}
|
||||
core/* info */.pq(`Resolved ${this.distribution} ${restored.release.version} for the current floating artifact`);
|
||||
this.floatingVersionVerified = true;
|
||||
return { ...javaRelease, version: restored.release.version };
|
||||
}
|
||||
async registerFloatingResolution(javaRelease) {
|
||||
@@ -609,6 +622,19 @@ class JavaBase {
|
||||
? architecturePath
|
||||
: null;
|
||||
}
|
||||
/**
|
||||
* Locates an installation of an exact version in the tool cache, unlike
|
||||
* `findInToolcache()` which returns the newest entry satisfying the requested
|
||||
* range. Used to reuse a JDK the runner already holds instead of downloading
|
||||
* the identical artifact again.
|
||||
*/
|
||||
findConcreteVersionInToolcache(version) {
|
||||
if (!semver_default().valid(version)) {
|
||||
return null;
|
||||
}
|
||||
const installedPath = this.getRestoredJdkPath(version);
|
||||
return installedPath ? { version, path: installedPath } : null;
|
||||
}
|
||||
getJdkReleaseIdentity(javaRelease) {
|
||||
if (javaRelease.checksum) {
|
||||
return `${javaRelease.checksum.algorithm}:${javaRelease.checksum.value}`;
|
||||
|
||||
@@ -33,6 +33,12 @@ export abstract class JavaBase {
|
||||
protected checkLatest: boolean;
|
||||
protected forceDownload: boolean;
|
||||
protected cacheJdk: boolean;
|
||||
/**
|
||||
* Whether the concrete version of a floating release has been established
|
||||
* from the checksum-bound resolution cache. Until then the release version is
|
||||
* only the requested major and says nothing about the bytes behind the URL.
|
||||
*/
|
||||
private floatingVersionVerified = false;
|
||||
protected setDefault: boolean;
|
||||
protected verifySignature: boolean;
|
||||
protected verifySignaturePublicKey: string | undefined;
|
||||
@@ -186,10 +192,16 @@ export abstract class JavaBase {
|
||||
let javaRelease = await this.resolveJavaRelease();
|
||||
core.info(`Resolved latest version as ${javaRelease.version}`);
|
||||
if (javaRelease.floating) {
|
||||
// A tool-cache entry has no source identity. Even when its concrete
|
||||
// version matches, only the checksum-bound JDK cache can prove that
|
||||
// it contains the bytes currently served by the mutable URL.
|
||||
foundJava = null;
|
||||
// A tool-cache entry has no source identity, and until the
|
||||
// checksum-bound resolution cache maps the current artifact to a
|
||||
// concrete version the release version is still just the requested
|
||||
// major — so nothing already on the runner can be trusted. Once that
|
||||
// mapping is known, an installation of exactly that version is the
|
||||
// artifact we would otherwise download.
|
||||
foundJava =
|
||||
this.floatingVersionVerified && !this.forceDownload
|
||||
? this.findConcreteVersionInToolcache(javaRelease.version)
|
||||
: null;
|
||||
}
|
||||
if (!this.forceDownload && foundJava?.version === javaRelease.version) {
|
||||
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
|
||||
@@ -390,6 +402,7 @@ export abstract class JavaBase {
|
||||
core.info(
|
||||
`Resolved ${this.distribution} ${restored.release.version} for the current floating artifact`
|
||||
);
|
||||
this.floatingVersionVerified = true;
|
||||
return {...javaRelease, version: restored.release.version};
|
||||
}
|
||||
|
||||
@@ -531,6 +544,22 @@ export abstract class JavaBase {
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates an installation of an exact version in the tool cache, unlike
|
||||
* `findInToolcache()` which returns the newest entry satisfying the requested
|
||||
* range. Used to reuse a JDK the runner already holds instead of downloading
|
||||
* the identical artifact again.
|
||||
*/
|
||||
private findConcreteVersionInToolcache(
|
||||
version: string
|
||||
): JavaInstallerResults | null {
|
||||
if (!semver.valid(version)) {
|
||||
return null;
|
||||
}
|
||||
const installedPath = this.getRestoredJdkPath(version);
|
||||
return installedPath ? {version, path: installedPath} : null;
|
||||
}
|
||||
|
||||
private getJdkReleaseIdentity(javaRelease: JavaDownloadRelease): string {
|
||||
if (javaRelease.checksum) {
|
||||
return `${javaRelease.checksum.algorithm}:${javaRelease.checksum.value}`;
|
||||
|
||||
Reference in New Issue
Block a user