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

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

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

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

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

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

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