Jenkins read Android app version from build.gradle file to environment variable - android

I want to push version tags to my git repository automatically when Jenkins creates a build. But in order to do this I need the version name and version code for the created build artifact.
I'm currently using the following setup to achieve this (it works fine):
Create a Gradle task in the build.
Run a Gradle task that creates a properties file with the version name and version code in it;
Using the EnvInject plugin read/inject the properties file, so that the environment variables are available to use in the current Jenkins job;
The code to generate the version.properties file looks like this:
android.applicationVariants.all { variant ->
def taskName = "createVersionFile" + variant.flavorName.capitalize();
if (tasks.findByPath(taskName) == null) {
tasks.create(name: taskName) {
doLast {
def prop = new Properties()
def propFile = new File("$buildDir/outputs/version.properties");
prop.setProperty('versionname', variant.versionName + '-' + variant.versionCode)
propFile.createNewFile();
prop.store(propFile.newWriter(), null);
}
}
}
}
This works (as mentioned before) but it's a quite unwieldy method, I'm forced to modify the build.gradle file in order for Jenkins to do it's work.
Is there an easier method, possibly without modifying the build.gradle file? Maybe by generating a second build.gradle file which
includes the "version.properties" task?

My build philosophy is that everything should be available by gradle. No matter what automation you want to do with your project. That gives you flexibility to repeat it in any environment and easily setup any CI that just supports command line runs.
So I would add gradle git plugin to your build to manipulate with git (or do it via command line).
Take a look to gradle.properties file. You can define version code and name there. You don't need extra code to get these values in your Android gradle script and git manipulations as parameters. So you can inject them in defaultConfig.

Related

Using a path alias to point to a folder results in a Gradle error

In order to point to the location of our build artifacts on a Jenkins node, we have the following gradle code:
def apks = file("$WORKSPACE/app/build/outputs/build-apks.tar.gz")
artifacts {
archives apks
}
When I use the full path as shown below, gradle builds fine.
def apks = file("/var/jenkins/workspace/ui_manager_nexusDeploy/app/build/outputs/build-apks.tar.gz")
When we use $WORKSPACE, I get
Could not get unknown property 'WORKSPACE' for project ':app' of type "org.gradle.api".Project. path
Ideally I'd like to use the pointer as opposed to hard-coding the path. In this reference doc, you can see that Gradle is using $buildDir just fine.https://docs.gradle.org/current/userguide/working_with_files.html
Can you try to access the workspace variable by "$System.env.WORKSPACE"?
ex:
def apks = file("$System.env.WORKSPACE"+"/app/build/outputs/build-apks.tar.gz")

Real time rules with ktlint

I am trying to use ktlint on my android project.
Currently I am using these gradle plugins:
org.jlleitschuh.gradle.ktlint
org.jlleitschuh.gradle.ktlint-idea
I can check the code using a manual gradle task, where the warnings will be displayed by the terminal.
Is it possible to achive something like eslint in javascript? Example:
Perhaps the following advice does not directly solve your requirement. But, what about making the auto format process part of the build process? I mean, your code will be auto formatted and then checked according to the ktlint rules and you only will be worried about to run the build command (i.e. ./gradlew clean build) before committing your code. This is how I have done that:
First of all, I am using Gradle 6.3 and I have implemented the version 9.1.2 of org.jlleitschuh.gradle.ktlint plugin.
Once the plugin has been imported to the project, some tasks are added as the plugin's documentation describes:
This plugin adds two maintasks to every source set: ktlint[source set name]SourceSetCheck and ktlint[source set name]SourceSetFormat
Usually the added tasks are:
ktlintMainSourceSetCheck and ktlintMainSourceSetFormat for src/kotlin/main source set
ktlintTestSourceSetCheck and ktlintTestSourceSetFormat for src/kotlin/test source set
In order to make ktlintMainSourceSetFormat and ktlintTestSourceSetFormat part of the build process, you should add the following gradle action inside subprojects { } section:
For kotlin-based gradle scripts (build.gradle.kts):
afterEvaluate {
tasks["ktlintMainSourceSetCheck"].dependsOn(tasks["ktlintMainSourceSetFormat"])
tasks["ktlintTestSourceSetCheck"].dependsOn(tasks["ktlintTestSourceSetFormat"])
}
For groovy-based gradle scripts (build.gradle):
afterEvaluate {
tasks.getByName('ktlintMainSourceSetCheck').dependsOn(tasks.getByName('ktlintMainSourceSetFormat'))
tasks.getByName('ktlintTestSourceSetCheck').dependsOn(tasks.getByName('ktlintTestSourceSetFormat'))
}
This makes the "format" tasks mentioned above run before the "check" tasks after the build process of the current sub project has succeeded. These "check" tasks are the ones that display formatting errors in the console whenever the build is run, but making them children of the "format" tasks will force the build process to format the code before checking it so that no formatting errors will show up in the console.
Additionally, you can also apply code checks and formats to build script files (build.gradle(.kts)) by adding the following code inside allprojects { } section:
afterEvaluate {
tasks["ktlintKotlinScriptCheck"].dependsOn(tasks["ktlintKotlinScriptFormat"])
}
For Git support:
If you want to add the auto formatting feature at pre-commit stage, you can use these tasks: addKtlintCheckGitPreCommitHook and addKtlintFormatGitPreCommitHook as explained here
For IntelliJ IDEA support:
If you want to apply the ktlint rules to your current project, just execute the following gradle command once you have imported the org.jlleitschuh.gradle.ktlint-idea plugin: ./gradlew ktlintApplyToIdea and this will replace the codeStyles file inside .idea/ folder as described here
Finally, here is an example of a build.gradle.kts where all the mentioned above is applied.
You will not get an error message like in eslint but using following method you won't need to deal with ktlint styling issues manually. Is not it much better than manually fixing things up?
Install ktlint commandline tools as described here.
Set IntelliJ IDEA's code styles to align with ktlint's code styles as described here.
So when you make a commit using IntelliJ IDEA's commit window (CTRL + K or Command + K) and you have reformat code before commit enabled then your commit will be made with correct code styles.
Try to install plugin Ktlint ​(unofficial)​ and follow setup instructions.
Go to Preferences > Tools > ktlint and make sure Enabled ktlint checkbox is selected.
In case of android project also select Android mode check box.

How to use different settings.gradle files for different environments

The problem
I have two projects, A (ui) and B (background service). Project A has a dependency on B. Project B gets published to a maven repository and included in project A like so in build.gradle
debugImplementation ('com.example:project-B:0.0.0-SNAPSHOT') { changing = true }
releaseImplementation ('com.example:project-B:1.6.2')
This works, but it's a pain to validate my service changes on the UI side. I need to publish project B to my nexus repo and resync project A.
I changed project A to the following:
build.gradle:
debugImplementation project(":project-b")
settings.gradle:
include ':project-a'
include 'project-b'
project(':project-b').projectDir = new File(settingsDir, "${project-b-path}")
I can have all my code in one IDE window and have A use local instance of B. But the problem is this will break on my build server since there is no local B project, only the one on nexus.
Is there a way to configure the settings.gradle for release vs debug? I can just commit my changes and overwrite the file on the build server, but I want to know if there are other ways?
You can use gradle command line to set which settings or build file should be used.
Settings File
-c, --settings-file
Specifies the settings file. For example: gradle --settings-file=somewhere/else/settings.gradle
Build File
-b, --build-file
Specifies the build file. For example: gradle --build-file=foo.gradle. The default is build.gradle, then build.gradle.kts, then myProjectName.gradle.
You can find more details here: Gradle docs: Environment options

How to get build variant output directory

I need to run some tasks that occur after an Android project's assemble* task finishes. In particular, these tasks need to know what was the output directory for all the compiled classes for a particular build variant. How do I retrieve the output directory for an assembleFlavor1Debug task?
My current workaround is something like this (although this workaround presents problems of its own, like not being able to find the assemble tasks even though it's been placed after the android configuration block):
android.buildTypes.all { theBuildType ->
android.productFlavors.all { theFlavor ->
String capitalizedType = ... //Type name with first letter capitalized
String capitalizedFlavor = ... //Flavor name with first letter capitalized
...
project.tasks["assemble${capitalizedType}${capitalizedFlavor}"].configure {
doLast {
project.ext.variantOutput = "build/intermediates/classes/${theFlavor.name}/${theBuildType.name}"
}
}
}
}
EDIT #1: I was able to fix my workaround. The major issue was that the Android assemble* tasks (assembleProdDebug, assembleProdRelease, etc.) were not yet created on the project, even though configuration was occurring after the Android configuration block. I was able to get the additional configuration on the assemble* tasks done by enclosing the entire code snippet above into a gradle.taskGraph.whenReady {...} block, but this did mean I lose out on the ability to continue configuring the dependency graph. Fortunately, not being to configure dependencies in my particular case was not a major loss; all I needed was the ability to record the last assembled build type and product flavor.
I'd also like to note that this behavior is with version 1.0.0 of the Android Gradle plugin. Although I have not checked, the absence of these Android tasks might not occur on newer versions of this plugin.
EDIT #2: So I've also tried version 1.3.0 of the Android Gradle plugin. I'd also like to note that this is the LIBRARY Android plugin, and not the application plugin (I suspect these missing assemble* tasks are not generated during project configuration for the application plugin as well, however).
You might want to try instead of wrapping the entire thing with gradle.taskGraph.whenReady try using afterEvaluate closure. The tasks should exist after the project is evaluated.
This means your closure would run at the end of the configuration phase and before the execution phase. At this time all tasks would have to be registered.
afterEvaluate { project ->
// do work on `Project` object just like normal
project.android.buildTypes.all { theBuildType ->
...
}
}
ref: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#afterEvaluate(groovy.lang.Closure)

How to add a dependent project to an android gradle project that builds a debug or release jar approprately

I need a clear example of how to extend an Android Gradle project with an arbitrary project.
By arbitrary I mean that it can't just use the 'java' plugin since it doesn't support buildTypes to my knowledge. I am currently using an 'ant' task for this, which has two targets for debug and release, however I don't see how to tie it into an Android project.
Assume that your dependent project must build pure Java source in two ways:
debug build that produces a debug version in 'purejava.jar'
release build that produces a release version in 'purejava.jar'
The jar 'purejava.jar' is to be placed such that the Android project (could be a multi-project) is able to reference it at compile time, and it must therefore be the correct build to support both the debug and release configurations of the Android project.
How should this be tackled?
Since I am new to Android Studio and Gradle, I don't have a clear idea of how to manipulate extensions generated by the Android plugin, which are not available until after project evaluation.
How should the Android project be made dependent on this pure java project?
If it weren't for the fact that you need debug and release versions of your library, then your Android app could depend on a plain Java module just fine -- you could set up the library with the java plugin and put a compile project statement in the app's dependencies and it would work fine.
However, the Java plugin is never going to understand Android's notion of build types (unless GradleWare adds it at some point), so you can't propagate that to your Java modules. You could set up your plain Java project as an Android library and use the android-library plugin (you'll have to dummy out the manifest and other Android-specific stuff it expects to see in Android libraries), but you'll run into a different problem: https://code.google.com/p/android/issues/detail?id=52962 is a bug that reports that the build type is not propagated to library modules.
Until that bug is fixed, or if you're unwilling to make your plain Java library an android library, I think your only approach is to make two different versions of your library, compile them to different jar files, and selectively pull in dependencies.
This is my answer, with following project structure:
MyProject
-- MyAndroidLib
-- JarProject
This represents the gradle top project 'MyProject' which has a sub-project 'MyAndroidLib' which is dependent on a pure java project 'JarProject' which is built with different code for debug than for release builds.
I'll take advantage of Android's 'debugCompile' and
'releaseCompile' configurations. In the Android sub-project ('MyAndroidLib') that is dependent on the jars, add following lines to the dependencies:
//MyAndroidLib build.gradle
def jarProject = project(':MyProject:MyAndroidLib:JarProject')
def jarPath = pcfProject.projectDir.toString()
dependencies {
....
compile jarProject
debugCompile files(jarPath + '/' + jarProject.debugJarName)
releaseCompile files(jarPath + '/' + jarProject.releaseJarName)
}
The 'jarProject' def is defined to simplify accessing it from the MyAndroidLib project. (If you know a better way ...)
The main point of this is to define a separate debug and release jar path for the 'debugCompile' and 'releaseCompile' configurations. The 'debugJarName' and 'releaseJarName' are defined in a gradle.properties file for the JarProject as follows:
//JarProject gradle.properties
debugJarName=jarproject_d.jar
releaseJarName=jarproject_r.jar
In the gradle file for JarProject define a task that builds BOTH jar files named by this properties file. In my case, they are built right in the project folder by the 'compile' target of an ant build file located in that project.
//JarProject build.gradle
apply plugin: 'java'
project.ext.set("debugJar", file(projectDir.toString() + "/" + debugJarName))
project.ext.set("releaseJar",file(projectDir.toString() + "/" + releaseJarName))
task buildJars(type: Exec) {
description 'Build the debug and release jars for the JarProject'
outputs.files debugJar,releaseJar
commandLine 'ant', 'compile'
}
task compileJava.dependsOn('buildJars')
artifacts {
buildJars
}
clean.dependsOn('cleanBuildJars')
clean << {
exec {
commandLine 'ant', 'clean'
}
}
I took advantage of the 'java' plugin since it defines a 'compile' interface, and I haven't figured out how to build this from scratch, or even from the 'base' plugin. This project takes advantage of the automatic 'cleanBuildJars' task created because I defined the outputs in 'buildJars' task. This is necessary in order to have them built as needed. I probably need to define the 'inputs' too, since if they change ...
If anyone sees how my first stumblings in the gradle/Android world can be improved, pls. add comments as needed.

Categories

Resources