Accessing classpath read-only in Gradle task

Hi!

I couldn’t find an existing answer doing web search, so I’m trying here…

While creating a small custom plugin, I want to just read (not change) all test runtime classpath roots from within a task. Here’s what I tried:

class JUnit5Plugin implements Plugin<Project> {
  void apply(Project project) {
     project.task('showClasspath', type: org.gradle.api.tasks.JavaExec,
overwrite: true) { task->
          project.sourceSets.test.runtimeClasspath.getFiles().each {println it}
    }
  }
}

Should be straightforward, I thought. However, running the task leads to the following error message:

Cannot change dependencies of configuration ':testRuntime' after it
has been resolved.

Any help appreciated.
Johannes

You may need to delay your task creation by putting it inside a project.afterEvaluate { ... } closure

Or even better you could likely use doLast (<< shorthand) eg:

class JUnit5Plugin implements Plugin<Project> {
  void apply(Project project) {
     project.task(overwrite: true, 'showClasspath') << {  

       project.sourceSets.test.runtimeClasspath.files.each {
        println it
      }
    }
  }
}

Thanks. That helped a lot. What I actually did:

class JUnit5Plugin implements Plugin<Project> {
   void apply(Project project) {
      project.afterEvaluate {
         project.task('showClasspath', overwrite: true) { task->
            def classpathRoots = project.sourceSets.test.runtimeClasspath.files
            classpathRoots.each { println it }
}  }  }  }

That snippet is not what you want. Your code snippet will always print the class path at configuration time, regardless of whether the ‘showClasspath’ task is executed or not. The closure passed to the task-method configures the task. The runtime behavior of the task (what you wanted) is added using doLast() or doFirst().

Below is the code that will print the class path only when the ‘showClasspath’ task is executed.

class JUnit5Plugin implements Plugin<Project> {
  void apply(Project project) {
    project.task('showClasspath', overwrite: true) { task->
      task.doLast {
        def classpathRoots = project.sourceSets.test.runtimeClasspath.files
        classpathRoots.each { println it }
      }
    }
  }
}

Even better, you could define a dedicated task class to make the distinction between task configuration and task execution clearer. Especially if your real use case is more than printing the class path. For instance, you may want the class path to be an input that is checked for up-to-dateness.

A little more background on the exception you got initially: You were resolving (by calling getFiles()) the test class path, right when your plugin was applied (so probably at the top of the build script). Later down the road, there were probably dependency declarations inside your build script. These declarations tried to add themselves to the test class path. But since it was already resolved, it could not accept these changes. This is why you should never resolve configurations early.

Many thanks, Stefan. Your explanation about why I got the error in the
first place pushed me a bit further towards understanding Gradle (if that’s
a feasible endeavour :wink:

cheers, Johannes