Gradle源集
1. 概述
源集为我们提供了一种在Gradle 项目中构建源代码的强大方法。 在本快速教程中,我们将了解如何使用它们。
2. 默认源集
在进入默认设置之前,让我们先解释一下什么是源集。顾名思义,源集代表源文件的逻辑分组。
我们将介绍 Java 项目的配置,但这些概念也适用于其他 Gradle 项目类型。
2.1. 默认项目布局
让我们从一个简单的项目结构开始:
source-sets
├── src
│ ├── main
│ │ └── java
│ │ ├── SourceSetsMain.java
│ │ └── SourceSetsObject.java
│ └── test
│ └── java
│ └── SourceSetsTest.java
└── build.gradle
现在让我们看一下build.gradle:
apply plugin : "java"
description = "Source Sets example"
test {
testLogging {
events "passed", "skipped", "failed"
}
}
dependencies {
implementation('org.apache.httpcomponents:httpclient:4.5.12')
testImplementation('junit:junit:4.12')
}
Java 插件假定src/main/java和src/test/java作为默认源目录。
让我们制作一个简单的实用程序任务:
task printSourceSetInformation(){
doLast{
sourceSets.each { srcSet ->
println "["+srcSet.name+"]"
print "-->Source directories: "+srcSet.allJava.srcDirs+"\n"
print "-->Output directories: "+srcSet.output.classesDirs.files+"\n"
println ""
}
}
}
我们在这里只打印一些源集属性。我们可以随时查看完整的JavaDoc 以获取更多信息。 让我们运行它,看看我们得到了什么:
$ ./gradlew printSourceSetInformation
> Task :source-sets:printSourceSetInformation
[main]
-->Source directories: [.../source-sets/src/main/java]
-->Output directories: [.../source-sets/build/classes/java/main]
[test]
-->Source directories: [.../source-sets/src/test/java]
-->Output directories: [.../source-sets/build/classes/java/test]
请注意**,我们有两个默认源集:main和test。**
2.2. 默认配置
Java 插件还会自动为我们创建一些默认的 Gradle配置 。
它们遵循特殊的命名约定:<sourceSetName><configurationName>。
我们使用它们在build.gradle中声明依赖项:
dependencies {
implementation('org.apache.httpcomponents:httpclient:4.5.12')
testImplementation('junit:junit:4.12')
}
请注意,我们指定implementation而不是mainImplementation。这是命名约定的一个例外。
默认情况下,testImplementation配置扩展implementation并继承其所有依赖项和输出。
让我们改进我们的辅助任务,看看这是关于什么的:
task printSourceSetInformation(){
doLast{
sourceSets.each { srcSet ->
println "["+srcSet.name+"]"
print "-->Source directories: "+srcSet.allJava.srcDirs+"\n"
print "-->Output directories: "+srcSet.output.classesDirs.files+"\n"
print "-->Compile classpath:\n"
srcSet.compileClasspath.files.each {
print " "+it.path+"\n"
}
println ""
}
}
}
让我们看一下输出:
[main]
// same output as before
-->Compile classpath:
.../httpclient-4.5.12.jar
.../httpcore-4.4.13.jar
.../commons-logging-1.2.jar
.../commons-codec-1.11.jar
[test]
// same output as before
-->Compile classpath:
.../source-sets/build/classes/java/main
.../source-sets/build/resources/main
.../httpclient-4.5.12.jar
.../junit-4.12.jar
.../httpcore-4.4.13.jar
.../commons-logging-1.2.jar
.../commons-codec-1.11.jar
.../hamcrest-core-1.3.jar
test源集在其编译类路径中包含main的输出,还包括其依赖项。
接下来,让我们创建我们的单元测试:
public class SourceSetsTest {
@Test
public void whenRun_ThenSuccess() {
SourceSetsObject underTest = new SourceSetsObject("lorem","ipsum");
assertThat(underTest.getUser(), is("lorem"));
assertThat(underTest.getPassword(), is("ipsum"));
}
}
这里我们测试一个存储两个值的简单 POJO。我们可以直接使用它,因为main 输出在我们的test类路径中。
接下来,让我们从 Gradle 运行它:
./gradlew clean test
> Task :source-sets:test
com.blogdemo.test.SourceSetsTest > whenRunThenSuccess PASSED
3. 自定义源集
到目前为止,我们已经看到了一些合理的默认设置。然而,在实践中,我们经常需要自定义源集,尤其是对于集成测试。
那是因为我们可能只想在集成测试类路径上拥有特定的测试库。我们也可能希望独立于单元测试来执行它们。
3.1. 定义自定义源集
让我们为集成测试制作一个单独的源目录:
source-sets
├── src
│ └── main
│ ├── java
│ │ ├── SourceSetsMain.java
│ │ └── SourceSetsObject.java
│ ├── test
│ │ └── SourceSetsTest.java
│ └── itest
│ └── SourceSetsITest.java
└── build.gradle
接下来,让我们使用sourceSets构造在build.gradle中配置它:
sourceSets {
itest {
java {
}
}
}
dependencies {
implementation('org.apache.httpcomponents:httpclient:4.5.12')
testImplementation('junit:junit:4.12')
}
// other declarations omitted
请注意,我们没有指定任何自定义目录。那是因为我们的文件夹与新源集 ( itest ) 的名称相匹配。
我们可以自定义srcDirs属性包含哪些目录:
sourceSets{
itest {
java {
srcDirs("src/itest")
}
}
}
还记得我们从一开始的助手任务吗?让我们重新运行它,看看它打印了什么:
$ ./gradlew printSourceSetInformation
> Task :source-sets:printSourceSetInformation
[itest]
-->Source directories: [.../source-sets/src/itest/java]
-->Output directories: [.../source-sets/build/classes/java/itest]
-->Compile classpath:
.../source-sets/build/classes/java/main
.../source-sets/build/resources/main
[main]
// same output as before
[test]
// same output as before
3.2. 分配源集特定依赖项
还记得默认配置吗?我们现在也获得了itest源集的一些配置。
让我们使用itestImplementation来分配一个新的依赖:
dependencies {
implementation('org.apache.httpcomponents:httpclient:4.5.12')
testImplementation('junit:junit:4.12')
itestImplementation('com.google.guava:guava:29.0-jre')
}
这仅适用于集成测试。
让我们修改我们之前的测试并将其添加为集成测试:
public class SourceSetsItest {
@Test
public void givenImmutableList_whenRun_ThenSuccess() {
SourceSetsObject underTest = new SourceSetsObject("lorem", "ipsum");
List someStrings = ImmutableList.of("Blogdemo", "is", "cool");
assertThat(underTest.getUser(), is("lorem"));
assertThat(underTest.getPassword(), is("ipsum"));
assertThat(someStrings.size(), is(3));
}
}
为了能够运行它,我们需要定义一个使用编译输出的自定义测试任务:
// source sets declarations
// dependencies declarations
task itest(type: Test) {
description = "Run integration tests"
group = "verification"
testClassesDirs = sourceSets.itest.output.classesDirs
classpath = sourceSets.itest.runtimeClasspath
}
**这些声明在配置阶段 **进行评估。因此,它们的顺序很重要。
例如,在声明 this 之前,我们不能引用任务主体中设置的itest源。
让我们看看如果我们运行测试会发生什么:
$ ./gradlew clean itest
// some compilation issues
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':source-sets:compileItestJava'.
> Compilation failed; see the compiler error output for details.
与之前的运行不同,这次我们得到了编译错误。所以发生了什么事?
这个新的源集创建了一个独立的配置。
换句话说,itestImplementation不继承JUnit依赖,也不获取main的输出。**
让我们在 Gradle 配置中解决这个问题:
sourceSets{
itest {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
java {
}
}
}
// dependencies declaration
configurations {
itestImplementation.extendsFrom(testImplementation)
itestRuntimeOnly.extendsFrom(testRuntimeOnly)
}
现在让我们重新运行我们的集成测试:
$ ./gradlew clean itest
> Task :source-sets:itest
com.blogdemo.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED
测试通过。
3.3. Eclipse IDE 处理
到目前为止,我们已经看到了如何直接使用 Gradle 处理源集。然而,大多数时候,我们将使用 IDE(例如 Eclipse)。
当我们导入项目时,我们会遇到一些编译问题:
但是,如果我们从 Gradle 运行集成测试,我们不会收到任何错误:
$ ./gradlew clean itest
> Task :source-sets:itest
com.blogdemo.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED
所以发生了什么事?在这种情况下,guava依赖属于 itestImplementation。
不幸的是,Eclipse Buildship Gradle 插件不能很好地处理这些自定义配置。
让我们在build.gradle中解决这个问题:
apply plugin: "eclipse"
// previous declarations
eclipse {
classpath {
plusConfigurations+=[configurations.itestCompileClasspath]
}
}
让我们解释一下我们在这里做了什么。我们将配置附加到 Eclipse 类路径中。
如果我们刷新项目,编译问题就消失了。
但是,这种方法有一个缺点:IDE 不区分配置。
这意味着我们可以轻松地在我们的test源中导入guava(我们特别想避免这种情况)。