update version number in README.md during gradle build - android

I am working on an Android library (aar) project. The project contains a README.md file which in turn contains these lines:
... declare library dependency:
Gradle: `compile 'com.acme:mylibrary:1.0.0#aar'`
My gradle.properties file contains:
VERSION_NAME=1.0.0
The problem is that currently I have to keep two files manually in sync. What I would like to do is keep the VERSION_NAME property and substitute it's value into README.md

If you have some pattern to find out where you used the version number it could be as easy as creating a tasks and replacing text based on a regex.
Something like:
task replaceVersionInREADME << {
// Maven
ant.replaceregexp(match:'<version>([0-9\\.]+)</version>', replace:"<version>${version}</version>", flags:'g', byline:true) {
fileset(dir: '.', includes: 'README.md')
}
// Gradle
ant.replaceregexp(match:'com\\.acme\\:mylibrary\\:([0-9\\.]+)', replace:"com.acme:mylibrary:${version}", flags:'g', byline:true) {
fileset(dir: '.', includes: 'README.md')
}
}
Change the regex as you need.

Related

How to pass arguments to the CMake build of a plugin from gradle build in flutter?

My question is similar to this one, except that in that question, he wanted to pass arguments directly from gradle to CMake. But in my case, I am not directly invoking the cmake build, but rather "flutter is doing it for me"
So to put it all together, when the app build starts:
gradle build of the app starts (build.gradle in the flutter project) ------(1)
the above build invokes gradle build of the plugin (build.gradle in the plugin's project) ------(2)
the gradle build of the plugin invokes the CMake build of the plugin (CMakeLists.txt in the plugin project) ------(3)
Now sending args from step (2) to step (3) is as mentioned in the link above.
My question is how to send args from step (1) to step (2)?
I am not very experienced with gradle but I tried inspecting the build.gradle file of step (1) to see from where it is invoking the build.gradle of the plugin, but I wasn't able to do that.
Update:
this answer is very similar to what I want, except that, as I mentioned, I want to pass variable from a gradle build that indirectly invokes cmake.
The use case for me is as follows: the plugin has some C code and uses shared libraries, these libraries have different versions and the user must choose one of them at build time, thats why I want the user to pass variables to cmake, for ex:
externalNativeBuild {
cmake {
arguments "-DLIB_VERSION=VERSION_WITH_SOME_OPTION"
}
}
and then in CMake:
if(${LIB_VERSION})
# link some version of the shared libs
else()
# link another version
So I found a solution, but I don't know if this is the right way to do it, as it defines the variables to be passed as "global gradle variables".
So we have 3 files:
(1) android/app/build.gradle in the flutter app's project folder
(2) android/build.gradle in the plugin's project folder
(3) CMakeLists.txt in the plugin's project folder
We want to pass an argument from (1) to (3) (since the user of the plugin only has access to (1)):
in (1), define the variables to be passed, put this at the end of the file:
ext {
Q16 = 1
HDRI = 0
}
in (2), read the variables at the beginning of the file:
def Q8
def Q16
def HDRI
if(rootProject.hasProperty("Q8")){
Q8 = 1
}
else{
Q8 = 0
}
if(rootProject.hasProperty("Q16")){
Q16 = 1
}
else{
Q16 = 0
}
if(rootProject.hasProperty("HDRI")){
HDRI = 1
}
else{
HDRI = 0
}
Then pass them to (3) (since (2) directly invokes (3)):
android{
...
defaultConfig{
...
externalNativeBuild{
cmake {
arguments "-DQ8=${Q8}" , "-DQ16=${Q16}" , "-DHDRI=${HDRI}"
}
}
}
}
Now we can read them in (3):
message("from CMakeLists.txt: Q8 = ${Q8}")
message("from CMakeLists.txt: Q16 = ${Q16}")
message("from CMakeLists.txt: HDRI = ${HDRI}")

How to generate OpenAPI sources from gradle when building Android app

What I'm trying to achieve
I'm trying to generate my REST API client for Android using OpenAPI Generator from the build.gradle script. That way, I wouldn't have to run the generator command line every time the specs change. Ideally, this would be generated when I build/assemble my app, and the sources would end up in the java (generated) folder, where generated sources are then accessible from the code (this is what happens with the BuildConfig.java file for example).
What I've tried so far
Following this link from their official GitHub, here's the build.gradle file I ended up with:
apply plugin: 'com.android.application'
apply plugin: 'org.openapi.generator'
...
openApiValidate {
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
recommend = true
}
openApiGenerate {
generatorName = "java"
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
outputDir = "$buildDir/generated/openapi"
groupId = "$project.group"
id = "$project.name-openapi"
version = "$project.version"
apiPackage = "com.example.mypackage.api"
invokerPackage = "com.example.mypackage.invoker"
modelPackage = "com.example.mypackage.model"
configOptions = [
java8 : "true",
dateLibrary : "java8",
library : "retrofit2"
]
}
...
First, I've never managed to get the API generated with the build/assemble task, even when I tried adding:
compileJava.dependsOn tasks.openApiGenerate
or
assemble.dependsOn tasks.openApiGenerate
The only way I could generate the sources was by manually triggering the openApiGenerate task:
Then, when I do generate my sources this way, they end up in the build folder but aren't accessible from my code, and aren't visible in the java (generated) folder:
I then have to manually copy/paste the generated source files to my project sources in order to use the API.
Even though I'm able to work around these issues by adding manual procedures, it would be way more maintainable if the whole process was simply automatic. I was able to achieve a similar result with another tool, Protobuf. Indeed, my gradle task gets triggered every time I build the app, and the sources end up in the java (generated) folder, so I don't have to do any additional work. The task is much simpler though, so I assume the main work that I'm not able to replicate with OpenAPI Generator is handled by the Protobuf plugin itself.
You have to specify path to the generated sources as a custom source set for your Gradle module, which is app in this case, as described here – https://developer.android.com/studio/build/build-variants#configure-sourcesets. That way Gradle will treat your sources as accessible from your code.
Something like this:
android {
...
sourceSets {
main {
java.srcDirs = ['build/generated/openapi/src/main/java']
}
}
...
}
I solved the issue you described like this, I'm using gradle.kts however.
See my build.gradle.kts
plugins {
// Your other plugins
id("org.openapi.generator") version "5.3.0"
}
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$rootDir/app/src/main/openapi/my-api.yaml")
outputDir.set("$buildDir/generated/api")
// Your other specification
}
application {
// Your other code
sourceSets {
main {
java {
// TODO: Set this path according to what was generated for you
srcDir("$buildDir/generated/api/src/main/kotlin")
}
}
}
}
tasks.compileKotlin {
dependsOn(tasks.openApiGenerate)
}
You need to build the application at least once for the IDE to detect the library (at least this is the case for me in Intellij)
Your build should automatically generate the open api classes , to refer the generated classes in your java project you should add the generated class path to your source directory like it was mentioned in the other answers
https://developer.android.com/studio/build/build-variants#configure-sourcesets
As far as the task dependency goes , in android tasks are generated after configuration thus for gradle to recognize the task , wrap it inside afterEvaluate block like
afterEvaluate {
tasks.compileDebugJavaWithJavac.dependsOn(tasks.openApiGenerate)
}
I had this issue, and this answer https://stackoverflow.com/a/55646891/14111809 led me to a more informative error:
error: incompatible types: Object cannot be converted to Annotation
#java.lang.Object()
Taking a look at the generated files that were causing this error, noticed:
import com.squareup.moshi.Json;
After including a Moshi in the app build.gradle, the build succeeded and the generated code was accessible.
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")

Gradle: How to define common dependency for multiple flavors?

My app has 3 flavors (free, paid, special) and there's a dependency LIB_1 need only for free flavor and other dependency LIB_2 needed for both paid and special flavors.
So, my question is how to define these dependencies in build.gradle file?
Currently, i define them like this:
dependencies {
freeImplementation 'LIB_1'
paidImplementation 'LIB_2'
specialImplementation 'LIB_2'
}
Is there a better way to define them instead of duplicating the same dependency for different flavors?
Yes, according to the documentation of android gradle dependency management, this is the only way of declaring flavor-specific dependencies.
If you are in a multi-modular project (and you don't want to repeat those lines in each submodule), you can also define these dependencies using the subproject block in the build.gradle of the root project:
subprojects {
//all subprojects` config
}
//or
configure(subprojects.findAll {it.name != 'sample'}) {
// subprojects that their name is not "sample"
}
Yes, it's possible to avoid duplicating the dependency across flavors, as I describe in my answer here: https://stackoverflow.com/a/75083956/6007104
Utility
Simply add these functions before your dependencies block:
/** Adds [dependency] as a dependency for the flavor [flavor] */
dependencies.ext.flavorImplementation = { flavor, dependency ->
def cmd = "${flavor}Implementation"
dependencies.add(cmd, dependency)
}
/** Adds [dependency] as a dependency for every flavor in [flavors] */
dependencies.ext.flavorsImplementation = { flavors, dependency ->
flavors.each { dependencies.flavorImplementation(it, dependency) }
}
Usage
You use the utility like this:
dependencies {
...
def myFlavors = ["flavor1", "flavor2", "flavor3"]
flavorsImplementation(myFlavors, "com.example.test:test:1.0.0")
flavorsImplementation(myFlavors, project(':local'))
...
}
How it works
The key to this utility is gradle's dependencies.add API, which takes 2 parameters:
The type of dependency, for example implementation, api, flavor1Implementation. This can be a string, which allows us to use string manipulation to dynamically create this value.
The dependency itself, for example "com.example.test:test:1.0.0", project(':local').
Using this API, you can add dependencies dynamically, with quite a lot of flexibility!

Gradle/Jenkins : Create a gradle file to direct to sub projects

I've got a directory with three android projects in it.
The MainDir looks like that :
/.gradle
/.git
/project1
/project2
/project3
.gitignore
.Jenkinsfile
.README.md
In jenkins I can't run a shell script during the build that launchs gradle tasks for eauch of those projects because he doesn't know these are projects (he says "no sub-project").
In a project dir it looks like :
/.gradle
/app
/build
/gradle
.gitignore
.build.gradle
.gradle.properties
.gradlew
Is there a way to make jenkins understand these are three projects he can launch gradle taks in ? Like creating a build.gradle file in the main directory doing that ?
Or should I just create 3 Jenkins items?
You could make three builds in jenkins but unless there is a need to build the libs seperately then it might just end up being extra effort. Sounds like what you really want is a multi project build [1]. A simple example could sit at the folder above your lib projects as two files, build.gradle and settings.gradle
The settings.gradle will define what projects are included in your build's scope.
For example given your project1, project2 and project3 example your settings.gradle may look like this.
rootProject.name = 'myRootProjectName'
// note the name is not required to match the actual path
include ":project1"
// but if the name is not the same as the path then we can just
// let gradle know where the project is expected
project(":project1").projectDir = new File(settingsDir, "pathToProject1")
include ":project2"
project(":project2").projectDir = new File(settingsDir, "pathToProject2")
include ":project3"
project(":project3").projectDir = new File(settingsDir, "pathToProject3")
//##### below would be instead of the code above, same thing just manual
// project setup vs letting gradle find the subprojects
// note sometimes you have lots of subprojects in that case it's sometimes
// easier to just use a little logic for finding and setting up the subprojects.
// don't use the code above ##### and below only use one or the other
// or you will have errors. The method below is the most scaleable since
// adding projects requires zero modifications to the root project
rootProject.name = 'myRootProjectName'
// set up a couple file filters to find the dirs we consider subprojects
FileFilter projectFilter = { File pathname ->
FileFilter gradleProjectFilter = { File file -> file.name == 'build.gradle' }
// add this folder if is a directory and that directory contains a build.gradle file
// here note `File#listFiles` is true if it's `size() > 0` due to
// groovy's concept of truth (details: http://groovy-lang.org/semantics.html#Groovy-Truth)
return pathname.isDirectory() && pathname.listFiles(gradleProjectFilter)
}
settingsDir.listFiles(projectFilter).each { dir ->
include ":$dir.name"
project(":$dir.name").projectDir = dir
}
now running gradle projects task should show the three submodules.
As for your build.gradle file you could specify some common properties to all the modules if needed or just leave the file blank, it must exist but can be empty. If you wanted to share some configurations then you might set up the build.gradle with something like this.
project.subprojects { Project subproject ->
// anything that is defined here will be executed before the subproject's build.gradle file
subproject.buildscript {
repositories {
jcenter()
// your private maven repo if needed
maven { url 'http://1.2.3.4:8081/nexus/content/repositories/release' }
}
dependencies {
// some plugin that is now available to be applied in any subproject
classpath 'my.sweet.gradle:plugin:0.1'
}
}
subproject.afterEvaluate {
// this block is executed after the subproject's build.gradle file
if (project.tasks.withType(org.gradle.jvm.tasks.Jar)) {
// for example you might want to set the manifest for each subproject
manifest {
attributes 'Implementation-Title': "Lib $subproject.name",
'Implementation-Version': version
}
}
}
}
[1] https://docs.gradle.org/current/userguide/multi_project_builds.html

Ignore proguard configuration of an external library

So, I want to add an external library to my project. The library itself is quite small, around 300 methods. But it is configured to be very liberal with it's proguard configuration. I ran a simple test with/without the library and with/without proguard on a barebones project and this is what I came up with
Proguard Lib Method Count
N N 15631
Y N 6370
N Y 15945
Y Y 15573
As you can see, with proguard enabled, the count is ~6000. But the moment I add the lib, count shoots up to ~15000 despite the library itself being only ~300 methods.
So my question is, how do I ignore the proguard configuration of this particular library?
UPDATE:
It is not possible with android gradle plugin now. I found android bug which doesn't have priority at all. Please avoid answers with mentioning "it is not possible" and keep question opened until a workaround or an official decision is possible. Otherwise, you will collect half of bounty without adding value. Thanks!
In this specific case you have a few options:
extract the classes.jar file from the aar and include it as normal jar dependency in your project (will not work when the aar includes resources)
change the aar and remove the consumer proguard rules from it
use DexGuard which allows you to filter out unwanted consumer rules
do a bit of gradle hacking, see below
Add the following to your build.gradle:
afterEvaluate {
// All proguard tasks shall depend on our filter task
def proguardTasks = tasks.findAll { task ->
task.name.startsWith('transformClassesAndResourcesWithProguardFor') }
proguardTasks.each { task -> task.dependsOn filterConsumerRules }
}
// Let's define our custom task that filters some unwanted
// consumer proguard rules
task(filterConsumerRules) << {
// Collect all consumer rules first
FileTree allConsumerRules = fileTree(dir: 'build/intermediates/exploded-aar',
include: '**/proguard.txt')
// Now filter the ones we want to exclude:
// Change it to fit your needs, replace library with
// the name of the aar you want to filter.
FileTree excludeRules = allConsumerRules.matching {
include '**/library/**'
}
// Print some info and delete the file, so ProGuard
// does not pick it up. We could also just rename it.
excludeRules.each { File file ->
println 'Deleting ProGuard consumer rule ' + file
file.delete()
}
}
When using DexGuard (7.2.02+), you can add the following snippet to your build.gradle:
dexguard {
// Replace library with the name of the aar you want to filter
// The ** at the end will include every other rule.
consumerRuleFilter '!**/library/**,**'
}
Mind that the logic is inverted to the ProGuard example above, the consumerRuleFilter will only include consumer rules that match the pattern.
In case you're using R8 (which replaced ProGuard since Android Gradle plugin 3.4.0) - you can filter-out specific consumer rule files by adding the following work-around to your module's build.gradle:
tasks.whenTaskAdded { Task task ->
// Once 'minifyEnabled' is set to 'true' for a certain build type/variant -
// a 'minify<variantName>WithR8' task will be created for each such variant
//
// - This task is implemented by com.android.build.gradle.internal.tasks.R8Task
// - R8Task extends from ProguardConfigurableTask
// - ProguardConfigurableTask exposes property 'configurationFiles'
// - configurationFiles contains all files that will be contributing R8 rules
// - configurationFiles is mutable (its type is ConfigurableFileCollection)
//
// Thus - we can overwrite the list of files and filter them out as we please
//
// More details: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt
if (task.name.startsWith("minify") && task.name.endsWith("WithR8")) {
afterEvaluate {
def filteredList = task.configurationFiles.filter {
// Example paths in this collection:
// /Users/me/MyProject/myModule/proguard-rules.pro
// (for library dependencies) /Users/me/.gradle/caches/<...>/okhttp3.pro
// The below filter condition will, for example, exclude consumer ProGuard rules file from the AndroidX RecyclerView library
!it.path.contains("recyclerview-1.1.0")
}
task.configurationFiles.setFrom(filteredList.files)
}
}
}
The above work-around was confirmed to be working on Android Gradle plugin 4.2.2
If one decides to rely on such a work-around - it might be a good idea to also add some sort of automated checks and/or tests to make sure this filtering is working. Given that the solution is quite fragile and can break with future updates of Android Gradle plugin.
Inspired by Jonas' answer, modified for Kotlin DSL and confirmed working on Android Gradle plugin 7.2.1:
import com.android.build.gradle.internal.tasks.ProguardConfigurableTask
afterEvaluate {
// Get each ProguardConfigurableTask
tasks.withType(ProguardConfigurableTask::class.java).forEach { task ->
// Remove proguard rules from lifecycle-runtime library
val filteredConfigurationFiles = task.configurationFiles.filter { file ->
!file.path.contains("lifecycle-runtime")
}
task.configurationFiles.setFrom(filteredConfigurationFiles)
}
}

Categories

Resources