Memakai Gradle Untuk Mem-build Griffon Dari Source


Selama ini saya memakai Maven yang sudah mature.   Dan kini, bertambah satu lagi tool untuk manajemen proyek yang saya temukan, yaitu Gradle.   Salah satu proyek yang menggunakan Gradle adalah framework Griffon.  Contoh lain, misalnya proyek Android SDK  yang  beralih memakai Gradle.  Sejujurnya, saya lebih memilih Maven sebagai satu-satunya produk manajemen proyek yang diterapkan secara universal.   Tapi bila proyek yang ingin saya modifikasi tidak mendukung Maven, maka cara tergampang mungkin adalah dengan mengikuti ‘arus’ kehendak si pembuat proyek🙂

Apa kelebihan Gradle? Gradle tidak memakai XML melainkan memakai Groovy sebagai DSL (Domain Specific Language)-nya. Dengan demikian, saya bisa menyertakan ‘kode program‘ di Gradle.   Penggunaan Groovy di script Gradle bahkan memungkinkan pengguna untuk membuat task secara dinamis, sebuah hal yang sulit dicapai dengan hanya berbekal XML.   Gradle juga dapat memanggil Ant task bila diperlukan.   Gradle tetap dapat memakai repository Maven yang saat ini sudah sangat banyak dipakai.

Untuk memakai Gradle, saya dapat men-download-nya di www.gradle.org/downloads.   Saya akan men-extract folder yang ada di dalam file Zip tersebut.   Setelah itu, saya mengatur environment variable GRADLE_HOME agar berisi lokasi folder tersebut.   Tidak lupa, saya juga menambahkan lokasi %GRADLE_HOME%\bin ke dalam environment variable PATH.

Untuk menguji apakah Gradle ter-install dengan baik, saya membuka Command Prompt dan memberikan perintah seperti berikut ini:

C:\>gradle -version

------------------------------------------------------------
Gradle 1.3
------------------------------------------------------------

...

Tidak lama kemudian, saya men-pull (mengambil kode program) Griffon dari GitHub.   Saya bisa melihat informasi mengenai proyek tersebut dengan masuk (baca: cd) ke lokasi folder yang berisi kode program Griffon, lalu memberikan perintah seperti ini:

C:\projects\griffon-1-2.0>gradle -q projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'griffon'
+--- Project ':griffon-cli'
+--- Project ':griffon-guide'
+--- Project ':griffon-resources'
+--- Project ':griffon-rt'
+--- Project ':griffon-scripts'
+--- Project ':griffon-shell'
\--- Project ':griffon-wrapper'

To see a list of the tasks of a project, run gradle :tasks
For example, try running gradle :griffon-cli:tasks

Untuk melihat task yang dapat dipanggil, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend
on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles the main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles the test classes.

Documentation tasks
-------------------
...

Help tasks
----------
...

IDE tasks
---------
cleanEclipse - Cleans all Eclipse files.
cleanIdea - Cleans IDEA project files (IML, IPR)
eclipse - Generates all Eclipse files.
idea - Generates IDEA project files (IML, IPR, IWS)

Installation tasks
------------------
izPackCreateInstaller - Creates an IzPack-based installer

Upload tasks
------------
uploadArchives - Uploads all artifacts belonging to configuration ':griffon-cli:
archives'

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
...

Rules
-----
...

Untuk melihat ketergantungan proyek, misalnya untuk melihat dependency subproyek griffon-scripts, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q dependencies griffon-scripts:dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

default - Configuration for default artifacts.
No dependencies

izpack - The IzPack standalone compiler libraries to be used for this project.
No dependencies

------------------------------------------------------------
Project :griffon-scripts
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

checkstyle - The Checkstyle libraries to be used for this project.
No dependencies

clover
\--- com.cenqua.clover:clover:3.1.2

cobertura
+--- net.sourceforge.cobertura:cobertura:1.9.4.1
|    +--- oro:oro:2.0.8
|    +--- asm:asm:3.0
|    +--- asm:asm-tree:3.0
|    |    \--- asm:asm:3.0 (*)
|    +--- log4j:log4j:1.2.9
|    \--- org.apache.ant:ant:1.7.0
|         \--- org.apache.ant:ant-launcher:1.7.0
\--- junit:junit:4.10
     \--- org.hamcrest:hamcrest-core:1.1

codenarc - The CodeNarc libraries to be used for this project.
No dependencies

compile - Classpath for compiling the main sources.
+--- org.codehaus.griffon:griffon-rt:1.2.0
|    +--- org.codehaus.groovy:groovy-all:2.0.6
|    +--- log4j:log4j:1.2.17
|    +--- org.slf4j:slf4j-log4j12:1.7.2
|    +--- org.slf4j:slf4j-api:1.7.2
|    +--- org.slf4j:jcl-over-slf4j:1.7.2
|    |    \--- org.slf4j:slf4j-api:1.7.2 (*)
|    \--- org.slf4j:jul-to-slf4j:1.7.2
+--- org.codehaus.griffon:griffon-cli:1.2.0
|    +--- org.codehaus.griffon:griffon-rt:1.2.0 (*)
|    +--- org.codehaus.griffon:griffon-resources:1.2.0
|    +--- junit:junit:4.10
|    |    \--- org.hamcrest:hamcrest-core:1.1
|    +--- asm:asm:3.2
|    +--- commons-cli:commons-cli:1.2
|    +--- commons-io:commons-io:2.4
|    +--- commons-lang:commons-lang:2.6
|    +--- commons-collections:commons-collections:3.2.1
|    +--- commons-codec:commons-codec:1.6
|    +--- org.apache.httpcomponents:httpcore:4.1.2
|    +--- com.jcraft:jzlib:1.1.1
|    +--- xml-resolver:xml-resolver:1.2
|    +--- org.fusesource.jansi:jansi:1.9
|    +--- jline:jline:0.9.94
|    |    \--- junit:junit:3.8.1 -> 4.10 (*)
|    +--- org.yaml:snakeyaml:1.9
|    +--- radeox:radeox:1.0-b2
|    +--- org.apache.ant:ant-launcher:1.8.4
|    +--- biz.aQute:bndlib:1.50.0
|    +--- org.apache.ant:ant:1.8.4
|    +--- org.apache.ant:ant-junit:1.8.4
|    +--- org.springframework:org.springframework.core:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.beans:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context.support:3.2.0.RELEASE
|    +--- org.xhtmlrenderer:core-renderer:R8
|    +--- com.lowagie:itext:2.0.8
|    +--- org.grails:grails-docs:2.2.0
|    |    +--- org.yaml:snakeyaml:1.8 -> 1.9 (*)
|    |    +--- org.grails:grails-gdoc-engine:1.0.1
|    |    |    \--- org.slf4j:jcl-over-slf4j:1.6.1 -> 1.7.2 (*)
|    |    +--- commons-lang:commons-lang:2.6 (*)
|    |    \--- org.slf4j:jcl-over-slf4j:1.6.2 -> 1.7.2 (*)
|    +--- org.grails:grails-gdoc-engine:1.0.1 (*)
|    +--- org.apache.ivy:ivy:2.2.0
|    +--- org.codehaus.gant:gant_groovy2.0:1.9.8
|    +--- org.codehaus.groovy.modules.http-builder:http-builder:0.5.2
|    +--- net.sf.json-lib:json-lib:2.4
|    +--- net.sf.ezmorph:ezmorph:1.0.6
|    +--- commons-beanutils:commons-beanutils:1.8.3
|    +--- org.apache.httpcomponents:httpclient:4.1.2
|    +--- com.jcraft:jsch:0.1.48
|    +--- xerces:xercesImpl:2.9.1
|    \--- org.codehaus.groovy:groovy-all:2.0.6 (*)
+--- org.codehaus.griffon:griffon-resources:1.2.0 (*)
\--- org.codehaus.groovy:groovy-all:2.0.6 (*)

...

Bagian penting dari sebuah proyek yang memakai Gradle adalah file script yang bernama build.gradle (setara dengan pom.xml di Maven). Pada bagian paling awal dari build.gradle milik source code Griffon, saya akan menemukan baris seperti berikut ini:

import java.text.SimpleDateFormat
import org.apache.ivy.plugins.resolver.URLResolver

Yup, terlihat sangat mirip kode program, bukan? Karena script ini memang adalah bentuk khusus dari kode program Groovy; bukan XML seperti di Maven.

Setelah itu, saya akan menemukan baris seperti berikut ini:

apply plugin: 'base'
apply plugin: 'idea'
apply plugin: 'eclipse'

Baris di atas menunjukkan bahwa apa saja plugin yang dipakai oleh build.gradle ini.   Plugin base menambahkan beberapa tasks untuk membuat dan menghasilkan file arsip dari proyek.   Plugin idea dan eclipse memungkinkan Gradle untuk menghasilkan file proyek IntelliJ IDEA dan Eclipse.   Beberapa cotnoh software development plugins lain adalah checkstyle, codenarc, eclipse-wtp, findbugs, jdepend, pmd, project-report, signing, dan sonar.

Setelah itu, saya akan menemukan baris seperti berikut ini:

evaluationDependsOn(':griffon-rt')
evaluationDependsOn(':griffon-cli')
evaluationDependsOn(':griffon-resources')
evaluationDependsOn(':griffon-scripts')
evaluationDependsOn(':griffon-shell')
evaluationDependsOn(':griffon-wrapper')
evaluationDependsOn(':griffon-guide')

evaluationDependsOn() dipakai untuk menandakan configuration dependencies, dimana file build.gradle yang ada di subproyek griffon-crt, griffon-cli, griffon-resources, griffon-scripts, griffon-shell, griffon-wrapper, dan griffon-guide akan di-evaluasi terlebih dahulu secara berurutan.

Subproyek? Yup!   Proyek Griffon merupakan sebuah contoh yang memakai multi-project builds di Gradle.   Bila saya membuka file settings.gradle, saya akan menemukan isinya seperti berikut ini:

include 'griffon-rt'
include 'griffon-cli'
include 'griffon-resources'
include 'griffon-scripts'
include 'griffon-wrapper'
include 'griffon-guide'
include 'griffon-shell'

rootProject.name = 'griffon'
rootProject.children.each {project ->
    String fileBaseName = project.name
    String projectDirName = "subprojects/$fileBaseName"
    project.projectDir = new File(settingsDir, projectDirName)
    project.buildFileName = "${fileBaseName}.gradle"
    assert project.projectDir.isDirectory()
    assert project.buildFile.isFile()
}

Secara sederhana, file tersebut akan mendaftarkan subproyek yang ada, dimana setiap subproyek berada di dalam folder subprojects.   Masing-masing subproyek memiliki file konfigurasi sesuai dengan nama subproyek tersebut, misalnya griffon-cli.gradle, griffon-guide.gradle, griffon-resources.gradle, dan sebagainya.

Kembali ke build.gradle yang ada di root project, saya akan menemukan baris seperti berikut ini:

subprojects { subproj ->
    apply plugin: 'idea'
    if(plugins.hasPlugin('java')){
        sourceCompatibility = 1.5
        targetCompatibility = 1.5
        group = 'org.codehaus.griffon'

        apply from: "${rootDir}/gradle/coverage.gradle"
        apply from: "${rootDir}/gradle/codeQuality.gradle"
    }

    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Bagian tersebut akan dikerjakan untuk seluruh subproyek yang ada.

Berikutnya, saya menemukan bagian seperti berikut ini:

Date buildTimeAndDate = new Date()
ext {
    buildTime = new SimpleDateFormat('dd-MMM-yyyy').format(buildTimeAndDate)
    buildDate = new SimpleDateFormat('hh:mm aa').format(buildTimeAndDate)
}

Ini adalah salah satu metode untuk memberikan variabel global di Gradle (melalui extra properties).

Lalu terdapat bagian seperti berikut ini:

configure(mavenizedProjects()) { proj ->
    proj.apply from: "${rootDir}/gradle/maven.gradle"
    proj.task('checkManifest') {
        dependsOn proj.classes
        doLast {
            proj.tasks.withType(Jar).each { jarfile ->
                jarfile.manifest {
                    attributes(
                        'Built-By': System.properties['user.name'],
                        'Created-By': System.properties['java.version'] + " (" + System.properties['java.vendor'] + " " + System.getProperty("java.vm.version") + ")",
                        'Build-Date': buildTime,
                        'Build-Time': buildDate,
                        'Specification-Title': proj.name,
                        'Specification-Version': project.version,
                        'Specification-Vendor': 'griffon-framework.org',
                        'Implementation-Title': proj.name,
                        'Implementation-Version': project.version,
                        'Implementation-Vendor': 'griffon-framework.org'
                    )
                }
            }
        }
    }
    proj.jar.dependsOn proj.checkManifest
}

Closure pada method configure() hanya akan diberlakukan pada subproyek yang diberikan sebagai parameternya.   Lalu, apa isi parameter mavenizedProjects() pada kode program di atas?  Saya dapat menemukannya di baris paling terakhir dari build.gradle:

def mavenizedProjects() {
    [
        project(':griffon-rt'),
        project(':griffon-cli'),
        project(':griffon-resources'),
        project(':griffon-scripts'),
        project(':griffon-shell')
    ]
}

Sekarang, saya akan mencoba melihat isi file konfigurasi untuk salah satu subproyek yang ada, yaitu griffon-cli. Saya akan menemukan baris seperti berikut ini di file griffon-cli.gradle (terletak di folder subprojects/griffon-cli):

apply plugin: 'groovy'
...
dependencies {
    groovy "org.codehaus.groovy:groovy-all:$groovyVersion"

    compile project(':griffon-rt'),
            project(':griffon-resources')
    compile 'junit:junit:4.10',
            'asm:asm:3.2',
            'commons-cli:commons-cli:1.2',
            'commons-io:commons-io:2.4',
            'commons-lang:commons-lang:2.6',
            'commons-collections:commons-collections:3.2.1',
            'commons-codec:commons-codec:1.6',
            'org.apache.httpcomponents:httpcore:4.1.2',
            'com.jcraft:jzlib:1.1.1',
            'xml-resolver:xml-resolver:1.2',
            'org.fusesource.jansi:jansi:1.9',
            'jline:jline:0.9.94',
            'org.yaml:snakeyaml:1.9',
            'radeox:radeox:1.0-b2',
            "org.apache.ant:ant-launcher:$antVersion",
            'biz.aQute:bndlib:1.50.0'
    ...
}

Method dependencies() menentukan apa saja yang dibutuhkan untuk bisa men-build subproyek griffon-cli.   groovy dan compile adalah salah satu configuration yang dapat dipakai bila subproyek mendaftarkan plugin groovy (atau plugin bahasa lainnya).

Bagian compile project(':griffon-rt'), project(':griffon-resources') menunjukkan bahwa subproyek griffon-cli bergantung pada subproyek griffon-rt dan griffon-resources.   Selain ketergantungan pada proyek, Gradle juga memungkinkan melakukan definisi ketergantungan terhadap file lain dengan files() atau isi pada folder dengan fileTree().

Kemana Gradle akan mencari dependencies yang dibutuhkan?   Gradle dapat mencari di repository Maven, Ivy, atau sebuah direktori yang ditentukan.   Bila saya kembali memperhatikan isi method subprojects() di build.gradle, saya akan menemukan lokasi pencarian, seperti yang terlihat berikut ini:

subprojects { subproj ->
    ...
    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Setelah memahami DSL yang dipakai Gradle, langkah berikutnya adalah men-import proyek Griffon agar dapat dipakai di IntelliJ IDEA.   Cara yang paling gampang adalah dengan membuka IntelliJ IDEA, memilih Import Project, kemudian pilih file build.gradle yang berada dalam folder griffon-1.2.0, kemudian men-klik tombol Next.   IntelliJ IDEA akan menampilkan struktur proyek yang ada disertai dengan pilihan untuk melakukan perubahan bila perlu.  Klik tombol Finish untuk selesai.

Struktur proyek yang dihasilkan berdasarkan file build.gradle Griffon akan terlihat seperti berikut ini:

Struktur Proyek Griffon

Struktur Proyek Griffon

IntelliJ IDEA juga memiliki sebuah window khusus bernama JetGradle project khusus untuk proyek Gradle, seperti yang terlihat pada gambar berikut ini:

Tampilan JetGradle Projects

Tampilan JetGradle Projects

Bagaimana cara menjalankan proyek?  Proyek Griffon tidak dijalankan, melainkan harus di-copy ke sebuah folder yaitu %GRIFFON_HOME% untuk dipakai oleh proyek lain.   Jika saya membuka file package.gradle di folder gradle, saya akan menemukan definisi task seperti berikut ini:

task installBinary(type: Sync, dependsOn: checkGriffonHome) {
    description = 'Installs the binary distribution at $GRIFFON_HOME.'
    destinationDir = griffonHomeDir as File
    with distBinSpec
    doLast {
        ant.chmod(dir: "${griffonHomeDir}/bin", excludes: '*.bat', perm: 'ugo+x')
    }
}

Dengan demikian, yang perlu saya lakukan setelah  melakukan perubahan kode program Griffon adalah memanggil task installBinary untuk men-copy hasil perubahan ke folder %GRIFFON_HOME% (sehingga seluruh proyek lain yang memakai framework Griffon akan memakai kode program Griffon hasil perubahan).

Untuk menjalankan perintah Gradle di IntelliJ IDEA, saya men-klik kanan file build.gradle kemudian memilih Create ‘build’…   Pada dialog yang muncul, saya mengisi Script parameters dengan installBinary, dan men-klik tombol OK. Sekarang, saya bisa men-klik icon Run atau menekan Shift+10 untuk mengerjakan task installBinary di IntelliJ IDEA.

Tapi ternyata tidak segampang ini! Build pertama gagal..   Berikut ini adalah pesan kesalahan yang saya peroleh:

Execution failed for task ':griffon-guide:java2htmlGenerateOverview'.
> Illegal/unsupported escape sequence near index 3
  C:\Projects\griffon-1.2.0\subprojects\griffon-guide\build\java2html
     ^

Saya sepertinya familiar dengan bug ini (kesalahan yang sama dengan yang saya temui di plugin CloudFoundry untuk Eclipse?!)  Di Windows, pemisah direktori adalah tanda “\” yang sekaligus adalah escape characters untuk String di Java.   Dengan demikian, karakter yang berusaha diterjemahkan adalah “\P” (tentunya tidak ada dan salah!).   Bila seandainya tidak ada developer Griffon yang mengalami kesalahan ini, berarti saya satu-satunya yang mencoba men-build Griffon di Windows :p

Salah satu solusi cepat yang saya tempuh untuk memperbaiki situasi di atas adalah dengan menghilangkan ketergantungan ke subproyek griffon-guide (sepertinya ini untuk dokumentasi, sehingga saat ini tidak begitu penting!). Caranya adalah dengan memberikan komentar (atau menghapus) bagian evaluationDependsOn(':griffon-guide') di build.gradle dan include 'griffon-guide' di settings.gradle. Tidak lupa, pada package.gradle, saya juga menghapus bagian from(project(':griffon-guide')) { ... | dan seluruh bagian into('doc') {..} into ('guide') {..}.

Setelah itu, saya kembali menklik icon Run di IntelliJ IDEA. Kali ini, build berjalan dengan lancar, yang ditandai dengan tulisan BUILD SUCCESSFUL.   Agar lebih yakin, saya dapat memeriksa lokasi %GRIFFON_HOME% apakah sudah berisi atau masih kosong.

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: