मान लीजिए आप कुछ हफ़्तों से चैट में एक टू-डू लिस्ट ऐप बना रहे हैं। अभी यह एक वेबसाइट है — React, एक डेटाबेस, कुछ खास नहीं। आप "मुझे एक Android वर्ज़न बना दो" टाइप करते हैं और एंटर दबाते हैं। यहां असल में देखिए कि उस कीस्ट्रोक और आपके डाउनलोड्स फ़ोल्डर में आने वाली .aab फ़ाइल के बीच क्या होता है, क्योंकि ज़्यादातर प्लेटफ़ॉर्म आपको यह हिस्सा नहीं दिखाते, और वही छिपाया हुआ हिस्सा है जहां पहले सारी तकलीफ़ रहती थी।
Android .aab
सबसे पहले Gradle संभालता है। आपके ऐप के नेटिव मॉड्यूल्स — कैमरा एक्सेस, लोकल स्टोरेज, बिल्ड में शामिल जो भी प्लगइन्स हों — हर एक यह बताता है कि उन्हें किस NDK वर्ज़न के खिलाफ़ कंपाइल किया गया था, और ये घोषणाएं हमेशा मेल नहीं खातीं। मैंने देखा है कि NDK r25 के खिलाफ़ बना एक मॉड्यूल किसी ऐसे मॉड्यूल के साथ लिंक होने से इनकार कर देता है जिसने r26 मान लिया था, और जो एरर आता है वह "वर्ज़न मिसमैच" नहीं कहता, बल्कि .so फ़ाइल के तीन लेयर नीचे किसी गायब सिंबल के बारे में कुछ कहता है। Kotlin वर्ज़न कुछ और चालाकी करते हैं: एक Gradle मॉड्यूल के अंदर पिन किया गया वर्ज़न आपकी बिल्ड स्क्रिप्ट के ऊपर घोषित वर्ज़न को चुपचाप छिपा सकता है, और बिल्ड सफल हो जाती है — बस यह एक ऐसा बाइनरी बनाती है जो फ़ील्ड में खास Android वर्ज़न पर क्रैश करता है। इसमें कुछ भी असाधारण नहीं है। यह नेटिव Android शिप करने का मानक टैक्स है, और यही वजह है कि टीमें एक ऐसे व्यक्ति को रखती हैं जिसका पूरा काम यह जानना है कि इस हफ़्ते की एरर को गायब करने वाला फ़्लैग कौन सा है।
यहां का बिल्ड असली टूलचेन चलाता है और यह रिज़ॉल्यूशन काम खुद संभालता है:
- रनटाइम क्रैश बनने से पहले ही पकड़े गए डिपेंडेंसी कॉन्फ़्लिक्ट्स
- टूलचेन कॉन्फ़िगरेशन जो हर नई विफलता से कुछ सीखकर बेहतर होता जाता है
इसकी एक तेज़, नकली वर्जन — जो असली Gradle टास्क चलाए बिना ही .aab को शेप कर दे — एक मिनट से भी कम में शिप हो जाएगी। लेकिन जैसे ही आपकी ऐप को कोई बैकग्राउंड सर्विस या नेटिव क्रिप्टो लाइब्रेरी चाहिए होगी, वह वहीं दम तोड़ देगी, और Play Store रिव्यू इसे एक दिन के भीतर फ्लैग कर देगा। हम बीस मिनट खर्च करना पसंद करेंगे।
macOS .dmg
इस एक फ़ाइल में दो बिल्ड शामिल होते हैं। Xcode के कमांड-लाइन टूल्स एक Apple Silicon बाइनरी और एक Intel बाइनरी अलग-अलग कंपाइल करते हैं, फिर lipo उन्हें जोड़कर एक ही यूनिवर्सल एक्ज़ीक्यूटेबल बना देते हैं।
यह लुभावना लगता है — एक ही बाइनरी शिप करके काम खत्म — जब तक आपको याद न आए कि बहुत से लोग अपनी पसंद के हार्डवेयर पर नहीं, बल्कि अपने एम्प्लॉयर के लैपटॉप पर काम करते हैं, और वह लैपटॉप तीन साल पुराना और Intel वाला हो सकता है। यूज़र को यह पता लगाने के लिए मजबूर करने के बजाय कि उसके पास कौन-सा चिप है (ज़्यादातर लोग बता ही नहीं सकते), हम दोनों शिप करते हैं और OS को चुपचाप चुनने देते हैं। इसका विकल्प, जिसे हमने शुरुआत में आज़माया था, Linux मशीन से एम्युलेटेड टूलचेन का उपयोग करके सब कुछ क्रॉस-कंपाइल करना है। यह तेज़ है। लेकिन इसी तरह आपको एक ऐसा कोडसाइनिंग एज केस मिलता है जो सिर्फ असली macOS 12 हार्डवेयर पर, शिप होने के छह हफ्ते बाद सामने आता है, और इसकी शिकायत उस उलझे हुए यूज़र से मिलती है जिसे यह समझ ही नहीं आता कि उसकी ऐप क्यों नहीं खुल रही।
Windows इंस्टॉलर
यहीं पहला अनुभव तय करता है कि आपका यूज़र ऐप पर भरोसा करेगा भी या नहीं। Windows SmartScreen अभी आपके इंस्टॉलर को पहचानता नहीं है — इसने Microsoft के सर्वर पर अभी तक कोई प्रतिष्ठा नहीं बनाई है — इसलिए यह "Windows protected your PC" वाली नीली स्क्रीन दिखाता है, जिसमें बोल्ड में "Don't run" बटन होता है और मुश्किल से दिखने वाला "More info" लिंक होता है, जिसे क्लिक करने पर "Run anyway" सामने आता है। macOS भी अपने अंदाज़ में यही नाटक करता है: राइट-क्लिक, Open, कन्फर्म — क्योंकि App Store के बाहर की ऐप्स भी डिफ़ॉल्ट रूप से भरोसेमंद नहीं मानी जातीं। शुरुआत में हमने इन दोनों को एक जनरल FAQ पेज से लिंक कर दिया था। सपोर्ट टिकटों ने बताया कि यह काम नहीं करता — जिस व्यक्ति को यह डर हो कि उसका डाउनलोड मैलवेयर हो सकता है, वह डॉक्यूमेंटेशन पढ़ने नहीं जाता, वह स्क्रीनशॉट लेकर पूछता है कि कहीं हैक तो नहीं हो गया। इसलिए इंस्टॉल फ्लो OS पहचानकर ठीक वही तीन क्लिक दिखाता है जो ज़रूरी हैं, बिना किसी FAQ के। छोटी-सी बात है, लेकिन फ़ाइल का नाम भी मायने रखता है: डाउनलोड का नाम आपके प्रोडक्ट के नाम पर रखा जाता है, किसी बिल्ड आर्टिफ़ैक्ट के नाम पर नहीं। किसी को भी चैट सपोर्ट पर यह समझाना नहीं पड़ना चाहिए कि उसने "app-release-signed-v2-final.exe" डाउनलोड किया है और यह नहीं बता पा रहा कि सही फ़ाइल यही है या नहीं।
ब्राउज़र एक्सटेंशन मेनिफ़ेस्ट
यह पूरे पाइपलाइन में सबसे अलग हिस्सा है — न Gradle, न NDK, न सामान्य अर्थ में कोई कंपाइल स्टेप। इसकी जगह इसमें एक मेनिफ़ेस्ट होता है, और यह मेनिफ़ेस्ट Chrome Web Store के उस रिव्यूअर के साथ एक बातचीत है जिससे आप कभी सीधे बात नहीं करेंगे।
| मांगी गई अनुमति | रिव्यू का नतीजा |
|---|---|
| <all_urls> (फ़ीचर की ज़रूरत से कहीं ज़्यादा व्यापक) | किसी ऐसे व्यक्ति के साथ दो हफ्ते की बातचीत जो साफ़-साफ़ नहीं बताता कि उसे आपत्ति किस बात पर है |
| activeTab (असली ज़रूरत तक सीमित) | उसी दिन क्लियर हो जाता है |
MV3 एक ऐसी चीज़ को भी जटिल बना देता है जिसे MV2 में करना आसान था: बैकग्राउंड सर्विस वर्कर्स डिज़ाइन के हिसाब से बीच टास्क में ही अनलोड हो जाते हैं — यह बैटरी लाइफ़ को ध्यान में रखते हुए लिया गया Google का नीतिगत फ़ैसला है — और जिस फ़ीचर को इससे बचना है, उसे इसके खिलाफ़ नहीं बल्कि इसके इर्द-गिर्द बनाना पड़ता है। हम हर एक्सटेंशन को डिफ़ॉल्ट रूप से सबसे संकरा परमिशन सेट देते हैं जो उसकी असल फ़ंक्शनैलिटी के लिए ज़रूरी हो, और सिर्फ़ तभी उसे बढ़ाते हैं जब कोई खास फ़ीचर उसकी मांग करे।
कीस्टोर
Android बिल्ड के नीचे वह एक आर्टिफ़ैक्ट छिपा होता है जिसे आप कभी देखते नहीं और जिसे खोना आपको महंगा पड़ सकता है: साइनिंग की। इसे खो देने पर सिर्फ़ अपनी ऐप को अपडेट करने की क्षमता ही नहीं जाती — बल्कि इसे उसकी मौजूदा पहचान के साथ अपडेट करने की क्षमता हमेशा के लिए चली जाती है, और Google इसमें कोई रिकवरी का रास्ता नहीं देता, कभी नहीं। यह कोई शानदार इन्फ्रास्ट्रक्चर नहीं है। यह बस एक फ़ाइल है। लेकिन यही तय करती है कि छह महीने बाद अगला वर्जन एक सहज अपडेट के रूप में शिप होगा या फिर एक बिल्कुल नई लिस्टिंग के रूप में जो शून्य इंस्टॉल और शून्य रिव्यू से शुरू होगी। हम हर प्रोजेक्ट के लिए एक कीस्टोर जनरेट करते हैं और उसे संभालकर रखते हैं ताकि भविष्य की हर बिल्ड पहले दिन वाली उसी की से साइन हो।
इन सबके नीचे मौजूद चैट थ्रेड
ऊपर बताई गई कोई भी चीज़ किसी अलग "मोबाइल प्रोजेक्ट" में नहीं रहती। यह वही बातचीत है जिसने वेब ऐप बनाया था। UI में बदलाव मांगें, तो वेब बिल्ड अपडेट होती है; इसके बाद Android बंडल मांगें, तो वह उसी मौजूदा स्थिति से कंपाइल होता है, न कि तीन हफ्ते पहले से अलग हो चुके किसी फोर्क से। मैंने ज़्यादातर टीमों को देखा है कि जब वे बाद में नेटिव जोड़ने की कोशिश करती हैं, तो अंत में दो कोडबेस बनाए रखने लगती हैं जो धीरे-धीरे एक-दूसरे से दूर होते जाते हैं — एक वेब ऐप जो रोज़ शिप होती है, और एक नेटिव रैपर जिसे हर रिलीज़ से पहले किसी को याद करके अपडेट करना पड़ता है। यही अंतर पुराने पड़ जाने की जड़ है, और एक साझा बिल्ड हिस्ट्री इसी को खत्म कर देती है। हालांकि यह दोनों तरफ़ काम करता है: अगर चैट में हाल में ढिलाई बरती गई है, तो Android बिल्ड भी उसे विरासत में पाती है। यह कोई अलग पॉलिश पास नहीं है, यह वहां जो कुछ भी असल में मौजूद है उसका सीधा कंपाइल है — जो व्यवहार में लोगों को ईमानदार बनाए रखता है, क्योंकि सबमिशन से पहले "हम इसे साफ़ कर देंगे" जैसा कोई साइड-क्वेस्ट छोड़ने का मौका नहीं होता।
जब यह किसी स्टोरफ्रंट के लिए तैयार हो जाता है, तो शिप पाथ इसे आपकी अपनी Play Store लिस्टिंग और आपके अपने Apple Developer अकाउंट को सौंप देता है। हमारे नहीं। हम आपके और आपके अपने डिस्ट्रीब्यूशन के बीच नहीं आना चाहते थे।



