मुझे Makefile पसंद है
(switowski.com)- नए build tools लगातार आते रहने के बावजूद, Makefile अब भी प्रोजेक्ट के दोहराए जाने वाले कामों को बांधने वाला हल्का डिफॉल्ट टूल बना हुआ उपयोगी है
make,make build,make install,make devजैसे परंपरागत commands की वजह से किसी अनजान प्रोजेक्ट में भी build, install और development server चलाने की जल्दी कोशिश की जा सकती है- Jekyll, Hugo, 11ty, Python web frameworks, Docker, gulp जैसे अलग-अलग stacks में भी Makefile common execution interface की भूमिका निभाता है
- व्यक्तिगत प्रोजेक्ट्स में अक्सर conditionals या flags से ज़्यादा
dev,build,deployजैसे कुछ सरल targets ही काफी होते हैं - GNU Make व्यापक रूप से उपलब्ध है, और अगर वह न भी हो तो Makefile के shell commands सीधे चलाए जा सकते हैं, इसलिए सीमित environments में भी इसका बोझ कम रहता है
अलग-अलग प्रोजेक्ट commands को एक entry point में बांधना
- Makefile का उपयोग ऐसे प्रोजेक्ट्स को जाने-पहचाने command set में लपेटने के लिए होता है जिनमें build, run और deploy करने के तरीके अलग होते हैं
- किसी अनजान प्रोजेक्ट में
Makefileदिखे तो आम तौर पर पहले ये commands आज़माए जा सकते हैंmakemake buildmake install
- पुराने व्यक्तिगत प्रोजेक्ट को फिर से खोलते समय भी सिर्फ
make devचलाकर ज़रूरी build steps और development server startup को एक साथ संभालने के लिए इसे सेट किया जा सकता है - Jekyll, Hugo, 11ty, कई Python web frameworks जैसे अलग-अलग technology stacks में भी वही entry point बनाए रखा जा सकता है
- Docker या gulp का उपयोग होने पर भी Makefile उन tools को replace करने के बजाय orchestration layer की तरह काम करता है
- उदाहरण:
make buildप्रोजेक्ट के हिसाब से अतिरिक्त arguments पास करते हुए ज़रूरी Docker image build करता है
- उदाहरण:
व्यक्तिगत प्रोजेक्ट्स में इसका सरल उपयोग
- Makefile का उपयोग जटिल features के बजाय shell commands के समूह पर केंद्रित रहता है
- conditionals या flags जैसे features का इस्तेमाल नहीं किया जाता
- make में काम की इकाई तकनीकी रूप से target होती है, लेकिन व्यवहार में इसे task की तरह लिया जाता है
- यह bash script functions से भी बनाया जा सकता है, लेकिन Makefile को अधिक आसान और तेज़ माना जाता है
- व्यक्तिगत प्रोजेक्ट्स में अक्सर इस्तेमाल होने वाले काम आम तौर पर 2~3 ही होते हैं
dev: development server शुरू करनाbuild: ज़रूरत होने पर प्रोजेक्ट build करनाdeploy: प्रोजेक्ट deploy या publish करनाwatch: source files बदलने पर build job को अपने-आप फिर चलाने वाला अतिरिक्त काम
- मौजूदा ब्लॉग के Makefile में सिर्फ
devtarget हैdev:के नीचेnpm run devचलाया जाता है
- अधिक जटिल व्यक्तिगत प्रोजेक्ट्स में निम्न कामों को Makefile में बांधा जाता है
dev:bundle exec jekyll serveसे development server चलानाbuild:npm run gulp buildसे assets build करनाwatch:npm run gulp watchसे किसी खास folder पर नज़र रखनाdeploy: production environment में Jekyll build करने के बादmake encrypt,netlify deploy --prodचलानाencrypt:npx staticryptसे_sitefolder की HTML files को encrypt करना
ध्यान रखने योग्य सीमाएँ और फायदे
- उदाहरण में phony targets को छोड़ा गया है, लेकिन अगर
dev,build,watch,deploy,encryptजैसे नाम की files मौजूद हों तो Makefile उम्मीद के मुताबिक काम नहीं कर सकता- Hacker News के पाठक ऐसे मामलों में phony target जोड़ने की सलाह देते हैं
- GNU Make वह software है जो makefile चलाता है; Linux में इसके पहले से installed होने की संभावना ज़्यादा रहती है, और MacBook पर भी यह इतना आम है कि अलग से install याद न रहे
- Make बहुत अधिक अतिरिक्त dependencies नहीं मांगता, इसलिए सुरक्षा कारणों से package install करना मुश्किल या असंभव हो ऐसे सीमित environments में यह उपयोगी हो सकता है
- अगर Make न भी हो, तब भी Makefile के commands को सीधे shell में copy करके चलाया जा सकता है
- इसके उलट, अगर server पर gulp न हो तो JavaScript code को सीधे terminal में चिपकाकर चलाना आसान नहीं होता
- भले ही बाद में कोई बेहतर और तेज़ नया build tool मिल जाए, Make अलग-अलग tools और settings को संभालने वाली common command layer के रूप में आगे भी उपयोगी रह सकता है
2 टिप्पणियां
यदि
makefileमें dependencies परिभाषित नहीं हैं, तो उसेjustfileसे बदलने पर बेहतर usability मिलती है.Hacker News की राय
इस thread में “आप make का गलत इस्तेमाल कर रहे हैं” जैसी बातों से हतोत्साहित होने की ज़रूरत नहीं है। Make के अच्छे tool होने की एक वजह यह है कि यह जितना दिखता है, उससे भी सरल तरीके से शुरू किया जा सकता है
अगर आप
.PHONYइस्तेमाल नहीं करते तो समस्या आ सकती है, लेकिन छोटे project में हो सकता है वह जाल आप एक साल बाद ही छुएँ, और तब भी शायद एक घंटे तक सिर खुजलाने से बात बन जाए। 99% मामलों में “सही तरीका” की चिंता किए बिना तुरंत शुरू किया जा सकता है, और Make सिर्फ उतनी ही complexity मांगता है जितनी उसे ढहने से बचाने के लिए ज़रूरी होMake input files और dependencies लेकर ठीक एक output file बनाने का काम करता है।
install,all,cleanजैसी rules जो output नहीं बनातीं, या लेख के सभी targets को.PHONYजैसे खास magic target पर निर्भर होना चाहिए, लेकिन.PHONY2017 के POSIX (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) में नहीं था और अब जाकर मौजूदा standard (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...) में शामिल हुआ है। अगर आप object files, modules, precompiled headers जैसी कई files बनाना चाहते हैं, तो आपको खुद नाज़ुक hacks लिखने पड़ते हैं। हर Make, GNU Make नहीं होता, और BSD, Solaris/Illumos जैसे nix family के systems अब भी मौजूद हैंइसका मतलब यह नहीं कि Make बेकार है। यह उन projects में उपयोगी है जो काफ़ी complex हैं, लेकिन अभी इतने नहीं कि “बेहतर” build system की ज़रूरत पड़े। समस्या यह है कि ऐसे projects code बढ़ने के साथ और complex हो जाते हैं, और आखिर में Makefile या उसके कुछ हिस्से generate करने वाली scripts/programs रखने लगते हैं, जिससे एक अस्थायी meta build system बन जाता है
इससे भी बड़ी समस्या उसका इस्तेमाल नहीं, बल्कि उसे “सबके लिए” solution के तौर पर सुझाना है। और जैसे ही project root में
buildयाdevजैसी directory या file बनती है, वह Makefile काम करना बंद कर देता हैआजकल मैं pnpm पर बदल तो जाता हूँ, फिर भी इसे बहुत complex नहीं मानता। syntax कुछ हद तक जानना पड़ता है, लेकिन दूसरे tools को शुरू से ठीक से चलाने की तुलना में यह कम मुश्किल है
.PHONYकाम की इतनी चिंता रहती है, उससे कभी समस्या नहीं हुईयह simple, पढ़ने में आसान, modify करने योग्य, composable है, और लगभग हर जगह पहले से installed होता है। सतह पर जो लिखा है वही करता है और उससे ज़्यादा लगभग कुछ नहीं करता
वैसे, जब दूसरे projects में trendy build systems या किसी nightmare जैसे build systems से निपटना पड़ता है, तब भी उन्हें make से wrap करके इस्तेमाल करता हूँ
हर किसी को कहीं न कहीं से शुरू करना होता है, और एक बार चीज़ चलने लगे तो incremental improvements आसानी से जोड़ी जा सकती हैं
$@, बनाना चाहिए। पूरा Makefile मनचाही संख्या में outputs बना सकता है, क्योंकि rules दूसरी rules पर depend कर सकती हैंयहीं से phony targets के लिए एक साफ़ rule of thumb मिलता है। अगर कोई recipe सिर्फ
$@को नहीं छूती, तो$@को phony के रूप में mark करना चाहिए। phony targets को list के रूप में manage करना कहीं आसान हैphonies :=phonies += somethingsomething:./do-somethingphonies += something-elsesomething-else: something./do-something-else# touches $@ and thus does not need to be phonycreate-file:./generate-some-output > $@.PHONY: $(phonies)Makefile एक खराब तकनीक है। बस इतना है कि हमारे बनाए ज़्यादातर दूसरे build systems से थोड़ा कम खराब है, इसलिए आत्म-पीड़क अर्थ में उपयोगी हो जाता है
Build systems आम तौर पर या तो इतने basic होते हैं कि खिलौने से ज़्यादा कुछ build करते ही अफरा-तफरी हो जाती है, या इतने जटिल कि पहले से knowledge, bureaucracy, synchronization और boilerplate बहुत ज़्यादा हो जाते हैं, या standard library नहीं होती इसलिए वही build patterns 10,000 असंगत implementations में बिखर जाते हैं, या इतने restrictive होते हैं कि ज़रूरतें बढ़ने पर migration करनी पड़ती है, या बहुत ज़्यादा magic होता है, या syntax कठिन और inconsistent होता है
मैं जो software इस्तेमाल करता हूँ, उसमें आधे से ज़्यादा सिर्फ साधारण Makefile और कभी-कभी configure script का इस्तेमाल करते हैं। autotools के बिना वैकल्पिक रूप से
./configureचलाते हैं औरmake,make installकरते हैं, तो बस काम हो जाता है। यह बिल्कुल भी खिलौना setup नहीं है, और यह एक काम अच्छी तरह करने वाले छोटे programs से बना हैMake और उसका काम करने का तरीका, कम-से-कम मेरे लिए, समझना आसान है। मुझे लगता है लोगों के खराब अनुभव अक्सर Make खुद के बजाय जटिल projects की खराब build settings या जरूरी knowledge की कमी की वजह से होते हैं
Standard library का न होना जानबूझकर tool independence के लिए है, और इसी वजह से यह अलग-अलग tools, languages और use cases पर सामान्य रूप से लागू हो सकता है। इसे defect नहीं, feature मानता हूँ। simple layer dependency tracking है, और targets और dependencies भी साफ हैं, इसलिए इसे magical कहना मुश्किल है। Syntax निश्चित रूप से बेहतर हो सकता है
कई languages को cover करने वाले general-purpose build systems सिर्फ
makeया CMake जैसे वे systems हैं जोmakeको target करते हैं। यह बेकार इसलिए है क्योंकिmakeबेकार है।makeको सही तरीके से इस्तेमाल करना सचमुच मुश्किल है, recursive make और non-recursive make जैसी समस्याएँ हैं, और Unix/POSIX make, BSD make, GNU make, Windowsnmakeजैसे कई incompatible variants हैं, और यह काफी बदसूरत भी हैफिर भी सही तरीके से लिखा गया
makeकाफी अच्छा है। least common denominator के रूप मेंmakeका होना सचमुच बड़ी राहत हैMake का subdirectories तक को साफ-सुथरे ढंग से handle न कर पाना काफी अजीब है। जानना चाहता हूँ कि Make से बेहतर कुछ है क्या। Ninja तो है, लेकिन उसे इंसानों के सीधे इस्तेमाल के लिए design नहीं किया गया है
तुम्हारे हिसाब से कितना? क्या 20%, 40%, 70% के आसपास तो सही नहीं होगा
मुझे Make सचमुच पसंद है। पिछले 20 सालों में जिस भी काम या project को छुआ है, हर एक के लिए एक या अधिक लिखे हैं
इसमें कोई चतुराई नहीं होती, बस कुछ variables वाले snippets का संग्रह होता है। जैसे
make run,make test,make lintmake recentlint के बाद सबसे हाल में modified script को चलाता है। Bash या दूसरे shell से भी कर सकते हैं, लेकिन तब developer-world में खिंच जाते हैं और complexity ही बढ़ती है, extra value नहीं मिलती। Make बस एक domain-specific language है: “इस तरह की file इस तरह की file से बनती है, और तब ये एक-दो commands चलाओ।” इसलिए यह बेहद powerful हैकई दशक पहले Java में test framework बनाया था, जिसमें make जैसी syntax से tests और dependent classes specify किए जा सकते थे। “baseline test set” define करने वाली test classes का set रखा था, और उस पर निर्भर दूसरी test class layers तभी चलती थीं जब पहले वाली सफल हो जाती थीं
आजकल लोग हर चीज़ को इतना complex क्यों बना देते हैं, समझ नहीं आता। मैंने हमेशा सलाह दी है कि standard Unix tools और उनके तरीके, यानी time-tested तरीके, पर टिके रहो। जब तक ऐसी स्थिति न हो जहाँ वैसा करना बिल्कुल संभव न हो। समय सीमित है, इसलिए उसे problem solving के मूल हिस्सों—system/program design, modeling, structure, patterns आदि—पर लगाना चाहता हूँ, बाकी सब secondary है
stack, language, repository चाहे कुछ भी हो,
make testचलाने पर काम करेगा—यह जानना बड़ी राहत हैपिछले project के Makefile में चार main commands थीं:
build,test,frontend,deploy। build और test maven को call करते थे, frontend npm को, और deploy docker और aws को call करता थाये सभी tools अपनी state tracking, caching और incremental processing करते हैं, लेकिन यह report नहीं करते कि उन्होंने क्या किया। इसलिए “build update हुआ हो तभी deploy करो” जैसे molecular-level rules नहीं लिख सकते। क्योंकि maven/cargo/dotnet/npm/go वह information expose नहीं करते
लेखक mtime-आधारित dependency tracking तक इस्तेमाल नहीं करता। targets को भी
.PHONYहोना चाहिए था, लेकिन उन्हें मार्क नहीं किया गया है। इसे ऐसे shell script से भी बदला जा सकता था जो$1पढ़कर match करे और तय करे कि क्या करना हैhttps://just.systems/
.PHONY: $(MAKECMDGOALS)./build.sh dev,make devसे कम स्पष्ट तो है। बिना किसी non-phony step के भी Make इस्तेमाल करने की एक और वजह यह है कि बाद में जरूरत पड़े तो ऐसे steps जोड़े जा सकते हैं। बेशक, मैं इस बात से सहमत हूँ कि लेखक को{dev,build,deploy}को phony के रूप में mark करना चाहिएउसका अपना Makefile तकनीकी रूप से बेहतर किया जा सकता है, लेकिन ज्यादा अहम बात यह है कि किसी project में जाकर “शुरू करने” का experience consistent रहता है
लोग चाहते हैं कि यह scripting language, dependency tracker वगैरह जैसे कई दूसरे काम भी करे, और फिर bloated tools ले आते हैं। नया होना हमेशा बेहतर होना नहीं है। Autoconf और automake, make नहीं हैं
तकनीकी तौर पर ये सभी make targets target name जैसे ही file को ढूँढते हैं। हर target को असल में
.PHONYके रूप में define किया जाना चाहिएपहले मैं भी ऐसे Makefile हमेशा इस्तेमाल करता था, लेकिन पिछले कुछ सालों में just और justfile पर shift हो गया हूँ। यह behavior वहाँ default है और कुल मिलाकर इस्तेमाल करना आसान है। parameters जैसी चीजें भी ज्यादा simple हैं
https://github.com/casey/just
:से शुरू हों—ऐसा कुछ होना चाहिए था, ताकि नाम देखकर ही फर्क पता चल जाएबेशक अब बहुत देर हो चुकी है
लगता है कि अब just, ripgrep जैसे नए और useful tools को bundle करके एक नए coreutils distribution जैसा कुछ आने का समय हो गया है, जो जहाँ चाहें वहाँ install हो जाए
यह मजेदार है कि make इतनी तीखी debate पैदा करता है। पुराने vi बनाम emacs वाली आधी धार्मिक लड़ाई जैसी बहसों से मिलता-जुलता है
मैं original post से पूरी तरह सहमत हूँ, और खासकर यह तरीका smart लगता है कि ज्यादा sophisticated build tool इस्तेमाल करने पर भी उसे top-level Makefile से wrap किया जाए। फायदा standardization है। कुछ याद रखने की जरूरत नहीं—अगर यह मेरा लिखा project है, तो बस
makeटाइप करूँगा और पता है कि काम करेगाउदाहरण के लिए, C इस्तेमाल करने वाला कोई व्यक्ति Rust project compile करना चाहे, तो cargo usage खोजने की जरूरत नहीं; बस
makeयाgmakeटाइप कर दे। GNU-specific features नहीं इस्तेमाल करता और POSIX compliance की कोशिश करता हूँ, लेकिन यह भी सच है कि practically लगभग 100% make, gmake ही होता हैसमय की कसौटी पर खरे उतरे
makeको एक तरह के top-level build system driver की तरह इस्तेमाल करने का सुझाव शायद 250 साल बाद भी काम करेगाcargo buildही इस्तेमाल होता हैMake खुद शानदार है, लेकिन C projects में जिन ज्यादातर cases में मैंने काम किया, वे cmake/autotools और global package installs के combination थे, और ऐसी चीजें बार-बार lookup करनी पड़ती थीं
यहाँ की भावना से सहमत हूँ, लेकिन कई चीजों को Justfile में फिर से लिख रहा हूँ
https://github.com/casey/just
Makefile की खास किस्म की कई अजीब चीजों से बचा जा सकता है
हाँ, Make शानदार है। मैं इसे सच में बहुत सारे कामों में इस्तेमाल करता हूँ। task automation के लिए अच्छा तरीका है
उदाहरण के लिए, मेरी personal website में Makefile bash script call करके updated web pages फिर से बनाता है, server पर git push करता है, और फिर server का git hook Make को call करके deploy करता है। हालांकि lecture material PDFs जैसे अक्सर बदलने वाले binary blobs को मैं Git repository में नहीं रखना चाहता। कोई बात नहीं। Makefile में
uploadstarget है जो सिर्फ modified PDFs को server पर upload करता है, और यह target git push करने वालेdeploytarget की dependency है, इसलिए मुझे इसकी चिंता नहीं करनी पड़तीसाथ ही, lecture material के updated PDFs को एक दूसरा Makefile website source tree में automatically डाल देता है। उस Makefile से मैं LaTeX source से अपने इस्तेमाल की PDF बना सकता हूँ, या उसी source से students के लिए alternate version—जैसे exercise answers के बिना version—बना सकता हूँ, उसे local website version पर auto-publish कर सकता हूँ, और जब चाहूँ updated website पर upload कर सकता हूँ
यह एक तरह की Makefiles की अंतहीन chain है। Makefile अच्छे हैं
इस case में लेखक को सच में make नहीं, just चाहिए
https://just.systems/man/en/
task runner के रूप में make बुरा नहीं है, लेकिन आजकल बाकी टिप्पणियों की तरह just जैसे बेहतर विकल्प मौजूद हैं
build system के रूप में make तब तक ठीक है जब तक आप इसकी कमियों से नहीं टकराते। make/Makefile standardized नहीं हैं, इसलिए automake मौजूद है। इसलिए आप सीधे Makefile नहीं लिखते, बल्कि template इस्तेमाल करते हैं और असली makefile generate करवाते हैं। अगर आप पूरे tool chain के मालिक हैं तो कोई दिक्कत नहीं, लेकिन ज़्यादातर मामलों में ऐसा नहीं होता, इसलिए portable Makefile सुनिश्चित करने के लिए ऐसा किया जाता है
make किसी भी तरह की dependency resolution नहीं कर पाता और मान लेता है कि जो चाहिए वह वहीं मौजूद है। इसलिए configure scripts आए, और configure scripts भी standard नहीं हैं, इसलिए make targets चलाने से पहले autoconf/autoreconf से configure script generate करवाई जाती है
make और automake/autoconf/autoreconf जैसे आस-पास के tools यह तय करने के लिए mtime का इस्तेमाल करते हैं कि inputs पुराने हैं या नहीं। autoconf/autoreconf/automake/configure चलाने के बाद भी inputs स्थायी रूप से पुराने state में रह सकते हैं और आप ऐसी स्थिति में फँस सकते हैं जहाँ कुछ भी build नहीं हो पाता। संदर्भ के तौर पर, कई build systems में अगर dependency tracking सही हो तो mtime इस्तेमाल करना ठीक रहता है
कुल मिलाकर make की मूल design flaw यह है कि यह Unix philosophy, यानी “एक काम करो और उसे अच्छी तरह करो” के हिसाब से बना है। वह एक काम है “अगर inputs पुराने हैं तो target को फिर से build करो”, लेकिन यह बहुत सीमित tool है, इसलिए modern build systems को इसे basic task runner से आगे उपयोगी बनाने के लिए इसके ऊपर बहुत सारा काम जोड़ना पड़ता है
makedependency resolution नहीं कर सकता?dependency:...target: dependency...make configureहै। यहmake [build]के लिए ज़रूरी चीज़ें set up करता हैइसलिए touchfile इस्तेमाल करने पड़ते हैं; गंदा तो है, लेकिन कई दूसरी चीज़ों से अभी भी बेहतर चलता है। मैं खास तौर पर docker build caching की बात कर रहा हूँ