What's the best way to add new methods in settings.gradle files?


(Matias Bjarland) #1

I read a while back that there was a general plan to support plugins in settings.gradle in addition to the current support in normal build files. I don’t know if that is still the plan and what the time line for such a change would be if so. In the mean time…

I have a method in a plugin class which I would like to call in settings.gradle. The method sets up a multi project structure. I currently do this via something like the following:

//settings.gradle
buildscript {
   repositories {
    ...
  }
  dependencies {
    classpath("reference.to.my.plugin.jar")
  }
}
  new MySettingsConvention(this).with {
  myplugin {
    conventionalMethodOne 'a'
         conventionalMethodTwo {
      ...
    }
  }
}

where in the ideal world I would like to just write the “myplugin { … }” section without the somewhat ugly “new … with {” syntax. Is this the best we can do with the current incarnation of gradle or is there some way that would make the dsl look a bit better?

Groovy meta programming via expandometaclass to enhance the settings/script instance we are executing comes to mind. Are there any hooks (as in “onBuildscriptBlockEvaluated(…)” or similar) I could use to do my metaprogramming? Is that even feasible/good idea?

Would the new “enterprise init script” functionality in m7 be a candidate? How would I go about using that to solve this issue?

Any thoughts welcomed. It works the way it stands, but the syntax grinds me.


(Luke Daley) #2

Hi Matias,

In future, could you please use more descriptive titles for posts. Generic ones like this are harder for people who might be able to respond to pick out of all the incoming. That would be appreciated. I’ll update this one.

I’ve added GRADLE-2059, which would at least make the code above:

configure(new MySettingsConvention(this)) {
  myplugin {
    conventionalMethodOne 'a'
    conventionalMethodTwo {
      ...
    }
  }
}

Besides being more concise, it would be consistent with out general approach for this which is slightly different to ‘with()’ (i.e. ‘configure()’ returns the target, ‘with()’ returns the return value of the closure).

I couldn’t find an issue for plugins for settings files so I added GRADLE-2066. The idea of plugins for settings blocks hasn’t really been explored. What kind of functionality are you wanting to add?

The best way to add script blocks, would be if the dsl:org.gradle.api.initialization.Settings (which is what you program against in a ‘settings.gradle’) object was dsl:org.gradle.api.plugins.ExtensionAware. I’ve raised GRADLE-2065 to get this added.

As for an init scripts, there’s another problem in that there’s no way to get hold of the ‘Settings’ object from an init script (at least that I can find). For this I’ve added GRADLE-2067.

So, all of that leaves us with your current code being the best available right now :slight_smile:


(Matias Bjarland) #3

Duly noted about the titles of posts and thank you for the correction.

Both the suggested solutions in the JIRAs sound excellent. I think Hans mentioned somewhere that there was a general plan for supporting plugins in settings.gradle at some point. I forget where this was though. But both the plugin support for settings and the configure support for the Script interface would be great. From the sound of it it might not even be all that hairy to get there, so great news and thank you for taking the time and adding the JIRAs.

As for my specific case; I have created a gradle plugin to support builds for a third party platform. The platform comes with its own project, project dependency, and classpath concepts and corresponding notation. My plugin scans through this third party directory structure, analyses project dependencies, and creates mirroring gradle projects, project dependencies, and classpaths on the fly. Since we are dealing with creating a gradle multi-project build, the natural place and phase to call the code from is afaik settings.gradle.

So to generalise a bit, the plugin functionality for settings.gradle would be useful for any plugin which wants to externalize the modification or creation of the gradle multi-project structure into the plugin code, thus presenting the user of the plugin with a consistent, coherent, and declarative view in the spirit of the rest of the gradle DSL.

Many thanks for thinking this through and I look forward to seeing this in action.


(Matias Bjarland) #4

Another thought I just had while staring at the initial code example. I have the following code in my settings.gradle:

buildscript {
      repositories {
    ...
  }
    dependencies {
    classpath('org.jfrog.buildinfo:build-info-extractor-gradle:2.0.7')
    ...
  }
    //add the above dependencies to the classpath of all sub project build scripts
  configurations.classpath.each { file -> settings.classLoader.addURL(file.toURI().toURL()) }
}

where the last line is there to “export” the classes in the settings buildscript classpath configuration to all build scripts used for the project. It’s a hack but it works. I would have been nice if we in the spirit of “allprojects”, “subprojects”, and “configurations.all” could have something like “buildscripts { }” in the settings file. I realize this is off topic and potentially involved to implement, but I figured I’d throw it out there.


(Luke Daley) #5

You shouldn’t count on this always working. If we change the classloader structure it will break.

You should do this as it’s safer:

gradle.allprojects {
  buildscript {
        repositories {
      ...
    }
    dependencies {
      classpath('org.jfrog.buildinfo:build-info-extractor-gradle:2.0.7')
      ...
    }
  }
}

This will effectively make this available to all build.gradle scripts.

It’s debatable whether or not ‘settings.gradle’ is the right place for this. An init script makes more sense semantically, but we currently don’t look for an init script inside the project implicitly which makes settings.gradle the most convenient choice.


(Luke Daley) #6

Raised this idea for project init scripts.


(Matias Bjarland) #7

gradle.allprojects? I don’t seem to have that method in settings.gradle or build.gradle files. I’m running m6 (as m7 has issues with snapshot updates).

As a side note, one of the classpath dependencies is needed both by the settings.gradle file and all build.gradle (and multi-project members without build files) files. Just doing “allprojects { }” in a build.gradle will not get the file onto the settings classpath so you end up with duplication.


(Luke Daley) #8

It’s this guy:

dsl:org.gradle.api.initialization.Settings:gradle

That’s been there for a long time. What error message are you getting?


(Matias Bjarland) #9

Well the Settings.gradle property is there, what is not there is Settings.gradle.allprojects(Closure), i.e. if we do a grep on AbstractProject (superclass of DefaultProject) vs DefaultGradle we get the following:

$ pwd
/Users/mbjarland/projects/gradle/subprojects/core/src/main/groovy/org/gradle
  $ cat api/internal/project/AbstractProject.java | grep "public void allprojects"
    public void allprojects(Action<? super Project> action) {
    public void allprojects(Closure configureClosure) {
  $ cat invocation/DefaultGradle.java | grep "public void allprojects"
    public void allprojects(final Action<? super Project> action) {

so the project object has an allprojects(Closure) and an allprojects(Action) whereas the gradle one only has the allprojects(Action) version. So the following settings.gradle:

gradle.allprojects {
  println "Hello World!"
}

gives the following error:

* What went wrong:
A problem occurred evaluating settings 'per'.
Cause: No signature of method: org.gradle.invocation.DefaultGradle.allprojects() is applicable for argument types: (settings_78h7k788t1b3bu2ft72cfrqeh7$_run_closure1) values: [settings_78h7k788t1b3bu2ft72cfrqeh7$_run_closure1@c736622]

perhaps I could use the Action version? Hmmm…


(Luke Daley) #10

Right, I gave you some bad info sorry.

Turns out ‘allprojects’ is new in m7. The other thing to note is that starting with m7 we automatically add ‘Closure’ based overloads of ‘Action’ methods.

So for m6, your best option is to use dsl:org.gradle.api.invocation.Gradle:beforeProject to add the stuff to project. But, this isn’t quite the same thing. For instance, it won’t work if you have this in your root ‘build.gradle’.

project(":somechild") {
  assert hasProperty("somePropertyAddedInSettings")
}

This is because the beforeEvaluated hook hasn’t fired for that project yet. You’d need to either move the configuration into the project’s own ‘build.gradle’ or use ‘evaluationDependsOn()’ to force the full evaluation of the project.

Or… wait for m8 :slight_smile:


(Matias Bjarland) #11

Sounds good. So we started off with a question about conventional plugin methods in settings.gradle, transferred over to a global buildscript classpath question, and finished off with transporting object instances from settings.gradle to a sub-project. Nice journey : )

I have a separate thread for the last question here. I’ll cross link the two. For the last issue, I can live with the groovy “@Singleton” until m8 is stable enough. Just to make sure I grok this correctly, you’re saying that in m8 I can do an “allprojects { myProperty = ‘Hello World’ }” in settings.gradle and have myProperty be present in all subprojects?


(Luke Daley) #12

You’ll need:

gradle.allprojects { myProperty = 'Hello World' }

But yeah, that will work. I’ve tested it.


(Matias Bjarland) #13

Most excellent! I will have to give m8 another try. How is the time line for releasing m8 looking?


(Matias Bjarland) #14

Any update on the plan for the four JIRAs mentioned earlier in this thread and for the “calling plugin code from settings.gradle” funectionality? Is this something that is planned for the 1.0 release or should not expect it until post 1.0?


(Luke Daley) #15

They will be post 1.0.