Skip to content

Commit

Permalink
Configure Renovate to suggest AGP version bumps (#1742)
Browse files Browse the repository at this point in the history
Fully automate the bumping of AGP versions to the latest patch. While
there's an [existing
workflow](https://github.com/gradle/dv-solutions/actions/workflows/upgrade-android-gradle-plugin-versions.yml)
for bumping the AGP versions in this project, it must be manually
triggered specifying the new version. Renovate can fill this gap by
suggesting updates whenever new versions come out. Other kinds of bumps
are still handled by Dependabot.

### Renovate

Renovate is a tool like Dependabot but much more flexible. It has
built-in ["managers"][1] such as `maven`, `gradle`, and
`pip_requirements` that look for certain patterns in certain files, then
query ["datasources"][2] for newer versions. No built-in manager
supports the custom `versions.json` file, but Renovate allows for
creating a custom regex-based manager.

### Renovate custom manager

A Renovate config file is added with a custom ["regex" manager][3]. This
can instruct Renovate to parse any version on any file. The goal is to
detect and update AGP versions annotated with a comment:

```
# renovate: AGP version
8.6.0
```

The "manager" config declares

- what files it should process
- what it should consider a version definition within those files
- what "datasource" it should query for newer versions
- what "versioning" (scheme) the versions adhere to
- the dependency name
- the Maven registry URL the dependency comes from

```json5
{
  // Matches AGP versions annotated with a "renovate: AGP version" comment
  customType: "regex",
  fileMatch: [
    "src/main/resources/versions\\.json5",
    "gradle\\.properties",
  ],
  matchStrings: [
    // For JSON: the first double-quoted string below the comment line, e.g. "1.0.0"
    "\/\/ renovate: AGP version\\s+?\"(?<currentValue>\\S+?)\"",
    // For properties: the value of the first property below the comment line, e.g. anyProperty=1.0.0
    "# renovate: AGP version\\s+?\\S+?=(?<currentValue>\\S+?)(?:\\s|$)",
  ],
  "datasourceTemplate": "maven",
  "versioningTemplate": "maven",
  "depNameTemplate": "com.android.tools.build:gradle",
  "registryUrlTemplate": "https://dl.google.com/dl/android/maven2/",
},
```

An example PR is available in a fork:
gabrielfeo#10.


![image](https://github.com/user-attachments/assets/4862932f-53e7-4283-b7bb-c426d6af59bc)

The last four elements are commonly set on each comment and captured
with regex ([example][4]), but I opted to centralize them in the
Renovate config to minimize duplication, simplify the comments and the
regex that parses them.

### Changes

- Annotate AGP versions in `versions.json` with a "renovate: AGP
version" comment
- Rename the file to `versions.json5` to keep syntax highlighting
support when adding comments
- Change all `JsonSlurper`s to `LAX` mode, which is lenient about
comments (Groovy has no support for JSON5)
- Add a Renovate config to bump versions in `versions.json5` and
`gradle.properties` too (for the build-time check described below)
- Change plugin tests to no longer require changes on every patch bump
PR. ["expectedOutcomes" JSON files][5] are now minor-specific and
`WorkaroundTest` no longer specifies patch numbers. The existing
workflow updated the tests automatically, but it can't be as easily done
with Renovate. Thanks to @erichaagdev for this suggestion.
- Declare the latest AGP version in `gradle.properties` and fail the
build if it's missing from `versions.json5`. The goal is to have
Renovate create a "nudge" PR if a new major or minor comes out
(`versions.json5` has major/minor bumps disabled in order to keep older
minor versions in the list of "supportedVersions"). The property isn't
used for anything else. Thanks to @erichaagdev for this suggestion.

These changes were tested running the Renovate GitHub App against a
fork. After adding the `dv.json5` preset to the config, which is located
in a private Gradle repository, they were tested with dry-runs of our
self-hosted Renovate runner, which has access to that repository.

- [PRs from GitHub App on fork][6]
- [dry-run of self-hosted app][7]

[1]: https://docs.renovatebot.com/modules/manager/
[2]: https://docs.renovatebot.com/modules/datasource/
[3]: https://docs.renovatebot.com/modules/manager/regex
[4]:
https://docs.renovatebot.com/modules/manager/regex/#regular-expression-capture-groups
[5]:
https://github.com/gradle/android-cache-fix-gradle-plugin/tree/a7aa73ba00f87bb2cdd08e58ab8190c34b70515c/src/test/resources/expectedOutcomes
[6]:
https://github.com/gabrielfeo/android-cache-fix-gradle-plugin/pulls?q=is%3Apr+author%3Arenovate%5Bbot%5D+
[7]:
https://github.com/gradle/renovate-agent/actions/runs/12675352428/job/35327825387
  • Loading branch information
gabrielfeo authored Jan 8, 2025
1 parent 75e59bc commit fc46588
Show file tree
Hide file tree
Showing 22 changed files with 100 additions and 23 deletions.
47 changes: 47 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"github>gradle/renovate-agent//presets/dv.json5",
],
// See https://docs.renovatebot.com/modules/manager/regex/
// Dependabot is used for all other updates.
"enabledManagers": ["regex"],
"customManagers": [
{
// Matches AGP versions annotated with a "renovate: AGP version" comment
customType: "regex",
fileMatch: [
"src/main/resources/versions\\.json5",
"gradle\\.properties",
],
matchStrings: [
// For JSON: the first double-quoted string below the comment line, e.g. "1.0.0"
"\/\/ renovate: AGP version\\s+?\"(?<currentValue>\\S+?)\"",
// For properties: the value of the first property below the comment line, e.g. anyProperty=1.0.0
"# renovate: AGP version\\s+?\\S+?=(?<currentValue>\\S+?)(?:\\s|$)",
],
"datasourceTemplate": "maven",
"versioningTemplate": "maven",
"depNameTemplate": "com.android.tools.build:gradle",
"registryUrlTemplate": "https://dl.google.com/dl/android/maven2/",
},
],
// Ensure patches to older minors are opened, even if a newer minor is available
// In versions.json5, means older minors will still be checked for a newer patch
"separateMinorPatch": true,
"packageRules": [
{
// In versions.json5, disable bumps of major/minor, which should be added as new properties
"matchDepNames": ["com.android.tools.build:gradle"],
"matchUpdateTypes": ["major", "minor"],
"matchFileNames": ["src/main/resources/versions\\.json5"],
"enabled": false,
},
{
// Group changes to versions.json5
"matchFileNames": ["src/main/resources/versions\\.json5"],
"groupName": "supportedVersions",
},
],
}
19 changes: 14 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionProfile.FAST
import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionProfile.STANDARD
import groovy.json.JsonSlurper
import groovy.json.JsonParserType

// Upgrade transitive dependencies in plugin classpath
buildscript {
Expand Down Expand Up @@ -162,7 +163,14 @@ tasks.test {
}
}

getSupportedVersions().keys.forEach { androidVersion ->
val latestVersion = providers.gradleProperty("org.gradle.android.latestKnownAgpVersion")
val supportedVersions = readSupportedVersions()

check(latestVersion.get() in supportedVersions) {
"The project must be updated to support AGP $latestVersion. Please add it to supported versions."
}

supportedVersions.keys.forEach { androidVersion ->
val versionSpecificTest = tasks.register<Test>(androidTestTaskName(androidVersion)) {
description = "Runs the multi-version tests for AGP $androidVersion"
group = "verification"
Expand Down Expand Up @@ -257,8 +265,9 @@ fun releaseNotes(): Provider<String> {
}

@Suppress("UNCHECKED_CAST")
fun getSupportedVersions(): Map<String, Array<String>> {
val versionFile = providers.fileContents(layout.projectDirectory.file("src/main/resources/versions.json"))
return (JsonSlurper()
.parse(versionFile.asBytes.get()) as Map<String, Map<String, Array<String>>>).getValue("supportedVersions")
fun readSupportedVersions(): Map<String, Array<String>> {
val versionFile = providers.fileContents(layout.projectDirectory.file("src/main/resources/versions.json5"))
val slurper = JsonSlurper().setType(JsonParserType.LAX)
val json = slurper.parse(versionFile.asBytes.get()) as Map<String, Map<String, Array<String>>>
return json.getValue("supportedVersions")
}
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ org.gradle.jvmargs=-Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8
org.gradle.kotlin.dsl.allWarningsAsErrors=true

systemProp.pts.enabled=true

# renovate: AGP version
org.gradle.android.latestKnownAgpVersion=8.9.0-alpha04
4 changes: 3 additions & 1 deletion src/main/groovy/org/gradle/android/Versions.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMultimap
import com.google.common.collect.ImmutableSortedSet
import com.google.common.collect.Multimap
import groovy.json.JsonSlurper
import groovy.json.JsonParserType
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import org.gradle.util.GradleVersion
Expand All @@ -17,7 +18,8 @@ class Versions {
static final VersionNumber CURRENT_ANDROID_VERSION

static {
def versions = new JsonSlurper().parse(AndroidCacheFixPlugin.classLoader.getResource("versions.json"))
def slurper = new JsonSlurper().setType(JsonParserType.LAX)
def versions = slurper.parse(AndroidCacheFixPlugin.classLoader.getResource("versions.json5"))

def builder = ImmutableMultimap.<VersionNumber, GradleVersion>builder()
versions.supportedVersions.each { String androidVersion, List<String> gradleVersions ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
{
"supportedVersions": {
// renovate: AGP version
"8.9.0-alpha04": [
"8.12"
],
// renovate: AGP version
"8.8.0-beta01": [
"8.12"
],
// renovate: AGP version
"8.7.2": [
"8.12"
],
// renovate: AGP version
"8.6.1": [
"8.12"
],
// renovate: AGP version
"8.5.2": [
"8.12"
],
// renovate: AGP version
"8.4.2": [
"8.12"
],
// renovate: AGP version
"8.3.2": [
"8.12"
],
// renovate: AGP version
"8.2.2": [
"8.12"
],
// renovate: AGP version
"8.1.4": [
"8.12"
],
// renovate: AGP version
"8.0.2": [
"8.0.2"
],
// renovate: AGP version
"7.4.2": [
"7.6.2"
],
// renovate: AGP version
"7.3.1": [
"7.4.2",
"7.6.2"
],
// renovate: AGP version
"7.2.2": [
"7.3.3",
"7.6.2"
],
// renovate: AGP version
"7.1.3": [
"7.2",
"7.6.2"
],
// renovate: AGP version
"7.0.4": [
"7.0.2",
"7.6.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ class CrossVersionOutcomeAndRelocationTest extends AbstractTest {

private static ExpectedResults expectedResults(VersionNumber androidVersion, VersionNumber kotlinVersion) {
def builder = new ExpectedOutcomeBuilder()
def outcomesResource = CrossVersionOutcomeAndRelocationTest.classLoader.getResource("expectedOutcomes/${androidVersion}_outcomes.json")
def path = "expectedOutcomes/${androidVersion.major}.${androidVersion.minor}_outcomes.json"
def outcomesResource = CrossVersionOutcomeAndRelocationTest.classLoader.getResource(path)

if (outcomesResource == null) {
throw new IllegalStateException("Could not find expectedOutcomes/${androidVersion}_outcomes.json - make sure an outcomes file exists for this version!")
Expand Down
32 changes: 16 additions & 16 deletions src/test/groovy/org/gradle/android/WorkaroundTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ class WorkaroundTest extends Specification {
expect:
workarounds.collect { it.class.simpleName.replaceAll(/Workaround/, "") }.sort() == expectedWorkarounds.sort()
where:
androidVersion | expectedWorkarounds
"8.9.0-alpha04" | ['JdkImage']
"8.8.0-beta01" | ['JdkImage']
"8.7.2" | ['JdkImage']
"8.6.1" | ['JdkImage']
"8.5.2" | ['JdkImage']
"8.4.2" | ['JdkImage']
"8.3.2" | ['MergeSourceSetFolders', 'JdkImage']
"8.2.2" | ['MergeSourceSetFolders', 'JdkImage', 'PackageForUnitTest']
"8.1.4" | ['MergeSourceSetFolders', 'JdkImage', 'PackageForUnitTest']
"8.0.2" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.4.2" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.3.1" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.2.2" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.1.3" | ['BundleLibraryClasses', 'CompileLibraryResources', 'DataBindingMergeDependencyArtifacts', 'LibraryJniLibs', 'MergeNativeLibs', 'MergeSourceSetFolders', 'StripDebugSymbols', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.0.4" | ['BundleLibraryClasses', 'CompileLibraryResources', 'DataBindingMergeDependencyArtifacts', 'LibraryJniLibs', 'MergeNativeLibs', 'MergeSourceSetFolders', 'StripDebugSymbols', 'ZipMergingTask', 'PackageForUnitTest']
androidVersion | expectedWorkarounds
"8.9" | ['JdkImage']
"8.8" | ['JdkImage']
"8.7" | ['JdkImage']
"8.6" | ['JdkImage']
"8.5" | ['JdkImage']
"8.4" | ['JdkImage']
"8.3" | ['MergeSourceSetFolders', 'JdkImage']
"8.2" | ['MergeSourceSetFolders', 'JdkImage', 'PackageForUnitTest']
"8.1" | ['MergeSourceSetFolders', 'JdkImage', 'PackageForUnitTest']
"8.0" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.4" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.3" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.2" | ['MergeSourceSetFolders', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.1" | ['BundleLibraryClasses', 'CompileLibraryResources', 'DataBindingMergeDependencyArtifacts', 'LibraryJniLibs', 'MergeNativeLibs', 'MergeSourceSetFolders', 'StripDebugSymbols', 'ZipMergingTask', 'JdkImage', 'PackageForUnitTest']
"7.0" | ['BundleLibraryClasses', 'CompileLibraryResources', 'DataBindingMergeDependencyArtifacts', 'LibraryJniLibs', 'MergeNativeLibs', 'MergeSourceSetFolders', 'StripDebugSymbols', 'ZipMergingTask', 'PackageForUnitTest']
}
}

0 comments on commit fc46588

Please sign in to comment.