How can I read setup resources from plugin jar?

Related to Stackoverflow

I want to have a central plugin that setup PMD, findbugs, checkstyle and all kind of quality related plugins and share that setup with all my projects, started creating a Quality plugin that creates the tasks with the setup but when I try to read the ruleset xml from my plugin resource folder it crash!

I have

class MyPmdTask extends Pmd {

  MyPmdTask() {
    source = project.fileTree('src/main/java').toList()
    reports {
      xml.enabled = false
      html.enabled = true
    }

    File file = new File(getClass().getClassLoader().getResource("pmd-ruleset.xml").getFile());
    project.extensions.pmd.with {
      ignoreFailures = true
      reportsDir = project.file("$project.buildDir/outputs/")
      ruleSetFiles = new SimpleFileCollection(file)
    }
  }
}

But I get:

Caused by: net.sourceforge.pmd.RuleSetNotFoundException: Can't find resource 'file:/.../plugin/build/m2repo/.../0.1.0/android_quality-0.1.0.jar!/pmd-ruleset.xml' for rule 'null'.  Make sure the resource is a valid file or URL and is on the CLASSPATH. Here's the current classpath:~/.gradle/wrapper/dists/gradle-2.9-all/m016i0sp3irpuo14928liklbg/gradle-2.9/lib/gradle-launcher-2.9.jar

Why??? it finds the File since .getFile dont crash beacuse of null.

Notice the path of the resource that displays in the error message. It references your JAR file with an exclamation mark at the end, followed by the file name in the JAR.

This is the location of your rule file found by the getResource(…) call. However, this is not a valid path for a File. You cannot treat a file inside of a JAR file (compressed archive) as a normal file.

You have several options here, one of which includes the incubating addition to the code quality extensions where you can specify a config rather than a file. You can either access the resource as a stream, read it in to a String and use resources.text.fromString(...) or you could try out resources.text.fromArchiveEntry(...), but you need to split out the archive name from the file name when you pass the arguments.

Alternatively, you could always extract the file from the jar so that you do have a normal file on disk to reference.

2 Likes

I will try those ideas, what I was thinking to have is just apply: “quality_plugin” in the other projects.

Thanks for the advice, in the doc it says:

TextResource ruleSetConfig

Note: This property is incubating and may change in a future version of Gradle.
The custom rule set to be used (if any). Replaces ruleSetFiles, except that it does not currently support multiple rule sets. See the official documentation for how to author a rule set. Example: ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml"))

But if I try

setRuleSetConfig(project.resources.text.fromFile(project.resources.file("pmd.xml")))

it says:

Caused by: groovy.lang.MissingMethodException: No signature of method: org.gradle.api.internal.resources.DefaultResourceHandler.file() is applicable for argument types: (java.lang.String) values: [pmd.xml]
Possible solutions: find(), find(groovy.lang.Closure), use([Ljava.lang.Object;), is(java.lang.Object), with(groovy.lang.Closure), wait()
	at com.barista_v.android_quality.MyPmdTask.<init>(MyPmdTask.groovy:20)
	at com.barista_v.android_quality.MyPmdTask_Decorated.<init>(Unknown Source)
	at org.gradle.api.internal.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:48)
	at org.gradle.api.internal.ClassGeneratorBackedInstantiator.newInstance(ClassGeneratorBackedInstantiator.java:36)
	at org.gradle.api.internal.project.taskfactory.TaskFactory$1.call(TaskFactory.java:121)
	... 82 more

Also I am trying in this way::smile:

class MyPmdTask extends Pmd {

  MyPmdTask() {
    project.extensions.pmd.with {
      reportsDir = project.file("$project.buildDir/outputs/")
    }

    source = project.fileTree('src/main/java').toList()
    ruleSets = []
    reports {
      xml.enabled = false
      html.enabled = true
    }

    setIgnoreFailures(true)
    setRuleSetConfig(ResourceUtils.readTextResource(project, getClass().getClassLoader(), "pmd.xml"))
  }
}
class ResourceUtils {
  static TextResource readTextResource(Project project, ClassLoader classLoader, String fileName) {
    println "reading config file $fileName"
    def inputStream = classLoader.getResourceAsStream(fileName)
    project.resources.text.fromString inputStream.text
  }
}

and it set the config file but it dont use my rules, dont crash but dont use my rules.
I tried setting in

    project.extensions.pmd.with {
  // HERE
    }

when I try getRuleSetConfig() it returns null but with setRuleSetConfig() appears to be setted but not used.

what can I do?

1 Like

The ResourceHandler returned by project.resources has 3 options: bzip2, gzip, and text. As also shown in the exception, file is not one of them. You really don’t want something that’s a File anyway or you’re back to the original problem.

For the second way, you may need to show more about what you’re doing, but I’ll take a guess. In order to have the pmd extension, you’re applying the pmd plugin which is adding some pmd tasks. You’re also extending the Pmd task with your MyPmdTask. Are you creating your own tasks of the MyPmdTask type and running those? The ones created when you apply the plugin would not be configured as you have specified in MyPmdTask.

Regardless, I would recommend that you configure the pmd tasks that are already created for you by the plugin rather than extending the Pmd task, which would require additional tasks to be added.

Sorry for that, yeah, I just created the extended task because I just want to include the plugin in my projects and have all configured, otherwise I will have to setup every plugin I want in my projects.

class AndroidQualityPlugin implements Plugin<Project> {

  void apply(Project project) {
    verifyRequiredPlugins project

    project.apply plugin: "pmd"
    project.apply plugin: "findbugs"

    project.afterEvaluate {
      project.tasks.create([name       : "backupApks",
                            type       : BackupApksTask.class,
                            description: "Backup apks inside folder defined by BARISTA_ANDROID_BACKUP",
                            group      : "quality"])

      project.tasks.create([name       : "pmd",
                            type       : MyPmdTask.class,
                            description: "Pmd",
                            group      : "quality"])

      project.tasks.create([name       : "findbugs",
                            type       : MyFindbugsTask.class,
                            description: "Findbugs",
                            group      : "quality"])
    }
  }

  // check if 'android' plugin is applied to the project
  private static void verifyRequiredPlugins(Project project) {
    if (!project.plugins.hasPlugin(AppPlugin) && !project.plugins.hasPlugin(LibraryPlugin)) {
      throw new TaskInstantiationException(
          "'android' or 'android-library' plugin has to be applied before")
    }
  }
}

I saw that I can create a configure artifact and use it everywhere but doesnt feel right for me like here https://github.com/MovingBlocks/TeraConfig.

But again, why the ruleSetConfig is not working? theres no crash or log warning but is not reading my configuration, it is working now but with default rules.

Thanks so much, it’s really useful!