vlambda博客
学习文章列表

读书笔记《gradle-effective-implementations-guide-second-edition》使用Gradle for Java项目

Chapter 4. Using Gradle for Java Projects

我们已经看到了如何在 Gradle 构建中编写任务以及如何执行它们,但我们还没有看到如何执行现实生活中的任务,例如编译源代码或使用 Gradle 进行测试。

在本章中,我们将讨论如何使用 Gradle Java 插件来获取编译和打包 Java 项目的任务。我们还将了解 Gradle 的按惯例构建功能如何让您轻松启动和使用源代码。

Why plugins?


在 Gradle 中,我们可以将插件应用到我们的项目中。插件基本上为我们的项目添加了额外的功能,例如任务和属性。通过使用插件,功能与核心 Gradle 构建逻辑分离。我们可以编写自己的插件,但 Gradle 还附带了开箱即用的插件。例如,Gradle 有一个 Java 插件。这个插件为我们的项目添加了编译、测试和打包 Java 源代码的任务。

与 Gradle 版本一起打包的插件永远不会针对该版本进行更新或更改,因此如果向插件添加新功能,则会发布全新的 Gradle 版本。在 Gradle 的未来版本中,这将发生变化。这不适用于我们自己编写的插件。我们可以发布我们自己的插件的新版本,独立于 Gradle 版本。让我们从作为 Gradle 发行版一部分的 Java 插件开始。

Getting started with the Java plugin


Java 插件提供了许多有用的任务和属性,我们可以使用它们来构建 Java 应用程序或库。如果我们遵循插件的约定优于配置的支持,我们不必在我们的 Gradle 构建文件中编写大量代码来使用它。如果我们愿意,我们仍然可以添加额外的配置选项来覆盖插件定义的默认约定。

让我们从一个新的构建文件开始并使用 Java 插件。我们只需要为我们的构建应用插件:

apply plugin: 'java' 

而已!只需添加这个简单的行,我们现在就可以在 Java 项目中使用很多任务。要查看插件添加的任务,我们在命令行上运行 tasks 命令并查看输出:

$ gradle tasks
:tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]
Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.
Help tasks
----------
components - Displays the components produced by root project 'getting_started'. [incubating]
dependencies - Displays all dependencies declared in root project 'getting_started'.
dependencyInsight - Displays the insight into a specific dependency in root project 'getting_started'.
help - Displays a help message.
model - Displays the configuration model of root project 'getting_started'. [incubating]
projects - Displays the sub-projects of root project 'getting_started'.
properties - Displays the properties of root project 'getting_started'.
tasks - Displays the tasks runnable from root project 'getting_started'.
Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.
Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.
To see all tasks and more detail, run gradle tasks --all
To see more detail about a task, run gradle help --task <task>
BUILD SUCCESSFUL
Total time: 0.849 secs

如果我们查看任务列表,我们可以看到现在可供我们使用的任务数量,这是我们以前没有的;所有这一切都是通过在我们的构建文件中添加一个简单的行来完成的。

我们有几个任务组,它们有各自的任务,可以使用。我们在 构建任务 部分有与构建源代码和打包相关的任务。  javadoc 任务用于生成 Javadoc 文档,位于 Documentation tasks 部分。运行测试和检查代码质量的任务在 验证任务部分。最后,我们有几个基于规则的任务来构建、上传和清理 Java 项目中的工件或任务。

Java插件添加的任务是我们项目新添加功能的可见部分。但是,该插件还将所谓的 convention 对象添加到我们的项目中。

convention 对象有几个属性和方法,由插件的任务使用。这些属性和方法被添加到我们的项目中,可以像普通的项目属性和方法一样访问。因此,通过约定对象,我们不仅可以查看插件中任务使用的属性,还可以更改属性的值来重新配置某些任务。

Using the Java plugin

要使用 Java 插件,我们首先要创建一个非常简单的 Java 源文件。然后我们可以使用插件的任务来构建源文件。您可以根据需要使此应用程序变得复杂,但为了保持主题,我们将使其尽可能简单。

通过应用 Java 插件,我们现在必须遵循项目目录结构的一些约定。要构建源代码,我们的 Java 源文件必须位于相对于项目目录的 src/main/java 目录中。如果我们有非 Java 源文件需要包含在 JAR 文件中,我们必须将它们放在  src/main/resources 目录中。我们的测试源文件需要在 src/test/java目录下,任何测试需要的非Java源文件都可以放在 src/test/resources.如果我们想要或需要,可以更改这些约定,但最好坚持使用它们,这样我们就不必在构建文件中编写任何额外的代码,这可能会导致错误。

我们将编写的示例 Java 项目是一个 Java 类,它使用外部属性文件来获取欢迎消息。名为Sample.java的源文件位于 src/main/java目录下,如下:

// File: src/main/java/gradle/sample/Sample.java 
package gradle.sample; 
 
import java.util.ResourceBundle; 
 
/** 
* Read welcome message from external properties file 
* <code>messages.properties</code>. 
*/ 
public class Sample { 
 
    public Sample() { 
    } 
 
    /** 
    * Get <code>messages.properties</code> file 
    * and read the value for <em>welcome</em> key. 
    * 
    * @return Value for <em>welcome</em> key 
    *  from <code>messages.properties</code> 
    */ 
    public String getWelcomeMessage() { 
        final ResourceBundle resourceBundle =                               ResourceBundle.getBundle("messages"); 
      final String message = resourceBundle.getString("welcome"); 
      return message; 
    } 
} 

在代码中,我们使用 ResourceBundle.getBundle() 来阅读我们的欢迎信息。欢迎消息本身定义在一个名为 messages.properties的 properties文件中,该文件将进入 < code class="literal">src/main/resources 目录:

# File: src/main/resources/gradle/sample/messages.properties 
welcome = Welcome to Gradle! 

为了编译 Java 源文件并处理属性文件,我们运行 classes 任务。请注意,Java 插件已添加了 classes 任务。这就是 Gradle 中所谓的生命周期任务。  classes 任务实际上依赖于另外两个任务——compileJava 和 processResources 。当我们使用 --all命令行选项运行 tasks命令时,我们可以看到这个任务依赖:

$ gradle tasks --all
...
classes - Assembles main classes.
compileJava - Compiles main Java source.
processResources - Processes main resources.
...

让我们从命令行运行 classes 任务:

$ gradle classes
:compileJava
:processResources
:classes
BUILD SUCCESSFUL
Total time: 1.08 secs

在这里,我们可以看到 compileJava 和 processResources 任务被执行,因为 classes< /code> 任务取决于这些任务。编译后的类文件和属性文件现在位于 build/classes/main 和 build/resources/main 目录中。  build 目录是 Gradle 用于构建输出文件的默认目录。

如果我们再次执行 classes 任务,我们会注意到这些任务支持 Gradle 的增量构建功能。由于我们没有更改 Java 源文件或属性文件,并且输出仍然存在,因此可以跳过所有任务,因为它们是最新的:

$ gradle classes
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.595 secs

为了打包我们的类文件和属性文件,我们调用 jar 任务。该任务也是由 Java 插件添加的,并且依赖于 classes 任务。这意味着如果我们运行 jar 任务,那么 classes 任务也会被执行。让我们尝试运行 jar任务,如下:

$ gradle jar
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
BUILD SUCCESSFUL
Total time: 0.585 secs

生成的 JAR 文件的默认名称是我们项目的名称。所以如果我们的项目叫做sample,那么JAR文件就叫做 sample.jar。我们可以在 build/libs目录中找到该文件。如果我们查看 JAR 文件的内容,我们会看到已编译的类文件和 messages.properties 文件。此外, jar 任务会自动添加一个清单文件:

$ jar tvf build/libs/sample.jar
0 Wed Oct 21 15:29:36 CEST 2015 META-INF/
25 Wed Oct 21 15:29:36 CEST 2015 META-INF/MANIFEST.MF
0 Wed Oct 21 15:26:58 CEST 2015 gradle/
0 Wed Oct 21 15:26:58 CEST 2015 gradle/sample/
685 Wed Oct 21 15:26:58 CEST 2015 gradle/sample/Sample.class
90 Wed Oct 21 15:26:58 CEST 2015   gradle/sample/messages.properties

我们还可以执行 assemble 任务来创建 JAR 文件。  assemble任务,另一个生命周期任务,依赖于 jar任务,可以被其他插件扩展。我们还可以添加对其他任务的依赖项,这些任务为 JAR 文件以外的项目创建包,例如 WAR 文件或 ZIP 存档文件:

$ gradle assemble
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.607 secs

要重新开始并清理之前任务生成的所有输出,我们可以使用 clean 任务。此任务会删除项目 build 目录以及该目录中所有生成的文件。所以,如果我们从命令行执行 clean任务,Gradle会删除 build目录:

$ gradle clean
:clean
BUILD SUCCESSFUL
Total time: 0.583 secs

请注意,Java 插件还添加了一些基于规则的任务。其中之一是 clean<TaskName>。我们可以使用此任务来删除特定任务的输出文件。  clean任务删除完整的 build目录;但是使用 clean<TaskName>,我们只删除了命名任务创建的文件和目录。例如,为了清理 compileJava任务生成的Java类文件,我们执行 cleanCompileJava任务。由于这是一项基于规则的任务,Gradle 将确定 clean 之后的所有内容都必须是我们项目中的有效任务。此任务创建的文件和目录然后由 Gradle 确定并删除:

$ gradle cleanCompileJava
:cleanCompileJava UP-TO-DATE
BUILD SUCCESSFUL
Total time: 0.578 secs

Working with source sets


Java 插件还为我们的项目添加了一个新概念——源集。源集是一起编译和执行的源文件的集合。这些文件可以是 Java 源文件或资源文件。源集可以用来在我们的项目中将具有一定意义的文件分组在一起,而不必创建一个单独的项目。例如,我们可以将描述我们 Java 项目 API 的源文件的位置分隔在一个源集中,并运行仅适用于该源集中文件的任务。

在没有任何配置的情况下,我们已经有了 Java 插件添加的 maintest 源集。对于每个源集,该插件还添加了以下三个任务: compile Java process Resources< /code> 和  <SourceSet>类。当源集命名为  main 时,我们在执行任务时不必提供源集名称。例如,  compileJava 适用于  main源代码测试,但  compileTestJava< /code> 适用于 test 源集。

每个源集还具有一些属性来访问构成源集的目录和文件。下表显示了我们可以在源集中访问的属性:

源集属性

类型

说明

java

SourceDirectorySet

这些是该项目的 Java 源文件。此集合中只有具有 .java 扩展名的文件。

allJava

SourceDirectorySet

默认情况下,这与 Java 属性相同,因此它包含所有 Java 源文件。其他插件可以将额外的源文件添加到此集合中。

资源

SourceDirectorySet

这些是此源集的所有资源文件。这包含资源源目录中的所有文件,不包括任何具有 .java 扩展名的文件。

allSource

SourceDirectorySet

默认情况下,这是资源和 Java 属性的组合。这包括该源集的所有源文件,包括资源和 Java 源文件。

输出

SourceSetOutput

这些是源集中源文件的输出文件。这包含已编译的类和已处理的资源。

java.srcDirs

设置<文件>

这些是包含 Java 源文件的目录。

resources.srcDirs

设置<文件>

这些是包含此源集的资源文件的目录。

output.classesDir

文件

这是包含此源集中 Java 源文件的已编译类文件的输出目录。

output.resourcesDir

文件

这是包含来自此源集中的资源的已处理资源文件的输出目录。

名称

字符串

这是带有源集名称的只读值。

我们可以通过项目的 sourceSets 属性访问这些属性。在以下示例中,我们将创建一个新任务来显示多个属性的值:

apply plugin: 'java' 
task sourceSetJavaProperties << { 
    sourceSets { 
        main { 
            println "java.srcDirs = ${java.srcDirs}" 
            println "resources.srcDirs = ${resources.srcDirs}" 
            println "java.files = ${java.files.name}" 
            println "allJava.files = ${allJava.files.name}" 
            println "resources.files = ${resources.files.name}" 
            println "allSource.files = ${allSource.files.name}" 
            println "output.classesDir = ${output.classesDir}" 
            println "output.resourcesDir = ${output.resourcesDir}" 
            println "output.files = ${output.files}" 
      } 
    } 
} 

当我们运行 sourceSetJavaproperties 任务时,我们得到以下输出:

$ gradle sourceSetJavaproperties
:sourceSetJavaProperties
java.srcDirs = [/gradle-book/Chapter4/Code_Files/sourcesets/src/main/java]
resources.srcDirs = [/gradle-book/Chapter4/Code_Files/sourcesets/src/main/resources]
java.files = [Sample.java]
allJava.files = [Sample.java]
resources.files = [messages.properties]
allSource.files = [messages.properties, Sample.java]
output.classesDir = /gradle-book/Chapter4/Code_Files/sourcesets/build/classes/main
output.resourcesDir = /gradle-book/Chapter4/Code_Files/sourcesets/build/resources/main
output.files = [/gradle-book/Chapter4/Code_Files/sourcesets/build/classes/main, /gradle-book/Chapter4/Code_Files/sourcesets/build/resources/main]
BUILD SUCCESSFUL
Total time: 0.594 secs

Creating a new source set

我们可以在项目中创建自己的源集。源集包含所有相互关联的源文件。在我们的示例中,我们将添加一个新的源集以包含一个 Java 接口。我们的Sample 类将实现接口;但是,由于我们使用单独的源集,我们可以稍后使用它来创建一个单独的 JAR 文件,其中仅包含已编译的接口类。我们将源集命名为 api,因为接口实际上是我们示例项目的API,我们可以与其他项目共享。

要定义这个源集,我们只需要将名称放在项目的 sourceSets 属性中,如下所示:

apply plugin: 'java' 
 
sourceSets { 
    api 
} 

Gradle 将基于此源集创建三个新任务——apiClassescompileApiJava处理ApiResources。执行 tasks命令后我们可以看到这些任务:

$ gradle tasks --all
...
Build tasks
-----------
apiClasses - Assembles api classes.
compileApiJava - Compiles api Java source.
processApiResources - Processes api resources.
...

我们在 src/api/java 目录中创建了我们的 Java 接口,该目录是  api< 的 Java 源文件的源目录/code> 源集。下面的代码可以让我们看到Java接口:

// File: src/api/java/gradle/sample/ReadWelcomeMessage.java 
package gradle.sample; 
 
/** 
* Read welcome message from source and return value. 
*/ 
public interface ReadWelcomeMessage { 
 
    /** 
    * @return Welcome message 
    */ 
    String getWelcomeMessage(); 
 
} 

要编译源文件,我们可以执行 compileApiJava 或 apiClasses 任务:

$ gradle apiClasses
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
BUILD SUCCESSFUL
Total time: 0.595 secs

源文件编译在 build/classes/api 目录下。

现在我们将更改我们的Sample类的源代码并实现 ReadWelcomeMessage接口,如下代码所示:

// File: src/main/java/gradle/sample/Sample.java 
package gradle.sample; 
 
import java.util.ResourceBundle; 
 
/** 
* Read welcome message from external properties file 
* <code>messages.properties</code>. 
*/ 
public class Sample implements ReadWelcomeMessage { 
 
    public Sample() { 
    } 
 
    /** 
    * Get <code>messages.properties</code> file 
    * and read the value for <em>welcome</em> key. 
    * 
    * @return Value for <em>welcome</em> key 
    *         from <code>messages.properties</code> 
    */ 
    public String getWelcomeMessage() { 
      final ResourceBundle resourceBundle =           ResourceBundle.getBundle("messages"); 
      final String message = resourceBundle.getString("welcome"); 
      return message; 
    } 
 
} 

接下来,我们运行 classes 任务来重新编译我们更改的 Java 源文件:

$ gradle classes
:compileJava
/gradle-book/Chapter4/src/main/java/gradle/sample/Sample.java:10: error: cannot find symbol
public class Sample implements ReadWelcomeMessage {
                              ^
symbol: class ReadWelcomeMessage
1 error
:compileJava FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.608 secs

我们得到一个编译错误! Java 编译器找不到 ReadWelcomeMessage 接口。但是,我们只是运行了 apiClasses 任务并编译了界面而没有错误。

为了解决这个问题,我们必须定义 classes 和 apiClasses 任务之间的依赖关系。  classes 任务依赖于 apiClasses 任务。首先,必须编译接口,然后必须编译实现该接口的类。

接下来,我们必须将编译后的接口类文件的输出目录添加到main源集的compileClasspath属性中。完成此操作后,我们确定 Java 编译器会选择已编译的类文件来编译  Sample 类。

为此,我们将更改构建文件并添加两个任务和主源集配置之间的任务依赖关系,如下所示:

apply plugin: 'java' 
n 
sourceSets { 
    api 
    main { 
      compileClasspath += files(api.output.classesDir) 
    } 
} 
 
classes.dependsOn apiClasses 

现在我们可以再次运行 classes 任务,没有错误:

$ gradle classes
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
:compileJava
:processResources
:classes
BUILD SUCCESSFUL
Total time: 0.648 secs

Custom configuration

如果我们将 Gradle 用于现有项目,我们可能具有与 Gradle 定义的默认结构不同的目录结构,或者我们可能出于其他原因想要具有不同的结构。我们可以通过配置源集并为源目录使用不同的值来解决这个问题。

考虑我们有一个具有以下源目录结构的项目:

. 
├── resources 
│   ├── java 
│   └── test 
├── src 
│   └── java 
├── test 
│   ├── integration 
│   │   └── java 
│   └── unit 
│       └── java 
└── tree.txt 

我们需要重新配置 main 和 test 源集,但我们还必须添加一个新的 集成测试 源集。以下代码反映了源集的目录结构:

apply plugin: 'java'

sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
        resources {
            srcDir 'resources/java'
        }
    }
    test {
        java {
            srcDir 'test/unit/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }

    'integeration-test' {
        java {
            srcDir 'test/integration/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }
}

请注意我们必须如何将 integration-test 源集的名称放在引号中;这是因为我们在名称中使用了连字符。然后,Gradle 将源集的名称转换为  integrationTest(不带连字符并带有大写  T< /跨度>)。例如,要编译集成 test 源集的源文件,我们使用 compileIntegrationTestJava 任务。

Working with properties


我们已经讨论过 Java 插件将任务和源集添加到我们的 Gradle 项目中;但是,我们也获得了许多可以使用的新属性。插件的自定义属性在 org.gradle.api.plugins.Convention 类型的 Convention 对象中设置。插件使用Convention 对象来公开我们可以在项目中使用的属性和方法。插件的 Convention 对象被添加到项目的 convention 属性中。 Gradle 项目的 convention 属性是插件中所有 Convention 对象的容器。

我们可以直接从插件的 Convention 对象中访问属性作为项目属性,也可以指定 Convention 对象的完整路径插件以获取属性或调用方法。

例如,sourceSets 属性是 Java 插件的 Convention 对象的属性。通过以下任务, showConvention,我们看到了访问此属性的不同方式:

task showConvention << { 
    println sourceSets.main.name 
    println project.sourceSets.main.name 
    println project.convention.plugins.java.sourceSets.main.name 
} 

要查看我们可用的所有属性,我们必须从命令行调用 properties 任务。以下输出显示了 properties 任务的部分输出:

$ gradle properties
:properties
...
targetCompatibility: 1.8
tasks: [task ':buildDependents', task ':buildNeeded', task ':check', task ':classes', task ':compileJava', task ':compileTestJava', task ':jar', task ':javadoc', task ':processResources', task ':processTestResources', task ':properties', task ':test', task ':testClasses']
test: task ':test'
testClasses: task ':testClasses'
testReportDir: /gradle-book/Chapter4/Code_Files/props/build/reports/tests
testReportDirName: tests
testResultsDir: /gradle-book/Chapter4/Code_Files/props/build/test-results
testResultsDirName: test-results
version: unspecified
...

如果我们查看列表,我们会看到很多属性,我们可以使用这些属性来重新定义 compiletest 的输出文件所在的目录 任务被存储。下表显示了目录属性:

属性名称

默认值

说明

distDirName

分布

这是相对于构建目录的目录名称,用于存储分发文件

libsDirName

这是存储生成的 JAR 文件的 directory 名称;它是相对于构建目录的

dependencyCacheDirName

依赖缓存

这是用于存储有关依赖项的缓存信息的目录的名称;它是相对于构建目录的

docsDirName

文档

这是存储生成文档的目录名称;它是相对于构建目录的

testReportDirName

测试

这是相对于构建目录的目录名称,用于存储测试报告

testResultsDirName

测试结果

这存储了测试结果 XML 文件;它是相对于构建目录的

Java 插件还为我们的项目添加了其他属性。这些属性可用于设置 Java 版本的源和目标兼容性以编译 Java 源文件或设置生成的 JAR 文件的基本文件名。

下表显示了 Java 插件的约定属性:

属性名称

类型

默认值

说明

archivesBaseName

Stringrce 和 Java 版本编译的目标兼容性

项目名称

这是用于归档任务创建的归档的基本文件名,例如 JAR

sourceCompatibility

StringNumberJavaVersion对象

用于运行 Gradle 的 Java 版本的 JDK

这是使用 compile 任务编译 Java 源文件时使用的 Java 版本兼容性

targetCompatibility

String,Number,JavaVersion,对象

sourceCompatibility 的值

这是为 Java 类文件生成的版本

sourceSets

SourceSetContainer

-

这些是项目的源集

清单

清单

空清单

这是要包含在所有 JAR 文件中的清单

metaInf

列表

空列表

这是项目中创建的所有 JAR 文件的 META-INF 目录中要包含的文件列表

在我们的示例项目中,我们已经看到生成的 JAR 文件是以项目名称命名的;但是通过 archivesBaseName 属性,我们可以改变它。我们还可以将项目的源代码兼容性更改为 Java 6。最后,我们还可以更改用于生成的 JAR 文件的清单。以下构建文件反映了所有更改:

apply plugin: 'java' 
 
archivesBaseName = 'gradle-sample' 
version = '1.0' 
 
sourceCompatibility = JavaVersion.VERSION_1_8 // Or '1.8' or 8 
 
jar { 
    manifest { 
      attributes( 
        'Implementation-Version' : version, 
        'Implementation-Title' : 'Gradle Sample' 
      ) 
    } 
} 
... 

现在,如果我们调用 assemble 任务来创建我们的 JAR 文件并查看 build/libs 目录,我们可以看到JAR 文件现在命名为 gradle-sample-1.0.jar

$ gradle assemble
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
:compileJava
:processResources
:classes
:jar
:assemble
BUILD SUCCESSFUL
Total time: 0.657 secs
$ ls build/libs
gradle-sample-1.0.jar

要查看生成的清单文件的内容,我们将首先从 JAR 文件中提取文件,然后查看内容:

$ cd build/libs
$ jar xvf gradle-sample-1.0.jar
created: META-INF/
inflated: META-INF/MANIFEST.MF
created: gradle/
created: gradle/sample/
inflated: gradle/sample/Sample.class
inflated: gradle/sample/messages.properties
$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Implementation-Version: 1.0
Implementation-Title: Gradle Sample

Creating Javadoc documentation


要生成Javadoc文档,我们必须使用org.gradle.api.tasks.javadoc.Javadocjavadoc任务类型。该任务为 main 源集中的 Java 源文件生成文档。如果我们想为项目中的源集生成文档,我们必须配置 javadoc任务或添加额外的 javadoc 我们项目的任务。

请注意,在我们的项目中,我们有一个包含 Java 源文件的 API 和主源集。如果我们想为两个源集生成文档,我们必须在我们的项目中配置 javadoc 任务。  javadoc 任务的 source 属性设置为 sourceSets.main.allJava 默认情况下。如果我们将 sourceSets.api.allJava添加到 source属性中,我们的接口文件也会被 javadoc 任务:

apply plugin: 'java' 
 
javadoc { 
    source sourceSets.api.allJava 
} 
... 

接下来,我们可以运行 javadoc 任务并生成文档并放入 build/docs/javadoc 目录:

$ gradle javadoc
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
:compileJava
:processResources
:classes
:javadoc
BUILD SUCCESSFUL
Total time: 1.503 secs

我们可以在 javadoc 任务上设置更多属性。例如,我们可以使用 title 属性为生成的文档设置标题。默认值为项目名称,后跟项目版本号(如果有)。

要更改目标目录,我们可以将 javadoc 任务的destinationDir 属性设置为我们想要的目录。

我们还可以使用 options 属性来定义很多我们从 Java SDK javadoc 工具中知道的属性。以下示例向我们展示了如何为项目中的 javadoc 任务设置一些选项:

apply plugin: 'java' 
 
javadoc { 
    source sourceSets.api.allJava 
 
    title = 'Gradle Sample Project' 
 
    options.links = ['http://docs.oracle.com/javase/6/docs/api/'] 
    options.footer = "Generated on ${new Date().format('dd MMM yyyy')}" 
    options.header = "Documention for version ${project.version}" 
} 
... 

Assembling archives

如果我们想将新 API 源集的输出打包到 JAR 文件中,我们必须自己定义一个新任务。 Gradle 并没有提供一些魔法来自动为我们做这件事。幸运的是,任务本身非常简单:

apply plugin: 'java' 
 
archivesBaseName = 'gradle-sample' 
version = '1.0' 
 
sourceSets { 
    api 
    main { 
        compileClasspath += files(api.output.classesDir) 
    } 
} 
 
classes.dependsOn apiClasses 
 
task apiJar(type: Jar) { 
    // Define appendix that will be 
    // appended to the archivesBaseName 
    // for the JAR. 
    appendix = 'api' 
 
    // Define the input for the JAR file. 
    from sourceSets.api.output 
} 

apiJar 任务是一个 Jar 任务。我们定义了 appendix 属性,用于生成 JAR 文件的最终文件名。我们使用 from() 方法来指向我们的API 源集的输出目录,因此所有生成的输出都包含在JAR 文件中。当我们运行 apiJar任务时, 中会生成一个新的 gradle-sample-api-1.0.jar JAR文件;build/libs目录,如下:

$ gradle apiJar
:compileApiJava
:processApiResources UP-TO-DATE
:apiClasses
:apiJar
BUILD SUCCESSFUL
Total time: 3.242 secs

JAR 文件的基本名称是项目名称,类似于 jar 任务的名称。如果我们查看内容,我们会看到我们编译的 ReadWelcomeMessage 类文件:

$ jar tvf build/libs/sample-api.jar
  0 Thu Oct 22 16:38:56 CEST 2015 META-INF/
25 Thu Oct 22 16:38:56 CEST 2015 META-INF/MANIFEST.MF
  0 Thu Oct 22 16:35:08 CEST 2015 gradle/
  0 Thu Oct 22 16:35:08 CEST 2015 gradle/sample/
182 Thu Oct 22 16:38:56 CEST 2015 gradle/sample/ReadWelcomeMessage.class

另请注意,我们没有在 apiJar 和 apiClasses 任务之间定义任务依赖关系;但是当我们运行 apiJar 任务时,Gradle 会自动运行 apiClasses 任务。发生这种情况是因为我们使用了 sourceSets.api.output 属性来定义需要包含在 JAR 文件中的文件; Gradle 注意到了这一点,并确定了负责在 sourceSets.api.output 目录中创建内容的任务。  apiClasses任务是编译Java源文件并将资源处理到构建目录的任务,因此Gradle会首先调用  apiClasses 任务在 apiJar 任务之前。

Summary


在本章中,我们讨论了 Gradle 对 Java 项目的支持。通过应用 Java 插件所需的简单行,我们获得了大量功能,我们可以将其用于我们的 Java 代码。我们可以编译源文件,将编译后的代码打包成 JAR 文件,并生成文档。

在下一章中,我们将看到如何将依赖项添加到外部库。我们还将讨论如何配置存储库并使用配置组织我们的依赖项。