vlambda博客
学习文章列表

读书笔记《gradle-effective-implementations-guide-second-edition》多项目构建

Chapter 7.  Multi-project Builds

当应用程序和项目变得更大时,我们通常会将应用程序的几个部分拆分为单独的项目。 Gradle 对多项目构建有很好的支持。我们可以轻松配置多个项目。 Gradle 还能够解决项目之间的依赖关系,并且可以按照正确的顺序构建必要的项目,因此我们不必切换到特定目录来构建代码; Gradle 将为我们解析正确的项目订单。

在本章中,我们将讨论多项目配置和依赖关系。首先,我们将了解如何配置项目和任务。然后我们将使用一个多项目 Java 应用程序来学习如何拥有项目间依赖关系以及 Gradle 如何为我们解析它们。

Working with multi-project builds


让我们从一个简单的多项目结构开始。我们有一个名为 garden 的根项目以及另外两个项目, tree 和 flower 。项目结构如下:

└── garden
    ├── flower 
    └── tree 

我们学习如何在多项目构建中调用任务,如下所示:

  1. 我们将为每个项目添加一个新的 printInfo 任务。该任务会将项目名称打印到 System.out。我们必须为每个项目添加一个build.gradle文件,内容如下:

            task printInfo << { 
                println "This is ${project.name}" 
           } 
    
  2. 要为每个项目执行任务,我们必须首先进入正确的目录,然后使用 Gradle 调用任务。我们还可以使用 Gradle 的 -b 参数为特定项目运行 build.gradle。如果我们为每个项目运行 printInfo 任务,我们将得到以下输出:

    garden $ gradle -q printInfo
    This is garden
    garden $ cd tree
    tree $ gradle -q printInfo
    This tree
    tree $ cd ..
    garden $ gradle -b flower/build.gradle -q printInfo
    This is flower
    garden $
    

    我们有多个项目,但我们还没有使用 Gradle 对多项目构建的支持。

  3. 让我们重新配置我们的项目并使用 Gradle 的多项目支持。我们需要在 garden 目录中添加一个新文件settings.gradle。在这个文件中,我们将定义属于我们的多项目构建的项目。我们使用 include() 方法来设置作为我们多项目构建一部分的项目。具有settings.gradle 文件的项目自动成为构建的一部分。我们将在 settings.gradle 文件中使用以下行来定义我们的多项目构建:

            include('tree', 'flower') 
    
  4. 现在,我们可以使用单个命令为每个项目执行 printInfo 任务。如果我们执行任务,我们将得到以下输出:

    garden $ gradle printInfo
    :printInfo
    This is garden
    :flower:printInfo
    This is flower
    :tree:printInfo
    This is tree
    BUILD SUCCESSFUL
    Total time: 0.684 secs
    

Executing tasks by project path

我们看到每次调用 printInfo 任务的输出。 project 任务的路径也会显示出来。根项目用冒号 ​​(:) 表示,没有明确的名称。 flower 项目被引用为 :flower,而 printInfo flower 项目的任务被引用为 :flower:printInfo。任务的路径是项目的名称,用冒号 (:) 后跟任务名称。冒号分隔项目和任务名称。我们还可以使用命令行中的这种语法来引用项目中的特定任务。如果我们要调用 flower项目的 printInfo任务,可以运行如下命令:

garde $ gradle :flower:printInfo
:flower:printInfo
This is flower
BUILD SUCCESSFUL
Total time: 0.649 secs

这也适用于从另一个项目目录执行根项目中的任务。如果我们先进入flower项目目录,想要执行根项目的 printInfo任务,必须使用  ;:printInfo 语法。如果我们从 执行根项目、当前项目和 flower项目的 printInfo任务,我们得到以下输出;tree 项目目录:

tree $ gradle :printInfo printInfo :flower:printInfo
:printInfo
This is garden
:tree:printInfo
This is tree
:flower:printInfo
This is flower
BUILD SUCCESSFUL
Total time: 0.632 secs

Gradle 需要几个步骤来确定一个项目是必须作为单项目构建还是多项目构建执行,如下所示:

  1. 首先,Gradle 在与当前目录同级的目录中寻找一个 settings.gradle 文件 master目录。

  2. 如果 settings.gradle 未找到,则在当前目录的父目录中搜索一个 settings.gradle 文件。

  3. 如果 settings.gradle 仍然没有找到,项目将作为单项目构建执行。

  4. 如果找到settings.gradle 文件,并且当前项目是多项目定义的一部分,则该项目将作为多项目构建的一部分执行。否则,项目将作为单项目构建执行。

我们可以强制 Gradle 不在父目录中查找 settings.gradle 文件,使用 --no-search-upward(或-u)命令行参数。

Using a flat layout

在我们当前的项目设置中,我们定义了项目的分层布局。我们将 settings.gradle 文件放在父目录下,并通过 include()方法,我们添加了 <将 code class="literal">tree 和 flower 项目添加到我们的多项目构建中。

我们还可以使用平面布局来设置我们的多项目构建,可以如下完成:

  1. 我们首先要在 garden目录下创建一个 master目录。

  2. 我们必须将我们的 build.gradle 和 settings.gradle 文件从 garden 目录到 master 目录。

  3. 由于我们不再有分层布局,我们必须将  include() 方法替换为  includeFlat()方法。我们的settings.gradle 文件现在看起来类似于以下代码:

            // Include tree and flower projects 
            // as part of the build. 
            includeFlat('tree', 'flower') 
    

    这些项目是通过 master 目录的父目录引用的。因此,如果我们将  tree 定义为  includeFlat() 方法的参数,则用于解析项目目录是 master/../tree

  4. 要为每个项目调用 printInfo 任务,我们使用以下命令从 master 目录运行 Gradle:

    master $ gradle printInfo
    Unresolved directive in Gradle-Effective-Implementation-Guide_07_ 1stDraft.adoc -include::/Users/mrhaki/Projects/ gradle-effective-implementation-guide-2/ gradle-impl-guide- 2/src/docs/asciidoc/Chapter7/Code_Files/ multi-project/garden-proj/flat-master/printinfo.output.txt[]
    

Ways of defining projects

我们在 tree和 flowerbuild.gradle文件> 具有printInfo 任务实现的项目。但是,有了 Gradle 的多项目支持,我们就不必这样做了。我们可以在根build.gradle 文件中定义所有项目任务和属性。我们可以使用它在一个地方为所有项目定义通用功能。

我们可以使用 project() 方法引用项目,并使用项目的完整名称作为参数。我们必须使用闭包来定义项目的任务和属性。

对于我们的示例项目,我们将首先从 tree和 build.gradle文件">花 目录。接下来,我们将更改 master目录下的 build.gradle文件。在这里,我们将使用 project()方法定义 printInfo任务>tree 和 flower 项目,如下:

task printInfo << { 
    println "This is ${project.name}" 
} 
 
project(':flower') { 
    // Add an extra action to the printInfo task. 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 
 
project(':tree') { 
    // Add an extra action to the printInfo task. 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 

如果我们从 master目录执行printInfo任务,我们可以看到所有 调用项目的 printInfo 任务:

master $ gradle printInfo
:printInfo
This is master
:flower:printInfo
This is flower
:tree:printInfo
This is tree
BUILD SUCCESSFUL
Total time: 0.674 secs

Gradle 还具有 allprojects{} 脚本块,用于将项目任务和属性应用于作为多项目构建一部分的所有项目。我们可以重写我们的 build.gradle 文件并使用 allprojects{} 脚本块来获得任务的清晰定义无需重复:

allprojects { 
    // Add task printInfo to all projects: 
    // master, flower and tree 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 

如果我们从master目录调用printInfo任务,我们可以看到每个项目都有新添加的任务:

master $ gradle -q printInfo
This is master
This is flower
This is tree

如果我们只想配置tree和 flower子项目,我们必须使用 子项目{} 脚本块。使用此脚本块,仅配置多项目构建的子项目的任务和属性。在下面的示例构建文件中,我们将只配置 子项目:

subprojects { 
    // Add task printInfo to all sub projects: 
    // flower and tree 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 

如果我们调用 printInfo 任务,我们可以看到我们的 master项目不再有 printInfo 任务:

master $ gradle -q printInfo
This is flower
This is tree

如果没有为单个项目定义 printInfo 任务,Gradle 不会抛出异常。 Gradle 将首先为作为多项目构建一部分的所有项目构建一个完整的任务图。如果任何项目包含我们要运行的任务,则执行该项目的任务。只有当所有项目都没有任务时,Gradle 才会使构建失败。

我们可以将 allprojects{} 和 subprojects{} 脚本块和 项目() 方法来定义常见行为并为特定项目应用特定行为。在以下示例构建文件中,我们在不同级别为printInfo 任务添加了额外的功能:

allprojects { 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 
 
subprojects { 
    // Add an extra action to the printInfo task. 
    printInfo << { 
        println "Can be planted" 
    } 
} 
 
project(':tree') { 
    // Add an extra action to the printInfo task. 
    printInfo << { 
        println "Has leaves" 
    } 
} 
 
project(':flower') { 
    // Add an extra action to the printInfo task. 
    printInfo << { 
        println "Smells nice" 
    } 
} 

现在当我们执行 printInfo 任务时,我们将得到以下输出:

master $ gradle printInfo
:printInfo
This is master
:flower:printInfo
This is flower
Can be planted
Smells nice
:tree:printInfo
This is tree
Can be planted
Has leaves
BUILD SUCCESSFUL
Total time: 0.631 secs

我们使用 project( ) 方法。但是,我们也可以将 build.gradle 文件添加到 和 flower 项目并在那里添加了额外的功能。

Filtering projects

要将特定配置应用于多个项目,我们还可以使用项目过滤。在我们的 build.gradle 文件中,我们必须使用 configure() 方法。我们将根据项目名称定义一个过滤器作为方法的参数。在闭包中,我们为每个找到的项目定义配置。

在以下示例构建文件中,我们使用项目过滤器来查找名称以 f 开头的项目,然后将配置应用于项目,如下所示:

allprojects { 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 
 
// Find all projects that start with an f. 
    ext { 
    projectsWithF = 
        allprojects.findAll { project -> 
            project.name.startsWith('f') 
     } 
} 
 
// Configure the found projects. 
configure(projectsWithF) { 
    printInfo << { 
        println 'Smells nice' 
    } 
} 

当我们执行 printInfo 任务时,我们得到以下输出:

master $ gradle printInfo
:flower:printInfo
This is flower
Smells nice
BUILD SUCCESSFUL
Total time: 0.231 secs

我们使用项目名称作为过滤器。我们还可以使用项目属性来定义过滤器。由于项目属性仅在定义构建后设置,可以使用 build.gradle 文件或 project()方法,我们必须使用 afterEvaluate()方法。一旦配置了所有项目并设置了项目属性,就会调用此方法。我们会将自定义配置作为闭包传递给 afterEvaluate() 方法。

在以下示例构建文件中,我们读取了 hasLeaves 项目属性,用于 tree 和 项目。如果属性是 true,我们为这个项目自定义 printInfo任务:

allprojects { 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 
 
subprojects { 
    // After all projects have been evaluated 
    // the properties are set and we can check 
    // the value. 
    afterEvaluate { project -> 
        if (project.hasLeaves) { 
            printInfo << { 
                println "Has leaves" 
          } 
      } 
  } 
} 
 
project(':tree') { 
    ext.hasLeaves = true 
} 
 
project(':flower') { 
    ext.hasLeaves = false 
} 

当我们从master目录执行printInfo任务时,我们得到以下输出:

master $ gradle printInfo
:printInfo
This is master
:flower:printInfo
This is flower
:tree:printInfo
This is tree

as leaves
BUILD SUCCESSFUL
Total time: 0.667 secs

Defining task dependencies between projects

如果我们调用 printInfo 任务,我们看到 flower的 printInfo任务 项目在 tree 项目之前执行。 Gradle 默认使用项目的字母顺序来确定任务的执行顺序。我们可以通过定义不同项目中任务之间的显式依赖关系来改变这个执行顺序。

如果我们首先要在 flower之前执行 tree项目的printInfo任务项目,我们可以定义flower项目的 printInfo任务依赖于 tree 项目的 "literal">printInfo 任务。在下面的示例构建文件中,我们将更改 flower项目中的 printInfo任务的依赖关系。我们将使用 dependsOn()方法来引用 的 printInfo任务树项目,如下:

allprojects { 
    task printInfo << { 
        println "This is ${project.name}" 
    } 
} 
 
project(':flower') { 
    printInfo.dependsOn(':tree:printInfo') 
} 

如果我们执行 printInfo任务,我们将在输出中看到printInfo任务literal">tree 项目在 flower 项目的 printInfo 任务之前执行:

master $ gradle printInfo
:printInfo
This is master
:tree:printInfo
This is tree
:flower:printInfo
This is flower
BUILD SUCCESSFUL
Total time: 0.637 secs

Defining configuration dependencies

除了项目之间的任务依赖外,我们还可以包含其他配置依赖。例如,我们可以让一个项目设置的项目属性被另一个项目使用。 Gradle 将按字母顺序评估项目。在下面的例子中,我们将在 tree目录下新建一个build.gradle文件,并在根目录下设置一个属性项目:

rootProject.ext.treeMessage = 'I am a tree' 

我们还将在 flower 项目中创建一个 build.gradle 文件,并根据根项目设置一个具有值的项目属性tree 项目设置的属性,如下:

ext.message = rootProject.hasProperty('treeMessage') ? 
 rootProject.treeMessage : 'is not set' 
 
printInfo << { 
    println "Tree say ${message}" 
} 

当我们执行 printInfo 任务时,我们得到以下输出:

master $ gradle printInfo
:printInfo
This is master
:flower:printInfo
This is flower
Tree say I am a tree
:tree:printInfo
This is tree
BUILD SUCCESSFUL
Total time: 0.578 secs

请注意,flower 项目中的 printInfo 任务无法显示根项目属性的值,因为该值尚未设置 tree 项目。为了改变项目的求值顺序,我们可以明确定义 flower项目依赖于 tree项目和  ;evaluationDependsOn() 方法。我们可以修改 flower目录下的 build.gradle文件,添加 evaluationDependsOn (':tree') 到文件顶部:

evaluationDependsOn(':tree') 
 
ext.message = rootProject.hasProperty('treeMessage') ? 
  rootProject.treeMessage : 'is not set' 
 
printInfo << { 
    println "Tree say ${message}" 
} 

当我们再次执行 printInfo 任务时,我们在输出中看到根项目属性的值在 flower 项目:

master $ gradle printInfo
:printInfo
This is master
:flower:printInfo
This is flower
Tree say I am a tree
:tree:printInfo
This is tree
BUILD SUCCESSFUL
Total time: 0.578 secs

Working with Java multi-project builds


在 Java 项目中,我们通常在项目之间存在编译或运行时依赖关系。例如,一个项目的输出是另一个项目的编译依赖项。这在 Java 项目中很常见。让我们创建一个带有 common 项目的 Java 项目,该项目包含其他项目使用的 Java 类。我们将添加一个 services 项目,该项目引用 common 项目中的类。最后,我们将添加一个 web 项目和一个 Java servlet 类,该类使用来自 services 项目的类。

我们的项目有以下目录结构:

.
├── build.gradle
├── common
│   └── src
│       └── main
│           └── java
│               └── sample
│                   └── gradle
│                       └── util
│                           └── Logger.java
├── services
│   └── sample
│       └── src
│           ├── main
│           │   └── java
│           │       └── sample
│           │           └── gradle
│           │               ├── api
│           │               │   └── SampleService.java
│           │               └── impl
│           │                   └── SampleImpl.java
│           └── test
│               └── java
│                   └── sample
│                       └── gradle
│                           └── impl
│                               └── SampleTest.java
├── settings.gradle
└── web
    └── src
        └── main
            ├── java
            │   └── gradle
            │       └── sample
            │           └── web
            │               └── SampleServlet.java
            └── webapp
                └── WEB-INF
                    └── web.xml

在根目录中,我们将创建一个 settings.gradle 文件。我们将使用 include()方法添加 commonweb 和 services/sample 项目到构建:

include('common', 'services:sample', 'web') 

接下来,我们将在根目录下创建一个 build.gradle 文件。我们将为每个子项目应用 Java 插件,并为 JUnit 库添加一个 testCompile 依赖项。此配置应用于我们构建中的每个子项目。我们的 :services:sample 项目依赖于 common 项目。我们会在 :services:sample的项目配置中配置这个依赖。我们将使用 project() 方法来定义这个项目间依赖关系。我们的 Web 项目使用 :common 和 :services:sample 项目中的类。我们只需要定义对 :services:sample项目的依赖。 Gradle 会自动将此项目的依赖添加到:web 项目中。在我们的项目中,这意味着 :common项目也被添加为传递项目依赖,我们可以使用 Logger SampleServlet 类中来自该项目的类。我们将为 servlet API 添加另一个外部依赖项到我们的 :web 项目,并将 war 插件应用到我们的 < code class="literal">:web 项目,如下:

subprojects { 
    apply plugin: 'java' 
 
    repositories { 
        mavenCentral() 
    } 
 
    dependencies { 
        testCompile 'junit:junit:4.8.12' 
    } 
} 
 
project(':services:sample') { 
    dependencies { 
        // Dependency on the common project classes. 
        compile project(':common') 
    } 
} 
 
project(':web') { 
    apply plugin: 'war' 
 
    dependencies { 
        // Dependency on the sample classes. 
        compile project(':services:sample') 
        compile 'javax.servlet:servlet-api:2.5' 
    } 
} 

项目依赖项也称为 lib 依赖项。这些依赖项用于评估项目的执行顺序。 Gradle 将分析依赖关系,然后决定首先需要构建的项目,以便依赖的项目可以使用生成的类。

让我们从根目录使用以下命令构建我们的项目:

$ gradle build
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:common:assemble
:common:compileTestJava UP-TO-DATE
:common:processTestResources UP-TO-DATE
:common:testClasses UP-TO-DATE
:common:test UP-TO-DATE
:common:check UP-TO-DATE
:common:build
:services:compileJava UP-TO-DATE
:services:processResources UP-TO-DATE
:services:classes UP-TO-DATE
:services:jar
:services:assemble
:services:compileTestJava UP-TO-DATE
:services:processTestResources UP-TO-DATE
:services:testClasses UP-TO-DATE
:services:test UP-TO-DATE
:services:check UP-TO-DATE
:services:build
:services:sample:compileJava
:services:sample:processResources UP-TO-DATE
:services:sample:classes
:services:sample:jar
:web:compileJava
:web:processResources UP-TO-DATE
:web:classes
:web:war

web:assemble
:web:compileTestJava UP-TO-DATE
:web:processTestResources UP-TO-DATE
:web:testClasses UP-TO-DATE
:web:test UP-TO-DATE
:web:check UP-TO-DATE
:web:build
:services:sample:assemble
:services:sample:compileTestJava
:services:sample:processTestResources UP-TO-DATE
:services:sample:testClasses
:services:sample:test
:services:sample:check
:services:sample:build
BUILD SUCCESSFUL
Total time: 3.786 secs

执行了很多任务,但我们不必担心它们的依赖关系。 Gradle 将确保执行正确的任务顺序。

我们还可以基于项目中的配置来拥有项目依赖项。假设我们在:services:sample 项目中定义了一个单独的 JAR 工件,其中只有 SampleService 类。我们可以将它作为一个单独的依赖项添加到我们的:web 项目中。在以下示例构建文件中,我们将使用 SampleService 类创建一个新的 JAR 文件,然后将其用作   中的 lib 依赖项:web 项目:

subprojects { 
    apply plugin: 'java' 
 
    repositories { 
        mavenCentral() 
    } 
 
    dependencies { 
        testCompile 'junit:junit:4.8.2' 
    } 
} 
 
project(':services:sample') { 
    configurations { 
        api 
    } 
 
    task apiJar(type: Jar) { 
        baseName = 'api' 
        dependsOn classes 
        from sourceSets.main.output 
        include 'sample/gradle/api/SampleService.class' 
    } 
 
    artifacts { 
        api apiJar 
    } 
 
    dependencies { 
        compile project(':common') 
    } 
} 
 
project(':web') { 
    apply plugin: 'war' 
 
    dependencies { 
        compile project(path: ':services:sample', configuration: 'api') 
        compile project(':services:sample') 
        compile 'javax.servlet:servlet-api:2.5' 
    } 
} 

Using partial builds

由于项目之间的 lib 依赖关系,我们可以在 Gradle 中执行部分构建。这意味着我们不必在项目的根目录中构建必要的项目。我们可以切换到项目目录并从那里调用构建任务,Gradle 将首先构建所有必要的项目,然后是当前项目。

让我们切换到 services/sample 目录,从那里调用构建任务,然后检查输出:

$ cd services/sample
sample $ gradle build
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:services:sample:compileJava
:services:sample:processResources UP-TO-DATE
:services:sample:classes
:services:sample:jar
:services:sample:assemble
:services:sample:compileTestJava
:services:sample:processTestResources UP-TO-DATE
:services:sample:testClasses
:services:sample:test
:services:sample:check
:services:sample:build
BUILD SUCCESSFUL
Total time: 1.676 secs

:common 项目是在我们的:services:sample 项目之前构建的。如果我们不想构建我们所依赖的项目,我们必须使用 --no-rebuild(或- a) 命令行参数。然后,Gradle 将跳过构建我们项目所依赖的项目,并将使用缓存版本的依赖项。

当我们在调用 build 任务时使用 -a 参数时,我们会得到以下输出:

sample $ gradle -a build
:services:sample:compileJava UP-TO-DATE
:services:sample:processResources UP-TO-DATE
:services:sample:classes UP-TO-DATE
:services:sample:jar UP-TO-DATE
:services:sample:assemble UP-TO-DATE
:services:sample:compileTestJava UP-TO-DATE
:services:sample:processTestResources UP-TO-DATE
:services:sample:testClasses UP-TO-DATE
:services:sample:test UP-TO-DATE
:services:sample:check UP-TO-DATE
:services:sample:build UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.638 secs

如果我们在我们的 :services:sample 项目中调用 build 任务,则 : common 项目也已构建。但是,有一个问题,因为只有  :common 项目的 jar 任务被执行。通常, build 任务也会运行测试并执行 check 任务。仅当项目构建为 lib 依赖项时,Gradle 才会跳过这些任务。

如果我们要执行依赖项目的测试和检查,我们必须执行 buildNeeded 任务。 Gradle 然后将执行所有依赖项目的完整构建。让我们从 s ervices/sample 目录执行 buildNeeded 任务并查看输出:

sample $ gradle buildNeeded
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE
:common:assemble UP-TO-DATE
:common:compileTestJava UP-TO-DATE
:common:processTestResources UP-TO-DATE
:common:testClasses UP-TO-DATE
:common:test UP-TO-DATE
:common:check UP-TO-DATE
:common:build UP-TO-DATE
:common:buildNeeded UP-TO-DATE
:services:sample:compileJava UP-TO-DATE
:services:sample:processResources UP-TO-DATE
:services:sample:classes UP-TO-DATE
:services:sample:jar UP-TO-DATE
:services:sample:assemble UP-TO-DATE
:services:sample:compileTestJava UP-TO-DATE
:services:sample:processTestResources UP-TO-DATE
:services:sample:testClasses UP-TO-DATE
:services:sample:test UP-TO-DATE
:services:sample:check UP-TO-DATE
:services:sample:build UP-TO-DATE
:services:sample:buildNeeded UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.651 secs

如果我们对 :services:sample 项目进行了更改,我们可能还需要依赖于 sample 项目的项目被建造。我们可以使用它来确保我们没有破坏任何依赖于我们项目的代码。 Gradle 有一个 buildDependents 任务来执行此操作。例如,让我们从我们的 :services:sample项目执行这个任务,我们的 :web项目也是这样构建的依赖于 :services:sample 项目。当我们执行 buildDependents任务时,我们会得到如下输出:

sample $ gradle buildDependents
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE
:services:sample:compileJava UP-TO-DATE
:services:sample:processResources UP-TO-DATE
:services:sample:classes UP-TO-DATE
:services:sample:apiJar
:services:sample:jar UP-TO-DATE
:web:compileJava
:web:processResources UP-TO-DATE
:web:classes
:web:war
:web:assemble
:web:compileTestJava UP-TO-DATE
:web:processTestResources UP-TO-DATE
:web:testClasses UP-TO-DATE
:web:test UP-TO-DATE
:web:check UP-TO-DATE
:web:build
:web:buildDependents
:services:sample:assemble UP-TO-DATE
:services:sample:compileTestJava UP-TO-DATE
:services:sample:processTestResources UP-TO-DATE
:services:sample:testClasses UP-TO-DATE
:services:sample:test UP-TO-DATE
:services:sample:check UP-TO-DATE
:services:sample:build UP-TO-DATE
:services:sample:buildDependents
BUILD SUCCESSFUL
Total time: 0.709 secs

Using the Jetty plugin


在上一节中,我们创建了一个带有 web 子项目的 Java 项目。  web 项目有一个简单的 servlet。要执行 servlet,我们必须创建 WAR 文件并将 WAR 文件部署到 servlet 容器,例如 Tomcat 或 Jetty。您可以在 http://www.eclipse.org/jetty/< /一>。使用 Jetty 插件,我们可以在 Jetty Web 容器中从命令行运行我们的web 项目。我们不必在我们的电脑上安装 Jetty,我们只需要将 Jetty 插件应用到我们的项目中。该插件将负责配置 Jetty 并启动 Web 容器。如果一切正常,我们可以打开 Web 浏览器并访问我们的 servlet。

要将 Jetty 插件添加到我们的 web 项目中,让我们在 web 目录中创建一个新的 build.gradle 文件。在这里,我们将使用 apply()方法将Jetty插件添加到项目中:

apply plugin: 'jetty' 

该插件为我们的项目添加了以下任务:jettyRun、 jettyRunWar和  jettyStop。下表显示了不同的任务:

任务

取决于

类型

说明

jettyRun

JettyRun

这是启动一个 Jetty web 容器并部署爆炸的 web 应用程序

jettyRunWar

战争

JettyRunWar

这是启动一个 Jetty web 容器并部署 WAR 文件

jettyStop

-

JettyStop

这是为了停止正在运行的 Jetty Web 容器

执行 jettyRunjettyWar 任务后,我们可以在 Web 浏览器中测试我们的 servlet。当我们从多项目构建的根目录执行 jettyRun 任务时,我们得到以下输出:

$ gradle :web:jettyRun
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:services:sample:compileJava
:services:sample:processResources UP-TO-DATE
:services:sample:classes
:services:sample:apiJar
:services:sample:jar
:web:compileJava
:web:processResources UP-TO-DATE
:web:classes
> Building 92% > :web:jettyRun > Running at http://localhost:8080/web

Gradle 将继续运行,最后,我们将看到应用程序正在运行在 http://localhost:8080/web 。我们可以打开网络浏览器并访问我们的网络应用程序。在以下屏幕截图中,我们可以看到 servlet 的输出:

读书笔记《gradle-effective-implementations-guide-second-edition》多项目构建

Web 浏览器中的 Web 应用程序的结果

要停止 Jetty Web 容器,请按 Ctrl + C命令行返回到我们的提示符。

我们可以通过Jetty插件添加的httpPort 项目约定属性或httpPort任务属性更改端口号 jettyRun 和 jettyRunWar 任务。要更改上下文路径,我们可以设置 jettyRun和 contextPath属性>jettyRunWar 任务。

如果我们希望 Jetty 容器自动扫描更改,我们可以将 reload 属性设置为 automatic。 如果设置了该属性要 manual,我们必须在命令行上按 Enter重新加载更改。我们可以使用 scanIntervalSeconds 属性以秒为单位设置扫描间隔。

在以下示例构建文件中,我们将使用另一个 HTTP 端口、上下文路径和自动重新加载来自定义 Jetty Web 容器:

apply plugin: 'jetty' 
 
httpPort = 8090 
 
jettyRun { 
    contextPath = 'sample' 
    reload = 'automatic' 
    scanIntervalSeconds = 10 
} 

我们甚至可以使用自定义 Jetty 配置文件进一步自定义 Jetty 容器。我们可以使用 jettyRun 任务属性 jettyConfig 来使用配置文件。我们还可以使用 additionalRuntimeJars 属性添加额外的运行时库。

如果我们要使用 jettyStop 任务,我们还必须定义 stopPort和 我们的项目或任务中的 stopKey 属性。如果我们定义了这些属性,我们可以打开一个新的命令行提示符并调用 jettyStop 任务来停止正在运行的 Jetty Web 容器。

在以下示例构建文件中,我们将应用其中一些属性和方法来自定义 Jetty 配置:

    apply plugin: 'jetty'
    
    configurations {
        // Extra configuration to
        // be used in the jettyRun task.
        jettyAdditionalLibs
    }
    
    dependencies {
        jettyAdditionalLibs 'org.slf4j:slf4j-simple:1.7.3'
    }
    
    // Properties for stopping Jetty with jettyStop
    stopPort = 8109
    stopKey = 'JettyStop'
    
    jettyRun {
        // External Jetty configuration file.
        jettyConfig = file('src/jetty/jetty.xml')
    
        // Extra libraries for Jetty runtime.
        additionalRuntimeJars configurations.jettyAdditionalLibs
    }

Summary


多项目构建在软件项目中非常常见。 Gradle 对多项目构建有很好的支持。我们可以使用分层布局作为项目结构,但我们可以轻松自定义此布局并使用其他布局。

配置项目很容易,并且可以在一个地方完成——”在项目的根目录。我们还可以在项目级别本身添加项目配置。我们不仅可以在项目库级别定义项目之间的依赖关系,还可以通过配置或任务依赖关系来实现。 Gradle 将解决构建完整项目的正确方法,这样我们就不必太担心了。

由于 Gradle 在执行任务之前知道将涉及的项目,因此我们可以进行部分多项目构建。 Gradle 会自动构建项目依赖,这是我们当前项目所必需的,我们可以使用单个任务来构建依赖于我们当前项目的项目。

我们还看到了如何使用 Jetty 插件在 Jetty Web 容器中运行我们的 Web 应用程序代码。我们应用了插件并执行了 jettyRun 或 jettyRunWar 任务以将我们的代码作为 Web 应用程序运行。我们现在可以打开网络浏览器并执行我们的代码。

在下一章中,我们将看看如何在 Gradle 中使用除 Java 之外的其他语言。