Přejít k navigační liště

Zdroják » Různé » Gradle tutorial: tasky (pokračování)

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

Články Různé

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ě:

Gradle dependencies

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ánované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

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řeskoč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

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

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ďme 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é, jenom 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

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

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

„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

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. 

Související

Komentáře

Subscribe
Upozornit na
guest
4 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
BoneFlute

Jaké vnímáte zásadní výhody proti Antu, nebo Mavenu? Jaké máte zkušenosti s čitelností a robusností build scriptů v tom napsané?

BoneFlute

Moje největší výhrady proti skriptovacím jazykům (bash, python, atd) spočívá v tom, že když do něčeho šáhnu, tak to rozbiju. Vulgárně řečeno více to neběží, než běží.

K tomu patří čitelné chybové hlášku (v případě make jsem je snad nikdy nepochopil)
A také aby se to snažilo dělat to co chci, nikoliv to co řeknu. Tudíž blbuvzdornost.

Jak byste to ohodnotil v tomto?

BoneFlute

Možná budu konkrétnější.

Napsal jsem relativně hodně různých build scriptů. Hlavně v Antu a jeho PHP reimplementace Phing. A největší problém spočívat v tom, že jak jsem chtěl složitější a složitější chování, tak ten script byl víc a víc zmatenej. Takže jsem si musel sám sobě definovat určitá pravidla, abych v tom udržel pořádek.

Když čtu o nespoutanosti Groovy, tak jsem si právě na to vzpoměl.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.