Zobrazují se příspěvky se štítkemtutorial. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemtutorial. Zobrazit všechny příspěvky

29. srpna 2013

Gradle tutorial: tasky (pokračování)

V minulém díle Gradle tutorialu jsme si vystřihli rafinované Hello world a řekli jsme si něco o základním stavebním prvku každého build skriptu - Task, to je to, oč tu běží. A ruku na srdce, milý čtenáři, jistě jsi zvědavý, kam nás naše povídání zavede. Protože to zajímavé teprve přijde.

Závislosti mezi tasky

Tasky téměř nikdy nestojí samostatně. To není nic moc překvapivého. Známe to z Antu, známe to z Mavenu, bylo by divný, kdyby to Gradle dělal jinak. Když si například vezmeme klasický Maven build lifecycle, tak vypadá nějak takhle:
  1. zpracuj resources,
  2. zkompiluj třídy,
  3. zpracuj test-resources,
  4. zkompiluj třídy testů,
  5. spusť testy,
  6. vytvoř archiv,
  7. atd.
Tyto jednotlivé úkony (v Mavenu nazývané goals) na sebe logicky navazují a jsou na sobě implicitně závislé. Implicitně znamená, že nemusím závislost jednotlivých tasků (goals) explicitně uvádět - Maven "už to nějak ví", jak si ty tasky má seřadit. Magie? Ne, "někdo" mu to řekl.

Podobné je to i v Gradlu. Některé tasky už mají závislosti definované, většinou je to v rámci nějakého pluginu. Třeba u Java pluginu (o kterém si budeme povídat v příště) vytváří závislosti cyklus, který je velmi podobný tomu Mavenímu. Velkou výhodou Gradlu je, že závislosti mezi tasky se definují velmi jednoduše a navíc nad nimi máme programovou kontrolu.

Závislost tasku se dá definovat buď pomocí property nebo metody dependsOn. Způsob zápisu se může podobat tomu, který známe z Antu, čili přímo při definici tasku:
task first << { task ->
    println "  Working on the ${task.name} task"
}

task second(dependsOn: first) << { task ->
    println "  Working on the ${task.name} task"
}
Anebo můžeme využít nespoutanosti Groovy a závislost definovat v podstatě na libovolném místě:
task third << { task ->
    println "  Working on the ${task.name} task"
}

third.dependsOn second 
Vzhledem k tomu, že zde máme závislost tasků third -> second -> first, výstup by při spuštění tasku third měl vypadat následovně:

Závislost tasků

Zajímovou možností je definovat závislosti tasku dynamicky. Vstupem do metody dependsOn totiž může být closure, jedinou podmínkou je, aby vracela objekt typu Task, nebo kolekci Tasků.
task fourth << { task ->
    println "  Working on the ${task.name} task"
}

fourth.dependsOn {
    tasks.findAll { task -> task.group.equals('sequence') }
}
V uvedeném výpisu je task fourth závislý na všech tascích projektu, které jsou seskupeny pomocí vlastnosti group.

Zdrojový kód na Bitbucketu: 02_TaskDependencies/build.gradle

Přeskočení tasku

Ať už jste agilní vývojáři, kteří milují dynamické prostředí, nebo jste naopak vyznavači petrifikovaného zadání, jedno je jisté - chtěná, či nechtěná, stejně přijde nějaká změna. A tak, jakkoliv je náš pracovní lifecycle vymazlený, bude do něj potřeba zasáhnout. Někdy třeba jen dočasně.

Dynamická práce s tasky může být dost komplexní a vydala by na samostatný článek. My se v rámci tutorialu podíváme na část, která je k dispozici out-of-the-box a sice na způsoby, jak zajistit "nevykonání" naplánovoného tasku.

(Při psaní příkladů pro tuto část jsem měl rozvernou náladu, takže v této sekci bude naším průvodcem jeden z hrdinů mého dětství, klokan Skippy :-)

První ze způsobů, jak přeskočit task, je využití metody onlyIf, pro kterou můžeme definovat predikát. Akce v tasku (a tím pádem i task samotný) jsou vykonány pouze tehdy, pokud je predikát vyhodnocen jako true.
task skippy << {
    println 'I\'m happy, so I jump!'
}

skippy.onlyIf { project.isHappy == 'true' }
V tomto příkladu je task skippy vykonán pouze tehdy, pokud je projektová property isHappy nastavena na true. Pokud takovou property v build skriptu nemáme definovanou, nevadí - přidáme si ji z příkazové řádky pomocí přepínače -P, např. -PisHappy=true.

Přeskočení tasku pomocí metody onlyIf

Další možnost, jak nevykonat task, nebo jeho část, je vyhodit výjimku StopExecutionException. To způsobí, že je zastaveno vykonávání aktuálního tasku a je spuštěno vykonání toho následujícího. Tato výjimka přeruší pouze daný task, build pokračuje dál.
task exceptionalSkippy << {
    if (project.isHappy == 'false') {
        throw new StopExecutionException()
    }

    println 'I\'m happy, so I jump!'
}
Jak je vidět na následujícím výstupu, task exceptionalSkippy nebyl přeškočen (jako v předešlém příkladu), ale byl normálně spuštěn a pak byl někde "uvnitř" přerušen.

Přerušení tasku pomocí StopExecutionException

Třetí možností, jak rozhodnout o vykonání tasku je použití property enabled - task tak můžeme vypnout, nebo zapnout.

V následujícím příkladu máme dva tasky. Task disabledSkippy (který nám řekne, jestli Skippy bude skákat) závisí na tasku skippyMood (který nám oznámí Skippyho náladu). Task skippyMood má možnost vypnout task disabledSkippy.
task skippyMood << {
    if (project.isHappy == 'false') {
        disabledSkippy.enabled = false

        println 'I\'m not happy :-('
    } else {
        println 'I\'m happy :-)'
    }
}

task disabledSkippy << {
    println 'I\'m happy, so I jump!'
}

disabledSkippy.dependsOn skippyMood

Přeskočení tasku pomocí property enabled

Zdrojový kód na Bitbucketu: 03_SkipTask/build.gradle

Task rules, dynamické generování tasků

Už jsem párkrát naznačoval cosi o dynamičnosti Gradle, pojďmě se tedy podívat na něco konkrétního. Co kdybychom potřebovali větší množství tasků, které dělají v podstatě to samé, akorát se to "to" liší nějakými parametry. Můžeme takovou potřebu řešit pomocí principů objektového programování, třeba. kompozicí. To může být docela otrava, hlavně správa a přetestovávání takových tasků.

Co si takhle potřebné tasky nechat dynamicky vygenerovat? Právě od toho jsou tady task rules.
def environments = [
    'DEV': ['url': 'http://dev:8080'],
    'SIT': ['url': 'http://sit:9090'],
    'UAT': ['url': 'http://uat:7001']]

tasks.addRule('Pattern: deploy<ENV>') { taskName ->
    if (taskName.startsWith('deploy')) {
        task(taskName) << {
            def env = taskName - 'deploy'

            if (environments.containsKey(env)) {
                def url = environments[env].url

                println "Deploying to ${env} environment, url: ${url}"
            } else {
                println "Unsupported environment: ${env}"
            }
        }
    }
}

Příklad spuštění jednotlivých task rules

Vzhledem ke své povaze jsou task rules uvedeny v samostatné sekci při výpisu tasků pomocí příkazu gradle tasks:

Výpis pravidel (rules) příkazem gradle tasks

Zdrojový kód na Bitbucketu: 04_TaskRules/build.gradle

Asi v tom bude nějakej háček

Jedním z nejčastějších problémů u tasků, se kterým se můžeme zpočátku setkávat, je záměna konfigurace a definice tasku. Definici tasku už známe - používali jsme ji celou dobu. V rámci definice přidáváme do tasku jednotlivé akce, které jsou pak vykonány během exekutivní fáze. Jen pro připomenutí, akce můžeme do tasku přidat metodami doFirst a doLast a daleko nejčastěji se používá operátor << který je aliasem pro metodu doLast.

Konfigurace tasku slouží... ehm, ke konfiguraci tasku. Podstatné je, že konfigurace je vykonána v jiné fázi buildu. A proběhne pokaždé, i když zrovna daný task nespouštíme. Task lze nakonfigurovat několika způsoby, ten "problematický" má stejnou syntaxi jako definice tasku, pouze je bez << operátoru.
task foo {
    // some task configuration here
}
V následujícím výpisu task pitfall prvně "nakonfigurujeme" a následně do něj přidáme akci.
task pitfall { task ->
    println "Printed from task ${task.name} configuration."
}

pitfall << { task ->
    println "Printed from task ${task.name} execution."
}
Když se podíváme na spuštění tasku pitfall, všimněte si, kdy proběhl "konfigurační" výpis. Ještě před začátkem spuštění tasku (tedy před vykonáním obsažených akcí).

"Vizualizace" konfigurační a exekuční fáze tasku

Druhým častým problémem s tasky bývá následující chyba:
Cannot add task ':exists' as a task with that name already exists.
Což znamená, že se snažíme definovat task, který už je definován někde jinde. Problém bude buď v chybné syntaxi, anebo v praxi daleko častěji, pokud pracujeme s projektem, kde používáme plugin, který task stejného jména již obsahuje. Obdobou téhož je, když si do projektu natáhneme antovské tasky (jeden z budoucích dílů tutorialu) a opět, dané jméno tasku už je použito.

Pokud řešíme druhý případ (duplicitní název/definice tasku), můžeme task explicitně předefinovat. Nemusím zdůrazňovat, že bychom měli vědět co a proč to děláme. Pokud máme buildovací skripty plně v rukou, bude tento problém indikovat chybu v designu buildu. Ovšem v případě, že se potýkáme s nějakým legacy buildem (staré Ant build soubory), může být redefinice tasku nutností.

Task lze předefinovat pomocí property overwrite:
task exists << {
    println 'primary task definition'
}

task exists(overwrite: true) << {
    println 'overwritten task definition'
}

Redefinice tasku pomocí property overwrite

Zdrojový kód na Bitbucketu: 05_TaskPitfalls/build.gradle

Zdrojové kódy

Zdrojové kódy k dnešnímu dílu jsou k dispozici na Bitbucketu. Můžete si je tam buď probrouzdat, stáhnout jako ZIP archiv, anebo - pokud jste cool hakeři jako já :-) - naklonovat Mercurialem:
hg clone ssh://hg@bitbucket.org/sw-samuraj/gradle-tutorial

Co nás čeká příště

O tascích by se dalo povídat ještě dlouho. Ale protože jsem příznivcem hesla better is enemy of done, tasky dnešním dílem opouštíme a příště se podíváme, jak Gradle řeší Java vývoj.

Tento článek byl původně publikován na serveru Zdroják.

Související články


Související externí články

8. července 2013

Gradle tutorial: tasky

Vítejte u prvního dílu tutorialu o automatizačním nástroji Gradle. Filozoficko-marketingovou masáž jsme si odbyli v minulém článku, takže je čas si vyhrnout rukávy: let's get our hands dirty!

3 informace na úvod

Asi nejdůležitější informací je: co by mělo být přínosem tohoto tutorialu? Gradle má velmi pěknou dokumentaci (User Guide, DSL Reference, Javadoc/Groovydoc), tak proč psát ještě tutorial? Důvody jsou dva. Jednak dokumentace ne vždy pomůže při řešení praktických věcí - dotazů je plný Stack Overflow, a jednak některé věci nejsou úplně ve stavu, v jakém bych je chtěl používat. Jako příklad můžu uvést Jetty: Gradle obsahuje out-of-the-box Jetty plugin, který ale používá Jetty ve verzi 6. Což je verze, která je deprecated. Pokud tedy chci používat nějakou stable verzi (7-9), musím to udělat jinak. Tutorial by tedy měl řešit praktické věci v aktuálních verzích použitých nástrojů.

Druhou informací je scope tutorialu. Ten by měl odpovídat mojí přednášce na SlideShare, plus něco navíc. Základní zaměření odpovídá mým potřebám, tedy na co Gradle používám já. Pokud byste si rádi přečetli o nějakých dalších tématech, dejte mi vědět v komentářích - rád tutorial přizpůsobím vašim potřebám. Tutorial by měl zahrnovat hlavně Java vývoj: tj. Java, unit testy, web, metriky kódu, multi projekty, kontinuální integraci, integraci s Antem. Předstupněm závěru by byl jednoduchý, ale reálný projekt - webová služba na JAX-WS a úplné finále by obstarala case study (opět reálný projekt).

Poslední informace je praktická. Ke každému dílu budou k dispozici zdrojové kódy build skriptů, které budu postupně publikovat na Bitbucketu. Zdrojové kódy v repozitáři budou obohacené o komentáře, takže budou (doufám) použitelné i samostatně.

Bitbucket je Mercurial (a Git) hosting, který funguje obdobně jako známější GitHub. Že jsem zvolil Mercurial a ne třeba populárnější Git je, dejme tomu, srdcová záležitost. (A pak, toho Gitu už je všude trochu moc ;-)

Instalace a verze Gradlu

Instalace Gradlu je jednoduchá ve stylu stáhnout-rozbalit-přidat bin do PATH-spustit. Prerekvizitou je JDK 1.5+. Pro přesný postup vás odkážu na stránky dokumentace: Installing Gradle. Funkčnost instalace ověříme příkazem gradle -v.

Verze Gradlu

V rámci tutorialu budu používat verzi 1.7 z nočního buildu. Předpokládám, že tutorial bude zdrojem informací i v budoucnu, ne jenom v čase publikování, tak si chci podržet iluzi, že takto vydrží být aktuální alespoň o něco déle. Může se sice stát, že některé funkčnosti, které popisuji se mohou ve stabilních releasech chovat trochu jinak (např. výstupy na konzoli), nicméně většina příkladů byla původně vyvinuta pro (stable) verzi 1.5 a ve verzi z nočního buildu funguje bez úprav, takže nepředpokládám problémy :-)

Pokud byste u příkladů narazili na nekompatibilitu mezi verzemi, dejte mi, prosím, vědět v komentářích.

Projekty a tasky

Dva základní koncepty, na kterých jsou Gradle build skripty postaveny jsou projekt a task. Koncept projektu by nám měl být familiární, např. z IDE. Zjednodušeně, projekt představuje nějaký komponent, který potřebujeme sestavit: JAR/WAR/EAR/ZIP archiv apod. Projekt je prezentován souborem build.gradle v root adresáři projektu.

Definice Gradle projektu (projekt 00_HelloWorld)

Projekt definuje související množinu tasků. Task je atomická jednotka práce, kterou můžeme spustit v rámci buildu. Každý task může obsahovat množinu akcí. Akce už je nějaká konrétní věc, kterou chceme provést: výpis na konzoli, přesun souboru, kompilace, spuštění jiného tasku atd.

Hello, world!

Nevím, jak vy, ale já když se učím něco nového (jazyk, framework, nástroj), tak si vždycky rád napíšu Hello world. Jak vypadá Hello world v Gradlu?
task hello << {
    println 'Hello, Gradle!'
}
Příklad spustíme příkazem
gradle hello
Výstup by měl vypadat nějak takhle:

Výstup tasku hello

Výstup je možná trochu ukecaný - kromě výpisu našeho textu je tam label daného tasku (:hello) a informace o úspěšnosti vykonání tasku. Pokud chceme kompaktnější výstup, můžeme spustit task s přepínačem -q. Gradle pak vypisuje (ve skutečnosti loguje) pouze to, co jsme poslali na standardní výstup (println) a zprávy se severitou error.
gradle hello -q
Zrojový kód na Bitbucketu: 00_HelloWorld/build.gradle

Jaké tasky jsou k dispozici?

Nejčastějším uživatelem Gradlu bude vývojář. Ale může se taky stát, že ho bude spouštět tester, nebo analytik, nebo... (nedejbože ;-) projekťák, či slečna asistentka. Pamatujete? Automatizace.

Tyto ostatní role se asi nebudou chtít vrtat v kódu build skriptu, aby zjistily, co že to má dělat apod. Jako dělaný speciálně pro ně je příkaz gradle tasks, který vypíše seznam tasků, které jsou k dispozici.

Výpis tasků

V závislosti na verzi Gradlu se výpis může lišit. Pokud nevidíte nějaký task, který byste vidět měli, zkuste příkaz spustit s přepínačem --all:
gradle tasks --all

Gradle GUI

Graficky orientovaní jedinci možná ocení práci s grafickou verzí Gradlu, která se spouští příkazem gradle --gui. Zde je také vidět přehled tasků. Ty se navíc dají rovnou spouštět, je vidět jejich výpis na konzoli atd.

Gradle GUI

Syntaxe tasku

Nejčastěji se budeme setkávat s definicí tasku, která byla uvedena v příkladu Hello world, čili:
task myList << {
    println 'middle point'
}
Na této syntaxi je nejzajímavější operátor <<, který je aliasem pro metodu doLast. Stejný výsledek dostaneme zápisem:
task myList {
    doLast {
        println 'middle point'
    }
}
Když máme metodu doLast, tak nepřekvapí, že máme i obdobnou doFirst. K čemu tyto metody slouží? Umožňují nám "dekorovat" již definovaný task - metoda doFirst přidává nové akce na začátek seznamu akcí a metoda doLast dělá totéž na konci seznamu akcí daného tasku. Každý task tedy můžeme postupně rovíjet a obohacovat jeho chování. V jednoduchém build skriptu to asi nebude potřeba, ale v hierarchii skriptů (multi project) už to může být zajímavé.
task myList << {
    println 'middle point'
}

myList {
    doLast {
        println 'last point'
    }
}

myList {
    doFirst {
        println 'first point'
    }
}

Výstup "dekorovaného" tasku myList

Zdrojový kód na Bitbucketu: 01_Tasks/build.gradle

Zdrojové kódy

Zdrojové kódy k dnešnímu dílu jsou k dispozici na Bitbucketu. Můžete si je tam buď probrouzdat, stáhnout jako ZIP archiv, anebo - pokud jste cool hakeři jako já :-) - naklonovat Mercurialem:
hg clone ssh://hg@bitbucket.org/sw-samuraj/gradle-tutorial

Co nás čeká příště?

Protože tasky jsou pro Gradle stěžejním konceptem, budu se jim věnovat i v příštím díle. Tři hlavní okruhy by měly být:
  • závislosti mezi tasky,
  • dynamické generování tasků - task rules
  • a "běžné" problémy, se kterými se můžeme při definici tasků setkat.

Tento článek byl původně publikován na serveru Zdroják.

Související články


Související externí články

8. května 2013

Gradle, moderní nástroj na automatizaci

Gradle je nástroj na automatizaci. Potřebujete udělat build, mít Continuous Integration, zprovoznit deployment, generovat dokumentaci, připravit release, dojít nakoupit a vyvenčit psa? Gradle je to pravé pro vás! Gradle je něco jako Ferrari, Land Rover a Mini Cooper v jednom. A funguje to.

Gradle? Co to je?

Gradle je nástroj na automatizaci. Čehokoliv. Zmáčknete tlačítko a vypadne banán :-)  Nejčastější využití asi bude na build nějakého projektu. Ale není potřeba se svazovat jen touto představou - jakmile potřebuju něco zautomatizovat, můžu na to použít Gradle (třeba na generování a odesílání měsíčních výkazů).

Gradle ve skutečnosti není automatizační nástroj - je to jazyk pro automatizaci. Je to Domain Specific Language (DSL), který nám umožňuje popsat to, co chceme zautomatizovat. Nepřekvapí, že Gradle je založený na Groovy (jestliže byl Groovy v něčem úspěšný (oproti svým konkurentům z oblasti dynamických JVM jazyků), tak je to právě jeho využití ve sféře DSL).

Výchozí těžiště Gradlu leží v buildování. V tomto směru na něj lze nahlížet jako na nástroj další generace v linii Ant -> Maven -> Gradle, přičemž ze svých předchůdců si bere (a kombinuje) to nejlepší: z Antu jeho sílu a flexibilitu, z Mavenu konvence, dependency management a pluginy.

Jádro Gradlu samo o sobě toho moc neumí - zkuste si po instalaci pustit příkaz gradle tasks. Distribuce Gradlu ale obsahuje sadu standardních pluginů, takže out-of-the-box je k dispozici podpora pro Javu, Groovy, Scalu, Web, OSGi aj. Další funkčnosti jsou k mání přes komunitní pluginy.

Tak jako spousta skvělých věcí v životě i Gradle je zadarmo - je k dispozici s licencí Apache License, Version 2.0.

Je opravdu tak dobrý?

Pokud jste se s Gradlem ještě nestkali, možná jste na pochybách, jestli má smysl se jím vůbec zabývat. Vždyť přece buildujeme Mavenem/Antem, tak na co další další nástroj? Třeba vás přesvědčí, že mezi uživatele Gradlu patří např. Spring, Hibernate, Grails, Software AG, nebo LinkedIn.

Dalším důvodem, proč u Gradle zpozornět je, že byl již podruhé uveden v prestižním(?) přehledu ThoughtWorks Technology Radar. V tom aktuálním z října 2012 je Gradle umístěn v sekci Trial, což znamená "Worth pursuing. It is important to understand how to build up this capability. Enterprises should try this technology on a project that can handle the risk."

Konkrétně o Gradlu a buildovacích nástrojích je zde napsáno: "Two things have caused fatigue with XML-based build tools like Ant and Maven: too many angry pointy braces and the coarseness of plug-in architectures. While syntax issues can be dealt with through generation, plug-in architectures severely limit the ability for build tools to grow gracefully as projects become more complex. We have come to feel that plug-ins are the wrong level of abstraction, and prefer language-based tools like Gradle and Rake instead, because they offer finer-grained abstractions and more flexibility long term."

[update 7. 6. 2013] Aktuálně vyšel nový ThoughtWorks Technology Radar (květen 2013), kde se Gradle posunul do sekce Adopt, čili: "We feel strongly that the industry should be adopting these items. We use them when appropriate on our projects." [/update]

My Way

O Gradlu jsem se dozvěděl v roce 2009, kdy jsem byl na přednášce CZJUGu v tehdejším Sunu (R.I.P.). Měl jsem to štěstí, že přednášejícím byl Hans Dockter - zakladatel a leader Gradlu (Hansova původní prezentace je v archivu CZJUGu).

Gradle se mi tehdy zalíbil. Vyzkoušel jsem ho a použil na pár vlastních drobných Java a Groovy projektech. Gradle byl tehdy ve verzi 0.8 a když se podíváte na historii verzí, je vidět, že to byla velmi čerstvá záležitost.

Mezitím urazil Gradle dlouhou cestu: 7. května vyšla verze 1.6 a osobně si myslím, že Gradle je dostatečně zralá technologie pro nasazení do enterprise prostředí. Nejsou to plané řeči - na posledním projektu jsem zvažoval, na čem postavit release management a po zralé úvaze, kdy ve hře byl kromě Gradlu také Maven, Ant, čisté Groovy a shell skripty, jsem zvolil právě Gradle jako řešení, které nejvíce vyhovovalo dané situaci a bylo dostatečně robustní, srozumitelné a rozšiřitelné, aby fungovalo i po mém odchodu z projektu.

Právě při přípravě release managementu tohoto projektu jsem se musel do Gradlu důkladně ponořit, protože jsem musel vyřešit některé ne úplně triviální záležitosti. Abych nabyté vědomosti nějak zúročil, uspořádal jsem rozlučkovou přednášku o Gradlu ve svém bývalém zaměstnání. Přednáška je k dispozici na SlideShare:



Tutorial

No a abych nabyté vědomosti ještě více zúročil :-)  rozhodl jsem se napsat o Gradlu tutorial, který budu na svém blogu postupně publikovat. Jednak bych si formát tutorialu rád vyzkoušel, ale hlavně bych se chtěl o své zkušenosti podělit. Právě sdílení informací je jedna z věcí, které mne (jako seniorního vývojáře) baví.

Pokud se již nemůžete dočkat první lekce, můžete si čekání ukrátit instalací Gradlu. V tutorialu budu používat nejaktuálnější verzi 1.7, která je dostupná v nočních buildech, ale můžete použít i stabilní verzi.


Pokud máte nějaké téma, které byste rádi v tutorialu viděli, napište mi a já se ho pokusím někam zařadit. No a samozřejmě budu rád za každý komentář.

[update 7. 6. 2013] Domluvil jsem se se serverem Zdroják, že budu (přednostně) tutorial publikovat i tam (seriál Gradle - moderní nástroj na automatizaci). Vzhledem k "publikační mašinerii" tak budou jednotlivé díly vycházet s většími prodlevami, než jsem původně počítal. Ale co to je z pohledu věčnosti? :-) [/update]

Související články