import { lang, query as q } from '@renovatebot/good-enough-parser'; import { isTruthy } from '@sindresorhus/is'; import { quote } from 'shlex'; import upath from 'upath'; import { TEMPORARY_ERROR } from '../../../constants/error-messages.ts'; import { logger } from '../../../logger/index.ts'; import { exec, getToolSettingsOptions, gradleJvmArg, } from '../../../util/exec/index.ts'; import type { ExecOptions } from '../../../util/exec/types.ts'; import { localPathExists, readLocalFile, writeLocalFile, } from '../../../util/fs/index.ts'; import { getRepoStatus } from '../../../util/git/index.ts'; import type { StatusResult } from '../../../util/git/types.ts'; import { Http } from '../../../util/http/index.ts'; import { newlineRegex, regEx } from '../../../util/regex.ts'; import { replaceAt } from '../../../util/string.ts'; import { isGradleExecutionAllowed } from '../gradle/artifacts.ts'; import { updateArtifacts as gradleUpdateArtifacts } from '../gradle/index.ts'; import type { UpdateArtifact, UpdateArtifactsConfig, UpdateArtifactsResult, } from '../types.ts'; import { extraEnv, getJavaConstraint, gradleWrapperFileName, prepareGradleCommand, } from './utils.ts'; const http = new Http('gradle-wrapper'); const groovy = lang.createLang('groovy'); type Ctx = string[]; async function addIfUpdated( status: StatusResult, fileProjectPath: string, ): Promise { if (status.modified.includes(fileProjectPath)) { return { file: { type: 'addition', path: fileProjectPath, contents: await readLocalFile(fileProjectPath), }, }; } return null; } function getDistributionUrl(newPackageFileContent: string): string | null { const distributionUrlLine = newPackageFileContent .split(newlineRegex) .find((line) => line.startsWith('distributionUrl=')); if (distributionUrlLine) { return distributionUrlLine .replace('distributionUrl=', '') .replace('https\\:', 'https:'); } return null; } async function getDistributionChecksum(url: string): Promise { const { body } = await http.getText(`${url}.sha256`); return body; } export async function updateBuildFile( localGradleDir: string, wrapperProperties: Record, ): Promise { let buildFileName = upath.join(localGradleDir, 'build.gradle'); if (!(await localPathExists(buildFileName))) { buildFileName = upath.join(localGradleDir, 'build.gradle.kts'); } const buildFileContent = await readLocalFile(buildFileName, 'utf8'); if (!buildFileContent) { logger.debug('build.gradle or build.gradle.kts not found'); return buildFileName; } let buildFileUpdated = buildFileContent; for (const [propertyName, newValue] of Object.entries(wrapperProperties)) { if (!newValue) { continue; } const query = q.tree({ type: 'wrapped-tree', maxDepth: 1, search: q .sym(propertyName) .op('=') .str((ctx, { value, offset }) => { buildFileUpdated = replaceAt( buildFileUpdated, offset, value, newValue, ); return ctx; }), }); groovy.query(buildFileUpdated, query, []); } await writeLocalFile(buildFileName, buildFileUpdated); return buildFileName; } export async function updateLockFiles( buildFileName: string, config: UpdateArtifactsConfig, ): Promise { const buildFileContent = await readLocalFile(buildFileName, 'utf8'); if (!buildFileContent) { logger.debug('build.gradle or build.gradle.kts not found'); return null; } return await gradleUpdateArtifacts({ packageFileName: buildFileName, updatedDeps: [], newPackageFileContent: buildFileContent, config, }); } export async function updateArtifacts({ packageFileName, newPackageFileContent, updatedDeps, config, }: UpdateArtifact): Promise { try { logger.debug({ updatedDeps }, 'gradle-wrapper.updateArtifacts()'); const localGradleDir = upath.join(upath.dirname(packageFileName), '../../'); const gradlewFile = upath.join(localGradleDir, gradleWrapperFileName()); let cmd = await prepareGradleCommand(gradlewFile); if (!cmd) { logger.info('No gradlew found - skipping Artifacts update'); return null; } if (!isGradleExecutionAllowed(gradlewFile)) { logger.trace( 'Not allowed to execute gradle due to allowedUnsafeExecutions - aborting update', ); return null; } // Limit the Gradle daemon Java heap memory size to prevent OOM errors // leading to Renovate kernel-OOMs and timeouts. See #39558 cmd += gradleJvmArg(getToolSettingsOptions(config.toolSettings)); cmd += ' :wrapper'; let checksum: string | null = null; const distributionUrl = getDistributionUrl(newPackageFileContent); if (distributionUrl) { cmd += ` --gradle-distribution-url ${quote(distributionUrl)}`; if (newPackageFileContent.includes('distributionSha256Sum=')) { //update checksum in case of distributionSha256Sum in properties then run wrapper checksum = await getDistributionChecksum(distributionUrl); await writeLocalFile( packageFileName, newPackageFileContent.replace( regEx(/distributionSha256Sum=.*/), `distributionSha256Sum=${checksum}`, ), ); cmd += ` --gradle-distribution-sha256-sum ${quote(checksum)}`; } } else { cmd += ` --gradle-version ${quote(config.newValue!)}`; } logger.debug(`Updating gradle wrapper: "${cmd}"`); const execOptions: ExecOptions = { cwdFile: gradlewFile, docker: {}, extraEnv, toolConstraints: [ { toolName: 'java', constraint: config.constraints?.java ?? (await getJavaConstraint(config.currentValue, gradlewFile)), }, ], }; try { await exec(cmd, execOptions); } catch (err) { // istanbul ignore if if (err.message === TEMPORARY_ERROR) { throw err; } logger.warn( { err }, 'Error executing gradle wrapper update command. This may not necessarily be a blocker to the update, so please verify with the gradle wrapper output logs.', ); } const buildFileName = await updateBuildFile(localGradleDir, { gradleVersion: config.newValue, distributionSha256Sum: checksum, distributionUrl, }); const lockFiles = await updateLockFiles(buildFileName, config); const status = await getRepoStatus(); const artifactFileNames = [ packageFileName, buildFileName, ...['gradle/wrapper/gradle-wrapper.jar', 'gradlew', 'gradlew.bat'].map( (filename) => upath.join(localGradleDir, filename), ), ]; const updateArtifactsResult = ( await Promise.all( artifactFileNames.map((fileProjectPath) => addIfUpdated(status, fileProjectPath), ), ) ).filter(isTruthy); if (lockFiles) { updateArtifactsResult.push(...lockFiles); } logger.debug( { files: updateArtifactsResult.map((r) => r.file?.path) }, `Returning updated gradle-wrapper files`, ); return updateArtifactsResult; } catch (err) { logger.debug({ err }, 'Error setting new Gradle Wrapper release value'); return [ { artifactError: { fileName: packageFileName, stderr: err.message, }, }, ]; } }