टास्क-आधारित बिल्ड सिस्टम

किसी समस्या की शिकायत करें सोर्स देखें Nightly · 7.4 .

इस पेज पर, टास्क पर आधारित बिल्ड सिस्टम के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि ये सिस्टम कैसे काम करते हैं और इनसे जुड़ी कुछ समस्याएं क्या हो सकती हैं. शेल स्क्रिप्ट के बाद, टास्क पर आधारित बिल्ड सिस्टम, बिल्डिंग के अगले लॉजिकल इवोल्यूशन हैं.

टास्क पर आधारित बिल्ड सिस्टम को समझना

टास्क पर आधारित बिल्ड सिस्टम में, काम की बुनियादी इकाई टास्क होती है. हर टास्क एक स्क्रिप्ट होती है, जो किसी भी तरह के लॉजिक को लागू कर सकती है. साथ ही, टास्क में अन्य टास्क को डिपेंडेंसी के तौर पर दिखाया जाता है, जिन्हें उनके पहले चलाना ज़रूरी होता है. फ़िलहाल, टास्क के आधार पर काम करने वाले ज़्यादातर मुख्य बिल्ड सिस्टम इस्तेमाल किए जा रहे हैं. जैसे, Ant, Maven, Gradle, Grunt, और Rake. ज़्यादातर आधुनिक बिल्ड सिस्टम में, इंजीनियरों को शेल स्क्रिप्ट के बजाय, बिल्ड फ़ाइलें बनानी पड़ती हैं. इन फ़ाइलों में, बिल्ड करने का तरीका बताया जाता है.

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>

बिल्डफ़ाइल को एक्सएमएल में लिखा जाता है. इसमें टास्क की सूची (एक्सएमएल में <target> टैग) के साथ-साथ, बिल्ड के बारे में कुछ आसान मेटाडेटा तय किया जाता है. (Ant, टास्क को दिखाने के लिए, टारगेट शब्द का इस्तेमाल करता है. साथ ही, कमांड के लिए, टास्क शब्द का इस्तेमाल करता है.) हर टास्क, Ant के बताए गए निर्देशों की सूची बनाता है. इन निर्देशों में डायरेक्ट्री बनाना और मिटाना, javac चलाना, और JAR फ़ाइल बनाना शामिल है. उपयोगकर्ता से मिले प्लग-इन की मदद से, कमांड के इस सेट को बढ़ाया जा सकता है, ताकि किसी भी तरह के लॉजिक को कवर किया जा सके. हर टास्क के लिए, 'इस पर निर्भर करता है' एट्रिब्यूट की मदद से यह भी बताया जा सकता है कि वह किन टास्क पर निर्भर करता है. ये डिपेंडेंसी, एक ऐसा ग्राफ़ बनाती हैं जिसमें कोई साइकल नहीं होता. जैसा कि पहले चित्र में दिखाया गया है.

डिपेंडेंसी दिखाने वाला ऐक्रिलिक ग्राफ़

चित्र 1. डिपेंडेंसी दिखाने वाला ऐसाइकल ग्राफ़

उपयोगकर्ता, Ant के कमांड-लाइन टूल को टास्क देकर बिल्ड करते हैं. उदाहरण के लिए, जब कोई उपयोगकर्ता ant dist टाइप करता है, तो Ant यह तरीका अपनाता है:

  1. यह मौजूदा डायरेक्ट्री में build.xml नाम की फ़ाइल को लोड करता है और उसे पार्स करके, पहली इमेज में दिखाया गया ग्राफ़ स्ट्रक्चर बनाता है.
  2. कमांड लाइन में दिया गया dist नाम का टास्क ढूंढने पर, उसे पता चलता है कि यह compile नाम के टास्क पर निर्भर है.
  3. compile नाम का टास्क ढूंढता है और पता चलता है कि यह init नाम के टास्क पर निर्भर है.
  4. init नाम का टास्क खोजता है और पता चलता है कि उस पर कोई अन्य टास्क निर्भर नहीं है.
  5. init टास्क में बताए गए निर्देशों को लागू करता है.
  6. compile टास्क में बताए गए निर्देशों को एक्ज़ीक्यूट करता है, क्योंकि उस टास्क की सभी डिपेंडेंसी चलाई जा चुकी हैं.
  7. dist टास्क में तय किए गए निर्देशों को तब चलाता है, जब उस टास्क की सभी डिपेंडेंसी चलाई जा चुकी हों.

आखिर में, dist टास्क को चलाते समय Ant के ज़रिए चलाया गया कोड, यहां दी गई शेल स्क्रिप्ट के बराबर होता है:

./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*

सिंटैक्स हटाने पर, बिल्ड फ़ाइल और बिल्ड स्क्रिप्ट में ज़्यादा अंतर नहीं होता. हालांकि, ऐसा करके हमने पहले ही बहुत कुछ हासिल कर लिया है. हम दूसरी डायरेक्ट्री में नई बिल्ड फ़ाइलें बनाकर उन्हें आपस में लिंक कर सकते हैं. हम ऐसे नए टास्क आसानी से जोड़ सकते हैं जो मौजूदा टास्क पर निर्भर हैं. ये टास्क, मनचाहे तरीके से और मुश्किल तरीकों से किए जा सकते हैं. हमें ant कमांड-लाइन टूल में सिर्फ़ एक टास्क का नाम पास करना होता है. इससे, चलाए जाने वाले सभी टास्क तय किए जाते हैं.

Ant एक पुराना सॉफ़्टवेयर है, जिसे पहली बार 2000 में रिलीज़ किया गया था. Maven और Gradle जैसे अन्य टूल ने इन सालों में Ant को बेहतर बनाया है. साथ ही, बाहरी डिपेंडेंसी के अपने-आप मैनेज होने और बिना किसी एक्सएमएल के बेहतर सिंटैक्स जैसी सुविधाएं जोड़कर, इसे बदल दिया है. हालांकि, इन नए सिस्टम का मकसद वही है: ये इंजीनियरों को टास्क के तौर पर, सिद्धांतों और मॉड्यूलर तरीके से बिल्ड स्क्रिप्ट लिखने की अनुमति देते हैं. साथ ही, इन टास्क को पूरा करने और उनके बीच डिपेंडेंसी मैनेज करने के लिए टूल उपलब्ध कराते हैं.

टास्क के आधार पर काम करने वाले बिल्ड सिस्टम के बुरे नतीजे

ये टूल, इंजीनियरों को किसी भी स्क्रिप्ट को टास्क के तौर पर तय करने की सुविधा देते हैं. इसलिए, ये बहुत ही असरदार टूल हैं. इनकी मदद से, अपनी कल्पना के मुताबिक कुछ भी किया जा सकता है. हालांकि, इस क्षमता में कुछ खामियां भी हैं. साथ ही, टास्क पर आधारित बिल्ड सिस्टम का इस्तेमाल करना मुश्किल हो सकता है, क्योंकि बिल्ड स्क्रिप्ट पहले से ज़्यादा जटिल होती जा रही है. इस तरह के सिस्टम की समस्या यह है कि वे इंजीनियर को ज़्यादा और सिस्टम को कम पावर देते हैं. सिस्टम को पता नहीं होता कि स्क्रिप्ट क्या कर रही हैं. इस वजह से, परफ़ॉर्मेंस पर असर पड़ता है. ऐसा इसलिए होता है, क्योंकि बिल्ड के चरणों को शेड्यूल करने और उन्हें लागू करने के तरीके में बहुत सावधानी बरतनी पड़ती है. साथ ही, सिस्टम के पास इस बात की पुष्टि करने का कोई तरीका नहीं होता कि हर स्क्रिप्ट ठीक से काम कर रही है या नहीं. इसलिए, स्क्रिप्ट की जटिलता बढ़ती जाती है और आखिर में उन्हें डीबग करना पड़ता है.

बिल्ड के चरणों को एक साथ चलाने में आने वाली समस्या

आधुनिक डेवलपमेंट वर्कस्टेशन काफ़ी बेहतर होते हैं. इनमें कई कोर होते हैं, जो एक साथ कई बिल्ड चरणों को पूरा कर सकते हैं. हालांकि, टास्क पर आधारित सिस्टम अक्सर टास्क को एक साथ पूरा नहीं कर पाते, भले ही ऐसा लग रहा हो कि वे ऐसा कर सकते हैं. मान लें कि टास्क A, टास्क B और C पर निर्भर करता है. टास्क B और C एक-दूसरे पर निर्भर नहीं हैं. इसलिए, क्या इन्हें एक साथ चलाना सुरक्षित है, ताकि सिस्टम टास्क A पर तेज़ी से पहुंच सके? ऐसा तब हो सकता है, जब वे एक ही संसाधन का इस्तेमाल न करें. हालांकि, ऐसा हो सकता है कि ऐसा न हो—शायद दोनों अपनी स्थिति ट्रैक करने के लिए एक ही फ़ाइल का इस्तेमाल करते हों और एक ही समय पर उन्हें चलाने से कोई समस्या आ रही हो. आम तौर पर, सिस्टम को यह पता नहीं चलता कि कौनसा वर्शन इस्तेमाल करना है. इसलिए, उसे इन विरोधों का जोखिम उठाना पड़ता है. इससे, बिल्ड से जुड़ी समस्याएं कभी-कभी होती हैं, लेकिन उन्हें डीबग करना बहुत मुश्किल होता है. इसके अलावा, सिस्टम को पूरे बिल्ड को एक प्रोसेस में एक थ्रेड पर चलाने की पाबंदी भी लगानी पड़ती है. इससे, डेवलपर की बेहतरीन मशीन का बहुत ज़्यादा इस्तेमाल हो सकता है. साथ ही, इससे एक से ज़्यादा मशीनों पर बिल्ड को डिस्ट्रिब्यूट करने की संभावना पूरी तरह से खत्म हो जाती है.

इंक्रीमेंटल बिल्ड करने में परेशानी

अच्छे बिल्ड सिस्टम की मदद से, इंजीनियर भरोसेमंद इंक्रीमेंटल बिल्ड कर सकते हैं. इससे, किसी छोटे बदलाव के लिए पूरे कोडबेस को फिर से बनाने की ज़रूरत नहीं होती. यह खास तौर पर तब ज़रूरी होता है, जब बिल्ड सिस्टम धीमा हो और ऊपर बताई गई वजहों से, बिल्ड के चरणों को साथ-साथ लोड न कर पाए. हालांकि, टास्क पर आधारित बिल्ड सिस्टम भी यहां काम नहीं करते. टास्क में कोई भी काम किया जा सकता है. इसलिए, आम तौर पर यह पता नहीं लगाया जा सकता कि टास्क पूरे हो चुके हैं या नहीं. कई टास्क में, सोर्स फ़ाइलों का एक सेट लिया जाता है और बाइनरी का एक सेट बनाने के लिए कंपाइलर चलाया जाता है. इसलिए, अगर सोर्स फ़ाइलों में कोई बदलाव नहीं हुआ है, तो उन्हें फिर से चलाने की ज़रूरत नहीं होती. हालांकि, ज़्यादा जानकारी के बिना सिस्टम यह पक्का नहीं कर सकता कि टास्क में कोई बदलाव हुआ है या नहीं. ऐसा हो सकता है कि टास्क में ऐसी फ़ाइल डाउनलोड की गई हो जिसमें बदलाव हो सकता है या फिर ऐसा हो सकता है कि टास्क में ऐसा टाइमस्टैंप लिखा गया हो जो हर बार अलग हो. सिस्टम को हर बिल्ड के दौरान हर टास्क को फिर से चलाना पड़ता है, ताकि यह पक्का किया जा सके कि वह सही है. कुछ बिल्ड सिस्टम, इंजीनियरों को उन शर्तों के बारे में बताने की सुविधा देते हैं जिनके तहत किसी टास्क को फिर से चलाना ज़रूरी है. इससे इंक्रीमेंटल बिल्ड की सुविधा चालू करने में मदद मिलती है. कभी-कभी ऐसा किया जा सकता है, लेकिन अक्सर यह समस्या जितनी आसान दिखती है उससे ज़्यादा मुश्किल होती है. उदाहरण के लिए, C++ जैसी भाषाओं में, फ़ाइलों के उस पूरे सेट को तय करना संभव नहीं है जो इनपुट सोर्स को पार्स किए बिना, बदलावों को देखने के लिए ज़रूरी है. इंजीनियर अक्सर शॉर्टकट लेते रहते हैं और इन शॉर्टकट की वजह से कभी-कभार ऐसी समस्याएं सामने आ सकती हैं जब किसी टास्क के नतीजे का फिर से इस्तेमाल किया जाता है. भले ही, उस नतीजे को पाना ही न हो. जब ऐसा बार-बार होता है, तो इंजीनियर हर बिल्ड से पहले, एक नया स्टेटस पाने के लिए क्लीन टास्क चलाने की आदत डाल लेते हैं. इससे, इंक्रीमेंटल बिल्ड का मकसद पूरा नहीं होता. किसी काम को फिर से चलाने की ज़रूरत का पता लगाना हैरानी की बात है और इस काम को इंसानों की तुलना में मशीनों ने बेहतर तरीके से संभाला है.

स्क्रिप्ट को मैनेज और डीबग करने में दिक्कत होना

आखिर में, टास्क पर आधारित बिल्ड सिस्टम की वजह से लागू होने वाली बिल्ड स्क्रिप्ट के साथ काम करना अक्सर मुश्किल होता है. आम तौर पर, इनकी जांच कम की जाती है. हालांकि, बिल्ड स्क्रिप्ट, बिल्ड किए जा रहे सिस्टम की तरह ही कोड होती हैं. साथ ही, इनमें बग आसानी से छिपे रहते हैं. यहां उन गड़बड़ियों के कुछ उदाहरण दिए गए हैं जो काम पर आधारित बिल्ड सिस्टम के साथ काम करते समय बहुत आम होती हैं:

  • टास्क A, टास्क B पर निर्भर करता है, ताकि आउटपुट के तौर पर कोई खास फ़ाइल जनरेट की जा सके. टास्क B के मालिक को यह पता नहीं होता कि दूसरे टास्क इस पर निर्भर करते हैं. इसलिए, वह आउटपुट को किसी दूसरी जगह पर बनाने के लिए उसे बदल देता है. इसकी जानकारी तब तक नहीं मिल सकती, जब तक कोई व्यक्ति टास्क A को चलाने की कोशिश न करे और उसे पता चल जाए कि वह काम नहीं कर रहा है.
  • टास्क A, टास्क B पर निर्भर करता है, जो टास्क C पर निर्भर करता है. टास्क C, आउटपुट के तौर पर एक खास फ़ाइल जनरेट करता है, जिसकी टास्क A को ज़रूरत होती है. टास्क B के मालिक ने तय किया कि उसे अब टास्क C पर निर्भर रहने की ज़रूरत नहीं है. इस वजह से, टास्क A पूरा नहीं हो पाता, भले ही टास्क B को टास्क C से कोई फ़र्क़ न पड़ता हो!
  • किसी नए टास्क का डेवलपर गलती से, टास्क को चलाने वाली मशीन के बारे में अनुमान लगा लेता है. जैसे, टास्क की जगह की जानकारी या किसी खास तरह के एनवायरमेंट वैरिएबल की वैल्यू. टास्क उनकी मशीन पर काम करता है, लेकिन जब भी कोई दूसरा डेवलपर उसे आज़माता है, तो फ़ेल हो जाता है.
  • टास्क में ऐसा कॉम्पोनेंट शामिल होता है जिसकी समयावधि तय नहीं होती. जैसे, इंटरनेट से कोई फ़ाइल डाउनलोड करना या किसी बिल्ड में टाइमस्टैंप जोड़ना. अब, जब भी लोग बिल्ड चलाते हैं, तो उन्हें अलग-अलग नतीजे मिल सकते हैं. इसका मतलब है कि इंजीनियर हमेशा एक-दूसरे की गड़बड़ियों को दोहराकर ठीक नहीं कर पाएंगे. इसके अलावा, ऑटोमेटेड बिल्ड सिस्टम में होने वाली गड़बड़ियों को भी ठीक नहीं कर पाएंगे.
  • एक से ज़्यादा डिपेंडेंसी वाले टास्क, रेस कंडीशन बना सकते हैं. अगर टास्क A के लिए टास्क B और टास्क C, दोनों का इस्तेमाल किया जाता है और टास्क B और C, दोनों एक ही फ़ाइल में बदलाव करते हैं, तो टास्क A को अलग नतीजा मिलेगा. यह इस बात पर निर्भर करेगा कि टास्क B और C में से कौनसा पहले पूरा करता है.

यहां बताए गए टास्क-आधारित फ़्रेमवर्क में, परफ़ॉर्मेंस, सटीक होने या रखरखाव से जुड़ी समस्याओं को हल करने का कोई आम तरीका नहीं है. जब तक इंजीनियर बिल्ड के दौरान चलने वाले आर्बिट्रेरी कोड लिख सकते हैं, तब तक सिस्टम में बिल्ड को तुरंत और सही तरीके से चलाने के लिए ज़रूरी जानकारी मौजूद नहीं होती. इस समस्या को हल करने के लिए, हमें इंजीनियरों से कुछ अधिकार वापस लेना होगा और उन्हें सिस्टम के पास वापस देना होगा. साथ ही, सिस्टम की भूमिका को टास्क चलाने के बजाय, आर्टफ़ैक्ट बनाने के तौर पर फिर से तय करना होगा.

इस तरीके से, आर्टफ़ैक्ट पर आधारित बिल्ड सिस्टम बनाए गए. जैसे, Blaze और Bazel.