Sistem Build Berbasis Tugas

Laporkan masalah Lihat sumber Per Malam · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Halaman ini membahas sistem build berbasis tugas, cara kerjanya, dan beberapa komplikasi yang dapat terjadi pada sistem berbasis tugas. Setelah skrip shell, sistem build berbasis tugas adalah evolusi logis berikutnya dari pembangunan.

Memahami sistem build berbasis tugas

Dalam sistem build berbasis tugas, unit kerja dasar adalah tugas. Masing-masing adalah skrip yang dapat mengeksekusi segala jenis logika, dan tugas menentukan tugas sebagai dependensi yang harus berjalan sebelum mereka. Sebagian besar sistem build utama yang digunakan saat ini, seperti Ant, Maven, Gradle, Grunt, dan Rake, berbasis tugas. Daripada fokus pada skrip shell, sebagian besar sistem build modern memerlukan insinyur untuk membuat file build yang menjelaskan cara menjalankan build.

Ambil contoh ini dari Manual 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>

Buildfile ditulis dalam XML dan mendefinisikan beberapa metadata sederhana tentang build beserta daftar tugas (tag <target> dalam XML). (Ant menggunakan kata target untuk mewakili tugas, dan menggunakan kata task untuk merujuk perintah.) Setiap tugas mengeksekusi daftar kemungkinan perintah yang ditentukan oleh Ant, yang di sini termasuk membuat dan menghapus direktori, menjalankan javac, dan membuat file JAR. Rangkaian perintah ini dapat diperluas dengan menyediakan {i>plug-in<i} untuk mencakup segala jenis logika. Setiap tugas juga bisa menentukan tugas yang bergantung pada atribut dependen. Ketergantungan ini membentuk grafik asiklik, seperti yang terlihat pada Gambar 1.

Grafik akrilik yang menunjukkan dependensi

Gambar 1. Grafik asiklik 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:

  1. Memuat file bernama build.xml dalam direktori saat ini dan mengurainya menjadi membuat struktur grafik yang ditunjukkan pada Gambar 1.
  2. Mencari tugas bernama dist yang disediakan pada command line dan menemukan bahwa ia memiliki dependensi pada tugas bernama compile.
  3. Mencari tugas bernama compile dan menemukan bahwa tugas tersebut memiliki dependensi pada tugas bernama init.
  4. Mencari tugas bernama init dan menemukan bahwa tugas tersebut tidak memiliki dependensi.
  5. Menjalankan perintah yang ditentukan dalam tugas init.
  6. Mengeksekusi perintah yang ditentukan dalam tugas compile mengingat semua itu dependensi tugas telah dijalankan.
  7. Mengeksekusi perintah yang ditentukan dalam tugas dist mengingat semua itu dependensi tugas telah dijalankan.

Pada akhirnya, kode yang dieksekusi oleh Ant saat menjalankan tugas dist setara ke 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/*

Ketika {i>syntax<i} dihilangkan, {i>buildfile<i} dan skrip build akan tidak terlalu berbeda. Namun, kami telah memperoleh banyak manfaat dengan melakukan hal ini. Kita dapat membuat {i>buildfile <i}baru di direktori lain dan menautkannya bersama-sama. Kita dapat dengan mudah menambahkan tugas baru yang tergantung pada tugas yang ada dengan cara yang sewenang-wenang dan kompleks. Rab hanya perlu meneruskan nama satu tugas ke alat command line ant, dan itu menentukan semua yang perlu dijalankan.

Ant adalah software lawas, awalnya dirilis pada tahun 2000. Alat lainnya seperti Maven dan Gradle telah memperbaiki Ant selama bertahun-tahun dan pada dasarnya menggantinya dengan menambahkan fitur seperti pengelolaan otomatis eksternal dependensi dan sintaks yang lebih bersih tanpa XML apa pun. Tetapi, sifat dari teknologi yang lebih baru ini tetap sama: sistem ini memungkinkan para insinyur untuk menulis skrip build dalam secara berprinsip dan modular sebagai tugas serta menyediakan alat untuk melaksanakan tugas-tugas tersebut dan mengelola ketergantungan di antara mereka.

Sisi gelap sistem build berbasis tugas

Karena alat-alat ini pada dasarnya memungkinkan para insinyur menentukan skrip apa pun sebagai tugas, mereka sangat canggih, memungkinkan Anda untuk melakukan hampir apa pun yang bisa Anda bayangkan dengan mereka. Namun, keunggulan tersebut ada kelemahannya, dan sistem build berbasis tugas menjadi sulit digunakan karena skrip build-nya menjadi lebih kompleks. Tujuan masalah pada sistem semacam itu adalah pada akhirnya sistem tersebut akan memberi banyak daya pada sistem teknisi dan tidak ada cukup daya ke sistem. Karena sistemnya tidak tahu apa yang dilakukan skrip, kinerjanya akan menurun, karena harus sangat konservatif dalam cara menjadwalkan dan mengeksekusi langkah-langkah build. Dan tidak ada cara bagi sistem untuk mengkonfirmasi bahwa setiap skrip melakukan apa yang seharusnya, sehingga skrip cenderung tumbuh dalam kompleks dan akhirnya menjadi hal lain yang memerlukan {i>debugging<i}.

Kesulitan dalam memparalelkan langkah build

Workstation pengembangan modern cukup canggih, dengan beberapa core yang mampu menjalankan beberapa langkah build secara paralel. Tetapi sistem berbasis tugas sering tidak dapat memparalelkan eksekusi tugas meskipun tampak seperti seharusnya Anda lakukan. Misalkan tugas A tergantung pada tugas B dan C. Karena tugas B dan C tidak saling bergantung satu sama lain, apakah aman untuk menjalankannya pada saat yang sama jadi bahwa sistem bisa lebih cepat menuju tugas A? Mungkin, jika mereka tidak menyentuh resource yang sama. Tapi mungkin tidak—mungkin keduanya menggunakan file yang sama untuk melacak status mereka dan menjalankannya bersamaan akan menyebabkan konflik. Tidak ada secara umum agar diketahui sistem, jadi ia harus mempertaruhkan konflik ini (yang menyebabkan masalah build yang jarang tetapi sangat sulit di-debug), atau harus membatasi seluruh build agar berjalan di satu thread dalam satu proses. Ini bisa menjadi pemborosan besar dari mesin pengembang yang canggih, dan itu sepenuhnya mengatur kemungkinan untuk mendistribusikan {i>build <i}di beberapa komputer.

Kesulitan saat melakukan build inkremental

Sistem build yang baik memungkinkan para engineer untuk melakukan build inkremental yang andal seperti bahwa perubahan kecil tidak memerlukan seluruh codebase untuk dibangun ulang dari awal. Hal ini sangat penting jika sistem build lambat dan tidak dapat memparalelkan langkah-langkah build karena alasan yang disebutkan di atas. Tapi sayangnya, sistem build berbasis tugas juga bermasalah di sini. Karena tugas dapat melakukan apa saja, secara umum, tidak ada cara untuk memeriksa apakah itu sudah dilakukan. Banyak tugas cukup mengambil satu set file sumber dan menjalankan kompilator untuk membuat satu set biner; jadi tidak perlu dijalankan ulang jika file sumber yang mendasarinya belum berubah. Namun, tanpa informasi tambahan, sistem tidak dapat mengatakannya pastinya—mungkin tugas tersebut mendownload file yang bisa diubah, atau mungkin menulis stempel waktu yang mungkin berbeda pada setiap operasi. Untuk menjamin ketepatan, sistem biasanya harus menjalankan kembali setiap tugas dalam setiap pembangunan. Agak besar sistem build mencoba mengaktifkan build inkremental dengan mengizinkan kondisi di mana tugas perlu dijalankan kembali. Terkadang hal ini bisa dilakukan, tetapi sering kali ini masalah yang jauh lebih rumit daripada yang terlihat. Misalnya, dalam bahasa seperti C++ yang memungkinkan file untuk disertakan langsung oleh file lain, tidak mungkin menentukan seluruh rangkaian file yang harus dipantau untuk melihat perubahan tanpa mengurai sumber input. Insinyur sering kali mengambil jalan pintas, dan pintasan ini dapat menyebabkan masalah yang jarang terjadi dan membuat frustrasi, di mana hasil tugas digunakan kembali bahkan ketika seharusnya tidak digunakan. Ketika hal ini sering terjadi, para insinyur mendapatkan menjadi kebiasaan untuk menjalankan bersih sebelum setiap bangunan untuk mendapatkan kondisi baru, sepenuhnya menggagalkan tujuan dari adanya build inkremental pada saat ini. Mencari tahu kapan suatu tugas perlu dijalankan kembali sangat halus, dan pekerjaan yang lebih baik ditangani oleh komputer daripada manusia.

Kesulitan mempertahankan dan men-debug skrip

Terakhir, skrip build yang diberlakukan oleh sistem build berbasis tugas sering kali hanya sulit untuk dikerjakan. Meskipun skrip tersebut sering kali menerima lebih sedikit pemeriksaan, buat skrip adalah kode seperti sistem yang sedang dibangun, dan merupakan tempat yang mudah bagi serangga untuk bersembunyi. Berikut adalah beberapa contoh {i>bug<i} yang sangat umum saat bekerja dengan sistem build berbasis tugas:

  • Tugas A bergantung pada tugas B untuk menghasilkan file tertentu sebagai {i>output<i}. Pemilik dari tugas B tidak menyadari bahwa tugas lain bergantung pada hal itu, jadi mereka mengubahnya menjadi menghasilkan {i>output<i} di lokasi yang berbeda. Ini tidak dapat dideteksi sampai seseorang mencoba menjalankan tugas A dan menemukan bahwa tugas itu gagal.
  • Tugas A tergantung pada tugas B, yang tergantung pada tugas C, yang menghasilkan {i>file<i} tertentu sebagai {i>output<i} yang diperlukan oleh tugas A. Pemilik tugas B memutuskan bahwa ia tidak perlu lagi bergantung pada tugas C, yang menyebabkan A gagal meskipun tugas B sama sekali tidak peduli dengan tugas C!
  • Pengembang tugas baru secara tidak sengaja membuat asumsi tentang seperti lokasi alat atau nilai variabel lingkungan tertentu. Tugas itu bekerja di komputer mereka, tetapi gagal setiap kali pengembang lain mencobanya.
  • Tugas berisi komponen nondeterministik, seperti mendownload file dari internet atau menambahkan stempel waktu ke build. Sekarang, orang-orang menjadi hasil yang berpotensi berbeda setiap kali mereka menjalankan build, artinya bahwa para insinyur tidak selalu dapat direproduksi dan memperbaiki kegagalan satu sama lain atau kegagalan yang terjadi pada sistem build otomatis.
  • Tugas dengan beberapa dependensi dapat membuat kondisi race. Jika tugas A tergantung pada tugas B dan tugas C, dan tugas B dan C keduanya memodifikasi , tugas A mendapatkan hasil yang berbeda tergantung pada tugas B dan C yang mana selesai lebih dahulu.

Tidak ada cara umum untuk menyelesaikan masalah performa, ketepatan, atau masalah pemeliharaan dalam kerangka kerja berbasis tugas yang diuraikan di sini. Selama ini karena para insinyur dapat menulis kode arbitrer yang berjalan selama pembangunan, maka tidak memiliki cukup informasi untuk dapat menjalankan {i>build <i}dengan cepat dan dengan benar. Untuk mengatasi masalah ini, kita perlu melepaskan beberapa kekuatan insinyur dan menyerahkannya kembali ke tangan sistem dan menyusun ulang konsep peran sistem bukan sebagai menjalankan tugas, tetapi sebagai menghasilkan artefak.

Pendekatan ini mengarah pada pembuatan sistem build berbasis artefak, seperti Blaze dan Bazel.