Sitelet https://github.com/actions/setup-java/pull/1113/commits/555b701141de7889ac38f2ef233f7e8847875c2c
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
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>
  • Loading branch information
brunoborges and Copilot committed Jul 14, 2026
commit 555b701141de7889ac38f2ef233f7e8847875c2c
99 changes: 73 additions & 26 deletions __tests__/toolchains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: altHome,
overwriteSettings: true
settingsDirectory: altHome
});

expect(fs.existsSync(m2Dir)).toBe(false);
Expand Down Expand Up @@ -95,8 +94,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -177,8 +175,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -263,8 +260,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -341,8 +337,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -412,8 +407,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -505,8 +499,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -565,8 +558,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -624,8 +616,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -708,8 +699,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand Down Expand Up @@ -788,8 +778,7 @@ describe('toolchains tests', () => {

await toolchains.createToolchainsSettings({
jdkInfo,
settingsDirectory: m2Dir,
overwriteSettings: true
settingsDirectory: m2Dir
});

expect(fs.existsSync(m2Dir)).toBe(true);
Expand All @@ -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',
Expand Down Expand Up @@ -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('<id>sun_1.6</id>');
expect(updated).toContain('<jdkHome>/opt/jdk/sun/1.6</jdkHome>');
// ... and the newly installed JDK must be appended.
Comment thread
Copilot marked this conversation as resolved.
Outdated
expect(updated).toContain('<id>temurin_17</id>');
expect(updated).toContain('<vendor>Eclipse Temurin</vendor>');
expect(updated).toContain(`<jdkHome>${jdkInfo.jdkHome}</jdkHome>`);
}, 100000);

it('generates valid toolchains.xml with minimal configuration', () => {
Expand Down Expand Up @@ -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(`<id>${run.id}</id>`);
expect(contents).toContain(`<jdkHome>${run.jdkHome}</jdkHome>`);
}
// Exactly one <toolchain> entry per run – no duplicates, none dropped.
expect((contents.match(/<toolchain>/g) || []).length).toBe(runs.length);
}, 100000);
});
27 changes: 13 additions & 14 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81751,37 +81751,34 @@ 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* () {
const vendor = core.getInput(constants.INPUT_MVN_TOOLCHAIN_VENDOR) || distributionName;
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,
vendor,
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)
// otherwise use the home/.m2/ path
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;
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 13 additions & 28 deletions src/toolchains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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: {
Expand All @@ -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}`
Expand All @@ -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
Expand Down Expand Up @@ -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, {
Expand Down