CheckStyle suppressions not working with config TextResource fromArchiveEntry

Since Gradle 2.2, checkstyle configurations can be linked through the config TextResources property. The Feature Spotlight on Code Quality Standards shows a SSCCE.

However, this fails when a suppressions.xml is included. Checkstyle requires that suppressions are defined in this separate file. There are two related errors:

If the files are in a subfolder, they are not found: "Expected ZIP to contain exactly one file, however, it contains no files."This error message is wrong. Surprisingly, this error does not show up if two files are in root of the zip archive.

However, only the checkstyle.xml file is extracted into the tmp folder leading to: "Unable to create a Checker: cannot initialize module SuppressionFilter - Cannot set property ‘file’ in module SuppressionFilter to \suppressions.xml". I can copy the suppressions.xml in the tmp folder to get it working temporarily, though.

One solution I see would be to add a suppressions TextResource property. If the constraint “only one file per zip” really holds, this would be a bit cumbersome though.

Sidenote 1: It would be nice if all configs (PMD, Checkstyle, FindBugs) could live side-by-side in one zip artifact.

Sidenote 2: I posted this before, but the entry was deleted or lost when moving to the new forums. Maybe someone wants to look into why/how that happened?

Sorry, we’re still trying to get the last week of forum posts from our old forum provider.

Do you have an example of the first error “Expected ZIP to contain exactly one file, however, it contains no files.”?

I’ve created a ZIP with two files (config/checkstyle.xml and config/suppressions.xml) and I’m able to extract both of them with resources.text.fromArchiveEntry()

For the second problem, your checkstyle.xml needs a path to the suppressions.xml file. This is usually done by defining something in configProperties and referencing it in checkstyle.xml. If your suppressions.xml is local to the project, that could just be a fixed path. e.g., file("config/suppressions.xml") If you have the suppressions file in the zip, you have to be a little more creative:

checkstyle {
    config = resources.text.fromArchiveEntry(makeZip, "config/checkstyle.xml")
    def suppressionConfigResource = resources.text.fromArchiveEntry(makeZip, "config/suppressions.xml")
    configProperties.checkstyleConfigDir = suppressionConfigResource.asFile().parent

makeZip is the name of the task I had that created the ZIP file, but it could be configurations.something

I believe you should be able to have all of your code quality configs in one zip.

Thank you for looking into this @sterling

Error 1: I noticed that the problem actually was that my code snippet was missing the path to the files, not the number of files. In general, it seems to work fine with multiple files in the zip. Maybe a simple error message such as “File not found” would be more accurate?

Error 2: Thanks, that helped a lot. To my surprise, the magic is in the asFile() method, which creates the actual file on the disk. I moved the snippet into checkstyleMain.doFirst - otherwise it gets deleted by the clean task before it can be read. Not pretty, but it works. For reference, here’s my complete working example:

checkstyle {
    config = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/checkstyle.xml")
    configProperties.samedir = config.asFile().parent

checkstyleMain.doFirst {
    def suppressions = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/suppressions.xml")
    // the asFile() method extracts the file from the zip archive and puts it next to checkstyle.xml

For the sake of correctness, a similar method is required for checkstyleTest.

I don’t find this very intuitive. Maybe this procedure could be simplified in a future gradle version, e.g. through an additional text resoure for suppressions?

Thanks again for your help, @sterling .

The error could be better. It’s coming from a common base class for file collections, so the message is disjoint from the real problem (the file name you want to extract from the zip doesn’t exist).

Yeah, it’s a little weird for this. The TextResources are sort of designed for representing a single file from somewhere and you don’t really care where it is. The suppression file makes that hard because it needs to be relative to the checkstyle file. If we added a suppression TextResource, we’d have to tell people how to properly reference it from their checkstyle files, which I think could be OK.

Glad you got it working. You can configure multiple tasks at a time with configure([ task1, task2 ]) { stuff }

Thank you for the clarification.

There should be one for each source set, including all subprojects. Is it possible to get the group of all CheckStyle tasks somehow?

Just for the record: as an alternative solution, we’re considering creating a new task that extracts the config files from the archive entry into a well-known folder to make them accessible for IDEs, too.

In any case, we need to ensure that all checkstyle-related tasks trigger the extraction of the file.

You can do something like:

subprojects {
   tasks.withType(CheckStyle) { 
      // configure Checkstyle task

You could do it with a separate task and configuration to download and extract. You could try something like…

configurations { checkstyleConf }
// add dependency for zip of checkstyle configs
task extractCheckstyle(type: Copy) {
   dependsOn configurations.checkstyleConf
   ext.xml = files(outputs).filter { == "checkstyle.xml" }
   ext.samedir = "${buildDir}/well-known"

   from {
      configurations.checkstyleConf.collect { zipTree(it) }
   into samedir

checkstyle {
   config = resources.text.fromFile(extractCheckstyle.xml)
   configProperties.samedir = extractCheckstyle.samedir

just typed it, didn’t try it. Tricky part is the ‘builtBy’ property on the task outputs needs to stick around on the xml extra property so that the resource will infer the dependency.