इस पेज पर, टास्क-आधारित बिल्ड सिस्टम, उनके काम करने के तरीके, और उनसे जुड़ी कुछ जटिलताओं के बारे में बताया गया है. शेल स्क्रिप्ट के बाद, टास्क-आधारित बिल्ड सिस्टम, बिल्ड करने के तरीके का अगला तार्किक तरीका है.
टास्क-आधारित बिल्ड सिस्टम के बारे में जानकारी
टास्क-आधारित बिल्ड सिस्टम में, काम की बुनियादी यूनिट टास्क होती है. हर टास्क एक स्क्रिप्ट होती है, जो किसी भी तरह की लॉजिक को एक्ज़ीक्यूट कर सकती है. साथ ही, टास्क, अन्य टास्क को डिपेंडेंसी के तौर पर तय करते हैं, जिन्हें उनके पहले रन करना ज़रूरी होता है. आजकल इस्तेमाल किए जाने वाले ज़्यादातर बड़े बिल्ड सिस्टम, जैसे कि Ant, Maven, Gradle, Grunt, और Rake, टास्क-आधारित हैं. शेल स्क्रिप्ट के बजाय, ज़्यादातर आधुनिक बिल्ड सिस्टम के लिए, इंजीनियरों को बिल्ड फ़ाइलें बनानी होती हैं. इनमें यह बताया जाता है कि बिल्ड कैसे किया जाए.
Ant के मैन्युअल से यह उदाहरण देखें: Ant manual:
<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 में दिखाया गया है.
इमेज 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.
