buildSrc/src/main/kotlin/RepositoryChangeset.kt
changeset 19 91a3ad3b1b9c
parent 10 9aad34f43f71
child 20 5d8a0555733d
equal deleted inserted replaced
18:3ccb29f83309 19:91a3ad3b1b9c
    19  * You should have received a copy of the GNU General Public License
    19  * You should have received a copy of the GNU General Public License
    20  * along with Geekdroid.  If not, see <http://www.gnu.org/licenses/>.
    20  * along with Geekdroid.  If not, see <http://www.gnu.org/licenses/>.
    21  */
    21  */
    22 package com.geekorum.build
    22 package com.geekorum.build
    23 
    23 
       
    24 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
       
    25 import com.android.build.api.variant.BuildConfigField
       
    26 import com.android.build.api.variant.VariantOutputConfiguration.OutputType
       
    27 import org.gradle.api.DefaultTask
    24 import org.gradle.api.Project
    28 import org.gradle.api.Project
       
    29 import org.gradle.api.file.RegularFileProperty
       
    30 import org.gradle.api.provider.Property
       
    31 import org.gradle.api.tasks.Input
       
    32 import org.gradle.api.tasks.OutputFile
       
    33 import org.gradle.api.tasks.TaskAction
       
    34 import org.gradle.configurationcache.extensions.capitalized
       
    35 import org.gradle.kotlin.dsl.register
       
    36 import org.gradle.process.ExecOperations
       
    37 import java.io.ByteArrayOutputStream
    25 import java.io.File
    38 import java.io.File
    26 import java.io.IOException
    39 import javax.inject.Inject
    27 import java.util.concurrent.TimeUnit
       
    28 
    40 
    29 internal fun Project.getGitSha1(): String? = runCommand("git rev-parse HEAD", workingDir = projectDir)?.trim()
    41 internal fun ExecOperations.getGitSha1(projectDir: File): String? = runCommand("git rev-parse HEAD", workingDir = projectDir)?.trim()
    30 
    42 
    31 internal fun Project.getHgSha1(): String? = runCommand("hg id --debug -i -r .", workingDir = projectDir)?.trim()
    43 internal fun ExecOperations.getHgSha1(projectDir: File): String? = runCommand("hg id --debug -i -r .", workingDir = projectDir)?.trim()
    32 
    44 
    33 internal fun Project.getHgLocalRevisionNumber(): String? = runCommand("hg id -n -r .", workingDir = projectDir)?.trim()
    45 internal fun ExecOperations.getHgLocalRevisionNumber(projectDir: File): String? = runCommand("hg id -n -r .", workingDir = projectDir)?.trim()
    34 
    46 
    35 fun Project.getChangeSet(): String {
    47 private fun ExecOperations.getChangeSet(projectDir: File): String {
    36     val git = rootProject.file(".git")
    48     val git = File(projectDir, ".git")
    37     val hg = rootProject.file(".hg")
    49     val hg = File(projectDir, ".hg")
    38     return when {
    50     return when {
    39         git.exists() -> "git:${getGitSha1()}"
    51         git.exists() -> "git:${getGitSha1(projectDir)}"
    40         hg.exists() -> "hg:${getHgSha1()}"
    52         hg.exists() -> "hg:${getHgSha1(projectDir)}"
    41         else -> "unknown"
    53         else -> "unknown"
    42     }
    54     }
    43 }
    55 }
    44 
    56 
    45 /**
    57 /**
    46  * Compute a version code following this format : MmmPBBB
    58  * Compute a version code following this format : MmmPBBB
    47  * M is major, mm is minor, P is patch
    59  * M is major, mm is minor, P is patch
    48  * BBB is build version number from hg
    60  * BBB is build version number from hg
    49  */
    61  */
    50 fun Project.computeChangesetVersionCode(major: Int = 0, minor: Int = 0, patch: Int = 0): Int {
    62 private fun ExecOperations.computeChangesetVersionCode(projectDir: File, major: Int = 0, minor: Int = 0, patch: Int = 0): Int {
    51     val base = (major * 1000000) + (minor * 10000) + (patch * 1000)
    63     val base = (major * 1000000) + (minor * 10000) + (patch * 1000)
    52     return base + (getHgLocalRevisionNumber()?.trim()?.toIntOrNull() ?: 0)
    64     return base + (getHgLocalRevisionNumber(projectDir)?.trim()?.toIntOrNull() ?: 0)
    53 }
    65 }
    54 
    66 
    55 private fun Project.runCommand(
    67 private fun ExecOperations.runCommand(
    56     command: String,
    68     command: String,
    57     workingDir: File = File("."),
    69     workingDir: File = File(".")
    58     timeoutAmount: Long = 60,
       
    59     timeoutUnit: TimeUnit = TimeUnit.MINUTES
       
    60 ): String? {
    70 ): String? {
    61     return try {
    71     val output = ByteArrayOutputStream()
    62         ProcessBuilder(*command.split("\\s".toRegex()).toTypedArray())
    72     val result = exec {
    63             .directory(workingDir)
    73         commandLine(command.split("\\s".toRegex()))
    64             .redirectOutput(ProcessBuilder.Redirect.PIPE)
    74         setWorkingDir(workingDir)
    65             .redirectError(ProcessBuilder.Redirect.PIPE)
    75         setStandardOutput(output)
    66             .start().apply {
    76         setErrorOutput(output)
    67                 waitFor(timeoutAmount, timeoutUnit)
    77     }
    68             }.inputStream.bufferedReader().readText()
    78     result.rethrowFailure()
    69     } catch (e: IOException) {
    79     return output.toString(Charsets.UTF_8)
    70         logger.info("Unable to run command", e)
    80 }
    71         null
    81 
       
    82 abstract class VersionCodeTask : DefaultTask() {
       
    83 
       
    84     @get:OutputFile
       
    85     abstract val versionCodeOutputFile: RegularFileProperty
       
    86 
       
    87     @get:OutputFile
       
    88     abstract val changesetOutputFile: RegularFileProperty
       
    89 
       
    90     @get:Input
       
    91     abstract val repositoryDirectory: Property<String>
       
    92 
       
    93     @get:Input
       
    94     abstract val major: Property<Int>
       
    95 
       
    96     @get:Input
       
    97     abstract val minor: Property<Int>
       
    98 
       
    99     @get:Input
       
   100     abstract val patch: Property<Int>
       
   101 
       
   102     @get:Inject
       
   103     abstract val exec: ExecOperations
       
   104 
       
   105     @TaskAction
       
   106     fun computeVersionCode() {
       
   107         val projectDir = File(repositoryDirectory.get())
       
   108         val versionCode = exec.computeChangesetVersionCode(projectDir, major.getOrElse(0), minor.getOrElse(0), patch.getOrElse(0))
       
   109         versionCodeOutputFile.get().asFile.writeText("$versionCode")
       
   110     }
       
   111 
       
   112     @TaskAction
       
   113     fun computeChangeset() {
       
   114         val projectDir = File(repositoryDirectory.get())
       
   115         val changeset = exec.getChangeSet(projectDir)
       
   116         changesetOutputFile.get().asFile.writeText(changeset)
    72     }
   117     }
    73 }
   118 }
       
   119 
       
   120 /**
       
   121  * @param versionNameSuffix extra string to add to version name
       
   122  */
       
   123 fun ApplicationAndroidComponentsExtension.configureVersionChangeset(project: Project, major: Int, minor: Int, patch: Int, versionNameSuffix: String = "") {
       
   124     // Note: Everything in there is incubating.
       
   125 
       
   126     // onVariantProperties registers an action that configures variant properties during
       
   127     // variant computation (which happens during afterEvaluate)
       
   128     onVariants {
       
   129         // Because app module can have multiple output when using mutli-APK, versionCode/Name
       
   130         // are only available on the variant output.
       
   131         // Here gather the output when we are in single mode (ie no multi-apk)
       
   132         val mainOutput = it.outputs.single { it.outputType == OutputType.SINGLE }
       
   133 
       
   134         // create version Code generating task
       
   135         val versionCodeTask = project.tasks.register<VersionCodeTask>("computeVersionCodeFor${it.name.capitalized()}") {
       
   136             this.major.set(major)
       
   137             this.minor.set(minor)
       
   138             this.patch.set(patch)
       
   139             repositoryDirectory.set(project.rootDir.absolutePath)
       
   140             versionCodeOutputFile.set(project.layout.buildDirectory.file("intermediates/versionCode.txt"))
       
   141             changesetOutputFile.set(project.layout.buildDirectory.file("intermediates/changeset.txt"))
       
   142         }
       
   143 
       
   144         // wire version code from the task output
       
   145         // map will create a lazy Provider that
       
   146         // 1. runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
       
   147         //    and therefore the file is created.
       
   148         // 2. contains task dependency information so that the consumer(s) run after the producer.
       
   149         mainOutput.versionCode.set(versionCodeTask.map { it.versionCodeOutputFile.get().asFile.readText().toInt() })
       
   150         mainOutput.versionName.set("$major.$minor.$patch$versionNameSuffix")
       
   151 
       
   152         it.buildConfigFields.put("REPOSITORY_CHANGESET", versionCodeTask.map {
       
   153             BuildConfigField("String", "\"${it.changesetOutputFile.get().asFile.readText()}\"", "Repository changeset")
       
   154         })
       
   155     }
       
   156 }