How to use SonarRunner in multi-language projects

Hello,

I’m trying to use the SonarRunner plugin on my multi-language project. I understand that Sonar is not able to analyze multiple languages in a single run, so I wanted to write an additional task that runs sonar a second time with a different language set in its properties. However, the task does not currently run as-written.

apply plugin: "sonar-runner"
  sonarRunner {
    sonarProperties {
        property "sonar.language", "grvy"
    }
}
  task sonarRunnerJS(type: org.gradle.api.sonar.runner.SonarRunner) {
    sonarProperties {
        property "sonar.language", "js"
    }
}

I get the following error when running sonarRunnerJS:

> Could not find method sonarProperties() for arguments [build_3kvjeua9g76jdb200hk6c2kank$_run_closure5_closure11@54a173ee] on root project

What am I doing wrong? Is there a better way to do this?

Thanks for your help,

Thomas

Is this a multi-project build?

Yes. Does that have any effect? Running sonarRunner works as expected (analyzing all the groovy code in all my subprojects) so I assumed that running my custom task would operate the same way.

If you declare your own ‘SonarRunner’ task, you will have to declare all Sonar properties manually. (‘SonarRunner.sonarProperties’ is of type ‘java.util.Properties’.) For a larger multi-project build, this will be somewhat involved. Then again, it’s what you’d have to do when using the standalone or Ant Sonar Runner.

To get an idea, execute ‘gradle --info sonarRunner’, which will print all Sonar properties configured by the Sonar Runner plugin. Note that you’ll need one ‘SonarRunner’ task per root of the project hierarchy to be analyzed (not per project).

I don’t know how Sonar handles the case where different analysis runs are performed for the same project hierarchy. You may have to set different Sonar branches.

OK. The following code is in my root build file:

task sonarRunnerJS(type: org.gradle.api.sonar.runner.SonarRunner) {
    sonarProperties = new java.util.Properties()
    sonarProperties.setProperty("sonar.projectKey", "${project.group}:${project.name}")
    sonarProperties.setProperty("sonar.projectName", "${project.name}")
    sonarProperties.setProperty("sonar.projectDescription", "${project.description}")
    sonarProperties.setProperty("sonar.projectVersion", "${project.version}")
    sonarProperties.setProperty("sonar.projectBaseDir", "${project.projectDir}")
    sonarProperties.setProperty("sonar.working.directory", "${project.buildDir/sonar}")
    sonarProperties.setProperty("sonar.dynamicAnalysis", "reuseReports")
    sonarProperties.setProperty("sonar.java.source", "${project.sourceCompatibility}")
    sonarProperties.setProperty("sonar.java.target", "${project.targetCompatibility}")
    sonarProperties.setProperty("sonar.sources", "${sourceSets.main.allSource.srcDirs}")
    sonarProperties.setProperty("sonar.tests", "${sourceSets.test.allSource.srcDirs}")
    sonarProperties.setProperty("sonar.binaries", "${sourceSets.main.runtimeClasspath}")
    sonarProperties.setProperty("sonar.libraries", "${sourceSets.main.runtimeClasspath}")
    sonarProperties.setProperty("sonar.surefire.reportsPath", "${test.testResultsDir}")
}

I get the following error during execution:

> Could not find property 'sonar' on task ':sonarRunnerJS'.

I did not find the property “sonar” set when using gradle --info sonarRunner. Do you have any idea what it is supposed to be set to?

Thanks,

Thomas

Looks like something is wrong with this GString: ‘"${project.buildDir/sonar}"’. Another thing you’ll have to do is to convert ‘srcDirs’ and ‘runtimeClasspath’ to the String notation expected by Sonar. For the latter you can probably use ‘runtimeClasspath.asPath’, unless Sonar expects a different notation. (Check the output of running the plugin-provided task with ‘–info’.)

Apparently not all of those properties are necessary. Here’s the latest code from my root build file:

task sonarRunnerJS(type: org.gradle.api.sonar.runner.SonarRunner) {
    sonarProperties = new java.util.Properties()
    sonarProperties.setProperty("sonar.projectKey", "${project.group}:${project.name}")
    sonarProperties.setProperty("sonar.projectName", "${project.name}")
    sonarProperties.setProperty("sonar.projectDescription", "${project.description}")
    sonarProperties.setProperty("sonar.projectVersion", "${project.version}")
    sonarProperties.setProperty("sonar.projectBaseDir", "${project.projectDir}")
    sonarProperties.setProperty("sonar.working.directory", "${project.buildDir}/sonar")
    sonarProperties.setProperty("sonar.dynamicAnalysis", "reuseReports")
    sonarProperties.setProperty("sonar.language", "js")
    sonarProperties.setProperty("sonar.sources", "${project(":common").projectDir}/src/main/javascript")
}

The “sources” needs to be set to something, so I pointed it at the folder containing the javascript files common to my subprojects. Obviously I’ll need to change “sonar.sources” in the subprojects’ build files so they point to the correct folders, but a couple of other issues popped up.

First, calling the built-in sonarRunner task from the root project will call itself recursively on subprojects to analyze the whole project hierarchy together (as explained in the user guide). However, my sonarRunnerJS task does not call itself on my subprojects – it just analyzes the code found in “sonar.sources” and ignores the subprojects’ build files. If I add sonarRunnerJS tasks to the subprojects { } in the root build file, or as new tasks in the subprojects’ build files, then they get executed separately and the analyses appear as separate projects in sonar. I don’t completely understand how sonarRunner’s recursive behavior works so it’s hard for me to replicate it (if that’s even possible).

I’m starting to think that this isn’t going to be possible. :frowning:

It’s all a matter of setting the right properties. Check the info output and/or the Sonar Runner documentation over at the Sonar site.

Looking at the source code, the SonarRunnerPlugin has the “recursive” behavior built-in, but the SonarRunner task does not. I’m not sure why the devs have chosen to do it like that (at least make it an option!), but I’ve been able to emulate it in a couple of ways.

First, the “sonar.sources” actually goes through each given source and its sub-directories, so you can have the custom sonar runner task analyze everything by pointing it at your top-level build directory. Here’s some code I’ve written for my sonarRunnerJS task; I exclude analysis of the external (3rd-party) libraries and the javascript created during the builds:

sonarProperties.setProperty("sonar.sources", "${projectDir}")
    sonarProperties.setProperty("sonar.exclusions", "file:/${project(":common").projectDir}/externalLibraries/**/*.js, file:/${projectDir}/**/build/**/*.js")

Also, looking closely at the output from gradle --info sonarRunner I was able to determine how to specify individual subprojects through the sonar properties. Unfortunately, I’m not sure how to do this dynamically like the SonarRunnerPlugin does (so if I create a new subproject I’ll have to create new sonar properties as well). Here’s some more code I’ve written for my sonarRunnerJS task (to replace the previous block):

sonarProperties.setProperty("sonar.modules", "mySubProject")
    sonarProperties.setProperty("sonar.sources", "${projectDir}/emptyDir") // It needs to point to something
    sonarProperties.setProperty("mySubProject.sonar.dynamicAnalysis", "reuseReports")
    sonarProperties.setProperty("mySubProject.sonar.projectBaseDir", "${project(":mySubProject").projectDir}")
    sonarProperties.setProperty("mySubProject.sonar.projectName", "mySubProject")
    sonarProperties.setProperty("mySubProject.sonar.projectVersion", "")
    sonarProperties.setProperty("mySubProject.sonar.sources", "${project(":mySubProject").projectDir}/src/main/javascript")

Again, since this isn’t dynamic, it isn’t my preferred solution, but it works. Maybe it will be helpful to someone with a similar problem who stumbles on this thread in the future.

Thanks for all the time and help you’ve given me.

Thomas

PS: Running sonarRunner and sonarRunnerJS creates two separate projects (with the same name) in Sonar. Since this is how it works outside gradle it doesn’t concern me too much. I’ve added “sonar.branch” properties to name them differently to avoid confusion.

Since there is only one task per hierarchy to be analyzed, the task itself cannot be “recursive”. The design follows the general Gradle approach - provide a plugin that makes 80% of use cases easy, and a self-contained task that makes the remaining 20% possible.

…Except this task doesn’t make true dynamic subproject analysis possible. But, until Sonar integrates better multi-language support, I have something that works – even if it’s not pretty.

Neither does the Sonar Ant runner or standalone runner. For the Gradle task you can at least implement it yourself, if you feel it’s worth the effort.

Thomas’ solution works and I haven’t found any other. It’s a pity that the following sample doesn’t work with Gradle (works with sonar-runner from command line): https://github.com/SonarSource/sonar-examples/tree/master/projects/languages/multi-language/multi-language-source-files-in-same-directory