From 555b701141de7889ac38f2ef233f7e8847875c2c Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 14 Jul 2026 13:02:48 -0400 Subject: [PATCH 1/2] Preserve Maven toolchains across repeated setup-java runs (#1099) Backport of #1111 to releases/v5. Toolchain generation was gated behind the `overwrite-settings` input, which is documented to control only regeneration of `settings.xml`. Because `generateToolchainDefinition` already performs a non-destructive merge (existing JDK, custom, and user-managed toolchains are preserved, and only an entry with the same `type` + `provides.id` is replaced), skipping the write when `overwrite-settings: false` caused later setup-java executions to drop toolchain entries registered by earlier runs. Decouple toolchains generation from `overwrite-settings`: the toolchains file is now always written, so consecutive runs accumulate every JDK. `settings.xml` behavior (auth.ts) is unchanged. - src/toolchains.ts: drop overwriteSettings from configureToolchains / createToolchainsSettings / writeToolchainsFileToDisk; always write. - __tests__/toolchains.test.ts: update call sites, rewrite the "does not overwrite" test to assert non-destructive extension, and add a regression test for consecutive configureToolchains executions. - docs/advanced-usage.md: clarify merge is non-destructive and independent of overwrite-settings. - dist/setup/index.js: rebuilt. Fixes #1099 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/toolchains.test.ts | 99 ++++++++++++++++++++++++++---------- dist/setup/index.js | 27 +++++----- docs/advanced-usage.md | 2 + src/toolchains.ts | 41 +++++---------- 4 files changed, 101 insertions(+), 68 deletions(-) diff --git a/__tests__/toolchains.test.ts b/__tests__/toolchains.test.ts index 38cac18ca..01676ecdd 100644 --- a/__tests__/toolchains.test.ts +++ b/__tests__/toolchains.test.ts @@ -46,8 +46,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: altHome, - overwriteSettings: true + settingsDirectory: altHome }); expect(fs.existsSync(m2Dir)).toBe(false); @@ -95,8 +94,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -177,8 +175,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -263,8 +260,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -341,8 +337,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -412,8 +407,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -505,8 +499,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -565,8 +558,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -624,8 +616,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -708,8 +699,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -788,8 +778,7 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: true + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); @@ -814,7 +803,7 @@ describe('toolchains tests', () => { ).toEqual(result); }, 100000); - it('does not overwrite existing toolchains.xml files', async () => { + it('extends existing toolchains.xml files instead of overwriting them', async () => { const jdkInfo = { version: '17', vendor: 'Eclipse Temurin', @@ -843,13 +832,20 @@ describe('toolchains tests', () => { await toolchains.createToolchainsSettings({ jdkInfo, - settingsDirectory: m2Dir, - overwriteSettings: false + settingsDirectory: m2Dir }); expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(toolchainsFile)).toBe(true); - expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(originalFile); + + const updated = fs.readFileSync(toolchainsFile, 'utf-8'); + // The pre-existing (Sun 1.6) toolchain must be preserved ... + expect(updated).toContain('sun_1.6'); + expect(updated).toContain('/opt/jdk/sun/1.6'); + // ... and the newly installed JDK must be appended. + expect(updated).toContain('temurin_17'); + expect(updated).toContain('Eclipse Temurin'); + expect(updated).toContain(`${jdkInfo.jdkHome}`); }, 100000); it('generates valid toolchains.xml with minimal configuration', () => { @@ -914,4 +910,55 @@ describe('toolchains tests', () => { ) ); }, 100000); + + it('preserves toolchains from previous executions across multiple setup-java runs', async () => { + // Regression test for https://github.com/actions/setup-java/issues/1099 + // Running setup-java several times in the same job (e.g. multiple steps / multiple + // java-version entries) must accumulate every JDK in toolchains.xml rather + // than replacing previously registered entries. + jest.spyOn(core, 'getInput').mockImplementation((name: string) => { + if (name === 'settings-path') return m2Dir; + return ''; + }); + + const runs = [ + { + version: '8', + distributionName: 'temurin', + id: 'temurin_8', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/8.0.1-12/x64' + }, + { + version: '11', + distributionName: 'temurin', + id: 'temurin_11', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.1-12/x64' + }, + { + version: '17', + distributionName: 'temurin', + id: 'temurin_17', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64' + } + ]; + + for (const run of runs) { + await toolchains.configureToolchains( + run.version, + run.distributionName, + run.jdkHome, + undefined + ); + } + + expect(fs.existsSync(toolchainsFile)).toBe(true); + const contents = fs.readFileSync(toolchainsFile, 'utf-8'); + + for (const run of runs) { + expect(contents).toContain(`${run.id}`); + expect(contents).toContain(`${run.jdkHome}`); + } + // Exactly one entry per run – no duplicates, none dropped. + expect((contents.match(//g) || []).length).toBe(runs.length); + }, 100000); }); diff --git a/dist/setup/index.js b/dist/setup/index.js index 7b098a526..fcd1d062c 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -81751,7 +81751,6 @@ const path = __importStar(__nccwpck_require__(16928)); const core = __importStar(__nccwpck_require__(37484)); const io = __importStar(__nccwpck_require__(94994)); const constants = __importStar(__nccwpck_require__(27242)); -const util_1 = __nccwpck_require__(54527); const xmlbuilder2_1 = __nccwpck_require__(54697); function configureToolchains(version, distributionName, jdkHome, toolchainId) { return __awaiter(this, void 0, void 0, function* () { @@ -81759,7 +81758,6 @@ function configureToolchains(version, distributionName, jdkHome, toolchainId) { const id = toolchainId || `${vendor}_${version}`; const settingsDirectory = core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); - const overwriteSettings = (0, util_1.getBooleanInput)(constants.INPUT_OVERWRITE_SETTINGS, true); yield createToolchainsSettings({ jdkInfo: { version, @@ -81767,13 +81765,12 @@ function configureToolchains(version, distributionName, jdkHome, toolchainId) { id, jdkHome }, - settingsDirectory, - overwriteSettings + settingsDirectory }); }); } exports.configureToolchains = configureToolchains; -function createToolchainsSettings({ jdkInfo, settingsDirectory, overwriteSettings }) { +function createToolchainsSettings({ jdkInfo, settingsDirectory }) { return __awaiter(this, void 0, void 0, function* () { core.info(`Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}`); // when an alternate m2 location is specified use only that location (no .m2 directory) @@ -81781,7 +81778,7 @@ function createToolchainsSettings({ jdkInfo, settingsDirectory, overwriteSetting yield io.mkdirP(settingsDirectory); const originalToolchains = yield readExistingToolchainsFile(settingsDirectory); const updatedToolchains = generateToolchainDefinition(originalToolchains, jdkInfo.version, jdkInfo.vendor, jdkInfo.id, jdkInfo.jdkHome); - yield writeToolchainsFileToDisk(settingsDirectory, updatedToolchains, overwriteSettings); + yield writeToolchainsFileToDisk(settingsDirectory, updatedToolchains); }); } exports.createToolchainsSettings = createToolchainsSettings; @@ -81867,19 +81864,21 @@ function readExistingToolchainsFile(directory) { return ''; }); } -function writeToolchainsFileToDisk(directory, settings, overwriteSettings) { +function writeToolchainsFileToDisk(directory, settings) { return __awaiter(this, void 0, void 0, function* () { const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); const settingsExists = fs.existsSync(location); - if (settingsExists && overwriteSettings) { - core.info(`Overwriting existing file ${location}`); - } - else if (!settingsExists) { - core.info(`Writing to ${location}`); + // The toolchains file is produced by a non-destructive merge (existing JDK, + // custom, and non-jdk toolchains are preserved – see generateToolchainDefinition), + // so it is always safe to write it. Unlike settings.xml, it is therefore not + // gated behind the `overwrite-settings` input; that would prevent subsequent + // setup-java runs from registering additional JDKs and silently drop the + // toolchain entries created by earlier runs. + if (settingsExists) { + core.info(`Updating existing file ${location}`); } else { - core.info(`Skipping generation of ${location} because file already exists and overwriting is not enabled`); - return; + core.info(`Writing to ${location}`); } return fs.writeFileSync(location, settings, { encoding: 'utf-8', diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 633ff45bb..bbd6a8903 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -684,6 +684,8 @@ The `setup-java` action generates a basic [Maven Toolchains declaration](https:/ ### Installing Multiple JDKs With Toolchains Subsequent calls to `setup-java` with distinct distribution and version parameters will continue to extend the toolchains declaration and make all specified Java versions available. +Toolchain entries are always merged non-destructively: existing JDK, custom, and user-managed toolchains are preserved, and only an entry with the exact same `type` and `provides.id` is replaced. This behavior is independent of the `overwrite-settings` input, which only controls regeneration of `settings.xml`. As a result, running `setup-java` several times in the same job (for example in multiple steps or with multiple `java-version` values) accumulates every JDK in `toolchains.xml` instead of dropping previously registered entries. + ```yaml steps: - uses: actions/setup-java@v5 diff --git a/src/toolchains.ts b/src/toolchains.ts index a94a56302..103173e3c 100644 --- a/src/toolchains.ts +++ b/src/toolchains.ts @@ -5,7 +5,6 @@ import * as core from '@actions/core'; import * as io from '@actions/io'; import * as constants from './constants'; -import {getBooleanInput} from './util'; import {create as xmlCreate} from 'xmlbuilder2'; interface JdkInfo { @@ -27,10 +26,6 @@ export async function configureToolchains( const settingsDirectory = core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); - const overwriteSettings = getBooleanInput( - constants.INPUT_OVERWRITE_SETTINGS, - true - ); await createToolchainsSettings({ jdkInfo: { @@ -39,19 +34,16 @@ export async function configureToolchains( id, jdkHome }, - settingsDirectory, - overwriteSettings + settingsDirectory }); } export async function createToolchainsSettings({ jdkInfo, - settingsDirectory, - overwriteSettings + settingsDirectory }: { jdkInfo: JdkInfo; settingsDirectory: string; - overwriteSettings: boolean; }) { core.info( `Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}` @@ -68,11 +60,7 @@ export async function createToolchainsSettings({ jdkInfo.id, jdkInfo.jdkHome ); - await writeToolchainsFileToDisk( - settingsDirectory, - updatedToolchains, - overwriteSettings - ); + await writeToolchainsFileToDisk(settingsDirectory, updatedToolchains); } // only exported for testing purposes @@ -172,22 +160,19 @@ async function readExistingToolchainsFile(directory: string) { return ''; } -async function writeToolchainsFileToDisk( - directory: string, - settings: string, - overwriteSettings: boolean -) { +async function writeToolchainsFileToDisk(directory: string, settings: string) { const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); const settingsExists = fs.existsSync(location); - if (settingsExists && overwriteSettings) { - core.info(`Overwriting existing file ${location}`); - } else if (!settingsExists) { - core.info(`Writing to ${location}`); + // The toolchains file is produced by a non-destructive merge (existing JDK, + // custom, and non-jdk toolchains are preserved – see generateToolchainDefinition), + // so it is always safe to write it. Unlike settings.xml, it is therefore not + // gated behind the `overwrite-settings` input; that would prevent subsequent + // setup-java runs from registering additional JDKs and silently drop the + // toolchain entries created by earlier runs. + if (settingsExists) { + core.info(`Updating existing file ${location}`); } else { - core.info( - `Skipping generation of ${location} because file already exists and overwriting is not enabled` - ); - return; + core.info(`Writing to ${location}`); } return fs.writeFileSync(location, settings, { From a3f640b261b3d129b75bf25a2ce2dfd27b4c47a1 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 14 Jul 2026 13:10:25 -0400 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- __tests__/toolchains.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/toolchains.test.ts b/__tests__/toolchains.test.ts index 01676ecdd..01b2ca595 100644 --- a/__tests__/toolchains.test.ts +++ b/__tests__/toolchains.test.ts @@ -842,7 +842,7 @@ describe('toolchains tests', () => { // The pre-existing (Sun 1.6) toolchain must be preserved ... expect(updated).toContain('sun_1.6'); expect(updated).toContain('/opt/jdk/sun/1.6'); - // ... and the newly installed JDK must be appended. + // ... and the newly installed JDK must be included in the merged result. expect(updated).toContain('temurin_17'); expect(updated).toContain('Eclipse Temurin'); expect(updated).toContain(`${jdkInfo.jdkHome}`);