SonarQube for Android Projects
How to monitor code quality of your Android application with SonarQube ?
SonarQube.org is a powerful tool to monitor and analyze code quality, and especially technical debt on Java projects (and more). As Android projects are based on Java sources, it is possible to analyze such projects with SonarQube. But if you already tried this, you probably noticed how difficult it is to find right and complete documentation.
So, we will see here how to configure an existing Android project built with gradle with 3 different modules: a android application, a android library, and a common java library.
A sample project is available here: https://github.com/sogilis/sonarqube-for-android-example.
Prerequisites
Gradle 3.x is not compatible with Sonar plugin, you have to choose Gradle 2.x.
Get SonarQube Server
First of all, a SonarQube server instance may be useful to check your configuration.
The simplest way to do this is maybe with the sonarqube docker image, especially with sonarqube:alpine lightweight image.
Configure Gradle
Add the following configuration in build.gradle for an android application module (i.e. with com.android.application plugin):
buildscript {
dependencies {
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.0.1"
}
}
apply plugin: 'org.sonarqube'
sonarqube {
properties {
// TODO Update your android.jar path (with path and Android version)
def libraries = project.android.sdkDirectory.getPath() + "/platforms/android-23/android.jar" + ", build/intermediates/exploded-aar/**/classes.jar"
property "sonar.sources", "src/main/java"
// Defines where the java files are
property "sonar.binaries", "build/intermediates/classes/debug"
property "sonar.libraries", libraries
// Defines where the xml files are
property "sonar.java.binaries", "build/intermediates/classes/debug"
property "sonar.java.libraries", libraries
property "sonar.tests", "src/test/java, src/androidTest/java"
property "sonar.java.test.binaries", "build/intermediates/classes/debug"
property "sonar.java.test.libraries", libraries
property "sonar.jacoco.reportPath", "build/jacoco/testDebugUnitTest.exec"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.junit.reportsPath", "build/test-results/Debug"
property "sonar.android.lint.report", "build/outputs/lint-results.xml"
}
}
Add the same configuration in build.gradle for an android library module (i.e. with com.android.library plugin) with following modifications:
-
Remove « build/intermediates/exploded-aar/**/classes.jar » from library variable as follow:
def libraries = project.android.sdkDirectory.getPath() + "/platforms/android-23/android.jar"
-
Remove folder src/androidTest/java from “sonar.tests” property as follow:
property "sonar.tests", "src/test/java"
SonarQube configuration for a java module is straightforward with a single line in build.gradle:
apply plugin: 'org.sonarqube'
Test your configuration
Now, you can test your configuration by analyzing your project with a SonarQube server:
./gradlew clean test jacocoTestReport sonarqube \
-Dsonar.host.url=http://[Sonarqube server IP]:9000 \
--info --stacktrace
Configure SonarQube analysis
In addition to standard Java rules plugin (like Plugin]1.
This plugin will parse and import android lint report.
Code coverage
The configuration above does not provide any code coverage metrics. This can be done with jacoco in android application and library:
buildscript {
dependencies {
classpath "com.dicedmelon.gradle:jacoco-android:0.1.1"
}
}
apply plugin: 'jacoco-android'
android {
debug {
testCoverageEnabled = true
}
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
sonarqube {
properties {
property "sonar.jacoco.reportPath", "build/jacoco/testDebugUnitTest.exec"
property "sonar.java.coveragePlugin", "jacoco"
}
}
jacoco {
toolVersion = "0.7.6.201602180812"
}
jacocoAndroidUnitTestReport {
csv.enabled false
html.enabled false
xml.enabled true
}
… and as follow with java modules:
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.6.201602180812"
}
Result
Here is what you can get :
Android Studio plugin
Android Studio can analyze your code in real time with SonarLint for IntelliJ and analyze your code based on SonarQube configuration.
Useful tips
You can skip some project from being analyzed like this:
sonarqube {
skipProject = true
}
… or like this from parent build.gradle:
project(":modulename_to_skip") {
sonarqube {
skipProject = true
}
}
You can overwrite sonar project name and key like this:
sonarqube {
properties {
property "sonar.projectName", "My project"
property "sonar.projectKey", "com.organisation:my-project"
}
}
Limitations
Android flavors are not managed natively with SonarQube gradle plugin. You have to choose a single flavor when analyzing your sources.
The given configuration here allow you to analyze a single module without others. But each module will appear as a stand alone project in SonarQube like this:
If you want all modules in a single project in SonarQube, you can move SonarQube plugins from modules to parent build.gradle (see branch merge_modules_in_single_sonarqube_project in example project):
Possible issues
- Follow gradle constraints (buildscript before plugins before else)
- StackOverflowError : remove involved rule or source file
- Exception while applying a rule : remove rule
Exception applying rule CommentDefaultAccessModifier on file /Users/Jibidus/Development/Sogilis/Repos/sonarqube-for-android-example/android-app/src/main/java/com/sogilis/example/android/app/MainActivity.java, continuing with next rule
java.lang.NullPointerException
- StackOverflowError with sonar-gradle-plugin 2.1 : downgrade to 2.0.1
- Jenkins does not find XML test report. Check Jenkins does look after /build/test-results/**/.xml.
- My workspace:module-name is not a valid project or module key. Allowed characters are alphanumeric,
-
,_
,.
and:
, with at least one non-digit. The Default SonarQube project name is the project folder. So if this folder contains invalid character, this could be the explanation. You can set project name and key (see tips above). - :jacocoTestReport SKIPPED: run test task before jacocoTestReport
- org.gradle.internal.jvm.Jvm.getRuntimeJar()Ljava/io/File : incompatible gradle version. Downgrade from gradle 3 to 2.
Appendix
Here are some useful links to go further:
Jean-Baptiste