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

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

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

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

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

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

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

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

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

टास्क पर आधारित बिल्ड सिस्टम के बुरे नतीजे

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

बिल्ड के चरणों को एक साथ चलाने में आने वाली समस्या

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

इंक्रीमेंटल बिल्ड करने में परेशानी

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