Best practice of using shared/common gradle Files between multi-module projects

Hello together

I want to setup a multi project build, and having some trouble understanding how to do it the right way.

My project structure is like this:

projectA
|
├── subproject1
| └── build.gradle
├── subproject2
| └── build.gradle
|
├── build.gradle
└── settings.gradle

I have similiar projects like “projectA” with the same structure, so I want to use some kind of “shared/common” settings.gradle and/or build.gradle where can I defined common properties and plugins used in all projects.

It should only act as a kind of “template”. The projects themselves are distinct and I don’t want them to build from that “common” configs.

parent
|
├── common
| ├── build.gradle
| └── settings.gradle
|
├── projectA
| └── …
└── projectB
└── …

what I have so far:

common/build.gradle

subprojects {
    apply plugin: 'com.test.DemoPlugin'

        // some project-wide default plugin settings
        demoPlugin {
            outputFolder = '/tmp'
        }
    }
}

common/settings.gradle:

// this is only to speed up development, later it's going to be an 
// exported Jar file included as dependency
includeBuild('D:/Development/intellij/workspace/GradleTest/DemoPlugin')

projectA/build.gradle

plugins {
    id 'com.example.DemoPlugin' apply false
}

apply from: 'common/build.gradle'

subprojects {
    demoPlugin {
	   // set another property in this plugin for all sub-projects
	   property1 = '123'
    }
}

projectA/settings.gradle

rootProject.name = 'projectA'
apply from: 'common/settings.gradle'

It works this way, but I also want to move that plugin { } block into the common files (build or settings.gradle). If I do so, I’m getting this error:

common\build.gradle’: 6: Only Project and Settings build scripts can contain plugins {} blocks

Am I on the right track or do I have to solve it completely different ?

I just want to have a “common template” between (technically independent) projects which I can re-use.

What you should use are convention plugins, for example implemented as precompiled script plugins.
In those you can also use the plugins { ... } block without the need to use the legacy way to apply plugins and the other quirks you earn from using script plugins (the things you use with apply from.

And besides that, you should not use subprojects { ... } or allprojects { ... } those are evil as they do cross-project configuration and thus introduce project coupling which is bad and also works against things like the upcoming configuration cache. Besides that it makes builds less readable, less maintainable, and less idiomatic.

1 Like

Thank you, I will have a look into the convention plugin thing. Never noticed that before. I thought I have one, but I just have a normal custom plugin. I check how another convention plugin can contribute to my problem :slight_smile:

I just edited my answer here, because a lot questions were unnessecary after reading about convention plugins. I think thats the way to go ! Just have to figure out how to set default parameter values for the convention plugin from my parent projects. Maybe using the ext { } block and passing the properties through ?

edit2: I’m already was able to implement a convention plugin and having code like this:

build.gradle of sub-projectA

plugins {
    id 'com.example.demo-convention'
}

demoPlugin {
  property1 = '123'
}

Question 1)
Is there a way of defining the plugin block directly in the build.gradle of the parent project to even save this repeated 3 liners in all sub-projects ?

com.test.demo-convention.gradle:

plugins {
    id 'groovy'
    id 'com.test.DemoPlugin'
}

demoPlugin {
   outputFolder = '/tmp'
   otherProperty = project.parent.ext.swe // get the property from the parent project's build.gradle
}

Question 2)

I would like to set the “otherProperty” in the build.gradle of the project which includes all the subprojects (projectA & projectB in the demo from above). Whats the best way of doing so? Currently I’m using the ext { } block and passing the properties through like this:

build.gradle of parent project:

ext {
  otherProperty = 'something'
}

Maybe using the ext { } block and passing the properties through ?

… no
extra properties are basically always not the answer but just a dirty work-around.

Is there a way of defining the plugin block directly in the build.gradle of the parent project to even save this repeated 3 liners in all sub-projects ?

Don’t even try to.
That’s exactly opposite of what you do with convention plugins.
Any allproject { ... } or subprojects { ... } is bad and introduces project coupling which you don’t want to have and decreases build readability and maintainability, as you cannot look at a build script of a project and immediately see what is going on but have to know where to look additionally for things that get injected from outside. Also think about your future-self that does not have in mind what exactly you did in that project.

Whats the best way of doing so?

Don’t.
As long as those are static values, just set them in gradle.properties of the root project, those are also available in the subprojects.

Ok thank you for your help. I’m coming from ANT builds, so I’ having a hard time getting warm with the gradle way of doing things.

Then the last question would be, how can it be implemented to have props from the parent project (aka plugin default values) be passed to the convention plugin which is then used by the subprojects?

again, my project looks like this :

<projectA>
  - subproject1
     - build.gradle // includes convention plugin
  - subproject2
     - build.gradle  // includes convention plugin
  - build.gradle  // here I want to overwrite values from the convention plugin, so that these project-specific
                        default values are usable in every subproject

<projectB>
  - subproject1
     - build.gradle 
  - subproject2
     - build.gradle 
  - build.gradle 

I have dozens of projects in the structure above, and they all should share a common config which is overwritable at the level.

In ANT I have a “common.xml” where all these default properties are defined and can be simply included
in all the projects. And all the subprojects can also overwrite these properties too, if they need to.

I’m not really sure what you mean.
The convention plugin is where you define your conventions.
The convention plugin is where you set those common configuration options.
And if really needed you can overwrite those conventions in the projects where you applied your plugin.

I have multiple projects of this 3 layer deep structure and want to use a shared “global convention” between them:

rootProject1
  - projectA
     - subproject1
     - subproject2
  - projectB
     - subproject1
     - subproject2
  - projectC
     - subproject1
     - subproject2

In ANT I have a global “global_config.xml” which contains default properties which a equal between each project.

After that, I have a “project_config.xml” which contains properties which are equal between each subproject. But here, it’s also possible to overwrite properties from the global_config.

On the lowest level, there is a build.xml for each subproject. All properties, defined in the higher layers
can be overridden too, if needed.

What do I have in gradle now ? Only the convention plugin at the project(A, B, C) - level.
I have a mult-multi-project setup, if you like to call it that way and want to declare top-level properties defaults which can be overwritten by the child projects if needed to.

As I said, write multiple convention plugins, each applied where appropriate, and if you want to change a value in a particular project, change it in that project.

Sounds not so nice… implementing this strategy requires more than 18 convention plugins (in my case). And if I want to refactor something or change a default parameter value which is global for all projects, I have to do it in 18 files again and again.

Maybe gradle wasn’t made for such kind of project structures, but I will find a compromise. Thank you :slight_smile:

If you want to change one thing and have to do it in 18 files, then you are just doing it wrong.
If you want to change one thing, you should have to do it in 1 file.

This is right and the reason I’m asking how to do it in gradle :wink:

Imagine we have 3 “Root Projects”, each with 6 “Projects” (and each with couple of subprojects):

rootProject1
  - projectA
     - subproject1
     - subproject2
  - projectB
     - subproject1

rootProject2
  - projectC
     - subproject1
  - projectD
     - subproject1
     - subproject2

rootProject3
  - projectE
     - subproject1
  - projectF
     - subproject1

... and so on

I have the need for a global configuration, which spans over all root Projects (lets call it “convention-convention plugin”). It inherits global properties to the convention plugins on the project<A,B,C…> level.

On the last level, each subproject has its own set of properties which is composed by the global config, the config at project level and last the subproject level. From top to bottom, each configuration property must be overwritable:

if I define a property on rootProject level. It must be overwriteable in the project and subproject level and so on.

Don’t get me wrong. I dont want to establish a build system over all rootProjects. I just want to have a global shared configuration over all of them.

Where’s the problem ?

If it’s not possible in gradle to provide such a hierarchical structure, then I have to write 18 convention plugins (3 Roots Projects * 6 projects).

What if I want to introduce a new configuration property which should be available to all projects and subprojects? Right… I have to do it in all 18 convention plugins instead of writing a single line in a common global config :slight_smile:

Imagine we have 3 “Root Projects”, each with 6 “Projects” (and each with couple of subprojects):

Ah, first time you mention that you have multiple builds for which you want the conventions to be used.

For that you would either publish your “convention-convention” plugin to some place and then still apply it to wherever you want those conventions to be in effect, or you could make sure that all those builds have access to the source of that “convention-convention” plugin, e.g. because your builds are all in a mono-repo, or by using Git submodules, or whatever other way, and then include the “convention-convention” plugin build as composite build to use it in each of the builds.

then I have to write 18 convention plugins (3 Roots Projects * 6 projects).

As I said, having one convention plugin for each project is a bit opposite of what a convention plugin is meant to be.
You for example have a convention plugin “my-java-conventions” that you apply to all projects that are Java projects and applies your conventions for a Java project.
You maybe have another convention plugin “my-java-library-conventions” that applies the “my-java-conventions” plugin and adds some conventions for Java library projects, then you can just apply the “my-java-library-conventions” plugin to projects that are Java libraries.
You maybe have another convention plugin “my-publication-conventions” that you apply to all projects that should be published with the conventions in that plugin, …

Actually, even if you would write one convention plugin per project - which still does not sound right - you could still have “sub-convention plugins” that you apply from your “project-specific convention plugins” and has the common logic / conventions.

currently I’m having it this way:

I have one convention plugin:

plugins {
    id 'groovy'
    id 'com.example.MyPlugin'
}

myPlugin {
    prop1 = project.parent.ext.prop1 // resolve property from the project's build.gradle
    prop2 = project.parent.ext.prop2
    prop3 = project.parent.ext.prop3
}

I’m setting the default properties for Project A, B, C etc. like this in the build.gradle of each project:

build.gradle

ext {
   prop1 = 'some' // these props, defined at project-level, can be overwritten by the subprojects
   prop2 = 'text'
   prop3 = 123
}

on the subproject level, I’m applying the convention-plugin and set subproject specific parameters:

plugins {
        id 'com.example.my-convention'
}

myPlugin {
        name = 'name'
        version = '2.0.0'
}

it’s all about the parameter hierarchy in my case. All sub-projects are of the same type (actually RPM packages which I build using rpmbuild). They all share the same build logic, but not the same default parameter values.

The parameter hierarchy should inherit from top to bottom:

root project level → project level → subproject level

Each level must be able to overwrite values, which are set at a higher level.

I want to archive a “default value - tree hierarchy” for all plugin props.
Root project and projects shall be able to provide default parameter values for the plugin which
is used in all subprojects.

Sorry, I having a hard time explaining it in english, because thats not my native language. I hope you
get the point.

Actually, every time you use extra (aka ext) properties, you are most probably doing a hack.
Using those is usually a dirty work-around for not doing it properly. :slight_smile:

Also, accessing the parent project like that is again against the sense of convention plugins, which is to avoid project coupling and work towards project isolation.

If you really want Gradle properties inherited and overwritten like that, put them to gradle.properties files. If you access them in a project, then the gradle.properties file of that project is checked, then the gradle.properties file of the parent project, and so on, up to the root project. (besides system properties, environment variables, commandline arguments, and the Gradle properties files in the Gradle user home that can override the value and the Gradle properties in the Gradle distribution that is checked at the end of the chain additionally).

Ok thank you. In my case, there is no way around that. Root-Project and Project’s are just like a kind of “shell” which encapsulate parameters needed by the subprojects below.