Skip to content

Commit

Permalink
Fix #194: implement up-to-date checks
Browse files Browse the repository at this point in the history
  • Loading branch information
TWiStErRob committed Dec 18, 2021
1 parent 2696417 commit f0a1f8f
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 20 deletions.
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")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.twisterrob.gradle.vcs

import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import java.io.File
import java.util.concurrent.Callable
Expand All @@ -15,7 +17,7 @@ fun createGitRepository(repoDir: File): Git =
.init().call {
setDirectory(repoDir)
}
.also { result -> println("Repository created at ${result}") }
.also { result -> println("Repository ${repoDir} created at ${result}") }

fun Git.doCommitSingleFile(file: File, message: String): RevCommit {
val relativePath = file.relativeTo(this.repository.directory)
Expand All @@ -32,5 +34,16 @@ fun Git.doCommitSingleFile(file: File, message: String): RevCommit {
.also { println("Committed revision ${it.id}: ${it.fullMessage}") }
}

fun Git.doCheckout(ref: ObjectId): Ref? =
this.doCheckout(ref.name)

fun Git.doCheckout(ref: String): Ref? {
return this
.checkout().call {
setName(ref)
}
.also { println("Checked out ${ref} as ${it?.objectId}") }
}

private fun <R, T : Callable<R>> T.call(block: T.() -> Unit): R =
this.apply(block).call()

0 comments on commit f0a1f8f

Please sign in to comment.