इस पेज पर, टास्क पर आधारित बिल्ड सिस्टम के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि ये सिस्टम कैसे काम करते हैं और इनमें कौन-कौनसी समस्याएं आ सकती हैं. शेल स्क्रिप्ट के बाद, टास्क पर आधारित बिल्ड सिस्टम, बिल्ड करने का अगला लॉजिकल तरीका है.
टास्क पर आधारित बिल्ड सिस्टम के बारे में जानकारी
टास्क पर आधारित बिल्ड सिस्टम में, काम की बुनियादी इकाई टास्क होती है. हर टास्क एक स्क्रिप्ट होती है, जो किसी भी तरह के लॉजिक को लागू कर सकती है. टास्क, अन्य टास्क को डिपेंडेंसी के तौर पर तय करते हैं. ये डिपेंडेंसी, टास्क से पहले लागू होनी चाहिए. आजकल इस्तेमाल किए जा रहे ज़्यादातर बिल्ड सिस्टम, टास्क पर आधारित होते हैं. जैसे, 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> टैग) तय करती है. (Ant, टास्क को दिखाने के लिए टारगेट शब्द का इस्तेमाल करता है. साथ ही, कमांड के लिए टास्क शब्द का इस्तेमाल करता है.) हर टास्क, Ant की ओर से तय की गई संभावित कमांड की सूची को लागू करता है. इसमें डायरेक्ट्री बनाना और मिटाना, javac चलाना, और JAR फ़ाइल बनाना शामिल है. उपयोगकर्ता से मिले प्लग-इन की मदद से, कमांड के इस सेट को बढ़ाया जा सकता है, ताकि किसी भी तरह के लॉजिक को शामिल किया जा सके. हर टास्क, depends एट्रिब्यूट के ज़रिए उन टास्क को भी तय कर सकता है जिन पर वह निर्भर करता है. ये डिपेंडेंसी, एक ऐसाइक्लिक ग्राफ़ बनाती हैं. जैसा कि पहले फ़िगर में दिखाया गया है.
इमेज 1. डिपेंडेंसी दिखाने वाला एक ऐसाइक्लिक ग्राफ़
उपयोगकर्ता, Ant के कमांड-लाइन टूल को टास्क देकर बिल्ड करते हैं. उदाहरण के लिए, जब कोई उपयोगकर्ता ant dist टाइप करता है, तो Ant ये कार्रवाइयां करता है:
- यह मौजूदा डायरेक्ट्री में
build.xmlनाम की फ़ाइल लोड करता है और उसे पार्स करके, ग्राफ़ स्ट्रक्चर बनाता है. यह ग्राफ़ स्ट्रक्चर, इमेज 1 में दिखाया गया है. - यह कमांड लाइन पर दिए गए
distनाम के टास्क को ढूंढता है और यह पता लगाता है कि यहcompileनाम के टास्क पर निर्भर है. compileनाम के टास्क को ढूंढता है और उसे पता चलता है कि यहinitनाम के टास्क पर निर्भर है.initनाम के टास्क को खोजता है और पता लगाता है कि इसकी कोई डिपेंडेंसी नहीं है.- यह
initटास्क में तय की गई कमांड को पूरा करता है. - यह
compileटास्क में तय किए गए निर्देशों को तब लागू करता है, जब उस टास्क की सभी डिपेंडेंसी पूरी हो गई हों. - यह
distटास्क में तय किए गए निर्देशों को तब लागू करता है, जब उस टास्क की सभी डिपेंडेंसी पूरी हो गई हों.
आखिर में, dist टास्क को चलाने पर Ant से लागू किया गया कोड, इस शेल स्क्रिप्ट के बराबर होता है:
./createTimestamp.shmkdir 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 पर निर्भर करता है. टास्क B, टास्क C पर निर्भर करता है. टास्क C, आउटपुट के तौर पर एक खास फ़ाइल जनरेट करता है, जिसकी ज़रूरत टास्क A को होती है. टास्क B का मालिक यह तय करता है कि अब इसे टास्क C पर निर्भर रहने की ज़रूरत नहीं है. इससे टास्क A पूरा नहीं हो पाता, भले ही टास्क B को टास्क C से कोई मतलब न हो!
- किसी नए टास्क का डेवलपर, टास्क को चलाने वाली मशीन के बारे में गलती से कोई अनुमान लगा लेता है. जैसे, किसी टूल की जगह या किसी खास एनवायरमेंट वैरिएबल की वैल्यू. यह टास्क उनकी मशीन पर काम करता है, लेकिन जब भी कोई दूसरा डेवलपर इसे आज़माता है, तो यह काम नहीं करता.
- टास्क में कोई ऐसा कॉम्पोनेंट शामिल है जो पहले से तय नहीं होता. जैसे, इंटरनेट से कोई फ़ाइल डाउनलोड करना या किसी बिल्ड में टाइमस्टैंप जोड़ना. अब, लोगों को हर बार बिल्ड चलाने पर अलग-अलग नतीजे मिलते हैं. इसका मतलब है कि इंजीनियर, एक-दूसरे की गड़बड़ियों या ऑटोमेटेड बिल्ड सिस्टम पर होने वाली गड़बड़ियों को हमेशा ठीक नहीं कर पाएंगे.
- एक से ज़्यादा डिपेंडेंसी वाले टास्क से रेस कंडीशन बन सकती हैं. अगर टास्क A, टास्क B और टास्क C, दोनों पर निर्भर करता है और टास्क B और C, दोनों एक ही फ़ाइल में बदलाव करते हैं, तो टास्क A का नतीजा अलग-अलग होगा. यह इस बात पर निर्भर करेगा कि टास्क B और C में से कौनसा टास्क पहले पूरा होता है.
यहां दिए गए टास्क-आधारित फ़्रेमवर्क में, परफ़ॉर्मेंस, सही जवाब, या रखरखाव से जुड़ी इन समस्याओं को हल करने का कोई सामान्य तरीका नहीं है. जब तक इंजीनियर, बिल्ड के दौरान चलने वाला कोई भी कोड लिख सकते हैं, तब तक सिस्टम के पास इतनी जानकारी नहीं हो सकती कि वह हमेशा बिल्ड को तेज़ी से और सही तरीके से चला सके. इस समस्या को हल करने के लिए, हमें कुछ काम इंजीनियरों से लेकर सिस्टम को देने होंगे. साथ ही, सिस्टम की भूमिका को फिर से तय करना होगा. सिस्टम को टास्क पूरा करने के बजाय, आर्टफ़ैक्ट बनाने का काम देना होगा.
इस तरीके से, आर्टफ़ैक्ट पर आधारित बिल्ड सिस्टम बनाए गए. जैसे, Blaze और Bazel.
