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

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

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

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

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

चींटी मैन्युअल से इस उदाहरण को देखें:

<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 के बताए गए निर्देशों की सूची बनाता है. इन निर्देशों में डायरेक्ट्री बनाना और मिटाना, javac चलाना, और JAR फ़ाइल बनाना शामिल है. उपयोगकर्ता से मिले प्लग-इन की मदद से, कमांड के इस सेट को बढ़ाया जा सकता है, ताकि किसी भी तरह के लॉजिक को कवर किया जा सके. डिपेंडेंसी एट्रिब्यूट का इस्तेमाल करके, हर टास्क अपने हिसाब से टास्क तय कर सकता है. ये डिपेंडेंसी एक असाइकलिक ग्राफ़ बनाती हैं, जैसा कि इमेज 1 में दिखाया गया है.

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

चित्र 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.