Multiple settings gradle files for multiple projects building
Asked Answered
C

6

55

I have the following project structure

-->Starnderd Location
        -->Project1 
           -->settings.gradle 
           -->build.gradle
           -->Subproject11
              -->build.gradle
           -->Subproject12
              -->build.gradle
        -->Project2 
           -->settings.gradle 
           -->build.gradle
           -->Subproject21
              -->build.gradle
           -->Subproject22
              -->build.gradle
        -->build.gradle
        -->settings.gradle

Idea of above project structure is that we have multiple projects which contains subprojects, each project can have dependencies to other projects. Also subprojects inside the project can have dependencies to other subprojects within the same project. Projects will be specified in the settings.gradle in the root. Also settings.gradle inside each project will say what are the subprojects of that particular project.

My settings.gradle in the root would looks like

include 'Project1',
         'Project2'

and Project1 settings.gradle will looks like

include 'SubProject11'
         'SubProject12'

other dependency orders are defined in the respective build.gradle files If I rand gradle clean build install inside the root location(Standar location) it doesn't seems to use configurations in the Project level settings.gradle file.

What I'm doing here wrong?

Cathee answered 28/8, 2012 at 6:43 Comment(1)
Have you figured out a workaround for this?Collegiate
W
30

I was able to solve this problem in a relatively clean way. Improvements are certainly welcome!

Although Gradle does not support multiple settings.gradle scripts out of the box, it is possible to create individual sub-projects each with their own settings.gradle file. Let's say you have multi-project A that depends on multi-project B, each with their own sub-projects. Your directory structure might look like:

A
- settings.gradle
- foo
- faz
\ B
  - settings.gradle
  - bar
  - bap

Out of the box, Gradle expects A/settings.gradle to look something like this:

include ':foo', ':faz', 'B:bar', 'B:bap'

The problem with this is that every time B adds a new project, A/settings.gradle must also change even if the new project is only used by B. To avoid this situation, you could try to apply B/settings.gradle in A/settings.gradle instead of adding redundant declarations:

apply from: 'B/settings.gradle'
include ':foo', ':faz'

If you try this, you'll find that Gradle fails because it generates the wrong projectDir for :bar and :bap. It mistakenly assumes that B's includes are relative to settingsDir which happens to be A/ when Gradle is invoked from that project root. To fix this you can add another script such as B/settings-parent.gradle (the exact name is not significant):

apply from: 'settings.gradle'

def updateProjectPaths(Set<ProjectDescriptor> projects) {
    projects.each { ProjectDescriptor project ->
        String relativeProjectPath = project.projectDir.path.replace(settingsDir.path, "")
        project.projectDir = new File("B/$relativeProjectPath")
        // Recursively update paths for all children
        updateProjectPaths(project.children)
    }
}

updateProjectPaths(rootProject.children)

This strips settingsDir.path and prefixes the path with B/. This can be extended to multiple layers of settings[-parent].gradle files by having each layer add itself onto the path. Now you will apply this script to A/settings.gradle:

apply from: 'B/settings-parent.gradle'
include ':foo', ':faz'

With this scheme, new B projects do not unnecessarily break A/settings.gradle and all projects can be used without explicitly referencing the B sub-project. For example, if ':foo' wanted to use 'B:bap', it may simply declare:

compile project(':bap')
Westminster answered 16/4, 2015 at 2:57 Comment(2)
Is there a need to merge build.gradle file of each sub-project. I followed the instruction here, but i'm getting an issue with missing plugin. Details in my question here #37331159Merca
Does any new workaround available ?. Am doing some project migration and timebeing in a need to connect my new project with existing code for day to day engineering and in production we use aar file.Mahon
U
16

If you are using Gradle 3.x, try includeBuild(): https://docs.gradle.org/current/userguide/composite_builds.html

// settings.gradle
includeBuild './Project1'
includeBuild './Project2'

If you are using Gradle 2.x, I have wrote a demo for this function. Hope it will help you: https://github.com/xujiaao/gradle-composite-build-demo

// settings.gradle
def includeProject(String path, Closure closure) {
    ...

    apply {
        from ...
        to new SettingsProxy(...)
    }
}

class SettingsProxy {
    ...

    public getRootProject() { ... }

    public void include(String... paths) {
        for (String path : paths) {
            if (!mProjectSpec.accept(path)) {
                continue
            }

            def descendantPath = generateDescendantPath(path)
            mSettings.include(descendantPath)

            def descendantProjectDir = new File(mProject.projectDir, path.replace(':', '/'))
            mSettings.project(descendantPath).projectDir = descendantProjectDir
        }
    }
}
Undergird answered 6/3, 2017 at 6:20 Comment(0)
B
11

Currently, Gradle only supports a single settings.gradle files per build. This may change in the future.

Badajoz answered 28/8, 2012 at 12:28 Comment(4)
Anyone know if this has changed yet? Current gradle version is 1.10. I don't see anything that it has changed but hoping it has.Kitchenmaid
It hasn't changed yet.Badajoz
Although it's still not supported, I was able to get the desired effect: https://mcmap.net/q/334177/-multiple-settings-gradle-files-for-multiple-projects-buildingWestminster
There is no direct support, but as usual with Gradle, there are certain possibilities :). Interested users can track GRADLE-803 on that topic or look to James and my example below.Billow
B
8

as this topic crossed my daily work quite often now and the improvement (GRADLE-803) on it is still open, I would like to share my approach as well:

At first sight, it looks similar to James Wald's answer but has one difference. You do not need to split your settings files somehow in the subproject. There is a clean way to do everything in the super project if that is acceptable for you. Normally your small sub projects should not have to care about the surrounding super projects. They include their sub dependencies and that's it. It should also be irrelevant how the module directory is named, in Wald's approach the module itself needs to know its directory name ('B') here:

project.projectDir = new File("B/$relativeProjectPath")

In contrast, usually, a super project knows its subprojects and directories well, because they could be git submodules for instance, which have well defined fix names which can be, from the super project perspective, safely referenced.

That's my setup (using Gradle 2.4):

Super Project
├── build.gradle
├── settings.gradle (include 'A' include 'B')
├── <Subproject A>
    ├── build.gradle
    ├── settings.gradle (include 'subA')
    ├── <Subsubproject subA>
        ├── build.gradle
├── <Subproject B>
    ├── build.gradle
    ├── settings.gradle (include 'subB')
    ├── <Subsubproject subB>
        ├── build.gradle

In the super project settings.gradle you can now code the following:

include 'A'
include 'B'
apply from: 'A/settings.gradle'
apply from: 'B/settings.gradle'
project(':subA').projectDir = new File(rootDir, 'A/subA')
project(':subB').projectDir = new File(rootDir, 'B/subB')

It still looks rather verbose (and still does not add real hierarchical behavior), but keeps the extra effort in the super project where you normally need to know everything about your contained modules anyways.

The rest is pretty straight forward again.

If you want to see my approach in practice, read section 5.) of this blog post, where I explicitly require this independence between the modules or just check out my github project on cross-language benchmarks. But be aware, you need a running native compiler toolchain like gcc or Clang to execute it ;)

Hope this helps! Cheers Ben

Billow answered 15/6, 2015 at 17:9 Comment(1)
That's a good point about the directory name being hard coded. It should be pretty straight forward to modify that code so it obtains the name using File.getName() or something similar.Westminster
I
5

I have also looked into this and you can kind of do it, but it is very ugly! The reason this works at all for us is that the vast majority of time, we just want to build from the top most level.

If it helps you at all, what you need to do is to have the top-most settings.gradle file properly reference every project-subproject directly. Get this working first.

Then if Project1 and Project2 (and so on) are capable of being independently built from one another you can make a local settings.gradle file for that project. Since, as I said above, this is not what we usually do, we call this file settings.project1. If we want to use this file, we copy it to settings.gradle. I know ugly.

But it actually gets worse :) Once you put this settings.gradle file in place, you build from Project1 will no longer see the top level build.gradle file where you probably have needed things defined. To invoke this, you would need something like this added to every project-level build.gradle file:

if (project.hasProperty('local')) {
    apply from: '../build.gradle'
}

Then you can run the build as: gradle -Plocal build

Ugly, but if you need it, it does at least work. And in the interest of full-disclosure, having put this into place a couple of weeks ago, none of the developers have needed and/or used it. Will probably remove it in another couple of weeks if it continues to not be used.

Remember, that if you build from subproject itself, only that subproject (and any dependent projects) will be built (although all the gradle scripts will be compiled/evaluated).

Intersex answered 28/8, 2012 at 13:45 Comment(2)
Think there is no need for copying settings files around. Probably the less up-voted answers help here. Do you still support it in that way in your project what you suggested?Billow
@BenSteinert as noted in my answer, although we put it in, no one used it and I don't know when, but within a one or two it was removed.Intersex
A
2

Building up on earlier answers this is what I came out with.

interface Includer {
  def include(String... args)
}

def applyFrom(String folder) {
  apply from: folder + '/settings.gradle'
  def includer = [
    include: { args ->
          args.each {
            project(it.startsWith(':') ? it : ':' + it).projectDir =
                new File(rootDir, folder + "/" + (it.startsWith(':') ? it.substring(1) : it).replace(':', '/'))
          }
    }] as Includer

  apply from: folder + '/settings.gradle', to: includer
}

applyFrom('A')
applyFrom('B')

The advantage is no duplication.

Alcuin answered 28/10, 2015 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.