diff --git a/lib/modules/manager/gradle/extract.spec.ts b/lib/modules/manager/gradle/extract.spec.ts index d1ebe3cea1353c0f7f870afd33706240564cf4e6..22f0f5663024122218334569a04b734754573828 100644 --- a/lib/modules/manager/gradle/extract.spec.ts +++ b/lib/modules/manager/gradle/extract.spec.ts @@ -1,4 +1,5 @@ -import { fs, loadFixture } from '../../../../test/util'; +import { Fixtures } from '../../../../test/fixtures'; +import { fs } from '../../../../test/util'; import type { ExtractConfig } from '../types'; import { extractAllPackageFiles } from '.'; @@ -191,8 +192,60 @@ describe('modules/manager/gradle/extract', () => { ]); }); + it('interpolates repository URLs', async () => { + const buildFile = ` + repositories { + mavenCentral() + maven { + url = "\${repositoryBaseURL}/repository-build" + } + } + + dependencies { + implementation "com.google.protobuf:protobuf-java:2.17.0" + } + `; + + mockFs({ + 'build.gradle': buildFile, + 'gradle.properties': 'repositoryBaseURL: https://dummy.org/whatever', + }); + + const res = await extractAllPackageFiles({} as ExtractConfig, [ + 'build.gradle', + 'gradle.properties', + ]); + + expect(res).toMatchObject([ + { + packageFile: 'gradle.properties', + datasource: 'maven', + deps: [], + }, + { + packageFile: 'build.gradle', + datasource: 'maven', + deps: [ + { + depName: 'com.google.protobuf:protobuf-java', + currentValue: '2.17.0', + managerData: { + fileReplacePosition: 227, + packageFile: 'build.gradle', + }, + fileReplacePosition: 227, + registryUrls: [ + 'https://repo.maven.apache.org/maven2', + 'https://dummy.org/whatever/repository-build', + ], + }, + ], + }, + ]); + }); + it('works with dependency catalogs', async () => { - const tomlFile = loadFixture('1/libs.versions.toml'); + const tomlFile = Fixtures.get('1/libs.versions.toml'); const fsMock = { 'gradle/libs.versions.toml': tomlFile, }; @@ -315,7 +368,7 @@ describe('modules/manager/gradle/extract', () => { }); it("can run Javier's example", async () => { - const tomlFile = loadFixture('2/libs.versions.toml'); + const tomlFile = Fixtures.get('2/libs.versions.toml'); const fsMock = { 'gradle/libs.versions.toml': tomlFile, }; @@ -507,7 +560,7 @@ describe('modules/manager/gradle/extract', () => { }); it('should change the dependency version not the comment version', async () => { - const tomlFile = loadFixture('3/libs.versions.toml'); + const tomlFile = Fixtures.get('3/libs.versions.toml'); const fsMock = { 'gradle/libs.versions.toml': tomlFile, }; diff --git a/lib/modules/manager/gradle/parser.spec.ts b/lib/modules/manager/gradle/parser.spec.ts index d33ed1ee31d924dae8a3a8f7b98e7b21d46edcf9..79d660d319f836b3ff629e737ee47af5ea939351 100644 --- a/lib/modules/manager/gradle/parser.spec.ts +++ b/lib/modules/manager/gradle/parser.spec.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '../../../../test/util'; +import { Fixtures } from '../../../../test/fixtures'; import { GOOGLE_REPO, GRADLE_PLUGIN_PORTAL_REPO, @@ -105,23 +105,29 @@ describe('modules/manager/gradle/parser', () => { describe('registryUrls', () => { test.each` - input | url - ${'url ""'} | ${null} - ${'url "#!@"'} | ${null} - ${'url "https://example.com"'} | ${'https://example.com'} - ${'url("https://example.com")'} | ${'https://example.com'} - ${'mavenCentral()'} | ${MAVEN_REPO} - ${'jcenter()'} | ${JCENTER_REPO} - ${'google()'} | ${GOOGLE_REPO} - ${'google { content { includeGroup "foo" } }'} | ${GOOGLE_REPO} - ${'gradlePluginPortal()'} | ${GRADLE_PLUGIN_PORTAL_REPO} - ${'maven("https://foo.bar/baz/qux")'} | ${'https://foo.bar/baz/qux'} - ${'maven { url = uri("https://foo.bar/baz")'} | ${'https://foo.bar/baz'} - ${"maven { url 'https://foo.bar/baz'"} | ${'https://foo.bar/baz'} - ${"maven { url = 'https://foo.bar/baz'"} | ${'https://foo.bar/baz'} - `('$input', ({ input, url }) => { + def | input | url + ${''} | ${'url ""'} | ${null} + ${''} | ${'url "#!@"'} | ${null} + ${''} | ${'url "https://example.com"'} | ${'https://example.com'} + ${'base="https://foo.bar"'} | ${'url "${base}/baz"'} | ${'https://foo.bar/baz'} + ${''} | ${'url("https://example.com")'} | ${'https://example.com'} + ${'base="https://foo.bar"'} | ${'url("${base}/baz")'} | ${'https://foo.bar/baz'} + ${''} | ${'mavenCentral()'} | ${MAVEN_REPO} + ${''} | ${'jcenter()'} | ${JCENTER_REPO} + ${''} | ${'google()'} | ${GOOGLE_REPO} + ${''} | ${'google { content { includeGroup "foo" } }'} | ${GOOGLE_REPO} + ${''} | ${'gradlePluginPortal()'} | ${GRADLE_PLUGIN_PORTAL_REPO} + ${''} | ${'maven("https://foo.bar/baz/qux")'} | ${'https://foo.bar/baz/qux'} + ${'base="https://foo.bar"'} | ${'maven("${base}/baz/qux")'} | ${'https://foo.bar/baz/qux'} + ${''} | ${'maven { url = uri("https://foo.bar/baz")'} | ${'https://foo.bar/baz'} + ${'base="https://foo.bar"'} | ${'maven { url = uri("${base}/baz")'} | ${'https://foo.bar/baz'} + ${''} | ${"maven { url 'https://foo.bar/baz'"} | ${'https://foo.bar/baz'} + ${'base="https://foo.bar"'} | ${'maven { url "${base}/baz"'} | ${'https://foo.bar/baz'} + ${''} | ${"maven { url = 'https://foo.bar/baz'"} | ${'https://foo.bar/baz'} + ${'base="https://foo.bar"'} | ${'maven { url = "${base}/baz"'} | ${'https://foo.bar/baz'} + `('$def | $input', ({ def, input, url }) => { const expected = [url].filter(Boolean); - const { urls } = parseGradle(input); + const { urls } = parseGradle([def, input].join('\n')); expect(urls).toStrictEqual(expected); }); }); @@ -138,7 +144,7 @@ describe('modules/manager/gradle/parser', () => { }); it('parses fixture from "gradle" manager', () => { - const content = loadFixture('build.gradle.example1'); + const content = Fixtures.get('build.gradle.example1'); const { deps } = parseGradle(content, {}, 'build.gradle'); const replacementIndices = deps.map(({ managerData, currentValue }) => content.slice(managerData.fileReplacePosition).indexOf(currentValue) @@ -154,6 +160,8 @@ describe('modules/manager/gradle/parser', () => { ${'foo=bar'} | ${'foo'} | ${'bar'} | ${4} ${' foo = bar '} | ${'foo'} | ${'bar'} | ${7} ${'foo.bar=baz'} | ${'foo.bar'} | ${'baz'} | ${8} + ${'foo.bar:baz'} | ${'foo.bar'} | ${'baz'} | ${8} + ${'foo.bar baz'} | ${'foo.bar'} | ${'baz'} | ${8} `('$input', ({ input, key, value, fileReplacePosition }) => { expect(parseProps(input)).toMatchObject({ vars: { [key]: { key, value, fileReplacePosition } }, diff --git a/lib/modules/manager/gradle/parser.ts b/lib/modules/manager/gradle/parser.ts index 3a8c45399cc2fdd6a9cdb3d27ae6058ac93e6ff2..6ecb2daf9d2eb60ebe3f30bd8233784e007c0f4e 100644 --- a/lib/modules/manager/gradle/parser.ts +++ b/lib/modules/manager/gradle/parser.ts @@ -278,8 +278,14 @@ function processPlugin({ function processCustomRegistryUrl({ tokenMap, + variables, }: SyntaxHandlerInput): SyntaxHandlerOutput { - const registryUrl = tokenMap.registryUrl?.value; + let registryUrl: string | null = tokenMap.registryUrl?.value; + if (tokenMap.registryUrl?.type === TokenType.StringInterpolation) { + const token = tokenMap.registryUrl as StringInterpolation; + registryUrl = interpolateString(token.children, variables); + } + try { if (registryUrl) { const { host, protocol } = url.parse(registryUrl); @@ -530,7 +536,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ matchValue: 'maven', }, { matchType: TokenType.LeftParen }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, { matchType: TokenType.RightParen }, endOfInstruction, ], @@ -549,7 +558,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ matchValue: 'url', }, { matchType: TokenType.Assignment }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, endOfInstruction, ], handler: processCustomRegistryUrl, @@ -572,7 +584,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ matchValue: 'uri', }, { matchType: TokenType.LeftParen }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, { matchType: TokenType.RightParen }, endOfInstruction, ], @@ -590,7 +605,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ matchType: TokenType.Word, matchValue: 'url', }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, endOfInstruction, ], handler: processCustomRegistryUrl, @@ -599,7 +617,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ // url 'https://repo.spring.io/snapshot/' matchers: [ { matchType: TokenType.Word, matchValue: ['uri', 'url'] }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, endOfInstruction, ], handler: processCustomRegistryUrl, @@ -609,7 +630,10 @@ const matcherConfigs: SyntaxMatchConfig[] = [ matchers: [ { matchType: TokenType.Word, matchValue: ['uri', 'url'] }, { matchType: TokenType.LeftParen }, - { matchType: TokenType.String, tokenMapKey: 'registryUrl' }, + { + matchType: [TokenType.String, TokenType.StringInterpolation], + tokenMapKey: 'registryUrl', + }, { matchType: TokenType.RightParen }, endOfInstruction, ], @@ -895,7 +919,7 @@ export function parseGradle( const propWord = '[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)*'; const propRegex = regEx( - `^(?<leftPart>\\s*(?<key>${propWord})\\s*=\\s*['"]?)(?<value>[^\\s'"]+)['"]?\\s*$` + `^(?<leftPart>\\s*(?<key>${propWord})\\s*[= :]\\s*['"]?)(?<value>[^\\s'"]+)['"]?\\s*$` ); export function parseProps(