Set di sorgenti Gradle

1. Panoramica

I set di sorgenti ci danno un modo potente per strutturare il codice sorgente nei nostri progetti Gradle.

In questo breve tutorial vedremo come usarli.

2. Set di sorgenti predefinite

Prima di passare alle impostazioni predefinite, spieghiamo innanzitutto quali sono i set di sorgenti. Come suggerisce il nome, i set di origine rappresentano un raggruppamento logico di file di origine .

Tratteremo la configurazione dei progetti Java, ma i concetti sono applicabili anche ad altri tipi di progetto Gradle.

2.1. Layout di progetto predefinito

Cominciamo con una semplice struttura del progetto:

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

Ora diamo un'occhiata a 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') }

Il plug-in Java assume src / main / java e src / test / java come directory di origine predefinite .

Creiamo una semplice attività di utilità:

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

Stiamo stampando solo alcune proprietà del set di origine qui. Possiamo sempre controllare il JavaDoc completo per ulteriori informazioni.

Eseguiamolo e vediamo cosa otteniamo:

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

Notare che abbiamo due set di sorgenti predefiniti: main e test .

2.2. Configurazioni predefinite

Il plugin Java crea anche automaticamente alcune configurazioni Gradle predefinite per noi .

Seguono una speciale convenzione di denominazione: .

Li usiamo per dichiarare le dipendenze in build.gradle :

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

Si noti che specifichiamo l' implementazione invece di mainImplementation . Questa è un'eccezione alla convenzione di denominazione.

Per impostazione predefinita, la configurazione testImplementation estende l' implementazione ed eredita tutte le sue dipendenze e output .

Miglioriamo il nostro compito di aiuto e vediamo di cosa si tratta:

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

Diamo uno sguardo all'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

Il set di sorgenti di test contiene gli output di main nel suo classpath di compilazione e include anche le sue dipendenze.

Successivamente, creiamo il nostro 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")); } }

Qui testiamo un semplice POJO che memorizza due valori. Possiamo usarlo direttamente perché gli output principali sono nel nostro classpath di test .

Quindi, eseguiamolo da Gradle:

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

3. Set di sorgenti personalizzate

Finora, abbiamo visto alcune impostazioni predefinite ragionevoli. Tuttavia, in pratica, spesso abbiamo bisogno di set di sorgenti personalizzati, soprattutto per i test di integrazione.

Questo perché potremmo voler avere specifiche librerie di test solo sul classpath dei test di integrazione. Potremmo anche volerli eseguire indipendentemente dai test unitari.

3.1. Definizione di set di sorgenti personalizzati

Creiamo una directory di origine separata per i nostri test di integrazione:

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

Successivamente, configuriamolo nel nostro build.gradle usando il costrutto sourceSets :

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

Notare che non abbiamo specificato alcuna directory personalizzata. Questo perché la nostra cartella corrisponde al nome del nuovo set di sorgenti ( itest ).

Possiamo personalizzare quali directory sono incluse con la proprietà srcDirs :

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

Ricordi il nostro compito di aiuto dall'inizio? Rieseguiamolo e vediamo cosa stampa:

$ ./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. Assegnazione di dipendenze specifiche del set di origini

Ricorda le configurazioni predefinite? Ora otteniamo anche alcune configurazioni per il set di sorgenti itest .

Usiamo itestImplementation per assegnare una nuova dipendenza :

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

Questo si applica solo ai test di integrazione.

Modifichiamo il nostro test precedente e aggiungiamolo come test di integrazione:

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

Per poterlo eseguire , è necessario definire un'attività di test personalizzata che utilizzi gli output compilati :

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

Queste dichiarazioni vengono valutate durante la fase di configurazione . Di conseguenza, il loro ordine è importante .

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.