Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #194: let Gradle check up-to-date-ness of buildConfig decorations #196

Merged
merged 3 commits into from
Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugin/building/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {

testImplementation(projects.test.internal)
testImplementation(testFixtures(projects.plugin.base))
testImplementation(testFixtures(projects.plugin.versioning))
// AndroidInstallRunnerTaskTest calls production code directly, so need com.android.xml.AndroidXPathFactory.
testImplementation(libs.android.tools.common)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,44 @@ import net.twisterrob.gradle.common.AGPVersions
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.UntrackedTask
import java.time.Instant
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

@UntrackedTask(because = "Input of this task is the current time.")
open class CalculateBuildTimeTask : DefaultTask() {
abstract class CalculateBuildTimeTask : DefaultTask() {

/**
* Default implementation returns a one-day precise time
* to minimize `compile*JavaWithJavac` rebuilds due to a single number change in BuildConfig.java.
*
* It can be overridden like this:
* `tasks.calculateBuildConfigBuildTime.configure { getBuildTime = { System.currentTimeMillis() }}`
* `tasks.calculateBuildConfigBuildTime.configure { buildTime.set(System.currentTimeMillis()) }`
*
* @returns a long representing the UTC time of the build.
*/
@Input
var getBuildTime: () -> Long =
{ OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli() }
@get:Input
abstract val buildTime: Property<Long>

@get:OutputFile
val buildTimeFile: RegularFileProperty =
intermediateRegularFile("buildConfigDecorations/buildTime.txt")

init {
description = "Calculates the build time for BuildConfig.java."
outputs.upToDateWhen { false }
// Not using a provider to prevent turning over midnight during build,
// each build will have a single calculation.
buildTime.convention(OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli())
}

@TaskAction
fun writeBuildTime() {
val buildTime = getBuildTime()
buildTimeFile.writeText(buildTime.toString())
buildTimeFile.writeText(buildTime.get().toString())
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,27 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.UntrackedTask
import org.gradle.kotlin.dsl.getByType

// https://docs.gradle.org/7.3/release-notes.html#plugin-development-improvements
@UntrackedTask(because = "Input of this task is .git or .svn folder.")
open class CalculateVCSRevisionInfoTask : DefaultTask() {
abstract class CalculateVCSRevisionInfoTask : DefaultTask() {

@get:OutputFile
val revisionFile: RegularFileProperty = intermediateRegularFile("buildConfigDecorations/revision.txt")
val revisionFile: RegularFileProperty =
intermediateRegularFile("buildConfigDecorations/revision.txt")

@get:OutputFile
val revisionNumberFile: RegularFileProperty = intermediateRegularFile("buildConfigDecorations/revisionNumber.txt")
val revisionNumberFile: RegularFileProperty =
intermediateRegularFile("buildConfigDecorations/revisionNumber.txt")

init {
outputs.upToDateWhen { false }
inputs.files(project.provider { vcs.current.files(project) })
}

private val vcs: VCSPluginExtension
get() = project.extensions.getByType()

@TaskAction
fun writeVCS() {
val vcs: VCSPluginExtension = project.extensions.getByType()
revisionFile.writeText(vcs.current.revision)
revisionNumberFile.writeText(vcs.current.revisionNumber.toString())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import java.time.ZoneOffset
* [Delete %TEMP%\robolectric-2 folder](https://github.com/robolectric/robolectric/issues/4567#issuecomment-475740375)
*
* @see AndroidBuildPlugin
* @see net.twisterrob.gradle.android.tasks.CalculateBuildTimeTask
* @see net.twisterrob.gradle.android.tasks.CalculateVCSRevisionInfoTask
*/
@ExtendWith(GradleRunnerRuleExtension::class)
class AndroidBuildPluginIntgTest : BaseAndroidIntgTest() {
Expand Down Expand Up @@ -416,7 +418,7 @@ class AndroidBuildPluginIntgTest : BaseAndroidIntgTest() {
//noinspection UnnecessaryQualifiedReference
testLogging.events = org.gradle.api.tasks.testing.logging.TestLogEvent.values().toList().toSet()
}
tasks.named("calculateBuildConfigBuildTime").configure { getBuildTime = { 1234567890 } }
tasks.named("calculateBuildConfigBuildTime").configure { buildTime.set(1234567890L) }
""".trimIndent()

val result = gradle.run(script, "testReleaseUnitTest").build()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.twisterrob.gradle.android.tasks

import net.twisterrob.gradle.android.BaseAndroidIntgTest
import net.twisterrob.gradle.test.GradleRunnerRule
import net.twisterrob.gradle.test.GradleRunnerRuleExtension
import net.twisterrob.gradle.test.assertSuccess
import net.twisterrob.gradle.test.assertUpToDate
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

/**
* @see CalculateBuildTimeTask
*/
@ExtendWith(GradleRunnerRuleExtension::class)
class CalculateBuildTimeTaskIntgTest : BaseAndroidIntgTest() {

override lateinit var gradle: GradleRunnerRule

@Test fun `will stay up to date`() {
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
tasks.register("calculateBuildConfigBuildTime", ${CalculateBuildTimeTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigBuildTime").build()
first.assertSuccess(":calculateBuildConfigBuildTime")

val second = gradle.run(null, "calculateBuildConfigBuildTime").build()
second.assertUpToDate(":calculateBuildConfigBuildTime")
}

@Test fun `will stay up to date with custom value`() {
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
tasks.register("calculateBuildConfigBuildTime", ${CalculateBuildTimeTask::class.java.name}) {
buildTime.set(1234L)
}
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigBuildTime").build()
first.assertSuccess(":calculateBuildConfigBuildTime")

val second = gradle.run(null, "calculateBuildConfigBuildTime").build()
second.assertUpToDate(":calculateBuildConfigBuildTime")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package net.twisterrob.gradle.android.tasks

import net.twisterrob.gradle.android.BaseAndroidIntgTest
import net.twisterrob.gradle.test.GradleRunnerRule
import net.twisterrob.gradle.test.GradleRunnerRuleExtension
import net.twisterrob.gradle.test.assertSuccess
import net.twisterrob.gradle.test.assertUpToDate
import net.twisterrob.gradle.test.root
import net.twisterrob.gradle.vcs.createTestFileToCommit
import net.twisterrob.gradle.vcs.doCheckout
import net.twisterrob.gradle.vcs.doCommitSingleFile
import net.twisterrob.gradle.vcs.doCreateRepository
import net.twisterrob.gradle.vcs.git
import net.twisterrob.gradle.vcs.svn
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

/**
* @see CalculateVCSRevisionInfoTask
*/
@ExtendWith(GradleRunnerRuleExtension::class)
class CalculateVCSRevisionInfoTaskIntgTest : BaseAndroidIntgTest() {

override lateinit var gradle: GradleRunnerRule

@Test fun `will stay up to date when didn't change`() {
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current == project.VCS.git || project.VCS.current == project.VCS.svn) {
throw new IllegalStateException("Not dummy: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").build()
second.assertUpToDate(":calculateBuildConfigVCSRevisionInfo")
}

@Test fun `will stay up to date when git didn't change`() {
git(gradle.root) {
doCommitSingleFile(gradle.root.createTestFileToCommit(), "First commit")
doCommitSingleFile(gradle.root.createTestFileToCommit(), "Second commit")
}
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current != project.VCS.git) {
throw new IllegalStateException("Not git: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").build()
second.assertUpToDate(":calculateBuildConfigVCSRevisionInfo")
}

@Test fun `will stay up to date when git sha didn't change`() {
git(gradle.root) {
val rev = doCommitSingleFile(gradle.root.createTestFileToCommit(), "First commit")
doCheckout(rev.id)
}
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current != project.VCS.git) {
throw new IllegalStateException("Not git: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").build()
second.assertUpToDate(":calculateBuildConfigVCSRevisionInfo")
}

@Test fun `will not stay up to date when git changed`() {
git(gradle.root) {
doCommitSingleFile(gradle.root.createTestFileToCommit(), "First commit")
}
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current != project.VCS.git) {
throw new IllegalStateException("Not git: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

git(gradle.root) {
doCommitSingleFile(gradle.root.createTestFileToCommit(), "Second commit")
}

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").withDebug(true).build()
second.assertSuccess(":calculateBuildConfigVCSRevisionInfo")
}

@Test fun `will stay up to date when svn didn't change`() {
svn {
val repoUrl = doCreateRepository(gradle.root.resolve(".repo"))
doCheckout(repoUrl, gradle.root)
doCommitSingleFile(gradle.root.createTestFileToCommit(), "First commit")
doCommitSingleFile(gradle.root.createTestFileToCommit(), "Second commit")
}
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current != project.VCS.svn) {
throw new IllegalStateException("Not svn: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").build()
second.assertUpToDate(":calculateBuildConfigVCSRevisionInfo")
}

@Test fun `will not stay up to date when svn changed`() {
svn {
val repoUrl = doCreateRepository(gradle.root.resolve(".repo"))
doCheckout(repoUrl, gradle.root)
doCommitSingleFile(gradle.root.createTestFileToCommit(), "First commit")
}
@Language("gradle")
val script = """
apply plugin: 'net.twisterrob.vcs'
if (project.VCS.current != project.VCS.svn) {
throw new IllegalStateException("Not svn: " + project.VCS.current)
}
tasks.register("calculateBuildConfigVCSRevisionInfo", ${CalculateVCSRevisionInfoTask::class.java.name})
""".trimIndent()

val first = gradle.run(script, "calculateBuildConfigVCSRevisionInfo").build()
first.assertSuccess(":calculateBuildConfigVCSRevisionInfo")

svn {
doCommitSingleFile(gradle.root.createTestFileToCommit(), "Second commit")
}

val second = gradle.run(null, "calculateBuildConfigVCSRevisionInfo").build()
second.assertSuccess(":calculateBuildConfigVCSRevisionInfo")
}
}
4 changes: 4 additions & 0 deletions plugin/versioning/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
kotlin
id("java-gradle-plugin")
id("java-test-fixtures")
id("net.twisterrob.gradle.build.publishing")
}

Expand Down Expand Up @@ -31,4 +32,7 @@ dependencies {

testImplementation(projects.test.internal)
testImplementation(testFixtures(projects.plugin.base))

testFixturesApi(libs.svnkit)
testFixturesApi(libs.jgit)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package net.twisterrob.gradle.vcs

import org.gradle.api.Project
import org.gradle.api.file.FileCollection

internal object DummyVcsExtension : VCSExtension {

override val isAvailableQuick: Boolean = false
override val isAvailable: Boolean = isAvailableQuick
override val revision: String = "no VCS"
override val revisionNumber: Int = 0
override fun files(project: Project): FileCollection = project.files()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.eclipse.jgit.lib.RepositoryCache
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.util.FS
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByName
import java.io.File
Expand Down Expand Up @@ -76,6 +77,26 @@ open class GITPluginExtension : VCSExtension {
return count()
}
}

override fun files(project: Project): FileCollection =
project.files(
".git/HEAD",
project.provider {
val headRef = project.rootDir.resolve(".git/HEAD")
if (headRef.exists() && headRef.isFile && headRef.canRead()) {
val headRaw = headRef.readText().trimEnd()
if (headRaw.startsWith("ref: ")) {
// HEAD contains a ref, resolve it to a file containing the SHA as the input.
project.rootDir.resolve(".git").resolve(headRaw.substringAfter("ref: "))
} else {
// HEAD contains an SHA, that's the input.
headRef
}
} else {
error("Cannot find ${headRef}, consider android.twisterrob.decorateBuildConfig = false")
}
}
)
}

private inline fun <T> inRepo(dir: File, block: Git.() -> T): T {
Expand Down
Loading