«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]

Обратите внимание, что у нас есть два исходных набора по умолчанию: основной и тестовый.

2.2. Конфигурации по умолчанию

Плагин Java также автоматически создает для нас некоторые конфигурации Gradle по умолчанию.

Они следуют специальному соглашению об именах: \u003csourceSetName\u003e\u003cconfigurationName\u003e.

Мы используем их для объявления зависимостей в build.gradle:

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

Обратите внимание, что мы указываем реализацию вместо mainImplementation. Это исключение из соглашения об именах.

По умолчанию конфигурация testImplementation расширяет реализацию и наследует все ее зависимости и выходные данные.

Давайте улучшим нашу вспомогательную задачу и посмотрим, о чем она:

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

Далее давайте создадим наш модульный тест:

Здесь мы тестируем простой POJO, который хранит два значения. Мы можем использовать его напрямую, потому что основные результаты находятся в нашем тестовом пути к классам.

public class SourceSetsTest {

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

Теперь давайте запустим это из Gradle:

3. Пользовательские исходные наборы

./gradlew clean test

> Task :source-sets:test

com.baeldung.test.SourceSetsTest > whenRunThenSuccess PASSED

До сих пор мы видели несколько разумных значений по умолчанию. Однако на практике нам часто нужны пользовательские наборы исходников, особенно для интеграционных тестов.

Это потому, что мы можем захотеть иметь определенные тестовые библиотеки только в пути к классам интеграционных тестов. Мы также можем захотеть выполнить их независимо от модульных тестов.

3.1. Определение пользовательских исходных наборов

Давайте создадим отдельный исходный каталог для наших интеграционных тестов:

Затем давайте настроим его в нашем build.gradle, используя конструкцию sourceSets:

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

Обратите внимание, что мы не указали любой пользовательский каталог. Это потому, что наша папка совпадает с именем нового исходного набора (itest).

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

Мы можем указать, какие каталоги включаются в свойство srcDirs:

Помните нашу вспомогательную задачу с самого начала? Давайте перезапустим его и посмотрим, что он напечатает:

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

3.2. Назначение конкретных зависимостей исходного набора

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

Помните конфигурации по умолчанию? Теперь мы также получаем некоторые конфигурации для исходного набора 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("Baeldung", "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
}

Например, мы не можем ссылаться на исходный набор 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.baeldung.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED

3.3. Обработка Eclipse IDE

«До сих пор мы видели, как работать с наборами исходных текстов напрямую с Gradle. Однако в большинстве случаев мы будем использовать IDE (например, Eclipse).

Когда мы импортируем проект, мы получаем некоторые проблемы с компиляцией:

Однако, если мы запускаем тест интеграции из Gradle, мы не получаем ошибок:

Так что же произошло? В этом случае зависимость guava принадлежит itestImplementation.

$ ./gradlew clean itest

> Task :source-sets:itest

com.baeldung.itest.SourceSetsItest > givenImmutableList_whenRun_ThenSuccess PASSED

К сожалению, подключаемый модуль Eclipse Buildship Gradle не очень хорошо обрабатывает эти пользовательские конфигурации.

Давайте исправим это в нашем build.gradle:

Давайте объясним, что мы здесь сделали. Мы добавили нашу конфигурацию в путь к классам Eclipse.

apply plugin: "eclipse"

// previous declarations

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

Если мы обновим проект, проблемы с компиляцией исчезнут.

Однако у этого подхода есть недостаток: среда IDE не делает различий между конфигурациями.

Это означает, что мы можем легко импортировать гуаву в наши тестовые исходники (чего мы специально хотели избежать).

4. Заключение

В этом уроке мы рассмотрели основы исходных кодов Gradle.

Затем мы объяснили, как работают пользовательские наборы исходных текстов и как их использовать в Eclipse.

Как обычно, мы можем найти полный исходный код на GitHub.

«