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

किसी समस्या की शिकायत करें सोर्स देखें रात · 7.4 को अपनाएं. 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

इंंक्रीमेंटल बिल्ड करने में समस्या आना

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

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

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

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

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

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