Skip to content
Merged
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
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 included in the merged result.
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
Loading