Halaman ini membahas sistem build berbasis tugas, cara kerjanya, dan beberapa komplikasi yang dapat terjadi dengan sistem berbasis tugas. Setelah skrip shell, sistem build berbasis tugas adalah evolusi logis berikutnya dari build.
Memahami sistem build berbasis tugas
Dalam sistem build berbasis tugas, unit kerja dasar adalah tugas. Setiap tugas adalah skrip yang dapat menjalankan logika apa pun, dan tugas menentukan tugas lain sebagai dependensi yang harus dijalankan sebelum tugas tersebut. Sebagian besar sistem build utama yang digunakan saat ini, seperti Ant, Maven, Gradle, Grunt, dan Rake, berbasis tugas. Alih-alih skrip shell, sebagian besar sistem build modern mengharuskan engineer untuk membuat file build yang menjelaskan cara menjalankan build.
Lihat contoh ini dari panduan Ant:
<project name="MyProject" default="dist" basedir=".">
<description>
simple example build file
</description>
<!-- set global properties for this build -->
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="dist" location="dist"/>
<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}"/>
</target>
<target name="compile" depends="init"
description="compile the source">
<!-- Compile the Java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}"/>
</target>
<target name="dist" depends="compile"
description="generate the distribution">
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
<!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>
<target name="clean"
description="clean up">
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>
File build ditulis dalam XML dan menentukan beberapa metadata sederhana tentang build
bersama dengan daftar tugas (tag <target>
dalam XML). (Ant menggunakan kata
target untuk mewakili tugas, dan menggunakan kata tugas untuk merujuk ke
perintah.) Setiap tugas mengeksekusi daftar kemungkinan perintah yang ditentukan oleh Ant,
yang di sini meliputi pembuatan dan penghapusan direktori, menjalankan javac
, dan
membuat file JAR. Kumpulan perintah ini dapat diperluas oleh plugin yang disediakan pengguna untuk mencakup semua jenis logika. Setiap tugas juga dapat menentukan tugas yang
diperlukan melalui atribut depends. Dependensi ini membentuk grafik acyclic,
seperti yang terlihat pada Gambar 1.
Gambar 1. Grafik acyclic yang menunjukkan dependensi
Pengguna melakukan build dengan memberikan tugas ke alat command line Ant. Misalnya,
saat pengguna mengetik ant dist
, Ant akan melakukan langkah-langkah berikut:
- Memuat file bernama
build.xml
di direktori saat ini dan mengurainya untuk membuat struktur grafik yang ditampilkan pada Gambar 1. - Mencari tugas bernama
dist
yang disediakan di command line dan menemukan bahwa tugas tersebut memiliki dependensi pada tugas bernamacompile
. - Mencari tugas bernama
compile
dan menemukan bahwa tugas tersebut memiliki dependensi pada tugas bernamainit
. - Mencari tugas bernama
init
dan menemukan bahwa tugas tersebut tidak memiliki dependensi. - Menjalankan perintah yang ditentukan dalam tugas
init
. - Menjalankan perintah yang ditentukan dalam tugas
compile
mengingat semua dependensi tugas tersebut telah dijalankan. - Menjalankan perintah yang ditentukan dalam tugas
dist
dengan mempertimbangkan bahwa semua dependensi tugas tersebut telah dijalankan.
Pada akhirnya, kode yang dieksekusi oleh Ant saat menjalankan tugas dist
setara
dengan skrip shell berikut:
./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*
Jika sintaksis dihilangkan, buildfile dan skrip build sebenarnya tidak terlalu berbeda. Namun, kita telah memperoleh banyak hal dengan melakukan hal ini. Kita dapat
membuat file build baru di direktori lain dan menautkannya. Kita dapat dengan mudah
menambahkan tugas baru yang bergantung pada tugas yang ada dengan cara arbitrer dan kompleks. Kita
hanya perlu meneruskan nama satu tugas ke alat command line ant
, dan alat ini
akan menentukan semua yang perlu dijalankan.
Ant adalah software lawas, awalnya dirilis pada tahun 2000. Alat lain seperti Maven dan Gradle telah meningkatkan Ant dalam beberapa tahun terakhir dan pada dasarnya mengganti Ant dengan menambahkan fitur seperti pengelolaan dependensi eksternal otomatis dan sintaksis yang lebih bersih tanpa XML. Namun, karakteristik sistem yang lebih baru ini tetap sama: sistem tersebut memungkinkan engineer untuk menulis skrip build dengan cara yang berprinsip dan modular sebagai tugas dan menyediakan alat untuk menjalankan tugas tersebut serta mengelola dependensi di antaranya.
Sisi gelap sistem build berbasis tugas
Karena alat ini pada dasarnya memungkinkan engineer menentukan skrip apa pun sebagai tugas, alat ini sangat canggih, sehingga Anda dapat melakukan hampir semua hal yang dapat Anda bayangkan dengan alat ini. Namun, kemampuan tersebut memiliki kekurangan, dan sistem build berbasis tugas dapat menjadi sulit digunakan seiring dengan semakin kompleksnya skrip build. Masalah dengan sistem tersebut adalah sistem tersebut akhirnya memberikan terlalu banyak kekuatan kepada engineer dan tidak cukup kekuatan untuk sistem. Karena sistem tidak tahu apa yang dilakukan skrip, performa akan terpengaruh, karena harus sangat konservatif dalam cara menjadwalkan dan menjalankan langkah-langkah build. Selain itu, sistem tidak dapat mengonfirmasi bahwa setiap skrip melakukan hal yang seharusnya, sehingga skrip cenderung menjadi lebih rumit dan akhirnya menjadi hal lain yang perlu proses debug.
Kesulitan dalam memparalelkan langkah build
Workstation pengembangan modern sangatlah canggih, dengan beberapa core yang mampu menjalankan beberapa langkah build secara paralel. Namun, sistem berbasis tugas sering kali tidak dapat melakukan paralelisasi eksekusi tugas meskipun tampaknya dapat melakukannya. Misalkan tugas A bergantung pada tugas B dan C. Karena tugas B dan C tidak saling bergantung, apakah aman untuk menjalankannya secara bersamaan sehingga sistem dapat lebih cepat menjalankan tugas A? Mungkin, jika keduanya tidak menyentuh resource yang sama. Namun, mungkin tidak—mungkin keduanya menggunakan file yang sama untuk melacak statusnya dan menjalankannya secara bersamaan menyebabkan konflik. Secara umum, sistem tidak dapat mengetahuinya, sehingga sistem harus mengambil risiko konflik ini (menyebabkan masalah build yang jarang terjadi tetapi sangat sulit di-debug), atau harus membatasi seluruh build agar berjalan di satu thread dalam satu proses. Hal ini dapat menjadi pemborosan besar bagi mesin developer yang canggih, dan sepenuhnya menghilangkan kemungkinan mendistribusikan build di beberapa mesin.
Kesulitan saat melakukan build inkremental
Sistem build yang baik memungkinkan engineer melakukan build inkremental yang andal sehingga perubahan kecil tidak memerlukan seluruh codebase untuk di-build ulang dari awal. Hal ini sangat penting jika sistem build lambat dan tidak dapat memparalelkan langkah-langkah build karena alasan yang disebutkan di atas. Namun sayangnya, sistem build berbasis tugas juga kesulitan. Karena tugas dapat melakukan apa saja, secara umum tidak ada cara untuk memeriksa apakah tugas tersebut telah selesai. Banyak tugas hanya mengambil kumpulan file sumber dan menjalankan compiler untuk membuat kumpulan biner; sehingga, tugas tersebut tidak perlu dijalankan ulang jika file sumber yang mendasarinya belum berubah. Namun, tanpa informasi tambahan, sistem tidak dapat memastikannya —mungkin tugas mendownload file yang dapat berubah, atau mungkin menulis stempel waktu yang mungkin berbeda setiap kali dijalankan. Untuk menjamin kebenaran, sistem biasanya harus menjalankan ulang setiap tugas selama setiap build. Beberapa sistem build mencoba mengaktifkan build inkremental dengan mengizinkan engineer menentukan kondisi saat tugas perlu dijalankan ulang. Terkadang hal ini bisa dilakukan, tetapi sering kali ini merupakan masalah yang jauh lebih rumit dari yang terlihat. Misalnya, dalam bahasa seperti C++ yang memungkinkan file disertakan langsung oleh file lain, Anda tidak dapat menentukan seluruh kumpulan file yang harus dipantau untuk perubahan tanpa mengurai sumber input. Engineer sering kali mengambil jalan pintas, dan jalan pintas ini dapat menyebabkan masalah yang jarang terjadi dan menjengkelkan saat hasil tugas digunakan kembali meskipun tidak seharusnya. Jika hal ini sering terjadi, engineer akan memiliki kebiasaan menjalankan clean sebelum setiap build untuk mendapatkan status baru, yang benar-benar mengalahkan tujuan memiliki build inkremental di awal. Menentukan kapan tugas perlu dijalankan ulang ternyata cukup rumit, dan merupakan tugas yang lebih baik ditangani oleh mesin daripada manusia.
Kesulitan mengelola dan men-debug skrip
Terakhir, skrip build yang diterapkan oleh sistem build berbasis tugas sering kali sulit digunakan. Meskipun sering kali tidak terlalu diteliti, skrip build adalah kode seperti sistem yang sedang dibuat, dan merupakan tempat yang mudah untuk menyembunyikan bug. Berikut adalah beberapa contoh bug yang sangat umum terjadi saat menggunakan sistem build berbasis tugas:
- Tugas A bergantung pada tugas B untuk menghasilkan file tertentu sebagai output. Pemilik tugas B tidak menyadari bahwa tugas lain bergantung padanya, sehingga ia mengubahnya untuk menghasilkan output di lokasi lain. Hal ini tidak dapat dideteksi hingga seseorang mencoba menjalankan tugas A dan mendapati bahwa tugas tersebut gagal.
- Tugas A bergantung pada tugas B, yang bergantung pada tugas C, yang menghasilkan file tertentu sebagai output yang diperlukan oleh tugas A. Pemilik tugas B memutuskan bahwa tugas tidak perlu bergantung pada tugas C lagi, yang menyebabkan tugas A gagal meskipun tugas B sama sekali tidak peduli dengan tugas C.
- Developer tugas baru tidak sengaja membuat asumsi tentang komputer yang menjalankan tugas, seperti lokasi alat atau nilai variabel lingkungan tertentu. Tugas tersebut berfungsi di komputernya, tetapi gagal setiap kali developer lain mencobanya.
- Tugas berisi komponen nondeterministik, seperti mendownload file dari internet atau menambahkan stempel waktu ke build. Sekarang, orang-orang mendapatkan hasil yang berpotensi berbeda setiap kali mereka menjalankan build, yang berarti bahwa engineer tidak akan selalu dapat mereproduksi dan memperbaiki kegagalan satu sama lain atau kegagalan yang terjadi pada sistem build otomatis.
- Tugas dengan beberapa dependensi dapat menyebabkan kondisi race. Jika tugas A bergantung pada tugas B dan tugas C, dan tugas B dan C mengubah file yang sama, tugas A akan mendapatkan hasil yang berbeda, bergantung pada tugas mana dari tugas B dan C yang selesai lebih dulu.
Tidak ada cara umum untuk mengatasi masalah performa, ketepatan, atau pemeliharaan dalam framework berbasis tugas yang diuraikan di sini. Selama engineer dapat menulis kode arbitrer yang berjalan selama build, sistem tidak akan memiliki cukup informasi untuk selalu dapat menjalankan build dengan cepat dan benar. Untuk mengatasi masalah ini, kita perlu mengambil beberapa kekuatan dari tangan engineer dan mengembalikannya ke tangan sistem serta merekonstruksi peran sistem bukan sebagai tugas yang berjalan, tetapi sebagai artefak yang dihasilkan.
Pendekatan ini menghasilkan pembuatan sistem build berbasis artefak, seperti Blaze dan Bazel.