To jest kolejny artykuł opisujący narzędzie Gradle. Jeśli nie udało Ci się wcześniej z nim pracować, to zachęcam Cię do przeczytania wstępu do Gradle. W tym artykule zakładam, że znasz podstawy, które omówiłem poprzednio.
Odrobina teorii
Gradle bazuje na plikach konfiguracyjnych. Każdy z tych plików konfiguracyjnych powiązany jest z obiektem, który jest tworzony w trakcie inicjalizacji procesu budowania. Na przykład plik build.gradle
powiązany jest z instancją klasy Project
a plik settings.gradle
z instancją klasy Settings
.
Konfiguracja używająca DSL zawarta w tych plikach odpowiednio konfiguruje instancje tych obiektów.
Cykl budowania projektu
Gradle jasno określa sposób w jaki budowany jest projekt. Cały proces podzielony jest na trzy fazy:
- inicjalizację,
- konfigurację,
- wykonanie.
W trakcie fazy inicjalizacji Gradle określa jakie projekty wchodzą w skład cyklu budowania. Gradle wspiera proste projekty (składające się wyłącznie z jednego projektu) jak i te złożone (składające się z wielu podprojektów). Jak wspomniałem wcześniej, dla każdego z nich tworzy obiekt, który przechowuje konfigurację danego projektu. W tej fazie wykonywany jest plik settings.gradle
.
Faza konfiguracji polega na wykonaniu każdego z plików konfiguracyjnych projektów wchodzących w skład procesu budowania. W wyniku tego wykonania obiekty utworzone w fazie inicjalizacji są odpowiednio konfigurowane (na podstawie wykonywanych plików build.gradle
).
Ostatnią fazą jest faza wykonania. To właśnie tutaj Gradle określa zestaw wymaganych zadań do wykonania wraz z ich kolejnością. Zadania pochodzą z obiektów skonfigurowanych w poprzednim kroku.
Wbudowana pomoc
Gradle posiada wbudowaną dokumentację. Możesz się do niej dobrać używając linii poleceń. Pierwszym przydatnym poleceniem może być:
$ gradle tasks
To polecenie wypisze wszystkie możliwe do wykonania zadania, które zawarte są w konfiguracji lub dostarczone są przez wtyczki.
Jeśli chcesz dowiedzieć się czegoś więcej o którymkolwiek z zadań z pomocą przychodzi gradle help
. Na przykład do dokumentacji zadania init
możesz dobrać się wywołując polecenie:
$ gradle help --task init
Schowek (ang. cache)
Z poprzedniego artykułu wiesz, że Gradle pomaga przy zarządzaniu zależnościami. Odpowiednia konfiguracja pozwala na określenie jakie zależności są niezbędne do działania Twojej biblioteki czy aplikacji. Dodatkowym atutem zarządzania zależnościami przez Gradle jest to, że trzymane one są w schowku na Twoim dysku. Domyślnie znajdziesz je w katalogu ~/.gradle/caches
1.
Wewnątrz tego katalogu znajdziesz zależności, które były pobrane przez Gradle. Dzięki takiemu podejściu zależności są współdzielone pomiędzy różnymi aplikacjami. Co więcej nie musisz ich za każdym razem ściągać – zanim Gradle zacznie ich szukać w repozytorium zajrzy do schowka na lokalnym dysku.
Nowy projekt z Gradle
Praktyczną przygodę z Gradle zacznę od utworzenia przykładowej konfiguracji. Do przygotowania projektu może posłużyć polecenie gradle init
. Po takim wywołaniu Gradle utworzy odpowiednią strukturę przygotowując podstawową konfigurację. Zanim do tego dojdzie zapyta Cię o kilka ustawień:
$ gradle init
Select type of project to generate:
1: basic
2: cpp-application
3: cpp-library
4: groovy-application
5: groovy-library
6: java-application
7: java-library
8: kotlin-application
9: kotlin-library
10: scala-library
Enter selection (default: basic) [1..10] 7
Select build script DSL:
1: groovy
2: kotlin
Enter selection (default: groovy) [1..2] 1
Select test framework:
1: junit
2: testng
3: spock
Enter selection (default: junit) [1..3] 1
Project name (default: samouczek):
Source package (default: samouczek): pl.samouczekprogramisty
BUILD SUCCESSFUL in 24s
2 actionable tasks: 2 executed
Pierwsze pytanie dotyczy typu generowanego projektu. W tym przypadku projekt będzie używał języka Java, więc biorę pod uwagę odpowiedzi 6 albo 7. Główna różnica pomiędzy biblioteką a aplikacją sprowadza się do tego, że aplikację można uruchomić samodzielnie, biblioteka włączana jest do innych bibliotek lub aplikacji. Konfiguracja biblioteki i aplikacji różni się jedynie wtyczkami. W przykładach, które pokazuję w dalszej części te różnice nie są istotne.
Kolejne pytanie dotyczy DSL’a (ang. Domain Specific Language), który powinien być używany w konfiguracji. Od wersji 5.0 Gradle jako DSL można także wybrać język Kotlin.
Następne pytanie dotyczy biblioteki użytej do testów. Ma to wpływ na wygenerowany przykładowy kod i konfigurację.
Ostatnie dwa pytania dotyczą nazwy projektu i nazwy pakietu (domyślnie jest to nazwa katalogu w którym wykonano polecenie gradle init
).
Po udzieleniu odpowiedzi na taki zestaw pytań Gradle przygotuje szkielet projektu wraz z przykładową konfiguracją:
$ tree .
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── pl
│ │ └── samouczekprogramisty
│ │ └── Library.java
│ └── resources
└── test
├── java
│ └── pl
│ └── samouczekprogramisty
│ └── LibraryTest.java
└── resources
13 directories, 8 files
Teraz omówię poszczególne elementy wygenerowane przez gradle init
.
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
settings.gradle
Plik settings.gradle
zawiera konfigurację projektu. Po pominięciu komentarza znajduje się w nim tylko nazwa projektu określona w trakcie działania gradle init
:
rootProject.name = 'samouczek'
Plik ten może jednak zawierać dużo więcej elementów. Wszystkie z nich znajdziesz w oficjalnej dokumentacji. Trochę więcej o tym pliku przeczytasz w dalszej części artykułu, kiedy będę opisywał budowanie zagnieżdżonych projektów.
build.gradle
Sercem projektu jest plik build.gradle
2. Wygenerowany plik, z pominięciem komentarzy, wygląda następująco:
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
api 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:27.0.1-jre'
testImplementation 'junit:junit:4.12'
}
Plik ten zawiera trzy bloki wewnątrz których znajduje się konfiguracja.
Pierwszy blok plugins
zawiera wtyczkę java-library
. W bloku repositories
dodawane jest repozytorium jcenter. Ostatni blok dependencies
zawiera zestaw przykładowych zależności.
W dużej części projektów to właśnie te trzy bloki będą stanowiły większość konfiguracji. W bardziej zaawansowanych przypadkach odsyłam Cię do dokumentacji.
wrapper
Standardowa struktura projektu pozwala na łatwe zorientowanie się w nowym projekcie informatycznym. Zarządzanie zależnościami pozwala na przygotowanie wszędzie takiej samej paczki programu3. Wszystko to dzięki plikowi wykonywalnemu gradle
. Ten plik jest tak naprawdę skryptem, który opakowuje uruchomienie maszyny wirtualnej Javy. Zachęcam Cię do zajrzenia do środka tego pliku. W przypadku systemów z rodziny Linux możesz spodziewać się skryptu bash
.
Ten skrypt różni się pomiędzy różnymi wersjami Gradle. Co więcej Gradle także ewoluuje, DSL używany w różnych wersjach może nie być ze sobą kompatybilny. Może to prowadzić do sytuacji, w której wersja Gradle zainstalowana na Twoim komputerze nie będzie w stanie zbudować projektu, który przygotowany był przy pomocy innej wersji Gradle.
Jest jednak proste rozwiązanie tego problemu. Tym rozwiązaniem jest Gradle wrapper. Jest on domyślnie tworzony po wywołaniu gradle init
. Można to także utworzyć samodzielnie w już istniejącym projekcie:
$ gradle wrapper --console=verbose
> Task :wrapper
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
Efektem działania tego zadania jest utworzenie dwóch katalogów i kilku plików4:
$ tree .
.
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
2 directories, 4 files
Warto zwrócić uwagę na zwartość pliku gradle/wrapper/gradle-wrapper.properties
:
$ cat gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Jedno z ustawień – distributionUrl
pokazuje wersję, która powinna być uruchomiona. Co jeśli ktoś inny na swoim komputerze nie będzie miał wersji, na którą wskazuje plik konfiguracyjny? gradlew
ściągnie odpowiednią wersję i zapisze ją w katalogu domowym użytkownika:
$ ls -lA ~/.gradle/
total 12
drwxr-xr-x 5 marcinek marcinek 4096 mar 22 18:34 5.2.1
drwxr-xr-x 5 marcinek marcinek 4096 mar 22 18:49 5.3
drwxr-xr-x 2 marcinek marcinek 4096 mar 22 18:49 buildOutputCleanup
Od tego momentu zamiast gradle
używaj gradlew
, który będzie dostępny w Twoim repozytorium.
Proszę zobacz czym różnią się między sobą gradlew
i gradle
. Dla czytelności zostawiłem najbardziej istotne fragmenty generowane przez program diff
:
$ diff -u ./gradlew /usr/local/bin/gradle
--- ./gradlew 2019-03-22 18:34:35.244396273 +0100
+++ /usr/local/bin/gradle 2019-02-08 20:01:44.000000000 +0100
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH=$APP_HOME/lib/gradle-launcher-5.2.1.jar
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.launcher.GradleMain "$APP_ARGS"
Linijki zaczynające się od -
są w gradlew
, te z +
na początku są w standardowym skrypcie gradle
. Jak widzisz różnic jest niewiele. Polegają one wyłącznie na tym, że uruchomienie gradlew
korzysta z gradlew-wrapper.jar
i używa innej klasy z metodą main org.gradle.wrapper.GradleWrapperMain
.
Używanie gradlew
pozwala na uniezależnienie się od wersji gradle
zainstalowanej na komputerze programisty.
Budowanie projektów
Używając gradlew tasks
i gradlew help
dowiesz się sporo o możliwych zadaniach do wykonania. Chciałbym zwrócić Twoją uwagę na dwa z nich: build
i test
.
Poniżej widzisz wywołanie zadania build
z przełącznikiem --console=verbose
, który sprawia, że na konsoli pokazuje się trochę więcej informacji:
$ ./gradlew build --console=verbose
> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
BUILD SUCCESSFUL in 0s
4 actionable tasks: 4 up-to-date
Wiesz już, że Gradle uruchamia wszystkie zależne zadania. Ten przykład doskonale to pokazuje. Poprosiłem o wywołanie build
a w efekcie została wykonana cała seria zadań, zaczynając od compileJava
a na build
kończąc. Niektóre z tych zadań generują tak zwane artefakty – efekty procesu budowania.
Na przykład artefaktem zadania compileJava
są pliki .class
ze skompilowanymi klasami. Artefakty procesu budowania umieszczane są w katalogu build
. Poniżej możesz zobaczyć część struktury tego katalogu:
$ tree build
build
├── classes
│ └── java
│ ├── main
│ │ └── pl
│ │ └── samouczekprogramisty
│ │ └── Library.class
│ └── test
│ └── pl
│ └── samouczekprogramisty
│ └── LibraryTest.class
...
├── libs
│ └── samouczek.jar
├── reports
│ └── tests
│ └── test
│ ├── classes
│ │ └── pl.samouczekprogramisty.LibraryTest.html
│ ├── css
│ │ ├── base-style.css
│ │ └── style.css
│ ├── index.html
│ ├── js
│ │ └── report.js
│ └── packages
│ └── pl.samouczekprogramisty.html
...
29 directories, 14 files
Drzewko powyżej pokazuje między innymi katalogi build/classes
, build/libs
i build/reports
. Pierwszy z nich zawiera skompilowane klasy. Drugi plik jar
(utworzony przez zadanie jar
). Zwróć też uwagę na ostatni katalog. Ten katalog powstaje po wykonaniu zadania test
. Zawiera on raporty z testów automatycznych uruchomionych w trakcie budowania projektu:
Wielką zaletą narzędzi typu Gradle jest to, że potrafią automatycznie uruchamiać takie testy w trakcie budowania.
Budowanie złożonych projektów
Gradle świetnie sprawdza się do budowania projektów, które zawierają podprojekty. Jako przykład może tu posłużyć Kurs Java czy Kurs Aplikacji Webowych.
W każdym z tych repozytoriów znajdziesz plik settings.gradle
. W przypadku pojedynczego projektu ten plik jest opcjonalny. W przypadku projektów zawierający podprojekty plik settings.gradle
jest wymagany. W tym drugim przypadku zawiera on ścieżki wskazujące na zagnieżdżone projekty. Fragment takiego pliku może wyglądać następująco:
rootProject.name = 'KursAplikacjeWebowe'
include '01_serwlety'
include '02_serwlety'
include '03_filtry'
include '04_kontekst'
Konfiguracja podprojektów
Każdy z projektów zagnieżdżonych może zawierać swój własny plik konfiguracyjny build.gradle
. Jednak nie zawsze jest to najlepszy pomysł. Często żeby wyeliminować duplikację wspólna konfiguracja wyciągnięta jest do głównego projektu. Służy do tego blok subprojects
. Jego przykład możesz znaleźć w pliku build.gradle
w Kursie Java:
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
repositories {
mavenCentral()
}
group = 'pl.samouczekprogramisty.kursjava'
version = '1.0-SNAPSHOT'
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
}
}
W tym przykładzie każdy z podprojektów będzie zawierał dwie wtyczki, będzie korzystał z repozytorium mavenCentral
. Będzie miał ustawiony atrybuty group
i version
.
Ostatni blok używa mechanizmu rozszerzeń Gradle. W ten sposób dołączam pewną akcję po wystąpieniu zdarzenia projectsEvaluated
. W tym przypadku dodaję do kompilatora javac
flagę -Xling:deprecation
, która włącza ostrzeżenia dotyczące używania przestarzałego API.
Niektóre podprojekty nie potrzebują dodatkowej konfiguracji. W takim przypadku nie mają własnego pliku build.gradle
. W innych przypadkach build.gradle
rozszerza konfigurację zawartą w bloku subprojects
.
Gradle a repozytorium kodu
Jeśli korzystasz z systemu kontroli wersji część plików związanych z Gradle powinna być w nim zawarta. Jeśli nie korzystasz, to najwyższy czas zacząć ;) – zapraszam Cię do kursu Git’a. Katalog gradle
, pliki gradlew
, gradlew.bat
wraz z wszystkimi plikami konfiguracyjnymi powinny zostać dodane do systemu kontroli wersji.
Natomiast ukryty katalog .gradle
powinien zostać pominięty. Sprawa wygląda podobnie z wszystkimi artefaktami powstałymi w wyniku budowania projektu. Z założenia mogą one być w prosty sposób odtworzone na podstawie plików źródłowych. Innymi słowy zawartość katalogu build
nie powinna wylądować w repozytorium kodu.
Dodatkowe materiały do nauki
W artykule wielokrotnie odwoływałem się do dokumentacji Gradle. Nie bez powodu. Moim zdaniem dokumentacja Gradle’a jest na prawdę przydatnym źródłem wiedzy:
- dokumentacja Gradle’a,
- dodatkowo na stronie Gradle’a znajdziesz serię dobrze przygotowanych poradników.
Ćwiczenie do wykonania
Na koniec mam dla Ciebie drobne ćwiczenie. Spróbuj utworzyć nowy projekt wywołując gradle init
z dodatkowymi parametrami opisanymi w dokumentacji. Czy dasz radę napisać polecenie, które utworzy nowy projekt i pominie wszystkie pytania, które Gradle zadaje po wywołaniu gradle init
?
Podsumowanie
Po przeczytaniu tego artykułu wiesz jak działa Gradle. Znasz elementy DSL pozwalające na budowanie nieskomplikowanych skryptów budowania. Udało Ci się poznać szereg komend Gradle’a, które pomogą Ci w efektywny sposób pracować z tym narzędziem.
Na koniec mam do Ciebie prośbę, proszę poleć ten artykuł znajomym, którym Twoim zdaniem może się on przydać. Dzięki temu pozwolisz dotrzeć mi do większego grona odbiorców, z góry dziękuję!
Jeśli nie chcesz ominąć kolejnych artykułów na Samouczku dopisz się do samouczkowego newslettera i polub Stronę samouczka na Facebook’u. To tyle na dzisiaj, trzymaj się i do następnego razu!
-
W systemie Windows jest to ścieżka
%userprofile%/gradle/caches
. ↩ -
Po odpowiedniej konfiguracji albo sposobie uruchomienia
gradle
nazwa tego pliku może być inna,build.gradle
jest wartością domyślną. ↩ -
Pomijam skrajne sytuacje, w których ktoś może zmodyfikować swoje lokalne środowisko w sposób, który pozwoli zbudować coś innego. Jednak taka sytuacja wymaga świadomego działa :). ↩
-
To wywołanie tworzy też ukryty katalog
.gradle
. ↩
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Zostaw komentarz