diff --git a/lib/manager/gradle/build-gradle.js b/lib/manager/gradle/build-gradle.js index 962dcbd96c0d9b6adefc150188418b0dda644c1d..d9ab74bbce7cbf03943511acc1d825cefdccf383 100644 --- a/lib/manager/gradle/build-gradle.js +++ b/lib/manager/gradle/build-gradle.js @@ -3,6 +3,8 @@ * gradle-use-latest-versions-plugin is licensed under MIT and Copyright (c) 2018 Patrik Erdes */ +let variables = {}; + function updateGradleVersion(buildGradleContent, dependency, newVersion) { if (dependency) { const updateFunctions = [ @@ -11,6 +13,8 @@ function updateGradleVersion(buildGradleContent, dependency, newVersion) { updateVersionMapVariableFormat, updateVersionStringVariableFormat, updateVersionExpressionVariableFormat, + updateGlobalVariables, + updatePropertyFileGlobalVariables, ]; // eslint-disable-next-line guard-for-in @@ -29,6 +33,31 @@ function updateGradleVersion(buildGradleContent, dependency, newVersion) { return buildGradleContent; } +function collectVersionVariables(dependencies, buildGradleContent) { + for (const dep of dependencies) { + const dependency = { + ...dep, + group: dep.depGroup, + }; + const regexes = [ + moduleStringVariableExpressionVersionFormatMatch(dependency), + moduleStringVariableInterpolationVersionFormatMatch(dependency), + moduleMapVariableVersionFormatMatch(dependency), + ]; + + for (const regex of regexes) { + const match = buildGradleContent.match(regex); + if (match) { + variables[`${dependency.group}:${dependency.name}`] = match[1]; + } + } + } +} + +function init() { + variables = {}; +} + function updateVersionStringFormat(dependency, buildGradleContent, newVersion) { const regex = moduleStringVersionFormatMatch(dependency); if (buildGradleContent.match(regex)) { @@ -93,6 +122,37 @@ function updateVersionExpressionVariableFormat( return null; } +function updateGlobalVariables(dependency, buildGradleContent, newVersion) { + const variable = variables[`${dependency.group}:${dependency.name}`]; + if (variable) { + const regex = variableDefinitionFormatMatch(variable); + const match = buildGradleContent.match(regex); + if (match) { + return buildGradleContent.replace( + variableDefinitionFormatMatch(variable), + `$1${newVersion}$3` + ); + } + } + return null; +} + +function updatePropertyFileGlobalVariables( + dependency, + buildGradleContent, + newVersion +) { + const variable = variables[`${dependency.group}:${dependency.name}`]; + if (variable) { + const regex = new RegExp(`(${variable}\\s*=\\s*)(.*)`); + const match = buildGradleContent.match(regex); + if (match) { + return buildGradleContent.replace(regex, `$1${newVersion}`); + } + } + return null; +} + function moduleStringVersionFormatMatch(dependency) { return new RegExp( `(["']${dependency.group}:${dependency.name}:)[^$].*?(["'])` @@ -130,9 +190,11 @@ function moduleStringVariableExpressionVersionFormatMatch(dependency) { } function variableDefinitionFormatMatch(variable) { - return new RegExp(`(${variable}\\s+=\\s*?["'])(.*)(["'])`); + return new RegExp(`(${variable}\\s*=\\s*?["'])(.*)(["'])`); } module.exports = { updateGradleVersion, + collectVersionVariables, + init, }; diff --git a/lib/manager/gradle/index.js b/lib/manager/gradle/index.js index 77eacbf687ba55fbb88c37d342ec726ec04e67be..af316d61de6549261944d2f76a7d074cea84c3c5 100644 --- a/lib/manager/gradle/index.js +++ b/lib/manager/gradle/index.js @@ -24,6 +24,14 @@ async function extractAllPackageFiles(config, packageFiles) { await fs.outputFile(localFileName, content); } } + + await configureUseLatestVersionPlugin(config.localDir); + const gradleSuccess = await executeGradle(config); + if (!gradleSuccess) { + return null; + } + + gradle.init(); const gradleFiles = []; for (const packageFile of packageFiles) { const content = await platform.getFile(packageFile); @@ -48,15 +56,8 @@ async function extractPackageFile(content, fileName, config) { const gradleFile = path.join(config.localDir, fileName); const baseDir = path.dirname(gradleFile); - if (isProjectRootGradle(fileName)) { - await configureUseLatestVersionPlugin(baseDir); - const gradleSuccess = await executeGradle(config); - if (!gradleSuccess) { - return null; - } - } - const deps = await extractDependenciesFromUpdatesReport(baseDir); + gradle.collectVersionVariables(deps, content); return deps.length > 0 ? { deps } : null; } @@ -177,17 +178,12 @@ async function executeGradle(config) { return true; } -function isProjectRootGradle(fileName) { - return fileName === 'build.gradle'; -} - function getDockerRenovateGradleCommandLine(localDir) { return `docker run --rm -v ${localDir}:${localDir} -w ${localDir} renovate/gradle ${GRADLE_DEPENDENCY_REPORT_COMMAND}`; } module.exports = { extractAllPackageFiles, - extractPackageFile, getPackageUpdates, updateDependency, language: 'java', diff --git a/test/manager/gradle/__snapshots__/index.spec.js.snap b/test/manager/gradle/__snapshots__/index.spec.js.snap index f1b545b4b942dad1ca73933710607e8726f78b1d..53ed5f5d7117383bc727939400e9b9321abae18e 100644 --- a/test/manager/gradle/__snapshots__/index.spec.js.snap +++ b/test/manager/gradle/__snapshots__/index.spec.js.snap @@ -1,77 +1,81 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`manager/gradle extractPackageFile should return gradle dependencies 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "0.1", - "depGroup": "com.fkorotkov", - "depName": "com.fkorotkov:gradle-libraries-plugin", - "name": "gradle-libraries-plugin", - "version": "0.1", - }, - Object { - "currentValue": "0.2.3", - "depGroup": "gradle.plugin.se.patrikerdes", - "depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin", - "name": "gradle-use-latest-versions-plugin", - "version": "0.2.3", - }, - Object { - "currentValue": "1.3", - "depGroup": "org.hamcrest", - "depName": "org.hamcrest:hamcrest-core", - "name": "hamcrest-core", - "version": "1.3", - }, - Object { - "available": Object { - "integration": null, - "milestone": null, - "release": "3.2.8", +Array [ + Object { + "deps": Array [ + Object { + "currentValue": "0.1", + "depGroup": "com.fkorotkov", + "depName": "com.fkorotkov:gradle-libraries-plugin", + "name": "gradle-libraries-plugin", + "version": "0.1", }, - "currentValue": "3.1", - "depGroup": "cglib", - "depName": "cglib:cglib-nodep", - "name": "cglib-nodep", - "version": "3.1", - }, - Object { - "available": Object { - "integration": null, - "milestone": null, - "release": "6.1.10.RELEASE", + Object { + "currentValue": "0.2.3", + "depGroup": "gradle.plugin.se.patrikerdes", + "depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin", + "name": "gradle-use-latest-versions-plugin", + "version": "0.2.3", }, - "currentValue": "6.0.9.RELEASE", - "depGroup": "org.grails", - "depName": "org.grails:gorm-hibernate5-spring-boot", - "name": "gorm-hibernate5-spring-boot", - "version": "6.0.9.RELEASE", - }, - Object { - "available": Object { - "integration": null, - "milestone": null, - "release": "8.0.12", + Object { + "currentValue": "1.3", + "depGroup": "org.hamcrest", + "depName": "org.hamcrest:hamcrest-core", + "name": "hamcrest-core", + "version": "1.3", }, - "currentValue": "5.1.41", - "depGroup": "mysql", - "depName": "mysql:mysql-connector-java", - "name": "mysql-connector-java", - "version": "5.1.41", - }, - Object { - "available": Object { - "integration": null, - "milestone": null, - "release": "2.0.5.RELEASE", + Object { + "available": Object { + "integration": null, + "milestone": null, + "release": "3.2.8", + }, + "currentValue": "3.1", + "depGroup": "cglib", + "depName": "cglib:cglib-nodep", + "name": "cglib-nodep", + "version": "3.1", }, - "currentValue": "1.5.2.RELEASE", - "depGroup": "org.springframework.boot", - "depName": "org.springframework.boot:spring-boot-starter-test", - "name": "spring-boot-starter-test", - "version": "1.5.2.RELEASE", - }, - ], -} + Object { + "available": Object { + "integration": null, + "milestone": null, + "release": "6.1.10.RELEASE", + }, + "currentValue": "6.0.9.RELEASE", + "depGroup": "org.grails", + "depName": "org.grails:gorm-hibernate5-spring-boot", + "name": "gorm-hibernate5-spring-boot", + "version": "6.0.9.RELEASE", + }, + Object { + "available": Object { + "integration": null, + "milestone": null, + "release": "8.0.12", + }, + "currentValue": "5.1.41", + "depGroup": "mysql", + "depName": "mysql:mysql-connector-java", + "name": "mysql-connector-java", + "version": "5.1.41", + }, + Object { + "available": Object { + "integration": null, + "milestone": null, + "release": "2.0.5.RELEASE", + }, + "currentValue": "1.5.2.RELEASE", + "depGroup": "org.springframework.boot", + "depName": "org.springframework.boot:spring-boot-starter-test", + "name": "spring-boot-starter-test", + "version": "1.5.2.RELEASE", + }, + ], + "manager": "gradle", + "packageFile": "build.gradle", + }, +] `; diff --git a/test/manager/gradle/build-gradle.spec.js b/test/manager/gradle/build-gradle.spec.js index 8ddd97cb9b00c9058d77dc5d44f3e5b0c9070bf4..1726b5e41fd7db5d7668a81d4228500f5dd27c29 100644 --- a/test/manager/gradle/build-gradle.spec.js +++ b/test/manager/gradle/build-gradle.spec.js @@ -1,6 +1,10 @@ const gradle = require('../../../lib/manager/gradle/build-gradle'); describe('lib/manager/gradle/updateGradleVersion', () => { + beforeEach(() => { + gradle.init(); + }); + it('returns the same file if dependency is null', () => { const gradleFile = "runtime('mysql:mysql-connector-java:6.0.5')"; const updatedGradleFile = gradle.updateGradleVersion( @@ -34,8 +38,8 @@ describe('lib/manager/gradle/updateGradleVersion', () => { }); it('returns a file updated if the version defined as map is found', () => { - const gradleFile = `compile group : 'mysql' , - name : 'mysql-connector-java', + const gradleFile = `compile group : 'mysql' , + name : 'mysql-connector-java', version: '6.0.5'`; const updatedGradleFile = gradle.updateGradleVersion( gradleFile, @@ -43,29 +47,29 @@ describe('lib/manager/gradle/updateGradleVersion', () => { '7.0.0' ); expect(updatedGradleFile).toEqual( - `compile group : 'mysql' , - name : 'mysql-connector-java', + `compile group : 'mysql' , + name : 'mysql-connector-java', version: '7.0.0'` ); }); it('should returns a file updated if the version defined in a variable as a string is found', () => { - const gradleFile = `String mysqlVersion = "6.0.5" - runtime ( 'mysql:mysql-connector-java:$mysqlVersion' ) + const gradleFile = `String mysqlVersion= "6.0.5" + runtime ( "mysql:mysql-connector-java:$mysqlVersion" ) `; const updatedGradleFile = gradle.updateGradleVersion( gradleFile, { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' }, '7.0.0' ); - expect(updatedGradleFile).toEqual(`String mysqlVersion = "7.0.0" - runtime ( 'mysql:mysql-connector-java:$mysqlVersion' ) + expect(updatedGradleFile).toEqual(`String mysqlVersion= "7.0.0" + runtime ( "mysql:mysql-connector-java:$mysqlVersion" ) `); }); it('should returns a file updated if the version defined in a expression as a string is found', () => { const gradleFile = `String mysqlVersion = "6.0.5" - runtime ( 'mysql:mysql-connector-java:\${mysqlVersion}' ) + runtime ( "mysql:mysql-connector-java:\${mysqlVersion}" ) `; const updatedGradleFile = gradle.updateGradleVersion( gradleFile, @@ -73,14 +77,14 @@ describe('lib/manager/gradle/updateGradleVersion', () => { '7.0.0' ); expect(updatedGradleFile).toEqual(`String mysqlVersion = "7.0.0" - runtime ( 'mysql:mysql-connector-java:\${mysqlVersion}' ) + runtime ( "mysql:mysql-connector-java:\${mysqlVersion}" ) `); }); it('should returns a file updated if the version defined in a variable as a map is found', () => { const gradleFile = `String mysqlVersion = "6.0.5" - compile group : 'mysql' , - name : 'mysql-connector-java', + compile group : 'mysql' , + name : 'mysql-connector-java', version : mysqlVersion `; const updatedGradleFile = gradle.updateGradleVersion( @@ -90,10 +94,92 @@ describe('lib/manager/gradle/updateGradleVersion', () => { ); expect(updatedGradleFile).toEqual( `String mysqlVersion = "7.0.0" - compile group : 'mysql' , - name : 'mysql-connector-java', + compile group : 'mysql' , + name : 'mysql-connector-java', version : mysqlVersion ` ); }); + + it('should replace a external groovy variable assigned to a specific dependency', () => { + const gradleFile = + 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + const mysqlDependency = { + group: 'mysql', + depGroup: 'mysql', + name: 'mysql-connector-java', + version: '6.0.5', + }; + gradle.collectVersionVariables([mysqlDependency], gradleFile); + + const gradleWithVersionFile = 'String mysqlVersion = "6.0.5"'; + const updatedGradleFile = gradle.updateGradleVersion( + gradleWithVersionFile, + mysqlDependency, + '7.0.0' + ); + expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + }); + + it('should replace a external property variable assigned to a specific dependency', () => { + const gradleFile = + 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + const mysqlDependency = { + group: 'mysql', + depGroup: 'mysql', + name: 'mysql-connector-java', + version: '6.0.5', + }; + gradle.collectVersionVariables([mysqlDependency], gradleFile); + + const propertyFile = 'mysqlVersion=6.0.5'; + const updatedGradleFile = gradle.updateGradleVersion( + propertyFile, + mysqlDependency, + '7.0.0' + ); + expect(updatedGradleFile).toEqual('mysqlVersion=7.0.0'); + }); + + it('should replace a external variable assigned to a map dependency', () => { + const gradleFile = `compile group : 'mysql' , + name : 'mysql-connector-java', + version : mysqlVersion + `; + const mysqlDependency = { + group: 'mysql', + depGroup: 'mysql', + name: 'mysql-connector-java', + version: '6.0.5', + }; + gradle.collectVersionVariables([mysqlDependency], gradleFile); + + const gradleWithVersionFile = 'String mysqlVersion = "6.0.5"'; + const updatedGradleFile = gradle.updateGradleVersion( + gradleWithVersionFile, + mysqlDependency, + '7.0.0' + ); + expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + }); + + it('should replace a external variable assigned to a interpolated dependency', () => { + const gradleFile = + 'runtime ( "mysql:mysql-connector-java:$mysqlVersion" )'; + const mysqlDependency = { + group: 'mysql', + depGroup: 'mysql', + name: 'mysql-connector-java', + version: '6.0.5', + }; + gradle.collectVersionVariables([mysqlDependency], gradleFile); + + const gradleWithVersionFile = 'String mysqlVersion = "6.0.5"'; + const updatedGradleFile = gradle.updateGradleVersion( + gradleWithVersionFile, + mysqlDependency, + '7.0.0' + ); + expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + }); }); diff --git a/test/manager/gradle/index.spec.js b/test/manager/gradle/index.spec.js index 1d6716fa9106202ae88160bded771b0f2c1d8d21..4351bdc866eaa1f039141e9940ed5fafa0a9e118 100644 --- a/test/manager/gradle/index.spec.js +++ b/test/manager/gradle/index.spec.js @@ -27,33 +27,29 @@ describe('manager/gradle', () => { fs.mkdir.mockReturnValue(true); fs.exists.mockReturnValue(true); exec.mockReturnValue({ stdout: 'gradle output', stderr: '' }); + platform.getFile.mockReturnValue('some content'); }); describe('extractPackageFile', () => { it('should return gradle dependencies', async () => { - const dependencies = await manager.extractPackageFile( - 'content', + const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', - config - ); - + ]); expect(dependencies).toMatchSnapshot(); }); - it('should return null if there are no dependencies', async () => { + it('should return empty if there are no dependencies', async () => { fs.readFile.mockReturnValue( fsReal.readFileSync( 'test/_fixtures/gradle/updatesReportEmpty.json', 'utf8' ) ); - const dependencies = await manager.extractPackageFile( - 'content', + const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', - config - ); + ]); - expect(dependencies).toEqual(null); + expect(dependencies).toEqual([]); }); it('should return null if gradle execution fails', async () => { @@ -61,32 +57,23 @@ describe('manager/gradle', () => { throw new Error(); }); - const dependencies = await manager.extractPackageFile( - 'content', + const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', - config - ); - + ]); expect(dependencies).toEqual(null); }); it('should return empty if there is no dependency report', async () => { - fs.readFile.mockImplementation(() => { - throw new Error(); - }); fs.exists.mockReturnValue(false); - - const dependencies = await manager.extractPackageFile( - 'content', + const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', - config - ); + ]); - expect(dependencies).toEqual(null); + expect(dependencies).toEqual([]); }); it('should execute gradle with the proper parameters', async () => { - await manager.extractPackageFile('content', 'build.gradle', config); + await manager.extractAllPackageFiles(config, ['build.gradle']); expect(exec.mock.calls[0][0]).toBe( 'gradle --init-script init.gradle dependencyUpdates -Drevision=release' @@ -97,28 +84,49 @@ describe('manager/gradle', () => { }); }); - it('should return null if no build.gradle', async () => { + it('should return null and gradle should not be executed if no build.gradle', async () => { const packageFiles = ['foo/build.gradle']; expect( await manager.extractAllPackageFiles(config, packageFiles) ).toBeNull(); + + expect(exec.mock.calls.length).toBe(0); }); it('should return empty if not content', async () => { - const packageFiles = ['build.gradle']; - const res = await manager.extractAllPackageFiles(config, packageFiles); + platform.getFile.mockReturnValue(null); + const res = await manager.extractAllPackageFiles(config, [ + 'build.gradle', + ]); expect(res).toEqual([]); }); it('should write files before extracting', async () => { - const packageFiles = ['build.gradle']; - platform.getFile.mockReturnValue('some content'); - const res = await manager.extractAllPackageFiles(config, packageFiles); - expect(res).not.toBeNull(); + const packageFiles = ['build.gradle', 'foo/build.gradle']; + await manager.extractAllPackageFiles(config, packageFiles); + + expect(toUnix(fs.outputFile.mock.calls[0][0])).toBe( + 'localDir/build.gradle' + ); + expect(toUnix(fs.outputFile.mock.calls[1][0])).toBe( + 'localDir/foo/build.gradle' + ); + }); + + it('should not write files if gitFs is enabled', async () => { + const configWithgitFs = { + gitFs: true, + ...config, + }; + + const packageFiles = ['build.gradle', 'foo/build.gradle']; + await manager.extractAllPackageFiles(configWithgitFs, packageFiles); + + expect(fs.outputFile.mock.calls.length).toBe(0); }); it('should configure the useLatestVersion plugin', async () => { - await manager.extractPackageFile('content', 'build.gradle', config); + await manager.extractAllPackageFiles(config, ['build.gradle']); expect(toUnix(fs.writeFile.mock.calls[0][0])).toBe( 'localDir/init.gradle' @@ -130,11 +138,7 @@ describe('manager/gradle', () => { binarySource: 'docker', ...config, }; - await manager.extractPackageFile( - 'content', - 'build.gradle', - configWithDocker - ); + await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']); expect(exec.mock.calls[0][0]).toBe( 'docker run --rm -v localDir:localDir -w localDir renovate/gradle gradle --init-script init.gradle dependencyUpdates -Drevision=release'