Gradle how to change version number in source code - gradle

Java code:
public static String VERSION = "version_number";
Gradle build.gradle
version = '1.0'
How to set the version in java code from grade? The version must be in source code.
Is there a convenient way? A not-so-nice way:
copy the java file to another location, e.g. build/changed-source
change the version in the source, by replacing token
add the build/changed-source in main source set.

I'd do similar to Michael Easter but with these differences
Store generated sources separately from main sources (src/main/java and $buildDir/generated/java). This has the added benefit of not needing custom gitignore
Generate in a subdirectory of $buildDir so that clean task will delete the generated sources
Use a separate task for code generation with proper up-to-date & skip support
Use Copy.expand(Map) to do the token replacement
Since its directory based, everything in src/template/java will have tokens replaced. You can easily add more templates in future
src/template/java/com/foo/BuildInfo.java
package com.foo;
public class BuildInfo {
public static String getVersion() {
return "${version}";
}
}
build.gradle
task generateJava(type:Copy) {
def templateContext = [version: project.version]
inputs.properties templateContext // for gradle up-to-date check
from 'src/template/java'
into "$buildDir/generated/java"
expand templateContext
}
sourceSets.main.java.srcDir "$buildDir/generated/java" // add the extra source dir
compileJava.dependsOn generateJava // wire the generateJava task into the DAG

One method is to similar to your not-so-nice way, but slightly easier. Consider a file in templates/BuildInfo.java:
package __PACKAGE;
public class BuildInfo {
private static final String version = "__VERSION";
private static final String buildTimestamp = "__BUILD_TIMESTAMP";
public String toString() {
return "version : " + version + "\n" +
"build timestamp : " + buildTimestamp + "\n";
}
}
This file can then be "stamped" with information as first thing in the compileJava task and written to src/main/java/your/package/BuildInfo.java:
def targetPackage = 'net/codetojoy/util'
def targetPackageJava = 'net.codetojoy.util'
def appVersion = project.appVersion // from gradle.properties
def buildTimeStamp = new Date().toString()
compileJava {
doFirst {
ant.mkdir(dir: "${projectDir}/src/main/java/${targetPackage}")
def newBuildInfo = new File("${projectDir}/src/main/java/${targetPackage}/BuildInfo.java")
def templateBuildInfo = new File("${projectDir}/templates/TemplateBuildInfo.java")
newBuildInfo.withWriter { def writer ->
templateBuildInfo.eachLine { def line ->
def newLine = line.replace("__PACKAGE", targetPackageJava)
.replace("__VERSION", appVersion)
.replace("__BUILD_TIMESTAMP", buildTimeStamp)
writer.write(newLine + "\n");
}
}
}
}
A working example is provided here. Everything would be stored in source-control except the src/main/java/your/package/BuildInfo.java file. Note the version would be stored in gradle.properties.

Related

Download Gradle dependencies with ".modules" files

I currently have an offline environment where I have all my dependencies (jar, aar & pom files). The thing is that I want to use Coil (image library) as a dependency in my Android project. This library requires kotlinx-coroutines-core to be also downloaded in my environment. I have been able to download all the required files except for .module file (Gradle Module Metadata), which is necessary because there are multiple variants of kotlinx-coroutines-core (versions for the JVM, JS and Native).
In other words, my code downloads kotlinx-coroutines-core-jvm-1.3.9.jar (with its POM) and kotlinx-coroutines-core-1.3.9.pom which is great but kotlinx-coroutines-core-1.3.9.module file is still missing and not sure how can I download it.
Here is my code based on this gist:
task copyDependencies() {
def name = "default"
def configuration = configurations.getByName(name)
copyJars(configuration)
copyPoms(configuration)
}
private void copyJars(Configuration configuration) {
File repoDir = new File(project.buildDir, 'repository')
configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->
def moduleVersionId = artifact.moduleVersion.id
File moduleDir = new File(repoDir, "${moduleVersionId.group.replace('.', '/')}/${moduleVersionId.name}/${moduleVersionId.version}")
GFileUtils.mkdirs(moduleDir)
GFileUtils.copyFile(artifact.file, new File(moduleDir, artifact.file.name))
}
}
private void copyPoms(Configuration configuration) {
def componentIds = configuration.incoming.resolutionResult.allDependencies.collect { it.selected.id }
def result = project.dependencies.createArtifactResolutionQuery()
.forComponents(componentIds)
.withArtifacts(MavenModule, MavenPomArtifact)
.execute()
for (component in result.resolvedComponents) {
def componentId = component.id
if (componentId instanceof ModuleComponentIdentifier) {
File repoDir = new File(project.buildDir, 'repository')
File moduleDir = new File(repoDir, "${componentId.group.replace('.', '/')}/${componentId.module}/${componentId.version}")
GFileUtils.mkdirs(moduleDir)
File pomFile = component.getArtifacts(MavenPomArtifact)[0].file
GFileUtils.copyFile(pomFile, new File(moduleDir, pomFile.name))
}
}
}
So my question is: How can I download .module files when downloading my dependencies? An example would be downloading this file.
To copy a configuration's files to a directory, you can do this:
task copyLibs(type: Copy) {
from configurations.runtimeClasspath
into file("$buildDir/repository")
}
This will copy everything including transitive dependencies.
Not need to write all that code yourself. Why do you want POMs and all that in your application's classpath? Just let Gradle sort it out for you.

Applying Gradle plugin from local file

I have the following gradle plugin that does the job of starting up a java process. The code for this lives under a file named startAppServerPlugin.gradle under the project's buildSrc directory.
The code of the plugin looks like this:
repositories.jcenter()
dependencies {
localGroovy()
gradleApi()
}
}
public class StartAppServer implements Plugin<Project> {
#Override
void apply(Project project) {
project.task('startServer', type: StartServerTask)
}
}
public class StartServerTask extends DefaultTask {
String command
String ready
String directory = '.'
StartServerTask(){
description = "Spawn a new server process in the background."
}
#TaskAction
void spawn(){
if(!(command && ready)) {
throw new GradleException("Ensure that mandatory fields command and ready are set.")
}
Process process = buildProcess(directory, command)
waitFor(process)
}
private waitFor(Process process) {
def line
def reader = new BufferedReader(new InputStreamReader(process.getInputStream()))
while ((line = reader.readLine()) != null) {
logger.quiet line
if (line.contains(ready)) {
logger.quiet "$command is ready."
break
}
}
}
private static Process buildProcess(String directory, String command) {
def builder = new ProcessBuilder(command.split(' '))
builder.redirectErrorStream(true)
builder.directory(new File(directory))
def process = builder.start()
process
}
}
I'm trying to figure out a way of having this imported into my main build.gradle file due everything I tried so far has been unsuccessful.
So far I have tried this:
apply from: 'startAppServerPlugin.gradle'
apply plugin: 'fts.gradle.plugins'
But it has been failing. I've tried searching online for examples of doing what I need to do but so far I've been unsuccessful. Can anyone please provide a hint as to how I'm supposed to do so?
The buildSrc folder is treated as an included build, where the code is compiled and put on the classpath of the surrounding project. The actual build.gradle file in buildSrc is only used for compiling that project, and the things you put in it will not be available elsewhere.
You are supposed to create your classes as a normal Java/Groovy/Kotlin project under buildSrc. I don't know if you can use the default package, but it is generally best practice to have a package name anyway.
For example, your StartAppServer plugin should be in buildSrc/src/main/groovy/my/package/StartAppServer.groovy. Then you can apply it in your build scripts with apply plugin: my.package.StartAppServer.
There are a lot of good examples in the user guide.
You are on the right path. The first order of business is to import the external gradle build using:
apply from: 'startAppServerPlugin.gradle'
Then you can apply the plugin with:
apply plugin: StartAppServer
See Script Plugins and Applying Binary Plugins

How to access variant.outputFileName in Kotlin

We've been using a snippet like this one to rename the APK file generated by our Gradle build:
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}
Source: https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output
I am now in the process of converting my build.gradle to build.gradle.kts, i. e. to the Gradle Kotlin DSL. This is one of the last missing pieces: I can't figure out how to access outputFileName.
According to the API docs it does not even seem to exist:
BaseVariant.getOutputs() returns a DomainObjectCollection<BaseVariantOutput> which provides the all method used in the snippet.
BaseVariantOutput extends OutputFile which extends VariantOutput but none of these has an outputFileName or any getters or setters of a matching name.
So, I suspect there is some advanced Groovy magic at work to make this work - but how do I get there in Kotlin?
A little simplified version of #david.mihola answer:
android {
/**
* Notes Impl: Use DomainObjectCollection#all
*/
applicationVariants.all {
val variant = this
variant.outputs
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
val outputFileName = "YourAppName - ${variant.baseName} - ${variant.versionName} ${variant.versionCode}.apk"
println("OutputFileName: $outputFileName")
output.outputFileName = outputFileName
}
}
}
Browsing through the source code of the Android Gradle plugin, I think I found the answer - here we go:
We are actually dealing with objects of type BaseVariantOutputImpl and this class does have both these methods:
public String getOutputFileName() {
return apkData.getOutputFileName();
}
public void setOutputFileName(String outputFileName) {
if (new File(outputFileName).isAbsolute()) {
throw new GradleException("Absolute path are not supported when setting " +
"an output file name");
}
apkData.setOutputFileName(outputFileName);
}
Using this knowledge we can now:
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
and then cast our target objects like so:
applicationVariants.all(object : Action<ApplicationVariant> {
override fun execute(variant: ApplicationVariant) {
println("variant: ${variant}")
variant.outputs.all(object : Action<BaseVariantOutput> {
override fun execute(output: BaseVariantOutput) {
val outputImpl = output as BaseVariantOutputImpl
val fileName = output.outputFileName
.replace("-release", "-release-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
.replace("-debug", "-debug-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
println("output file name: ${fileName}")
outputImpl.outputFileName = fileName
}
})
}
})
So, I guess: Yes, there is some Groovy magic at work, namely that Groovy's dynamic type system allows you to just access getOutputFileName and setOutputFileName (by way of the abbreviated outputImpl.outputFileName syntax, as in Kotlin) from your code, hoping they will be there at runtime, even if the compile time interfaces that you know about don't have them.
Shorter version using lambdas:
applicationVariants.all{
outputs.all {
if(name.contains("release"))
(this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
}
}
This will place APK into app/apk folder with name made of variant name and version code.
You can change the format of filename as you wish.
Important: it must be done only on release builds, because ".." in path corrupts debug build process with strange errors.
For libraryVariants it is possible to change output file name without accessing internal api:
libraryVariants.all {
outputs.all {
packageLibraryProvider {
archiveFileName.set("yourlibrary-${buildType.name}.aar")
}
}
}
For Kotlin KTS.
NOTE: This is considered a temporal soluciĆ³n, until a proper way to do it in KTS is released by Android team.
Working in AGP v7.1.2 it might work also in lower versions of AGP.
:app build.gradle
android {
// ...
this.buildOutputs.all {
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variantName: String = variantOutputImpl.name
val outputFileName = "custom-name-${variantName}.apk"
variantOutputImpl.outputFileName = outputFileName
}
}

How to use gradle extension correctly in plugins using GradleBuild task?

EDIT : I rephrased my question in taken the propositon of David M. Karr into account.
I am writing a gradle plugin. This plugin is launching a task extending GradleBuild. The external gradle build file needs some info as parameters. These parameters are given in project extension.
Plugin code
class MyPlugin implements Plugin<Project> {
def mExt
void apply(Project project) {
mExt = project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
def param = new StartParameter()
param.setProjectProperties([target:getTarget()])
// Problem here
startParameter = param
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
}
def getMyBuildPath(){
...
}
// Problem here
def getTarget(){
return {mExt.target}
}
}
class MyExt {
def String target = "uninitialised"
}
Gradle build file :
apply plugin : 'com.example.myplugin'
ext{
target = "myTarget"
}
External Gradle build file :
task build(){
println project.target
}
If I put a closure in getTarget(), println project.target shows the closure and not the string.
If I don't put the closure :
// Problem here
def getTarget(){
return mExt.target
}
Then I got "uninitialised" instead of "myTarget".
How can I get the value of myext.target here ?
I am using gradle 2.3
Try this:
Define an instance variable called "myext", of type "MyExt".
In the "apply" method, do this:
myext = project.extensions.create('myext',MyExt)
In the "getTarget" method, return "myext.target".
I have succeeded in getting what I wanted to in using project.afterEvaluate method. Thanks to this question
1) In gradle build task, startParameter.projectProperties is waiting for a map, not a closure. So the idea to put a closure for a lazy definition cannot work.
2) If I put directly in my plugin a reference to mExt.target or project.myext.target, then the initial value is set. The value put in my build.gradle file is not used because the plugin is already evaluated.
3) project.afterEvaluate() solve my problem. The closure ends configuring myTask in using the project's extension.
void apply(Project project) {
project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
project.afterEvaluate { proj ->
proj.myTask.startParameter.projectProperties = [target:proj.myext.target]
}
}

Accessing Gradle inputs in task constructor

I'm new to Gradle and having troubles creating a custom Task that can correctly determine when it's up to date. I'd like to set the member decorated with #OutputFile in the custom task's constructor based off the other input variables, but they all set to the defaults in the constructor. I do see the values set correctly in the TaskAction.
I'm sure I'm missing something simple but I've combed the docs, tried to dig up examples and I'm not finding much.
Here is a simplified version of the build script:
apply plugin: 'java'
sourceCompatibility = 1.5
version = '1.0'
class TestTask extends DefaultTask {
#Input
String [] className = []
#Input
String outputDir = ""
#OutputFile
File targetFile
TestTask() {
println("Constructor")
println("outputDir: " + outputDir)
// I'd like to set the targetFile here, based on the outputDir and className
}
#TaskAction
def action() {
println("Action")
println("outputDir: " + outputDir )
}
}
task runTest_Opus(type:TestTask) {
className = ['class.name.here']
outputDir = 'jni/outputDir/'
}
task runAll {
dependsOn tasks.withType(TestTask)
}
build.dependsOn.add("runAll")
If the value of targetFile is derived then use a getter method instead of an instance field.
#OutputFile
File getTargetFile() {
// code to resolve output file based on other inputs
}
Depending on your exact needs, #OutputDirectory File outputDir could be an easy solution. Derived defaults are more commonly set by a plugin. In any case, their computation needs to be deferred in one way or another (e.g. project.afterEvaluate { ... }).

Resources