Problem with afterProject when used from a plugin


(nm2501) #1

If I have a build script containing:

gradle.afterProject { p ->
    println "afterProject: $p"
}

and when I invoke gradle I will see something like ‘afterProject: root project ‘test’’.

However if I move this logic into a plugin (compiled to a Jar) and apply that plugin from the build-script then the ‘afterProject’ closure will not be invoked for the root project. In a multi-project workspace, the plugin’s ‘afterProject’ closure will be invoked for child projects.

So to summarise: * ‘afterProject’ from a build-script - closure invoked for root project AND all subprojects * ‘afterProject’ from a plugin - closure invoked ONLY for subprojects

Is this possibly a defect?

Can you recommend any work-around or solution so that the ‘afterProject’ closure IS invoked for the root project when I’m applying it in a plugin?


(Peter Niederwieser) #2

To which project did you apply the plugin?


(nm2501) #3

all projects: the root and the children


(Peter Niederwieser) #4

In that case, you’ll register a lot of callbacks, not all of which will be executed the same number of times. Specifically, a callback for a child project will not receive an invocation for a parent project, because it will only be registered after configuration for the parent project has finished.


(nm2501) #5

Yes, I do notice a lot of callbacks but having more than one callback per project isn’t a problem because I can filter them. However it is problematic if I don’t have any callback for a particular project.


(Peter Niederwieser) #6

It might be better to use ‘Project.afterEvaluate’.


(nm2501) #7

Unfortunately I can’t use that because of what I want to do in the callback i.e. to load another gradle build script.


(nm2501) #8

Maybe I should restate what my objective is. What I would like to happen is to automatically load a ‘user.gradle’ script if it exists in the same directory as the original build script. The build script will be committed to source-control so I want to allow developers to place user-specific configuration in their own ‘user.gradle’, and it should behave in the same way as if that code was at the bottom of ‘build.gradle’.


(Peter Niederwieser) #9

Works fine for me. Can you provide a reproducible example?


(detelinyordanov) #10

Gradle.afterProject works fine for me too - if plugin is applied on root project, the passed closure is executed after the root project is evaluated.

But I wonder how you are going to achieve this with ‘afterProject’ - if the user-specific build script is used to configure the project after it is evaluated, then it will NOT behave the same way as if it was at the bottom of the build script, because at the bottom of the build script the project is not yet evaluated and one could still use e.g. ‘afterEvaluate’, while in the user-specific build script one would not be able to do so since the project is already evaluated.


(nm2501) #11

Will do. I’ll strip my plugin down to the bare essentials. How should I provide the example?


(Peter Niederwieser) #12

Ideally a GitHub project or a zip that I can download from somewhere.


(nm2501) #13

While making a bare essentials example I realised what the problem was - I’ve got an init script which ‘helping’ to apply plugins. In there I was also using ‘afterProject’, so it’s not surprising that it doesn’t work when registering another ‘afterProject’ within the callback for the first one. Sorry about that guys.

Detelin, you’ve got a good point. Do you have any suggestions for a better way to implement this? i.e. a mechanism whereby the user-specific build script really would behave the same way as if it was at the bottom of the main build script.


(detelinyordanov) #14

I have one idea, not sure whether this is public Gradle API though. I figured that if you want to do this, you will have to plug into the point after the build script is executed but before the ‘afterEvaluate’ event is fired, this can be done by implementing a custom ‘ScriptExecutionListener’:

package org.foo
  import org.gradle.api.Project
import org.gradle.api.Plugin
  import org.gradle.groovy.scripts.Script
import org.gradle.groovy.scripts.BasicScript
import org.gradle.groovy.scripts.ScriptExecutionListener
  class MyPlugin implements Plugin<Project> {
    public void apply(final Project project) {
    project.gradle.addListener(new ScriptExecutionListener() {
      void beforeScript(Script script) { /* not implemented */ }
              void afterScript(Script script, Throwable error) {
        //skip if script caused any error
        if (error) {
          return
        }
                  if (script instanceof BasicScript) {
          Object target = (script as BasicScript).scriptTarget
          if (project == target) {
            //must skip if this is not project's build script
            if (project.buildScriptSource.fileName != script.scriptSource.fileName) {
              return
            }
                          //skip if we already applied the user build script
            if (project.ext.has('userBuildScriptApplied')) {
              return
            }
                          File userBuildScript = new File(project.projectDir, 'user.gradle')
            //skip if user build script is not present
            if (!userBuildScript.exists()) {
              return
            }
                          try {
              project.apply(from: userBuildScript.toURI())
            }
            finally {
              project.ext['userBuildScriptApplied'] = 'true'
            }
          }
        }
      }
    })
  }
}

P.S. I figured it is required to use some flag to guard against multiple apply of ‘user.gradle’ plugin. Reason is that Gradle runs the build script twice - once to read and update the buildscript’s classpath and then a second time - to configure everything else.


(nm2501) #15

Thank you very much for this. I really appreciate it. I’ll give that a try.