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

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

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

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

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

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

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

  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 में से कौनसा टास्क पहले पूरा होगा.

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

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