📜 ⬆️ ⬇️

Cheat Sheet by Gradle

As it seems to me, most people start to deal with gradle only when something needs to be added to the project or something suddenly breaks down - and after solving the problem of "overwork", the experience is safely forgotten. Moreover, many examples on the Internet are similar to the spetsspecialized spells that do not add an understanding of what is happening:


android { compileSdkVersion 28 defaultConfig { applicationId "com.habr.hello" minSdkVersion 20 targetSdkVersion 28 } buildTypes { release { minifyEnabled false } } } 

I'm not going to describe in detail what each line is needed above - these are particular details of the implementation of the android plugin. There is something more valuable - understanding how everything is organized. The information is scattered on various sites / official documentation / source code of the hail and plug-ins to it - in general, this is a little more universal knowledge that you don’t want to forget.


Further text can be viewed as a cheat sheet for those who only master gradle or have already forgotten.


useful links



Console


Android studio / IDEA diligently hides the gradle commands from the developer, and even when the build.gradle changes, the files start to blunt or reload the project.


In such cases, calling the gradle from the console is much easier and faster. Wrapper for gradle usually goes along with the project and works great in linux / macos / windows, except that in the latter one should call the bat-file instead of the wrapper.


Challenge tasks


 ./gradlew tasks 

writes accessible tasks.


 ./gradlew subprojectName:tasks --all 

You can display the tasks of a separate subproject, and with the --all option all the tasks, including the minor ones, will be displayed.


You can call any task, all tasks on which it depends will be called.


 ./gradlew app:assembleDevelopDebug 

If you are too lazy to write the name entirely, you can throw out small letters:


 ./gradlew app:assembleDD 

If the grad will not be able to unambiguously guess which particular task was meant, then it will display a list of suitable options.


Logging


The amount of information output to the console when the task is started is highly dependent on the level of logging.
In addition to the default, there is -q, -w, -i, -d , well, or --quiet, --warn, --info, --debug by increasing the amount of information. On complex projects, the output from -d may take up more megabytes, and therefore it is better to save it immediately to a file and already look there by searching for keywords:


 ./gradlew app:build -d > myLog.txt 

If an exception is thrown somewhere, for the stacktrace option is -s .


You can write yourself to the log:


 logger.warn('A warning log message.') 

logger is an implementation of SLF4J.


Groovy


What is happening in the build.gradle files is just groovy code.


Groovy as a programming language for some reason is not very popular, although, as it seems to me, it is in itself worthy of at least a little learning. Language was born in 2003 and slowly developed. Interesting features:



In general, why languages ​​like Python / Javascript soared, and Groovy is not - a mystery to me. For its time, when jambda wasn’t even in java, and alternatives like kotlin / scala just appeared or didn’t exist yet, Groovy should have looked like a really interesting language.


It is the flexibility of the groovy syntax and dynamic typing that made it possible to create concise DSLs in gradle.


Now in the official Gradle documentation, the examples are duplicated on Kotlin, and it seems like it is planned to switch to it, but the code no longer looks so simple and becomes more like ordinary code:


 task hello { doLast { println "hello" } } 

vs


 tasks.register("hello") { doLast { println("hello") } } 

However, the renaming to Kradle is not planned yet.


Build stages


They are divided into initialization, configuration, and execution.


The idea is that gradle collects an acyclic dependency graph and only causes the necessary minimum of them. If I understand correctly, the initialization stage occurs at the moment when the code from build.gradle is executed.


For example, such:


 copy { from source to dest } 

Or this:


 task epicFail { copy{ from source to dest } } 

Perhaps this is not obvious, but the above will inhibit initialization. In order not to copy files at each initialization, you need a doLast{...} or doFirst{...} block in the task - then the code will turn into a closure and it will be called at the moment of the task execution.


 task properCopy { doLast { copy { from dest to source } } } 

or so


 task properCopy(type: Copy) { from dest to source } 

In the old examples, instead of doLast , the << operator can be found, but it was later abandoned due to non-obviousness of the behavior.


 task properCopy << { println("files copied") } 

tasks.all


What is funny, with doLast and doFirst you can hang some actions on any tasks:


 tasks.all { doFirst { println("task $name started") } } 

The IDE suggests that tasks have the whenTaskAdded(Closure ...) method, but the all(Closure ...) method works much more interestingly - the closure is called for all existing tasks, as well as for new tasks when they are added.


Create a task that prints dependencies of all tasks:


 task printDependencies { doLast { tasks.all { println("$name dependsOn $dependsOn") } } } 

or so:


 task printDependencies { doLast { tasks.all { Task task -> println("${task.name} dependsOn ${task.dependsOn}") } } } 

If tasks.all{} called at runtime (in the doLast block), then we will see all the tasks and dependencies.
If you do the same without doLast (i.e., during initialization), then the printed tasks may not have enough dependencies, since they have not been added yet.


Oh yes, addictions! If another task should depend on the results of our implementation, then it is worth adding a dependency:


 anotherTask.dependsOn properCopy 

Or even like this:


 tasks.all{ task -> if (task.name.toLowerCase().contains("debug")) { task.dependsOn properCopy } } 

inputs, outputs and incremental build


A normal task will be called every time. If you specify that a task based on file A generates file B, then gradle will skip the task if these files have not changed. Moreover, gradle does not check the date of the file change, but its contents.


 task generateCode(type: Exec) { commandLine "generateCode.sh", "input.txt", "output.java" inputs.file "input.txt" output.file "output.java" } 

Similarly, you can specify the folder, as well as some values: inputs.property(name, value) .


task description


When you call ./gradlew tasks --all standard tasks have a beautiful description and are somehow grouped. For your tasks, this is added very simply:


 task hello { group "MyCustomGroup" description "Prints 'hello'" doLast{ print 'hello' } } 

task.enabled


you can "turn off" the task - then its dependencies will still be caused, but she herself will not.


 taskName.enabled false 

several projects (modules)


multi-project builds in documentation


In the main project you can arrange several more modules. For example, this is used in android projects - there is almost nothing in the root project, the android plugin is enabled in the subproject. If you want to add a new module, you can add another one, and there, for example, also connect the android plugin, but use other settings for it.


Another example: when publishing a project using jitpack, the root project describes with what settings to publish a child module that may not even be aware of the fact of publication.


Child modules are specified in settings.gradle:


 include 'name' 

You can read more about the dependencies between projects here.


buildSrc


If build.gradle lot of code in build.gradle or it is duplicated, it can be put into a separate module. You need a folder with the magic name buildSrc , in which you can place the code on groovy or java. (well, more correctly, in buildSrc/src/main/java/com/smth/ code, tests can be added to buildSrc/src/test ). If you want something else, for example, to write your task on scala or use any dependencies, then you need to create build.gradle directly in build.gradle and specify the necessary dependencies / enable plugins in it.


Unfortunately, with the project in buildSrc IDE can be stupid with hints, there you have to write imports and classes / tasks from there build.gradle will also need to import from there to the usual build.gradle . Write import com.smth.Taskname - not difficult, you just need to remember this and not wrestle with why the task from buildSrc not found).


For this reason, it is convenient to first write something that works directly in build.gradle , and only then transfer the code to buildSrc .


Own type of task


The task is inherited from DefaultTask , in which there are many, many fields, methods and other things. AbstractTask code, from which the DefaultTask is inherited.


Useful points:



When for our task someone in build.gradle writes


 taskName { ... //some code } 

the task calls the configure(Closure) method.


I'm not sure that this is the right approach, but if the task has several fields, the mutual state of which is difficult to control by getters-setters, then it seems quite convenient to redefine the method as follows:


 override def configure(Closure closure){ def result = super().configure(closure) //    / - return result; } 

And even if you write


 taskName.fieldName value 

then the configure method will still be called.


Own plugin


Like a task, you can write your own plugin that will customize something or create tasks. For example, what happens in android{...} is completely merit dark magic Android plugin, which in addition creates a whole bunch of tasks like app: assembleDevelopDebug for all possible combinations of flavor / build type / dimenstion. There is nothing difficult in writing your own plugin, for a better understanding you can see the code of other plugins.


There is a third step - you can not place the code in the buildSrc , but make it a separate project. Then, using https://jitpack.io or something else, publish the plugin and connect it similarly to the rest.


The end


In the examples above, there may be typographical errors and inaccuracies. Write in a personal note or mark with ctrl+enter - correct. It is better to take concrete examples from the documentation, and to look at this article as a list of how to do it.


')

Source: https://habr.com/ru/post/458046/


All Articles