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

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

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

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

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

<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. डिपेंडेंसी दिखाने वाला एसाइकल ग्राफ़

उपयोगकर्ता, 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 कमांड-लाइन टूल को भेजना होता है. इससे वह सब कुछ तय किया जाता है जिसे चलाने की ज़रूरत होती है.

चींटी, सॉफ़्टवेयर का एक पुराना टुकड़ा है, जिसे मूल रूप से 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 पूरा हुआ है.

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

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