Gradle Source Sets – Gradle源代码集

最后修改: 2020年 8月 29日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Source sets give us a powerful way to structure source code in our Gradle projects.

源代码集为我们提供了一种强大的方式来组织我们Gradle项目中的源代码。

In this quick tutorial, we’re going to see how to use them.

在这个快速教程中,我们将看到如何使用它们。

2. Default Source Sets

2.默认的源集

Before jumping into the defaults, let’s first explain what source sets are. As the name implies, source sets represent a logical grouping of source files.

在进入默认值之前,让我们首先解释一下什么是源码组。顾名思义,源码集代表了源码文件的逻辑分组

We’ll cover the configuration of Java projects, but the concepts are also applicable to other Gradle project types.

我们将介绍Java项目的配置,但这些概念也适用于其他Gradle项目类型。

2.1. Default Project Layout

2.1.默认的项目布局

Let’s start with a simple project structure:

让我们从一个简单的项目结构开始。

source-sets 
  ├── src 
  │    ├── main 
  │    │    └── java 
  │    │        ├── SourceSetsMain.java
  │    │        └── SourceSetsObject.java
  │    └── test 
  │         └── java 
  │             └── SourceSetsTest.java
  └── build.gradle 

Now let’s take a look at the 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')
}

The Java plugin assumes src/main/java and src/test/java as default source directories

Java插件假定src/main/javasrc/test/java为默认的源代码目录

Let’s craft a simple utility task:

让我们来制作一个简单的实用任务。

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 ""
        }
    }
}

We’re printing just a few source set properties here. We can always check the full JavaDoc for more information.

我们在这里只打印了几个源集属性。我们可以随时查看完整的JavaDoc以了解更多信息。

Let’s run it and see what we get:

让我们运行它,看看我们得到什么。

$ ./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]

Notice we have two default source sets: main and test.

注意我们有两个默认的源代码集。maintest

2.2. Default Configurations

2.2.默认配置

The Java plugin also automatically creates some default Gradle configurations for us.

Java插件还自动创建了一些默认的Gradle 配置。#000000″>配置给我们。

They follow a special naming convention: <sourceSetName><configurationName>.

它们遵循一个特殊的命名惯例。<sourceSetName><configurationName>/em>.

We use them to declare the dependencies in build.gradle:

我们用它们来声明build.gradle中的依赖关系。

dependencies { 
    implementation('org.apache.httpcomponents:httpclient:4.5.12') 
    testImplementation('junit:junit:4.12') 
}

Notice that we specify implementation instead of mainImplementation. This is an exception to the naming convention.

注意,我们指定了implementation而不是mainImplementation。这是对命名惯例的一个例外。

By default, testImplementation configuration extends implementation and inherits all its dependencies and outputs.

默认情况下,testImplementationconfiguration扩展了implementation并继承了它的所有依赖和输出

Let’s improve our helper task and see what this is about:

让我们改进我们的辅助任务,看看这是什么原因。

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 ""
        }
    }
}

Let’s take a look at the output:

让我们看一下输出。

[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

The test source set contains the outputs of main in its compile classpath and also includes its dependencies.

test源集在其编译classpath中包含main的输出,也包括其依赖性。

Next, let’s create our unit test:

接下来,让我们创建我们的单元测试。

public class SourceSetsTest {

    @Test
    public void whenRun_ThenSuccess() {
        
        SourceSetsObject underTest = new SourceSetsObject("lorem","ipsum");
        
        assertThat(underTest.getUser(), is("lorem"));
        assertThat(underTest.getPassword(), is("ipsum"));
    }
}

Here we test a simple POJO that stores two values. We can use it directly because the main outputs are in our test classpath.

这里我们测试一个简单的POJO,它可以存储两个值。我们可以直接使用它,因为 main输出在我们的test classpath

Next, let’s run this from Gradle:

接下来,让我们从Gradle运行这个。

./gradlew clean test

> Task :source-sets:test

com.baeldung.test.SourceSetsTest > whenRunThenSuccess PASSED

3. Custom Source Sets

3.自定义源集

So far, we’ve seen some sensible defaults. However, in practice, we often need custom source sets, especially for integration tests.

到目前为止,我们已经看到了一些合理的默认值。然而,在实践中,我们经常需要自定义源集,特别是对于集成测试。

That’s because we might want to have specific test libraries only on the integration tests classpath. We also might want to execute them independently of unit tests.

这是因为我们可能想让特定的测试库只在集成测试的classpath上。我们也可能想独立于单元测试来执行它们。

3.1. Defining Custom Source Sets

3.1.定义自定义源集

Let’s craft a separate source directory for our integration tests:

让我们为我们的集成测试制作一个单独的源代码目录。

source-sets 
  ├── src 
  │    └── main 
  │         ├── java 
  │         │    ├── SourceSetsMain.java
  │         │    └── SourceSetsObject.java
  │         ├── test 
  │         │    └── SourceSetsTest.java
  │         └── itest 
  │              └── SourceSetsITest.java
  └── build.gradle 

Next, let’s configure it in our build.gradle using the sourceSets construct:

接下来,让我们在我们的build.gradle中使用sourceSets结构进行配置

sourceSets {
    itest {
        java {
        }
    }
}
dependencies {
    implementation('org.apache.httpcomponents:httpclient:4.5.12')
    testImplementation('junit:junit:4.12')
}
// other declarations omitted

Notice we did not specify any custom directory. That’s because our folder matches the name of the new source set (itest).

注意我们没有指定任何自定义目录。这是因为我们的文件夹与新源集的名称相匹配(itest)。

We can customize what directories are included with the srcDirs property:

我们可以通过srcDirs属性自定义包括哪些目录

sourceSets{
    itest {
        java {
            srcDirs("src/itest")
        }
    }
}

Remember our helper task from the beginning? Let’s rerun it and see what it prints:

还记得我们开始时的辅助任务吗?让我们重新运行它,看看它能打印出什么。

$ ./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. Assigning Source Set Specific Dependencies

3.2.分配源集的具体依赖关系

Remember default configurations? We now get some configurations for the itest source set as well.

还记得默认配置吗?我们现在也得到了一些用于itest源集的配置。

Let’s use itestImplementation to assign a new dependency:

让我们使用itestImplementation来分配一个新的依赖关系

dependencies {
    implementation('org.apache.httpcomponents:httpclient:4.5.12')
    testImplementation('junit:junit:4.12')
    itestImplementation('com.google.guava:guava:29.0-jre')
}

This one only applies to integration tests.

这一条只适用于集成测试。

Let’s modify our previous test and add it as an integration test:

让我们修改之前的测试,把它作为一个集成测试加入。

public class SourceSetsItest {

    @Test
    public void givenImmutableList_whenRun_ThenSuccess() {

        SourceSetsObject underTest = new SourceSetsObject("lorem", "ipsum");
        List someStrings = ImmutableList.of("Baeldung", "is", "cool");

        assertThat(underTest.getUser(), is("lorem"));
        assertThat(underTest.getPassword(), is("ipsum"));
        assertThat(someStrings.size(), is(3));
    }
}

To be able to run it, we need to define a custom test task that uses the compiled outputs:

为了能够运行它,我们需要定义一个使用编译输出的自定义测试任务

// source sets declarations

// dependencies declarations 

task itest(type: Test) {
    description = "Run integration tests"
    group = "verification"
    testClassesDirs = sourceSets.itest.output.classesDirs
    classpath = sourceSets.itest.runtimeClasspath
}

These declarations are evaluated during the configuration phase. As a result, their order is important.

这些声明是在配置阶段评估的。因此,它们的顺序很重要

For example, we cannot reference the itest source set in the task body before this is declared.

例如,在声明之前,我们不能引用任务主体中的itest源集。

Let’s see what happens if we run the test:

让我们看看如果我们运行这个测试会发生什么。

$ ./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.

Unlike the previous run, we get a compilation error this time. So what happened?

与之前的运行不同,这次我们得到一个编译错误。那么发生了什么?

This new source set creates an independent configuration.

这个新的源集创建了一个独立的配置。

In other words, itestImplementation does not inherit the JUnit dependency, nor does it get the outputs of main.

换句话说,itestImplementation没有继承JUnit的依赖性,也没有得到main的输出。

Let’s fix this in our Gradle configuration:

让我们在Gradle的配置中解决这个问题。

sourceSets{
    itest {
        compileClasspath += sourceSets.main.output
        runtimeClasspath += sourceSets.main.output
        java {
        }
    }
}

// dependencies declaration
configurations {
    itestImplementation.extendsFrom(testImplementation)
    itestRuntimeOnly.extendsFrom(testRuntimeOnly)
}

Now let’s rerun our integration test:

现在让我们重新运行我们的集成测试。

$ ./gradlew clean itest

> Task :source-sets:itest

com.baeldung.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED

The test passes.

测试通过。

3.3. Eclipse IDE Handling

3.3.Eclipse IDE处理

We’ve seen so far how to work with source sets directly with Gradle. However, most of the time, we’ll be using an IDE (such as Eclipse).

到目前为止,我们已经看到了如何直接用Gradle来处理源码集。然而,大多数时候,我们会使用一个IDE(如Eclipse)。

When we import the project, we get some compilation issues:

当我们导入该项目时,我们得到一些编译问题。

However, if we run the integrations test from Gradle, we get no errors:

然而,如果我们从Gradle中运行集成测试,我们没有得到任何错误。

$ ./gradlew clean itest

> Task :source-sets:itest

com.baeldung.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED

So what happened? In this case, the guava dependency belongs to itestImplementation.

那么发生了什么?在这种情况下,guava依赖属于itestImplementation

Unfortunately, the Eclipse Buildship Gradle plugin does not handle these custom configurations very well.

不幸的是,Eclipse Buildship Gradle插件并不能很好地处理这些自定义的配置

Let’s fix this in our build.gradle:

让我们在我们的build.gradle中修复这个问题。

apply plugin: "eclipse"

// previous declarations

eclipse {
    classpath {
        plusConfigurations+=[configurations.itestCompileClasspath] 
    } 
}

Let’s explain what we did here. We appended our configuration to the Eclipse classpath.

让我们解释一下我们在这里做了什么。我们把我们的配置追加到Eclipse的classpath中。

If we refresh the project, the compilation issues are gone.

如果我们刷新项目,编译的问题就会消失。

However, there’s a drawback to this approach: The IDE does not distinguish between configurations.

然而,这种方法有一个缺点:IDE并不区分配置。

This means we can easily import guava in our test sources (which we specifically wanted to avoid).

这意味着我们可以在我们的测试源中轻松导入guava(我们特别想避免的)。

4. Conclusion

4.总结

In this tutorial, we covered the basics of Gradle source sets.

在本教程中,我们介绍了Gradle源码集的基础知识。

Then we explained how custom source sets work and how to use them in Eclipse.

然后我们解释了自定义源码集的工作原理以及如何在Eclipse中使用它们。

As usual, we can find the complete source code over on GitHub.

像往常一样,我们可以在GitHub上找到完整的源代码超过