Gradle-Quellensätze

1. Übersicht

Quellensets bieten uns eine leistungsstarke Möglichkeit, den Quellcode in unseren Gradle-Projekten zu strukturieren.

In diesem kurzen Tutorial werden wir sehen, wie man sie benutzt.

2. Standardquellensätze

Bevor wir in die Standardeinstellungen springen, erklären wir zunächst, was Quellensätze sind. Wie der Name schon sagt, stellen Quellensätze eine logische Gruppierung von Quelldateien dar .

Wir werden die Konfiguration von Java-Projekten behandeln, aber die Konzepte sind auch auf andere Gradle-Projekttypen anwendbar.

2.1. Standardprojektlayout

Beginnen wir mit einer einfachen Projektstruktur:

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

Schauen wir uns jetzt die build.gradle an :

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') }

Das Java-Plugin setzt src / main / java und src / test / java als Standardquellverzeichnisse voraus .

Lassen Sie uns eine einfache Dienstprogrammaufgabe erstellen:

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

Wir drucken hier nur einige Eigenschaften des Quellensatzes. Wir können immer das vollständige JavaDoc für weitere Informationen überprüfen.

Lassen Sie es uns laufen und sehen, was wir bekommen:

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

Beachten Sie, dass wir zwei Standardquellensätze haben: main und test .

2.2. Standardkonfigurationen

Das Java-Plugin erstellt auch automatisch einige Standard-Gradle-Konfigurationen für uns .

Sie folgen einer speziellen Namenskonvention: .

Wir verwenden sie, um die Abhängigkeiten in build.gradle zu deklarieren :

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

Beachten Sie, dass wir die Implementierung anstelle von mainImplementation angeben . Dies ist eine Ausnahme von der Namenskonvention.

Standardmäßig erweitert die testImplementation- Konfiguration die Implementierung und erbt alle Abhängigkeiten und Ausgaben .

Lassen Sie uns unsere Hilfsaufgabe verbessern und sehen, worum es geht:

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

Werfen wir einen Blick auf die Ausgabe:

[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

Der Testquellensatz enthält die Ausgaben von main in seinem Kompilierungsklassenpfad und auch seine Abhängigkeiten.

Als nächstes erstellen wir unseren 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")); } }

Hier testen wir ein einfaches POJO, das zwei Werte speichert. Wir können es direkt verwenden , weil die Hauptausgänge in unseren sind Test Classpath .

Als nächstes führen wir dies von Gradle aus:

./gradlew clean test > Task :source-sets:test com.baeldung.test.SourceSetsTest > whenRunThenSuccess PASSED 

3. Benutzerdefinierte Quellensätze

Bisher haben wir einige sinnvolle Standardeinstellungen gesehen. In der Praxis benötigen wir jedoch häufig benutzerdefinierte Quellensätze, insbesondere für Integrationstests.

Dies liegt daran, dass wir bestimmte Testbibliotheken möglicherweise nur im Klassenpfad für Integrationstests haben möchten. Möglicherweise möchten wir sie auch unabhängig von Komponententests ausführen.

3.1. Benutzerdefinierte Quellensätze definieren

Erstellen wir ein separates Quellverzeichnis für unsere Integrationstests:

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

Als nächstes konfigurieren wir es in unserem build.gradle mithilfe des sourceSets- Konstrukts :

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

Beachten Sie, dass wir kein benutzerdefiniertes Verzeichnis angegeben haben. Das liegt daran, dass unser Ordner mit dem Namen des neuen Quellensatzes ( itest ) übereinstimmt .

Wir können anpassen, welche Verzeichnisse in der Eigenschaft srcDirs enthalten sind :

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

Erinnern Sie sich von Anfang an an unsere Hilfsaufgabe? Lassen Sie es uns noch einmal ausführen und sehen, was es druckt:

$ ./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. Zuweisen von quellensatzspezifischen Abhängigkeiten

Erinnern Sie sich an Standardkonfigurationen? Wir erhalten jetzt auch einige Konfigurationen für den itest- Quellensatz.

Verwenden wir itestImplementation , um eine neue Abhängigkeit zuzuweisen :

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

Dieser gilt nur für Integrationstests.

Ändern wir unseren vorherigen Test und fügen ihn als Integrationstest hinzu:

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)); } }

Um es ausführen zu können , müssen wir eine benutzerdefinierte Testaufgabe definieren, die die kompilierten Ausgaben verwendet :

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

Diese Deklarationen werden während der Konfigurationsphase ausgewertet . Daher ist ihre Reihenfolge wichtig .

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

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.

Let's fix this in our Gradle configuration:

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

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).

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

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

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

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

Let's fix this in our 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.

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.

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

4. Conclusion

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

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

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