多个设置Gradle文件用于多个项目构建

55
我有以下的项目结构。
-->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

以上项目结构的想法是我们有多个包含子项目的项目,每个项目可以依赖于其他项目。此外,项目内的子项目可以依赖同一项目中的其他子项目。项目将在根目录下的 settings.gradle 中指定。另外,每个项目内的 settings.gradle 将说明该特定项目的子项目。

我在根目录下的 settings.gradle 如下:

include 'Project1',
         'Project2'

Project1的settings.gradle将会像这样

include 'SubProject11'
         'SubProject12'

其他依赖项的顺序在各自的build.gradle文件中定义。如果我在根目录(标准位置)内运行gradle clean build install,则似乎不使用项目级别的settings.gradle文件中的配置。

我做错了什么?


你想出了解决这个问题的方法吗? - Weishi Z
6个回答

30

我能以相对简洁的方式解决这个问题,欢迎提出改进意见!

虽然Gradle未原生支持多个settings.gradle脚本,但可以创建各自具有自己的settings.gradle文件的子项目。假设您拥有一个多项目A,它依赖于多项目B,每个项目都有自己的子项目。您的目录结构可能如下:

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

在默认情况下,Gradle希望A/settings.gradle的内容类似于以下内容:

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

这样做的问题是每次B添加新项目时,即使新项目只被B使用,A/settings.gradle也必须更改。为避免这种情况,您可以尝试在A/settings.gradle中应用B/settings.gradle而不是添加冗余声明:
apply from: 'B/settings.gradle'
include ':foo', ':faz'

如果你尝试这样做,你会发现Gradle失败了,因为它为:bar:bap生成了错误的projectDir。它错误地假设B的包含是相对于settingsDir的,而当从该项目根目录调用Gradle时,settingsDir恰好是A/。要解决这个问题,你可以添加另一个脚本,比如B/settings-parent.gradle(确切的名称不重要):
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)

这将去除settingsDir.path并在路径前缀添加B/。通过让每个层将自己添加到路径中,可以将此扩展为多层settings[-parent].gradle文件。现在,您将把此脚本应用于A/settings.gradle

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

通过这种方案,新的B项目不会不必要地破坏 A/settings.gradle,而且所有项目都可以在不显式引用B子项目的情况下使用。例如,如果':foo'想使用'B:bap',它可以简单地声明:

compile project(':bap')

有必要合并每个子项目的build.gradle文件吗?我按照这里的说明进行操作,但我遇到了一个缺少插件的问题。问题的详细信息在这里:http://stackoverflow.com/questions/37330659/gradle-multi-project-build-plugin-not-found - prashant
有没有新的解决方法可用?我正在进行一些项目迁移,暂时需要将我的新项目与现有代码连接起来,以便进行日常工程和生产中使用aar文件。 - Bytecode

16

如果您使用的是Gradle 3.x,请尝试使用includeBuild(): https://docs.gradle.org/current/userguide/composite_builds.html

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

如果您正在使用Gradle 2.x版本,我已经为此功能编写了一个演示。希望它能帮到您: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
        }
    }
}

11

目前,Gradle每个构建只支持一个settings.gradle文件。这可能会在将来发生变化。


4
有人知道这个情况是否已经改变了吗?当前的gradle版本是1.10。我没有看到任何变化,但希望已经有所改变。 - Matt Wolfe
7
还没有改变。 - Peter Niederwieser
虽然它仍未得到支持,但我成功地获得了所需的效果:https://dev59.com/mGct5IYBdhLWcg3wWMJF#29664571 - James Wald
虽然没有直接的支持,但是像往常一样,Gradle 有一些可能性 :). 感兴趣的用户可以跟踪GRADLE-803这个话题或者参考下面 James 和我的示例。 - Ben Steinert

8

由于这个主题在我的日常工作中经常出现,而且它的改进(GRADLE-803)仍然是开放的,我也想分享一下我的方法:

乍一看,它看起来与James Wald的答案类似,但有一个区别。如果可以接受,您不需要以某种方式在子项目中拆分设置文件。有一种干净的方法可以在超级项目中完成所有操作。通常,您的小型子项目不必关心周围的超级项目。他们包含其子依赖项,就这样。模块目录的名称也应该是无关紧要的,在Wald的方法中,模块本身需要知道其目录名称('B'),如下所示:

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

相反,通常,超级项目很好地了解其子项目和目录,因为它们可以是git子模块,例如,这些子模块具有明确定义的固定名称,从超级项目的角度来看,可以安全地引用。

这是我的设置(使用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

在超级项目的settings.gradle中,您现在可以编写以下代码:
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')

它看起来仍然很冗长(并且仍然没有添加真正的分层行为),但在超级项目中保留了额外的工作量,你通常需要完全了解所包含模块的所有内容。
其余部分又变得非常简单了。
如果您想看到我的实践方法,请阅读博客文章的第5节,其中我明确要求模块之间的独立性,或者只需查看我在跨语言基准测试上的github项目。但请注意,您需要运行像gcc或Clang这样的本地编译器工具链才能执行它 ;)
希望这有所帮助! 祝好 Ben

关于目录名称被硬编码的观点很有道理。修改代码以使用File.getName()或类似方法获取名称应该相当简单明了。 - James Wald

5

我也研究了这个问题,你可以做到,但是会非常丑陋!这样做的原因是绝大多数时候,我们只想从最高级别开始构建。

如果有帮助的话,你需要做的是让最高级的 settings.gradle 文件正确地引用每个项目子项目。先让这个工作起来。

然后,如果 Project1 和 Project2(等等)能够相互独立地构建,您可以为该项目创建一个本地的 settings.gradle 文件。由于我们通常不这样做,所以我们将此文件命名为 settings.project1。如果我们想使用此文件,我们将其复制到 settings.gradle 中。我知道这很丑陋。

但事实上,情况更糟糕 :) 一旦您放置了这个 settings.gradle 文件,从 Project1 构建将不再看到顶层 build.gradle 文件,在那里您可能已经定义了需要的内容。要调用它,您需要在每个项目级别的 build.gradle 文件中添加类似以下内容:

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

接下来你可以运行以下命令进行构建: gradle -Plocal build

这个方法可能有点丑陋,但如果需要的话它至少可以起作用。值得一提的是,在实施这个方法几周后,我们的开发人员都没有需要或使用它。如果在接下来的几周里继续没有使用,可能会将其删除。

请记住,如果从子项目本身构建,则只构建该子项目(以及任何依赖项目),尽管所有gradle脚本都将被编译/评估。


认为没有必要复制设置文件。 可能在此处不太受欢迎的答案会有所帮助。 您仍然支持您建议的方式在您的项目中吗? - Ben Steinert
@BenSteinert 如我回答所述,尽管我们添加了它,但没有人使用过,我不知道什么时候,但在一两个时间内它被移除了。 - JoeG

2

在之前的回答基础上,这是我得出的结论。

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')

优点是没有重复。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接