Skip to content

Commit c08656f

Browse files
authored
feat(gradle): allow specifying Gradle daemon heap size (renovatebot#40090)
This change introduces a heap size limitation for the Gradle daemon used when updating the Gradle wrapper, defaults to 512m. The heap size is limited to the globally configured limit, which defaults to 512m. It can be configured on the repository level, but not exceed the globally configured limit. A hard coded mimiumum heap size of 512m is enforced. The configuration option is named `toolSettings`, which has two properties for the maximum and initial heap size. See `docs/usage/configuration-options#toolSettings` for more details.
1 parent e8925a9 commit c08656f

File tree

9 files changed

+443
-10
lines changed

9 files changed

+443
-10
lines changed

docs/usage/configuration-options.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4690,6 +4690,36 @@ We recommend that you only configure the `timezone` option if _both_ of these ar
46904690

46914691
Please see the above link for valid timezone names.
46924692

4693+
## toolSettings
4694+
4695+
When Renovate updates a dependency and needs to invoke processes leveraging Java, for example Gradle for [the `gradle-wrapper` manager](./modules/manager/gradle-wrapper/index.md), the repository's Gradle Wrapper will be invoked, if present.
4696+
4697+
The JVM heap size for the Java invocations is 512m by default.
4698+
This can be overridden using the following options.
4699+
4700+
This option can be used on the repository level and in the [Renovate configuration](./self-hosted-configuration.md) using the following options.
4701+
4702+
<!-- prettier-ignore -->
4703+
!!! note
4704+
The JVM memory settings specified in the global self-hosted configuration set by the administrator in [`toolSettings.jvmMaxMemory`](./self-hosted-configuration.md) limits the memory settings for all repositories.
4705+
The default limit for all repositories is 512m.
4706+
4707+
<!-- prettier-ignore -->
4708+
!!! note
4709+
The JVM memory settings are considered for the `gradle-wrapper` manager.
4710+
4711+
### jvmMaxMemory
4712+
4713+
Maximum heap size in MB for Java VMs.
4714+
Defaults to `512` for both the repository level and self-hosted configuration.
4715+
4716+
To allow repositories to use _more_ than 512m of heap during the Gradle Wrapper update, configure the `jvmMaxMemory` option in the [`toolSettings.jvmMaxMemory`](./self-hosted-configuration.md).
4717+
4718+
### jvmMemory
4719+
4720+
Initial heap size in MB for Java VMs. Must be less than or equal to `jvmMaxMemory`.
4721+
Defaults to `jvmMaxMemory`.
4722+
46934723
## updateInternalDeps
46944724

46954725
Renovate defaults to skipping any internal package dependencies within monorepos.

lib/config/global.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class GlobalConfig {
4343
'presetCachePersistence',
4444
's3Endpoint',
4545
's3PathStyle',
46+
'toolSettings',
4647
'userAgent',
4748
];
4849

lib/config/options/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3352,6 +3352,35 @@ const options: Readonly<RenovateOptions>[] = [
33523352
default: false,
33533353
globalOnly: true,
33543354
},
3355+
{
3356+
name: 'toolSettings',
3357+
description:
3358+
'Tool specific configuration. Global self-hosted configuration takes precedence.',
3359+
type: 'object',
3360+
default: {
3361+
jvmMaxMemory: 512,
3362+
jvmMemory: 512,
3363+
},
3364+
cli: false,
3365+
},
3366+
{
3367+
name: 'jvmMaxMemory',
3368+
description:
3369+
'Maximum JVM memory in MB to use for updates that use a Java VM, like the Gradle Wrapper, defaults to 512. Repo configuration for this value will be ignored if it exceeds the global configuration for `manager.jvmMaxMemory`',
3370+
type: 'integer',
3371+
parents: ['toolSettings'],
3372+
cli: false,
3373+
env: false,
3374+
},
3375+
{
3376+
name: 'jvmMemory',
3377+
description:
3378+
'Initial JVM memory in MB to use for updates that use a Java VM, like the Gradle Wrapper, defaults to `jvmMaxMemory`. Repo configuration for this value will be ignored if it exceeds the global configuration for `manager.jvmMaxMemory`',
3379+
type: 'integer',
3380+
parents: ['toolSettings'],
3381+
cli: false,
3382+
env: false,
3383+
},
33553384
];
33563385

33573386
export function getOptions(): Readonly<RenovateOptions>[] {

lib/config/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export interface RepoGlobalConfig extends GlobalInheritableConfig {
254254
ignorePrAuthor?: boolean;
255255
allowedUnsafeExecutions?: AllowedUnsafeExecution[];
256256
onboardingAutoCloseAge?: number;
257+
toolSettings?: ToolSettingsOptions;
257258
}
258259

259260
/**
@@ -462,6 +463,7 @@ export interface RenovateConfig
462463
minimumGroupSize?: number;
463464
configFileNames?: string[];
464465
minimumReleaseAgeBehaviour?: MinimumReleaseAgeBehaviour;
466+
toolSettings?: ToolSettingsOptions;
465467
}
466468

467469
const CustomDatasourceFormats = [
@@ -599,6 +601,7 @@ export type AllowedParents =
599601
| 'packageRules'
600602
| 'postUpgradeTasks'
601603
| 'vulnerabilityAlerts'
604+
| 'toolSettings'
602605
| ManagerName
603606
| UpdateTypeOptions;
604607
export interface RenovateOptionBase {
@@ -803,3 +806,8 @@ export interface BumpVersionConfig {
803806
matchStrings: string[];
804807
name?: string;
805808
}
809+
810+
export interface ToolSettingsOptions {
811+
jvmMaxMemory?: number;
812+
jvmMemory?: number;
813+
}

lib/modules/manager/gradle-wrapper/artifacts.spec.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { StatusResult } from '../../../util/git/types.ts';
99
import { getPkgReleases } from '../../datasource/index.ts';
1010
import { updateArtifacts as gradleUpdateArtifacts } from '../gradle/index.ts';
1111
import type { UpdateArtifactsConfig, UpdateArtifactsResult } from '../types.ts';
12-
import { updateBuildFile, updateLockFiles } from './artifacts.ts';
12+
import { gradleJvmArg, updateBuildFile, updateLockFiles } from './artifacts.ts';
1313
import { updateArtifacts } from './index.ts';
1414
import { envMock, mockExecAll } from '~test/exec-util.ts';
1515
import { Fixtures } from '~test/fixtures.ts';
@@ -51,6 +51,9 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
5151
GlobalConfig.set(adminConfig);
5252
resetPrefetchedImages();
5353

54+
// remove any test-specific overrides
55+
delete config.toolSettings;
56+
5457
fs.readLocalFile.mockResolvedValue('test');
5558
fs.statLocalFile.mockResolvedValue(
5659
partial<Stats>({
@@ -70,7 +73,56 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
7073
});
7174
});
7275

76+
describe('gradleJvmArg()', () => {
77+
it('takes the values given to it, and returns the JVM arguments', () => {
78+
const result = gradleJvmArg({ jvmMemory: 256, jvmMaxMemory: 768 });
79+
expect(result).toBe(' -Dorg.gradle.jvmargs="-Xms256m -Xmx768m"');
80+
});
81+
});
82+
7383
describe('updateArtifacts()', () => {
84+
it('Custom Gradle Wrapper heap settings are populated', async () => {
85+
const execSnapshots = mockExecAll();
86+
httpMock
87+
.scope('https://services.gradle.org')
88+
.get('/distributions/gradle-6.3-bin.zip.sha256')
89+
.reply(
90+
200,
91+
'038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768',
92+
);
93+
git.getRepoStatus.mockResolvedValueOnce(
94+
partial<StatusResult>({
95+
modified: ['gradle/wrapper/gradle-wrapper.properties'],
96+
}),
97+
);
98+
GlobalConfig.set({
99+
...adminConfig,
100+
toolSettings: { jvmMaxMemory: 600 },
101+
});
102+
103+
const result = await updateArtifacts({
104+
packageFileName: 'gradle/wrapper/gradle-wrapper.properties',
105+
updatedDeps: [],
106+
newPackageFileContent: `distributionSha256Sum=336b6898b491f6334502d8074a6b8c2d73ed83b92123106bd4bf837f04111043\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.3-bin.zip`,
107+
config,
108+
});
109+
110+
expect(result).toEqual([
111+
{
112+
file: {
113+
contents: 'test',
114+
path: 'gradle/wrapper/gradle-wrapper.properties',
115+
type: 'addition',
116+
},
117+
},
118+
]);
119+
expect(execSnapshots).toMatchObject([
120+
{
121+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms600m -Xmx600m" :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip --gradle-distribution-sha256-sum 038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768',
122+
},
123+
]);
124+
});
125+
74126
it('replaces existing value', async () => {
75127
const execSnapshots = mockExecAll();
76128
git.getRepoStatus.mockResolvedValue(
@@ -107,7 +159,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
107159
);
108160
expect(execSnapshots).toMatchObject([
109161
{
110-
cmd: './gradlew :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip',
162+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip',
111163
options: {
112164
cwd: '/tmp/github/some/repo',
113165
env: {
@@ -119,7 +171,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
119171
]);
120172
});
121173

122-
it('aborts if allowedUnsafeExecutions does not include `gradleWrapper`', async () => {
174+
it('aborts if allowedUnsafeExecutions does not include `toolSettings`', async () => {
123175
GlobalConfig.set({
124176
...adminConfig,
125177
allowedUnsafeExecutions: [],
@@ -189,7 +241,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
189241
expect(result).toBeEmptyArray();
190242
expect(execSnapshots).toMatchObject([
191243
{
192-
cmd: './gradlew :wrapper --gradle-version 5.6.4',
244+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" :wrapper --gradle-version 5.6.4',
193245
options: { cwd: '/tmp/github/some/repo' },
194246
},
195247
]);
@@ -246,7 +298,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
246298
' bash -l -c "' +
247299
'install-tool java 11.0.1' +
248300
' && ' +
249-
'./gradlew :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip --gradle-distribution-sha256-sum 038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768' +
301+
'./gradlew -Dorg.gradle.jvmargs=\\"-Xms512m -Xmx512m\\" :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip --gradle-distribution-sha256-sum 038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768' +
250302
'"',
251303
options: { cwd: '/tmp/github/some/repo' },
252304
},
@@ -288,7 +340,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
288340
expect(execSnapshots).toMatchObject([
289341
{ cmd: 'install-tool java 11.0.1' },
290342
{
291-
cmd: './gradlew :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip --gradle-distribution-sha256-sum 038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768',
343+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip --gradle-distribution-sha256-sum 038794feef1f4745c6347107b6726279d1c824f3fc634b60f86ace1e9fbd1768',
292344
options: { cwd: '/tmp/github/some/repo' },
293345
},
294346
]);
@@ -355,7 +407,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
355407
);
356408
expect(execSnapshots).toMatchObject([
357409
{
358-
cmd: './gradlew :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip',
410+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" :wrapper --gradle-distribution-url https://services.gradle.org/distributions/gradle-6.3-bin.zip',
359411
options: {
360412
cwd: '/tmp/github/some/repo/sub',
361413
env: {
@@ -486,7 +538,7 @@ describe('modules/manager/gradle-wrapper/artifacts', () => {
486538
expect(res).toStrictEqual(updatedArtifacts);
487539
expect(execSnapshots).toMatchObject([
488540
{
489-
cmd: './gradlew :wrapper --gradle-version 8.2',
541+
cmd: './gradlew -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" :wrapper --gradle-version 8.2',
490542
},
491543
]);
492544
});

lib/modules/manager/gradle-wrapper/artifacts.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { lang, query as q } from '@renovatebot/good-enough-parser';
22
import { isTruthy } from '@sindresorhus/is';
33
import { quote } from 'shlex';
44
import upath from 'upath';
5+
import type { ToolSettingsOptions } from '../../../config/types.ts';
56
import { TEMPORARY_ERROR } from '../../../constants/error-messages.ts';
67
import { logger } from '../../../logger/index.ts';
7-
import { exec } from '../../../util/exec/index.ts';
8+
import { exec, getToolSettingsOptions } from '../../../util/exec/index.ts';
89
import type { ExecOptions } from '../../../util/exec/types.ts';
910
import {
1011
localPathExists,
@@ -131,6 +132,10 @@ export async function updateLockFiles(
131132
});
132133
}
133134

135+
export function gradleJvmArg(config: ToolSettingsOptions): string {
136+
return ` -Dorg.gradle.jvmargs="-Xms${config.jvmMemory}m -Xmx${config.jvmMaxMemory}m"`;
137+
}
138+
134139
export async function updateArtifacts({
135140
packageFileName,
136141
newPackageFileContent,
@@ -156,6 +161,9 @@ export async function updateArtifacts({
156161
return null;
157162
}
158163

164+
// Limit the Gradle daemon Java heap memory size to prevent OOM errors
165+
// leading to Renovate kernel-OOMs and timeouts. See #39558
166+
cmd += gradleJvmArg(getToolSettingsOptions(config.toolSettings));
159167
cmd += ' :wrapper';
160168

161169
let checksum: string | null = null;

lib/modules/manager/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReleaseType } from 'semver';
22
import type {
33
MatchStringsStrategy,
4+
ToolSettingsOptions,
45
UpdateType,
56
ValidationMessage,
67
} from '../../config/types.ts';
@@ -48,6 +49,7 @@ export interface UpdateArtifactsConfig {
4849
registryAliases?: Record<string, string>;
4950
skipArtifactsUpdate?: boolean;
5051
lockFiles?: string[];
52+
toolSettings?: ToolSettingsOptions;
5153
}
5254

5355
export interface RangeConfig<T = Record<string, any>> extends ManagerData<T> {

0 commit comments

Comments
 (0)