My company uses Buildkite for our CI services. When the CI server triggers a build, all logs from the build are sent to Buildkite and made available through the UI. This is useful since it allows us to check how a build failed.
Buildkite has a feature which creates collapsible groups from logs formatted like:
--- Compiling
logs after this point will be in a collapsible group named 'Compiling'
--- Testing
logs after this point will be in a collapsible group named 'Testing'
How can I add custom logging to a gradle build which will output these 'groups'? I'd like to have a group for compiling/assembling, one for running unit tests, etc.
I've considered adding tasks which perform the logging and making them dependencies of the built-in tasks, but I'm not sure how to do that, or if it's a good idea in general.
I'm using Gradle 2.12 and whipped up an example from the Logging doc. The example does not use Android Studio nor BuildKite, but I believe this will help with the fundamental question.
Given a simple build.gradle file for a typical Java project:
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.11'
}
compileJava << { println "TRACER example log from compileJava" }
compileTestJava << { println "TRACER example log from compileTestJava" }
test << { println "TRACER example log from test" }
and an init.gradle file:
useLogger(new CustomEventLogger())
class CustomEventLogger extends BuildAdapter implements TaskExecutionListener {
public void beforeExecute(Task task) {
if (task.name ==~ "compileJava") {
println "--- Compiling"
} else if (task.name == "test") {
println "--- Testing"
}
}
public void afterExecute(Task task, TaskState state) {}
public void buildFinished(BuildResult result) {}
}
then this command-line:
$ gradle -I init.gradle test
yields this output:
--- Compiling
TRACER example log from compileJava
TRACER example log from compileTestJava
--- Testing
TRACER example log from test
Related
Task
I need to connect an Android client with a python server using gRPC. Making the server and generating the protos was easy in Python, but the lack of tutorials and confusing documentation for the Kt client makes it appear overwhelmingly complicated.
Background
Until now I've made some simple Android apps using Kotlin, I got used to adding dependencies to either the module or app level build.gradle.
What have I tried?
My first thought was to go to the official documentation as I did with Python.
I found the guide from there pretty confusing (I felt like there's something missing from that article), so I went to see the full examples from their GitHub. I also cloned the repo and compiled the protos with the gradlew installDist command. Then the things got awfully complicated:
When you create an Android Studio project, you get a bunch of gradle things(module and app level build.gradle's, gradlew and gradlew.bat, settings, etc)
After you clone the repo, you get another bunch of gradle things inside the grpc-kotlin folder.
You also get build.gradle.kts which seem to be the same build logic/package manager helper files, but with other dependencies and with the Kotlin Script syntax.
This is when I went off to YouTube in order to search for a simple implementation and found out that there's only a handful of videos on the gRPC with Kotlin subject, and most of those are presentation videos about the features of gRPC in Kotlin when using Coroutines.
What I have until now
I migrated all my build.gradle's to .kts ones.
This is how my module-level build.gradle.kts looks like:
buildscript {
val kotlin_version = "1.5.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:4.2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}")
classpath("org.jetbrains.kotlin:kotlin-android-extensions:${kotlin_version}")
classpath("com.google.gms:google-services:4.3.8")
classpath ("com.google.protobuf:protobuf-gradle-plugin:0.8.14")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register("clean",Delete::class){
delete(rootProject.buildDir)
}
This is how my app level build.gradle.kts looks like:
import com.google.protobuf.gradle.generateProtoTasks
import com.google.protobuf.gradle.id
import com.google.protobuf.gradle.plugins
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc
plugins {
id("com.android.application")
id("com.google.protobuf")
kotlin("android")
}
android {
compileSdkVersion(30)
buildToolsVersion = "30.0.3"
defaultConfig {
applicationId = "com.example.myapplication"
minSdkVersion(26)
targetSdkVersion(30)
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.12.0" }
plugins {
id("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:1.35.0"
}
}
generateProtoTasks {
all().forEach { task ->
task.plugins.create("java") {
option("lite")
}
task.plugins {
id("grpc") {
this.option("lite")
}
}
}
}
}
dependencies {
val kotlin_version = "1.5.10"
implementation("org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}")
implementation("androidx.core:core-ktx:1.5.0")
implementation("androidx.appcompat:appcompat:1.3.0")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.2")
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
// GRPC Deps
implementation("io.grpc:grpc-okhttp:1.37.0")
implementation("io.grpc:grpc-protobuf-lite:1.37.0")
implementation("io.grpc:grpc-stub:1.36.0")
implementation("org.apache.tomcat:annotations-api:6.0.53")
}
I could generate the protos but something was off about them.
Problem
When implementing the request functions, respectively a bi-directional stream, I found out that all my rpc functions asked for an extra StreamObserver parameter(which was absent in all of the tutorials I've found on the internet). At a closer look I observed that all the generated files were in java and on the official docs, the generated files are both POJOs and Kotlin.
This is how my generated Stub class looks like:
public static final class ChatServiceStub extends io.grpc.stub.AbstractAsyncStub<ChatServiceStub> {
private ChatServiceStub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
#java.lang.Override
protected ChatServiceStub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new ChatServiceStub(channel, callOptions);
}
/**
* <pre>
* This bi-directional stream makes it possible to send and receive Notes between 2 persons
* </pre>
*/
public void chatStream(grpc.Chat.Empty request,
io.grpc.stub.StreamObserver<grpc.Chat.Note> responseObserver) {
io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getChatStreamMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void sendNote(grpc.Chat.Note request,
io.grpc.stub.StreamObserver<grpc.Chat.Empty> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getSendNoteMethod(), getCallOptions()), request, responseObserver);
}
}
I do not know how to replicate a gradle script for my project, I found no one on the internet explaining how are all those build.gradle's linked together(I figured out that module level build.gradle's are describing how the module they're in is supposed to build and app level build.gradle's are idem but for the entire app). Most of the articles I found are the same as the official docs.
What I want
I just want a simple-simple project or a step by step tutorial, without "clone this and run a command in the terminal, it just works".
I do not blame the devs or whoever wrote the official docs, I actually bet I'm the stupid one here, but I struggle to understand these concepts and I would be grateful if someone can explain to me what I did wrong or where to learn.
Also, sorry for the long question, I tried to expose my POV the best I could, this is my second question since I started learning programming and I'm sorry if the problem and my goals aren't clear enough, I'll edit anything if it's needed.
I do not have a step-by-step process I can share and do not want to trivialize an excellently asked question. However, I wanted to respond that in researching a similar problem, I found that Square has a library that seems to be more Kotlin friendly:
https://square.github.io/wire/#wire-kotlin
I created the simplest project I could. The minimal configuration of Android project can be found in this commit.
Start a new project from Basic activity template in Android Studio and then:
Modify app/build.gradle
Add Gradle plugin, so it can generate all stubs in the build step:
plugins { // this section should be already at the top of the file
// ...
id 'com.google.protobuf' version "0.9.1" // "0.9.2 causes compilation errors!"
}
WARNING: At the time of writing, the latest version of
protobuf gradle plugin is 0.9.2. Using this version causes
build errors I wasn't able to deal with using information I found
on the Internet.
Add project dependencies. Lack of one can cause usually
non-understandable error messages.
dependencies { // this section should be already in the file
// ...
implementation 'io.grpc:grpc-stub:1.52.1'
implementation 'io.grpc:grpc-protobuf:1.52.1'
implementation 'io.grpc:grpc-okhttp:1.52.1'
implementation 'io.grpc:protoc-gen-grpc-kotlin:1.3.0'
implementation 'io.grpc:grpc-kotlin-stub:1.3.0'
implementation 'com.google.protobuf:protobuf-kotlin:3.21.12'
}
Add protobuf section - the one that is responsible for generating protobuf data classes and gRPC client stubs:
protobuf { // this section needs to be added
protoc {
artifact = "com.google.protobuf:protoc:3.21.12"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.52.1"
}
grpckt {
// I don't really know what ":jdk8#jar" does...
artifact = "io.grpc:protoc-gen-grpc-kotlin:1.3.0:jdk8#jar"
// ...but it doesn't work without it.
}
}
generateProtoTasks {
all().forEach {
it.plugins {
grpc {}
grpckt {}
}
it.builtins {
kotlin {}
java {}
}
}
}
}
4*. I placed my proto file in app/src/main/proto directory. In case you store your protos in some other directory you can configure it by following instructions from protobuf-gradle-plugin repo readme.
Update AndroidManifest.xml
You also need to add proper permission to app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<!-- ... -->
</manifest>
Use gRPC client
class FirstFragment : Fragment() {
// ...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val port = 50051
val channel = ManagedChannelBuilder.forAddress("192.168.0.13", port).usePlaintext().build()
val stub = PingPongGrpcKt.PingPongCoroutineStub(channel)
binding.buttonFirst.setOnClickListener {
runBlocking {
val request = Pingpong.PingPongMsg.newBuilder().setPayload("World").build()
val response = stub.ping(request)
Log.i("result", response.toString())
}
}
}
// ...
}
I am trying to write a library that compiles to a jar file and can be included in an android project however whenever I add protocol buffers to the library it throws the following exception at runtime but not compile time:
10-18 10:10:10.310 25071-25071/com.example.s.manualinsttest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.s.manualinsttest, PID: 25071
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/MyLibrary/Tracemsg$MyProtoContainer$MyProto;
...
I have validated that this is a problem with a minimal example where the library consists of:
public class MyLibrary{
public static void myMethod(){
Tracemsg.MyProtoContainer.MyProto proto = Tracemsg.MyProtoContainer.MyProto.newBuilder().setMynumber(5).build();
}
}
And a protocol buffer:
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package ...
message MyProtoContainer {
message MyProto {
required uint64 mynumber = 1;
}
}
The library compiles just fine and runs when called from a normal java application however when included as a library I get the previously mentioned exception. Additionally I have tried decompiling the resulting jar file using jadx-gui and the class is in fact there.
Thank you for the help
The solution turned out to be that I was using a Gradle plugin which handled the protobuf inner workings for me and it needed to be configured to compile to the lite version. I found this snippet of code in https://github.com/google/protobuf-gradle-plugin/tree/master/testProjectLite which has the following:
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'
}
plugins {
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.plugins {
javalite { }
}
}
}
}
As far as I can tell you aren't supposed to use full protobufs in Android and its a little tricky validating that it didn't fall back to using full protobufs if you don't get everything set up correctly.
After updating Android Studio to version 2.2 and the Gradle-plugin to 2.2.0, I get following error:
"Could not get unknown property 'assembleRelease' for project ':app' of type org.gradle.api.Project."
When I change the plugin version back to 2.1.3 the code still works, but that's no long-term option for me.
My code:
apply plugin: 'com.android.application'
dependencies {
...
}
android {
...
}
...
assembleRelease.doLast {
file('build/outputs/apk/app-release.apk').renameTo("AppName-1.0.0-${project.ext.androidVersionCode}.apk")
}
Hint:
project.ext.androidVersionCode is a variable defined otherwhere and contains a build number. The code in assembleRelease.doLast shall just move/rename the generated apk file.
Thank you for advices!
tangens
tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
task.finalizedBy 'yourRenameTasks'
}
}
You may rewrite your task a bit and try like this:
task renameBuildTask() << {
file('build/outputs/apk/app-release.apk').renameTo("AppName-1.0.0-${project.ext.androidVersionCode}.apk")
dependsOn 'assembleRelease'
}
Also you can check this question to get better understanding.
EDIT
As #tangens said in a comment:
It works when I replace the call gradle assemble by e.g. gradle renameBuildTask. Thank you! The answer contains an error. Correct would be: task renameBuildTask() << { ... }
maybe wrap code in afterEvaluate{} will be work:
afterEvaluate {
assembleRelease.doLast {
file('build/outputs/apk/app-release.apk').renameTo("AppName-1.0.0-${project.ext.androidVersionCode}.apk")
}
}
gradle-2.14.1 and android gradle plugin 2.2.0
details:
Could not get unknown property 'assembleDebug' (2.2-beta)
I had the same problem after upgrading Android Studio to 2.2 and Gradle to 2.2.
I have task copyApk that needs to be run at the end of building. For brevity, let me skip what was working before, and post only what is working right now:
tasks.create(name: 'copyApk', type: Copy) {
from 'build/outputs/apk/myapp-official-release.apk'
into '.../mobile'
rename('myapp-official-release.apk', 'myapp.apk')
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
task.dependsOn 'copyApk'
}
}
Gradle console shows copyApk was run near the end after packageOfficialRelease, assembleOfficialRelease, right before the last task assembleRelease. "Official" is a flavor of the app.
I got the workaround from this SO post. I essentially copied the answer here for your convenience. All credits go to the author of that post.
inside buildTypes {} method, I put this code : worked like a charm
task setEnvRelease << {
ant.propertyfile(
file: "src/main/assets/build.properties") {
entry(key: "EO_WS_DEPLOY_ADDR", value: "http://PRODUCTION IP")
}
}
task setEnvDebug << {
ant.propertyfile(
file: "src/main/assets/build.properties") {
entry(key: "EO_WS_DEPLOY_ADDR", value: "http://DEBUG IP TEST")
}
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleDebug') {
task.dependsOn 'setEnvDebug'
} else if (task.name == 'assembleRelease') {
task.dependsOn 'setEnvRelease'
}
}
you can do this:
task copyApk(dependsOn: "assembleRelease") << {
file('build/outputs/apk/app-release.apk').renameTo("AppName-1.0.0-${project.ext.androidVersionCode}.apk")
}
I am running android tests using the Gradle Android plugin and want to see individual test results.
From answers to this Question Gradle: How to Display Test Results in the Console in Real Time? it seems I can either use --info (which prints a LOT of other verbose junk I don't care about) or use this closure which only works for the Java plugin (not the Android plugin)
test {
afterTest { desc, result ->
println "Executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
}
}
Is there some other option / closure I can use when I am running the connectedCheck task just to print the individual test results without all the other "verbosity".
Use Gradle info
This will print all information from Gradle:
gradle --info
or Use Android Gradle plugin:
android.testOptions.unitTests.all {
// Configure whether failing tests should fail the build
ignoreFailures false
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
}
}
or Use Gradle directly:
allprojects {
tasks.withType(Test) {
testLogging {
exceptionFormat "full"
showCauses true
showExceptions true
showStackTraces true
showStandardStreams true
events = ["passed", "skipped", "failed", "standardOut", "standardError"]
}
}
}
See: https://github.com/jaredsburrows/android-gradle-java-app-template/blob/master/gradle/compile.gradle#L20
Output:
io.github.hidroh.materialistic.data.SessionManagerTest > testView PASSED
io.github.hidroh.materialistic.data.SessionManagerTest > testIsViewFalse PASSED
io.github.hidroh.materialistic.data.SessionManagerTest > testIsViewNull PASSED
io.github.hidroh.materialistic.data.SessionManagerTest > testIsViewTrue PASSED
io.github.hidroh.materialistic.data.SessionManagerTest > testViewNoId PASSED
Source: https://github.com/hidroh/materialistic/blob/master/robolectric.gradle
Gradle Docs: https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/logging/TestLogEvent.html
For me all the options mentioned in the other answer still printed a lot of verbose information. So despite the requirement that info should not be used, I successfully use the following. For example, if your tests are in the package com.example.android, you can use:
gradle --info connectedDebugAndroidTest | grep "com\.example\.android\..* >"
Will print e. g.:
com.example.android.login.LoginActivityTest > enterCredentialsTest[Testing_emulator(AVD) - 9] SUCCESS
And the word "SUCCESS" will be green, which is awesome.
For Android Studio (tested on com.android.tools.build:gradle:2.1.0 and gradle version gradle-2.10) I added the following section to print exceptions in full format as well as logging every executed test:
apply plugin: 'com.android.application'
android { ... }
dependencies { ...}
tasks.withType(Test) {
testLogging {
exceptionFormat "full"
}
afterTest { desc, result ->
println "Executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
}
}
I did search a lot but unfortunately couldn't get it to work.
Based on my search I found that I need to add following code into build.gradle file. However, Gradle seems doesn't recognize it and always says Geadle DSL method not found: test()
test {
testLogging.showStandardStreams = true
testLogging.events("passed", "skipped", "failed", "standardOut", "standardError")
afterTest { desc, result ->
println "Executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
}
}
Update
I can confirm above code or better than that following code is working fine if you create a test project and move all your test cases inside that instead of src/test/java and src/androidTest/java in main project. It's because you can apply java plugin in build.gradle file. However, It's not possible to use following code in any build.gradle file that com.android.* has been defined. Since these two libraries are not compatible :(
apply plugin: 'java'
evaluationDependsOn(':YOUR-LIB')
test {
testLogging.showStandardStreams = true
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
exceptionFormat = 'full'
}
afterTest { desc, result ->
println "Executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
}
forkEvery = 5
maxParallelForks = java.lang.Runtime.runtime.availableProcessors() / 2
}
tasks.withType(Test) {
// Need Gradle to ignore classes that are inner class of Test classes but not actually Tests
scanForTestClasses = false
include "**/*Test.class"
}
So, my question is does anyone's INVENTED any way to print out logs under android plugin?