JAVA exPress > Archiwum > Numer 6 (2009-12-09) > Gradle - Mocarne narzędzie do budowy projektów

Gradle - Mocarne narzędzie do budowy projektów

Wstęp

"Jakiego narzędzia używasz do budowy projektu i dlaczego?" W świecie Javy, na tak zadane pytanie można spodziewać się jednej z dwóch odpowiedzi:

Używam Mavena. Świetne pluginy pozwalają mi w trzech liniach uzyskać efekt, na który potrzebowałbym kilkadziesiąt linii XMLa, gdybym używał Anta. Czasami przeszkadza mi brak elastyczności Mavena, ale radzę sobię wówczas używając plugina AntRun.

-- użytkownik Mavena

Używam Anta i dzięki jego elastyczności mogę zrobić wszystko - co tylko zechcę. Maven zdobył popularność dzięki zarządzaniu dependencjami, ale ja mam Apache Ivy, który potrafi o wiele więcej.

-- użytkownik Anta

Nie mam zamiaru powtarzać tu argumentów, którymi zwolennicy Anta i Mavena wymieniali się wielokrotnie w niezliczonych dyskusjach na czatach, forach i blogach. Po pierwsze dlatego, że nie pojawiły się żadne nowe argumenty na rzecz któregokolwiek z tych dwóch rozwiazań. Po drugie dlatego (i to jest główny powód), że na scenie narzędzi do budowy projektów pojawił się nowy zawodnik - Gradle. I jest to gracz tak silny, że ma wszelkie szanse by usunąć w cień zarówno Anta jak i Mavena, a dyskusję o wyższości jednego nad drugim przesunąć do historii komputerowych "flame wars".

Jakiego narzędzia używasz do budowy projektu i dlaczego?

Nim zajmiemy się Gradlem, przyjrzyjmy się najbardziej znanym (czyt. uciążliwym) słabościom Anta i Mavena:

  • ciężko jest zaimplementować jakikolwiek algorytm w skrypcie budującym; trudno wyrazić nawet tak podstawowe konstrukcję jak if czy for,

  • ciężko jest wykonać nawet pozornie proste taski, których wykonywanie nie zostało przewidziane przez developerów Anta/Mavena, i które nie są dostępne w postaci gotowych tasków lub pluginów,

  • potrzebna jest dogłębna znajomość tych narzędzi, by zrozumieć złożony skrypt budujący,

  • podejście "build by convention" nie jest wspierane (Ant), albo skutecznie utrudnia konfigurację (Maven),

  • wsparcie dla budowy projektów wielomodułowych jest niewystarczające,

  • skrypty budujące są niepotrzebnie duże i nieczytelne z uwagi na narzut samego języka (XML),

Aby móc w pełni skorzystać z lektury tego artykułu, zachęcam do instalacji Gradle. Instrukcja instalacji znajduje się na stronie projektu. Dla Twojej wygody do artykułu dołączone są kody źródłowe. Zarówno artykuł jak i kod źródłowy jest zgodny z wersją 0.8 Gradle (najnowszą w momencie pisania artykułu).

Celem tego artykułu jest przedstawienie projektu Gradle i sprawienie, byś zapragnął poznać go lepiej. Nie zamierzam konkurować z wyczerpującą dokumentacją ani z dołączonymi do dystrybucji Gradle przykładami.

Gradle - krótkie wprowadzenie

W artykule przedstawię Gradle prezentując kilka z życia wziętych przykładów jego zastosowania. Jednak nim to nastąpi, pozwolę sobie pokrótce scharakteryzować ten projekt, tak byś wiedział czego możesz oczekiwać.

Gradle jest bardzo elastycznym narzędziem do budowy projektów, w szczególności projektów Javy. Umożliwia pisanie skryptów budujących w opartym na Groovym DSLu, co sprawia, że wyrażenie w nich algorytmów staje się banalnie proste. Gradle pozwala na tworzenie zależności między zadaniami (taskami) i opiera się na paradygmacie "convention over configuration" (z tym że konfiguracja jest zawsze możliwa i łatwa do przeprowadzenia). Gradle wykracza poza możliwości innych narzędzi, jednocześnie stosuje się do przyjętych przez społeczność Javy niepisanych standardów, obniżając w ten sposób koszt związany ze zmianą narzędzia budującego.

Gradle jest nadal w trakcie developmentu, osiągnął już jednak stabilność, pozwalającą na jego produkcyjne użycie. Lista zaimplementowanych funkcjonalności jest już obecnie imponująca (na uwagę zasługuje m.in. zaawansowane wsparcie dla projektów wielomodułowych), a do realizacji czeka jeszcze wielu wartościowych pomysłów (m.in. możliwość pisania skryptów budujących w DSLach opartych o inne niż Groovy języki JVM - np. Scala, JRuby).

Pierwszy skrypt budujący

Obiecałem zaprezentować Gradle poprzez przykłady jego użycia, zajmijmy się więc napisaniem kawałka kodu. Zaczniemy od bardzo prostej aplikacji webowej. Następnie rozszerzę ten przykład, co pozwoli mi zaprezentować różne cechy Gradle - przede wszystkim dynamicznę naturę jego skryptów oraz wsparcie dla projektów wielomodułowych.

Najmniejsza na świecie aplikacja webowa ;)

Layout naszego projektu prezentuje się następująco:

    myWebApp
    `-- src
        `-- main
            |-- java
            |   `-- org
            |       `-- gradle
            |           `-- sample
            |               `-- Greeter.java
            `-- webapp
                `-- index.jsp

Jak widać projekt jest prosty aż do bólu. Ma tylko jedną klasę (Greeter.java), która wykorzystuje metody z biblioteki Commons Lang ze zbioru Apache Commons, oraz jedną stronę JSP - index.jsp - która używa tejże klasy.

build.gradle

Czas dodać skrypt budujący - nadamy mu domyślną nazwę build.gradle. Skrypt ten powinien wykonać następujące zadania:

  • załadować wymagane dependencje,

  • skompliować klasy Javy,

  • zbudować plik WAR.

Gradle jest bardzo elastycznym narzędziem
do budowy projektów, w szczególności projektów Javy.

A więc proszę - przed nami pierwszy skrypt budujący napisany z użyciem Gradle.

    usePlugin 'war'
    version = 0.1

    repositories {
        mavenCentral()
    }

    dependencies {
        compile "commons-lang:commons-lang:2.4"
    }

Informacje o procesie budowania

Używając Gradle możesz z łatwością dowiedzieć się wielu użytecznych informacji na temat procesu budowania projektu. Wykonaj komendę gradle --tasks aby poznać dostępne zadania (te napisane przez Ciebie i dostarczone przez zadeklarowane w skrypcie budującym pluginy). Wykonaj gradle --properties żeby poznać wszystkie propertiesy i gradle --dependencies by dowiedzieć się o wszystkich zależnościach projektu. Ale przede wszystkim wykonaj komendę gradle -? by poznać mnóstwo innych przydatnych komend.

Umieść ten plik w głównym folderze projektu myWebApp main i uruchom pisząc w linii komend gradle war. Wynikowy plik myWebApp-0.1.war znajdziesz w folderze build/libs. Zdeployuj go w dowolnym kontenerze webowym (np. Tomcatcie) i rozkoszuj się aplikacją działającą pod adresem http://YourHostName/myWebApp-0.1.

Podejrzewam, że w powyższym skrypcie Twoją uwagę przykuło kilka spraw:

  • skrypty Gradle mogą być bardzo zwięzłe,

  • nie ma XMLa, jest DSL,

  • Gradle potrafi "rozmawiać" z repozytoriami Maven.

Gradle stosuje rozpowszechnioną przez Mavena
strukturę projektu.

Powiedz to tak po prostu

Chciałbyś użyć w skrypcie plugina War ? Chciałbyś ustawić wersję na 1.0 ? Po prostu to powiedz:

    usePlugin 'war'
    version = 0.1
    

Chciałbyś wypisać komunikat jeżeli zajdzie pewien warunek ? Po prostu to powiedz:

    if (someCondition) {
      println "some message"
    }
    

Jeżeli zastanowisz się nad tymi dwoma przykładami, dojdziesz do wniosku, że nie ma w nich nic niezwykłego. "Tak to powinno wyglądać". Masz rację. A jednak, wyrażenie tych jakże prostych rzeczy jest bardzo trudne w Antcie i Mavenie. Gradle, używając mieszanki DSL i Groovyego, daje możliwość bezpośredniego wyrażania tego, co pragniesz powiedzieć.

Tak, to wszystko prawda, ale zapewniam że to dopiero początek. Póki co, przyjrzyjmy się dokładniej temu, co (i jak) robi ten skrypt.

DSL i "convention over configuration"

Prawdopodobnie zauważyłeś, że zaprezentowany plik build.gradle jest bardzo zwięzły. Jest to możliwe dzięki dwóm cechom Gradle:

  • Gradle używa DSL, który jest o wiele bardziej zwięzły niż XML (łatwiej też go czytać i zrozumieć),

  • Gradle działa zgodnie z paradygmatem "convention over configuration", co pozwala zminimalizować wielkość pliku budującego, pod warunkiem że używane są domyślne wartości (takie jak layout projektu, nazwy plików wynikowych itp.).

Przykładowo, Gradle stosuje rozpowszechnioną przez Mavena strukturę projektu, i domyślnie zakłada, że pliki źródłowe znajdują się w następujacych folderach:[1]

 

    `-- src
        |-- main
        |   |-- groovy    // kod źródłowy pisany w Groovym
        |   |-- java      // kod źródłowy pisany w Javie
        |   |-- resources // zasoby aplikacji
        |   `-- webapp    // kod źródłowy aplikacji webowej
        `-- test
            |-- groovy    // testy napisane w Groovym
            |-- java      // testy napisane w Javie
            `-- resources // zasoby testowe

Inna konwencja sprawia, że stworzony przez Gradle plik WAR domyślnie nosi nazwę tworzoną według wzorca nazwaProjektu-wersja.war (z kolei nazwa projektu jest identyczna z nazwą folderu, w którym projekt się znajduje, o ile nie wyspecyfikowano inaczej). Inną domyślną wartością jest użycie kompilatora Javy w wersji 1.5.

Konfiguracja jest możliwa i łatwa do wykonania

Jeżeli zajdzie taka potrzeba, wówczas wszystkie powyższe domyślne ustawienia można z łatwością zmodyfikować. Przykładowo, załóżmy że chcemy, by wszelkie artefakty (pliki JAR i WAR) trafiały do katalogu build/artifacts zamiast do domyślnego build/libs. Wystarczy ustawić wartość zmiennej libsDirName na "artifacts":

    libsDirName = "artifacts"

Aby zmienić nazwę generowanego pliku JAR czy WAR należy z kolei ustawić wartość zmiennej archivesBaseName:

    archivesBaseName = "i-dont-like-the-default-name"

Ponieważ prezentowana aplikacja myWebApp wykorzystuje domyślne ustawienia, nie ma więc potrzeby modyfikowania wartości tych zmiennych, skutkiem czego plik budujący jest bardzo krótki.

Gradle pluginami stoi.

W pluginach siła

Zgodnie z aktualną modą, Gradle pluginami stoi. :) Każdy plugin może rozszerzyć zasób funkcji oferowanych przez Gradle na trzy sposoby. Dla przykładu, użycie w skrypcie budującym pluginu Java niesie za sobą następujące konsekwencje:

  • dostępnych staje się prawie 20 nowych tasków (np. clean, compileJava, test, jar),

  • stworzone zostają zależności miedzy taskami, które wymuszają rozsądną kolejność ich wykonywania (np. task test task wykonywany jest po taskach compileJava i compileJavaTests tasks),

  • do konfiguracji projektu dołączany jest tzw. "convention object", co skutkuje ustawieniem wielu domyślnych wartości, np.:

        javadoc.destinationDir= file('build/docs/javadoc')
        archivesBaseName = projectName
            

W zaprezentowanym skrypcie użyty został plugin War, co ma następujące konsekwencje:

  • zaimportowany zostaje plugin Java (plugin War jest od niego zależny), a wraz z nim wszystkie jego zadania i ustawienia konfiguracyjne,

  • dodany zostaje task war, który robi to, czego należy oczekiwać po tasku war:

    • kopiuje pliki z src/main/webapp do głównego katalogu archiwum,

    • kopiuje skomplikowane klasy do WEB-INF/classes,

    • umieszcza wszelkie zależności runtime w katalogu WEB-INF/lib,

    • tworzy plik archiwum WAR w katalogu build/libs

Obecnie Gradle oferuje 9 pluginów (Java, Groovy, War, OSGi, Eclipse, Jetty, Maven, Project-reports, Code-quality), a jeżeli nie znajdziesz wśród nich potrzebnej Ci funkcjonalności, zawsze możesz napisać swój własny (jest to bardzo proste, acz wykracza poza ramy tego artykułu - zerknij proszę do dokumentacji Gradle).

Repozytoria i zarządzanie zależnościami

Mam dla Ciebie dobrą wiadomość - jak już przepiszesz swoje skrypty budujące na Gradle, będziesz mógł nadal używać tych samych repozytoriów artefaktów, których używasz obecnie. Gradle opiera się na Apache Ivy, i potrafi "dogadać się" z każdym możliwym do wyobrażenia typem repozytorium - od "standardowych" repozytoriów Mavena, poprzez staroświeckie foldery libs używane tradycyjnie w buildach Anta, aż po niemal dowolne repozytorium o zdefiniowanej przez programistę strukturze.

Jak widać na przykładzie pierwszego skryptu, osiągnięcie rzeczy prostych jest w Gradle nie tylko łatwe, ale i daje się elegancko wyrazić. Aby Gradle przeszukiwał centralne repozytorium Mavena w poszukiwaniu wymaganych artefaktów, wystarczy dodać takie oto linijki do skryptu budujacego:

    repositories {
        mavenCentral()
    }

Osiągnięcie rzeczy prostych
jest w Gradle nie tylko łatwe,
ale i daje się elegancko wyrazić.

Deklaracje dependencji są o wiele bardziej zwięzłe, niż te, którymi posługujesz się w Mavenie 2[2] lub Ivy. Zamieszczony poniżej kawałek kodu dodaje JAR biblioteki Commons Lang do classpatha komplikacji:

    dependencies {
        compile "commons-lang:commons-lang:2.4"
    }

Ant i Gradle

Gradle zapewnia świetną integrację z Antem. Możesz zaimportować Antowy plik build.xml bezpośrednio do skryptu budującego Gradle. Dzięki temu wszelkie targety z pliku Anta, staną się dostępne jako taski Gradle. Inny typ integracji zapewnia dostaczana przez Groovyego klasa AntBuilder. Obiekt ant jest dostępny w każdym skrypcie budującym Gradle, dzięki czemu możesz używać całego bogactwa tasków Anta - począwszy od prostych takich jak javac, copy, jar, aż po bardziej złożone - np. selenese (do uruchamiania testów Selenium) czy findbugs (do generowania raportów ze statycznej analizy kodu narzędziem Findbugs).

To wszystko czyni migrację z Anta do Gradle możliwie bezbolesną... a gdy już jej dokonasz, to nie spodziewam się byś kiedykolwiek zechciał do Anta powrócić. :)

Pisanie własnej logiki

Jak dotąd polegaliśmy wyłącznie na funkcjonalnościach oferowanych przez Gradle. Najwyższy czas, by dorzucić nieco własnej wymyślnej funkcjonalności. W ten sposób poznamy też klika nowych możliwości Gradle.

Liczenie sum kontrolnych pliku (z użyciem Anta)

Napiszemy teraz taska, który policzy sumy kontrolne plików z katalogu build/libs (domyślny katalog, do którego trafiają stworzone pliki JAR i WAR). Ponieważ wierzymy mocno w reużycie kodu, wykorzystamy w tym celu task checksum dostępny w Antcie. Dodaj poniższy kawałek kodu do pliku build.gradle i wykonaj polecenie gradle clean checksum.

 

 

 

    task checksum (dependsOn: assemble) << {
        def files = file(libsDir).listFiles()
        files.each { File file ->
            ant.checksum(file: file, property: file.name)
            println "$file.name Checksum: ${ant.properties[file.name]}"
        }
    }

Gradle zapewnia świetną integrację z Antem.

W tych kilku linijkach kodu dzieje się całkiem sporo - widać użycie skryptów Groovyego, definiowanie zależności miedzy taskami i użycie tasków Anta.

Po pierwsze, tworzony jest nowy task o nazwie checksum, który zależy od taska assemble dostarczanego przez plugin Java (ten task odpowiedzialny jest za tworzenie plików JAR i WAR). Następna linijka, napisana w Groovym, sprawia że wszystkie pliki z katalogu libsDir (wartosc tego property jest domyślnie ustawiana przez plugin Java i wskazuje na katalog build/libs) trafiają do listy. W końcu na każdym z plików z listy wywoływany jest Antowy task checksum, a wynik wypisywany jest na standardowe wyjście.

Snapshoty i wersje stabilne

W świecie Javy przyjęło się, że nazwy artefaktów odzwierciedlają fakt, czy artefakty te są stabilne (tzw. releasy) czy też nie (snapshoty). Snapshoty zwyczajowo otrzymują suffix "-SNAPSHOT", co sprawia że łatwo odróżnić je od wersji stabilnych. Zaimplementujmy teraz w naszym skrypcie budującym ten schemat nazewniczy artefaktów.

Poniższy fragment pliku build.gradle pokazuje bardzo interesującą cechę Gradla. Otóż wywołanie skryptu budującego podzielone jest na dwie fazy. W pierwszej konstruowany jest acykliczny graf zależności miedzy taskami (DAG, ang. dependency acyclic graph). W drugiej fazie następuje właściwe wywołanie tasków. Poniższy prosty przykład pokazuje, że jest możliwe "wejście" pomiedzy te dwie fazy i wykonanie pewnych operacji na grafie tasków, nim nastąpi ostateczne wykonanie tasków. W tym prostym przykładzie, graf tasków jest analizowany i na podstawie tej analizy podejmowana jest decyzja o nazwie wynikowego pliku:

    task release(dependsOn: assemble) << {
        println 'We release now'
    }

    build.taskGraph.whenReady { taskGraph ->
        if (taskGraph.hasTask(':release')) {
            version = '1.0'
        } else {
            version = '1.0-SNAPSHOT'
        }
    }

Wykonaj skrypt budujący dwukrotnie - pierwszy raz wywołując komendę gradle clean assemble, a następnie gradle clean release. Sprawdź w katalogu build/libs jakie nazwy otrzymały wynikowe pliki.

Tworzenie raportu z testów

Wyobraź sobie, że wykorzystujesz Gradle do umieszczenia bundli z testami integracyjnymi (napisanymi z użyciem TestNG) w środowisku OSGi, w którym działają moduły testowanej aplikacji. Modułów tych jest ponad 10, i ulegają one częstym zmianom (jako że wszystkie są jeszcze we wczesnym stadium developmentu). Chciałbyś wygenerować raport z testów, który zawierałby:

  • dane na temat środowiska (wersja Javy, system operacyjny itp.),

  • lista bundli zdeployowanych w środowisku OSGi (wraz z numerem wersji każdego z bundli),

  • wyniki testów.

Dużo lepszym rozwiązaniem jest
stworzenie własnego taska i wywołanie go
z odpowiednimi parametrami z poziomu skryptu budującego.

Możliwe jest upchnięcie całej logiki tworzenia raportu bezpośrednio do skryptu budującego, ale wówczas ten urósłby nieprzyzwoicie i stałby się nieczytelny. Dużo lepszym rozwiązaniem jest stworzenie własnego taska i wywołanie go z odpowiednimi parametrami z poziomu skryptu budującego. Plik build.gradle wyglądałby w zarysie tak:

    import org.gradle.sample.report.ReportTask

    ...

    dependencies {
        compile 'commons-lang:commons-lang:2.4'
        testRuntime 'org.easymock:easymock:2.5.2'
        // much more dependencies here
    }

    ...

    task report(type: ReportTask, dependsOn: itest) {
            reportDir = 'build/report'
            testNgResultsDir = 'build/itest/test-result'
            serverLogDir= 'build/itest/server-log'
            jars = configurations.testRuntime
    }

A tak wygladałaby klasa tasku (z pominięciem nieistotnych szczegółów):

    public class ReportTask extends DefaultTask {

        def String reportDir
        def String testNgResultsDir
        def String serverLogDir
        def FileCollection jars

        @TaskAction
        def createReport() {
            def text = new StringBuilder()
            ["os.name", "os.version", "java.version", "java.vm.version",
                "java.runtime.version", "user.language", "user.name"].each {
                    text.append(it + ":\t${System.getProperty(it)}\n")
             }

            text.append("gradle -v".execute().text)

            jars.each {
                    text.append("$it.name\n")
            }

            ...

            ant.zip(destfile: zipReportFile, basedir: tmpDir)
        }

        ...
    }

Te dwa kawałki kodu demonstrują kilka godnych uwagi rzeczy. Po pierwsze, wszelkie typowe operacje na plikach (kopiowanie, tworzenie archiwum ZIP, usuwanie itd.) są bardzo łatwe do wykonania, gdy można użyć skryptów Groovyego (w tym klasy AntBuilder). Po drugie, stworzenie własnego, reużywalnego taska, a następnie skonfigurowanie go i wywołanie, jest bardzo proste. W tym przypadku task ReportTask przyjmuje cztery parametry, których dostarcza mu skrypt budujący (taki podział pozwala na wyrzucenie prawdziwej logiki biznesowej poza skrypt). Po trzecie w końcu, przykład ten pokazuje, że dependencje w Gradle można przetwarzać w najróżniejszy sposób - w powyższym przykładzie wykorzystujemy ten fakt do banalnego zadania wypisania ich do pliku raportu.

Jeden czy wiele skryptów budujących ?

Najważniejsze jest to, że można uzyskać ten sam efekt stosując oba podejścia. Dla przykładu, jeżeli chcemy dodać do projektu core dependencję w postaci biblioteki Commons Lang, to możemy albo dodać ten kawałek kodu do pliku build.gradle w głównym katalogu:

    project (':core') {
      dependencies {
        compile "commons-lang:commons-lang:2.4"
      }
    }
    

albo dodać tę linijkę do pliku build.gradle w podkatalogu core:

    dependencies {
        compile "commons-lang:commons-lang:2.4"
    }
    

Jak widzisz, jest to jedynie kwestia smaku.

Budowa projektów wielomodułowych

Nasza aplikacja webowa potrzebuje kolejnego interfejsu użytkownika - napiszemy go w Swingu. Jest to dobry moment, by podzielić projekt na trzy części - przy okazji prezentując fragment tego, co Gradle ma nam do zaoferowania w kontekście budowy projektów wielomodułowych:

  • core - projekt zawierający logikę biznesową. Napisany w Javie, zależny od biblioteki Commons Lang. Oba projekty interfejsu użytkownika używają jego klas.

  • swing - odpowiedzialny za interfejs użytkownika dla aplikacji desktopowej. Napisany w Groovym.

  • web - tworzy webowy interfejs użytkownika. Napisany w JSP. Wynikowy WAR musi zawierać wszystkie wymagane w runtimie biblioteki - nie tylko JAR z projektu core, ale również jego dependencje (tj. bibliotekę Commons Lang).

Layout projektów wielomodułowych

Gradle oferuje dużą elastyczność, jeżeli chodzi o layout projektów wielomodułowych. Na potrzeby tego artykułu, zastosuję layout hierarchiczny (na poniższym schemacie pominąłem zawartość katalogów src):

    .
    |-- build.gradle
    |-- settings.gradle
    |-- core
    |   `-- src
    `-- ui
        |-- swing
        |   `-- src
        `-- web
            `-- src

W głównym katalogu znajdują się dwa podkatalogi - na projekt core, i drugi nazwany ui. W podkatalogu ui znajdują się wszystkie projekty związane z budową interfejsu użytkownika - obecnie są to dwa projektu swing i web.

Gradle oferuje dużą elastyczność,
jeżeli chodzi layout projektów wielomodułowych.

Jak widać cały projekt wielomodułowy posiada tylko jeden skrypt budujący build.gradle. Można zastanawiać się, czy dobrym pomysłem jest zawarcie całej logiki budowania w jednym miejscu. Jeżeli uznamy, że nam to nie odpowiada, Gradle spełi naszą zachciankę i pozwoli na stworzenie oddzielnych skryptów budujących dla każdego podkatalogu.[3]

settings.gradle

Po piersze przyjrzyjmy się plikowi, którego dotąd jeszcze nie napotkaliśmy: settings.gradle. W nim właśnie znajdują się informacje o layoucie projektu[4]. Jak widać poniżej, wskazuje on Gradleowi w jakich podkatalogach znajdują się poszczególne moduły (podprojekty):

    include "core", "ui:swing", "ui:web"

Zanim przyjrzymy się skryptowi budującemu
zastanówmy się jakie zadania powinien on wykonywać.

build.gradle

Zanim przyjrzymy się skryptowi budującemu, zastanówmy się jakie zadania powinien on wykonywać. Wydaje się, że są one następujace:

  • projekt core - kompilacja klas Javy i budowa archiwum JAR,

  • projekt web - kompilacja klas Javy i plików JSP, i budowa archiwum WAR,

  • projekt swing - kompilacja klas Groovyego i budowa archiwum JAR,

  • umieszczenie wszystkich stworzonych artefaktów (plików JAR i WAR) do wspólnego zasobu (podkatalogu repo w główym katalogu projektu).

Plik budujący build.gradle przedstawiony jest poniżej.

    subprojects {
      usePlugin 'java'
      group = 'org.gradle.sample'
      version = '1.0'

      repositories {
        mavenCentral()
        flatDir(name: 'fileRepo', dirs: "./repo")
      }

      uploadArchives {
        repositories {
          add project.repositories.fileRepo
        }
      }

    }

    project (':core') {
      dependencies {
        compile "commons-lang:commons-lang:2.4"
      }
    }

    project (':ui') {
      subprojects {
        dependencies {
         compile project (':core')
        }
      }
    }

    project (':ui:web') {
      usePlugin 'war'
    }

    project (':ui:swing') {
      usePlugin 'groovy'
      dependencies {
        groovy "org.codehaus.groovy:groovy-all:1.6.5"
      }
    }

Nawet jeżeli pierwszy raz w życiu widzisz plik build.gradle opisujący budowę projektu wielomodułowego, nie powinieneś mieć problemów z jego zrozumieniem. Po pierwsze, korzystając z property subprojects, skrypt ustawia pewne własności wspólne dla wszystkich podprojektów (core, swing i web). W ten sposób wszystkie podprojekty będą:

  • używały pluginu Java,

  • miały własność group ustawioną na org.gradle.sample a wersję na 1.0,

  • szukały dependencji w centralnym repozytorium Mavena i w lokalnym katalogu repo,

  • składowały stworzone artefakty w katalogu repo.

Po ustawieniach wspólnych dla wszystkich podprojektów, każdy z podprojektów konfigurowany ejst osobno. Projekt core otrzymuje zależność od biblioteki Commons Lang. Oba projekty interfejsu użytkownika są zależne od projektu core. Następnie do projektu web dodany jest plugin War. W końcu, projekt swing zostaje skonfigurowany jako projekt Groovyego (co sprowadza się do dodania do niego pluginu Groovy i dopisania zależności od biblioteki Groovyego).

Budowa projektu - pełna i częściowa

W przypadku projektu wielomodułowego pojawia się pytanie "ale co właściwie chciałbyś zbudować?". A więc, chciałbyś zbudować oba projekty interfejsu użytkownika, czy może tylko jeden z nich?

Aby zbudować wszystkie projekty, wykonaj komendę gradle build z poziomu głównego katalogu projektu. Zauważysz, że w procesie budowania powstaną trzy artefakty: core-0.1.jar, swing-0.1.jar i web-0.1.war.

Gradle umożliwia wykonywanie częściowych buildów,
a nam nie pozostaje nic innego
jak z tej możliwości ochoczo korzystać.

Jeżeli chcemy wybudować wyłącznie jeden z interfejsów użytkownika, możemy użyć tasku buildNeeded[5]. Na przykład, wykonanie polecenia gradle buildNeeded w podkatalogu ui/swing, poskutkuje zbudowaniem wyłącznie projektów core i swing oraz stworzeniem dwóch artefaktów: core-0.1.jar i swing-0.1.jar.

Wniosek z tego wszystkiego taki, że Gradle umożliwia wykonywanie częściowych buildów, a nam nie pozostaje nic innego jak z tej możliwości ochoczo korzystać (w końcu funkcjonalność ta pozwala znacząco skrócić czas budowy projektu).

Podsumowanie

...ah, czyżby kolejny projekt-zbawca ? Kolejne narzędzie, które obiecuje rozwiązanie wszelkich problemów związanych z budowaniem projektów ? Nie całkiem. Gradle nie obiecuje aż tak wiele. Za to próbuje

uczynić niemożliwe możliwym, możliwe łatwym, a łatwe eleganckim

-- Moshé Feldenkrais

I nawet już teraz, przed wydaniem wersji 1.0, Gradle oferuje mnóstwo interesujących i przydatnych możliwości. W tym artykule zaprezentowałem kilka z nich, i nie ukrywam, że jest to jedynie wierzchołek (wciąż rosnącej) góry lodowej. :)

Chciałem zachęcić cię do zainwestowania odrobiny czasu by lepiej poznać Gradle. Początkowo jego elastyczność i brak sztywnych reguł może sprawić, że będziesz czuł się nieco nieswojo (szczególnie jeżeli jesteś doświadczonym użytkownikiem Mavena). W moim przypadku, nawet po kilku miesiącach pracy z Gradle, mam poczucie, że wciąż nie dokonałem jeszcze całkowitego "przeskoku myślowego". Wciąż też zadziwia mnie łatwość i elastyczność, z jaką Gradle pozwala mi tworzyć skrypty budujące projekt. Musisz dać sobie nieco czasu, a przekonasz się że w tym projekcie tkwi potęga, o jakiej nawet nie marzyłeś. Koszt przejścia (z Anta lub Mavena) jest minimalizowany przez to, że Gradle wykorzystuje wiele rozwiązań, które są Ci dobrze znane z innych projektów (np. standardowy layout projektu, taski Anta, repozytoria Mavena itd.).

Przekonasz się, że w tym projekcie tkwi potęga,
o jakiej nawet nie marzyłeś.

Na stronie Gradle znajdziesz bardzo rozbudowany podręcznik użytkownika, a w kodzie źródłowym mnóstwo przykłądów, które pomogą Ci rozpocząć pracę z Gradle (zajrzyj też do CookBook po różnorakie pomocne wskazówki). Możesz też liczyć na wsparcie członków społeczności (dostępna jest lista mailingowa i wiki), a w przypadku gdybyś potrzebował profesjonalnych szkoleń, zapoznaj się z ofertą na stronie Gradle Inc.

Links



[1] Dla ścisłości dodam, że tak naprawdę to pluginy Java i War narzucają taki układ projektu jako domyślny.

[2] Maven 3 pozwala na użycie równie zwięzłej składni, ale jak zobaczymy nieco później, w przypadku dependencji, Gradle oferuje o wiele więcej.

[3] W załączonym do artykułu kodzie źródłowym znajdziesz obie wersja - sam oceń, która wydaje Ci się bardziej odpowiednia.

[4] Plik settings.gradle może też służyć innym celom.

[5] Inne taski o zbliżonym działaniu opisane są w podręczniku użytkownika Gradle.

Komentarze

  1. Dlaczego nie ma tutaj opcji do wydruku?

  2. To jest strona html, ktora z latwoscia kazda przegladarka moze wydrukowac. Wersje html nie sa przeznaczone do wydruku (do tego jest werjsa pdf), wiec dlatego dodawania funkcjonalnosci, zeby przygotowac osobnego htmla bez wszystkich grafik naokolo ma dosyc niski priorytet. Mamy duzo wiecej bardziej potrzebnych rzeczy do zrobienia, wiec nie sadze, zeby w niedalekiej przyszlosci pojawil sie przycisk do wyduku wersji html.
    A czy standardowy wydruk z przegladarki nie spelnia oczekiwan?

  3. A próbowałeś zrobić wydruk? Polecam sprawdzić jak to wyjdzie na podglądzie...

    Przykładowy artykuł z theserverside.com
    http://www.theserverside.com/news/2240016831/Part-3-of-dependency-injection-in-Java-EE-6
    Zrób podgląd wydruku tej strony, a następnie kliknij "PRINT" (na górze po prawej, obok "EMAIL THIS") i zobacz jak wygląda wersja do wydruku. Tak samo jest na każdym innym portalu, np. java.dzone.org

    http://java.dzone.com/articles/design-patterns-template-method

    Znowu - zobacz jak wygląda wydruk tej strony i porównaj z wersją "printer-friendly" (link znajduje się po lewej stronie zaraz pod informacją o autorze na końcu artykułu, ale przed komentarzami).

    Tutaj nie dość, że tego nie ma, to jeszcze nielogiczna odpowiedź w stylu:

    "To jest strona html, ktora z latwoscia kazda przegladarka moze wydrukowac]. Wersje html nie sa przeznaczone do wydruku (do tego jest werjsa pdf), wiec dlatego dodawania funkcjonalnosci, zeby przygotowac osobnego htmla bez wszystkich grafik naokolo ma dosyc niski priorytet [...]"

    Tak więc: to jest strona html, którą (niby) z łatwością można wydrukować i jednocześnie html nie jest przeznaczony do druku...
    Spodziewałem się raczej słów w stylu - "faktycznie, pracujemy nad tym, wiemy jakie to jest WAŻNE, ale w tej chwili nie mamy wystarczająco czasu"...

    Proszę nie odbierać moich słów jako atak na JavaExpress, cieszę się, gdy powstają tego typu inicjatywy. Martwi mnie tylko strona techniczna i podejście prowadzących serwis.

  4. Probowalem w Firefoxie i wyglada calkiem OK. Co prawda druk jest szerokosci polowy strony, ale da sie to przeczytac.

    Nie wiem, dlaczego twierdzisz, ze moja odpowiedz jest nielogiczna. Moze sprobujmy w ten sposob:
    Java exPress jest w dwoch wersjac: pdf i html. Celem artykulow w html nie jest ich drukowanie. Do tego bardziej sluzy wersja pdf. Niemniej jednak artykuly html sa zwyklymi stronami html, ktore moga byc drukowane z opcji przegladarki. Wiec jesli potrzebujesz koniecznie wydrukowac html, to przegladarka powinna Ci to umozliwic.

    Zaspol Java exPress w najblizszym czasie nie planuje dodawac funkcjonalnosci ulatwiajacej wydruk artykulow w wersji html (w tym takze stron "Printer Friendly"). Tak jak piszesz "nie mamy wystarczajaco czasu". Pracujemy nad lepszym wygladem strony.

    Java exPress jest budowane przez ludzi, ktorzy robia to wolnej chwili. Jesli chcialbys dodac funkcjonalnosc do latwiejszych wydrukow, to napisz please na kontakt@dworld.pl. Moze dolaczysz do nas i wspolnie bedziemy dbac o strone techniczna i lepsze podejscie prowadzacych serwis.

  5. Czyli podsumowując: wersja HTML się nie nadaje do druku - i dobrze, bo do druku jest przecież wersja PDF w postaci pisma, które jeszcze bardziej nie nadaje się do druku, bo:
    http://www.javaexpress.pl/comment/show/9#c9

    ...jak się nie obrócisz z tyłu zawsze dupa :)

    P.S.
    Ja też próbowałem z Firefox i faktycznie - tak jak mówisz - zupełnie dobrze, wszystko jest całkiem OK, tylko jeden maluteńki drobiazg: wydruk jest szerokości połowy strony, druga połowa wszystkich kartek jest pusta. No, ale przeczytać się to da. Sam nie wiem, chyba się czepiam bez sensu.

  6. Zacznijmy od poczatku. Java exPress jest czasopismem "elektronicznym". I tak tez jest przygotowywane.

    Pracujemy nad Java exPress w swoim wolnym czasie, wiec mamy bardzo ograniczone mozliwosci co do rozwoju w kierunku, ktory nie jest kluczowym. A wydruk html nie jest kluczowy dla nas w tym momencie.

    Co do wydruku pdf-a to przygladniemy sie problemowi z wielkoscia czcionki. Natomiast problem z kolorami i obrazkami po bokach nie bedzie "naprawiony", poniewaz spowoduje to lawine innych requestow, na ktore teraz nie mozemy sobie pozwolic.

    Moze sprobuj wydrukowac pdf na drukarce kolorowej, to wtedy uzyskasz efekt zblizony do tego co na ekranie. Nie zwalaj calej winy na wydruk na zespol JAVA exPress. Nie jestesmy w stanie w tym momencie spelnic oczekiwan wszystkich czytelnikow.

    Na koniec powtorze moje pytanie - jesli oczekujesz czegos od nas, to dlaczego nie dolaczysz do nas i zrobisz cos dla innych (np. ladniejszy wydruk wersji html)?

  7. Super, wersja do wydruku jest wyśmienita!
    Dzięki!

  8. Prosze bardzo ;) To Marka dzielo...

  9. Gradle świetnie się prezentuję. W elastycznym projekcie znalazlby dla siebie miejsce po poswieceniu troche czasu na nauke.
    Niedawno przestalem uzywac mavena po 6 latach doswiadczen i powiem ze nie warto... (ograniczony, rozwlekly i skomplikowany - zbyt czesto developerom trzeba bylo tlumaczyc jak to dziala i czemu akurat TO nie dziala). Teraz uzywam ant'a ze sprarciem ivy i skryptow grooviego - bardzo elastyczne rozwiazanie i poza tym ma wazna zalete - przekonanie ludzi w duzym zespolne do nowego narzedzia budujacego nie jest latwe. Teraz wystarczy aby developer sciagnal sobie dystrybucje anta i wykonal standardowe taski np "install full-environment". To ze polowa kodu budujacego to w rzeczywistosci groovy to uzytkownikow az tak nie obchodzi ;)

  10. komentarze w losowej kolejności przy kazdym odswiezeniu strony.. ciekawe ;D

  11. poprawiona kolejnosc komentarzy :)

Tylko zalogowani użytkowincy mogą pisać komentarze

Developers World