6 पॉइंट द्वारा GN⁺ 2025-06-21 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Makefile C/C++ build automation और dependency management को सरल बनाने वाला एक टूल है
  • यह timestamp का उपयोग करके बदली हुई फ़ाइलों का पता लगाता है, और केवल ज़रूरत होने पर ही compilation चलाता है
  • rule, command, prerequisite जैसी मुख्य संरचनाओं को उदाहरणों के साथ समझाया गया है
  • automatic variables, pattern rules, variable expansion जैसे उन्नत फीचर्स को भी व्यावहारिक रूप से कवर किया गया है
  • मध्यम आकार के प्रोजेक्ट्स के लिए व्यावहारिक Makefile template के जरिए scalability और maintainability के महत्व को दिखाया गया है

Makefile ट्यूटोरियल गाइड का परिचय

  • Makefile प्रोजेक्ट build automation और dependency management के लिए एक मुख्य टूल है
  • कई छिपे हुए rules और symbols की वजह से यह पहली बार में जटिल लग सकता है, लेकिन यह गाइड मुख्य बातों को संक्षेप में और सीधे चलाए जा सकने वाले उदाहरणों के साथ प्रस्तुत करती है
  • हर section में hands-on उदाहरणों के जरिए समझ विकसित की जा सकती है

शुरुआत करें

Makefile का उद्देश्य

  • Makefile का उपयोग बड़े प्रोग्रामों में सिर्फ बदले हुए हिस्सों को दोबारा compile करने के लिए किया जाता है
  • C/C++ के अलावा भी कई भाषाओं के अपने build tools होते हैं, लेकिन Make का उपयोग सामान्य build scenarios में व्यापक रूप से होता है
  • बदली हुई फ़ाइलों को पहचानकर केवल ज़रूरी काम चलाना इसका मुख्य logic है

Make के वैकल्पिक build systems

  • C/C++ परिवार: SCons, CMake, Bazel, Ninja आदि कई विकल्प उपलब्ध हैं
  • Java परिवार: Ant, Maven, Gradle आदि
  • Go, Rust, TypeScript आदि भी अपने build tools उपलब्ध कराते हैं
  • Python, Ruby, JavaScript जैसी interpreted languages में compilation की ज़रूरत नहीं होती, इसलिए Makefile जैसी अलग प्रबंधन आवश्यकता अपेक्षाकृत कम होती है

Make के versions और प्रकार

  • Make के कई implementations हैं, लेकिन यह गाइड GNU Make (मुख्यतः Linux, MacOS पर उपयोग होने वाला) के लिए अनुकूलित है
  • उदाहरण GNU Make 3 और 4 दोनों versions के साथ compatible हैं

उदाहरण चलाने का तरीका

  • terminal में make install करने के बाद, हर उदाहरण को Makefile फ़ाइल के रूप में सेव करें और make कमांड चलाएँ
  • Makefile में command lines को हमेशा tab character से indent करना ज़रूरी है

Makefile का बुनियादी syntax

Rule की संरचना

  • target: prerequisites

    • command
    • command
  • target: build output फ़ाइल का नाम (आमतौर पर एक)

  • command: वास्तव में चलने वाली shell script (tab से शुरू)

  • prerequisite: उन फ़ाइलों की सूची जो target build होने से पहले तैयार होनी चाहिए


Make का सार

Hello World उदाहरण

hello:  
	echo "Hello, World"  
	echo "This line will print if the file hello does not exist."  
  • target hello की कोई dependency नहीं है और यह 2 commands चलाता है
  • make hello चलाने पर, अगर hello फ़ाइल मौजूद नहीं है तो commands चलेंगी। अगर फ़ाइल पहले से है, तो वे नहीं चलेंगी
  • आमतौर पर target को फ़ाइल नाम के बराबर लिखा जाता है

C फ़ाइल compile करने का बुनियादी उदाहरण

  1. blah.c फ़ाइल बनाएँ (int main() { return 0; } सामग्री के साथ)
  2. नीचे दिया गया Makefile लिखें
blah:  
	cc blah.c -o blah  
  • make चलाने पर, अगर blah target मौजूद नहीं है तो compilation चलेगा और blah फ़ाइल बनाई जाएगी
  • blah.c बदलने के बाद भी automatic recompilation नहीं होगा → dependency जोड़नी होगी

dependency जोड़ने का तरीका

blah: blah.c  
	cc blah.c -o blah  
  • अब अगर blah.c नया बदला है, तो blah target फिर से build होगा
  • बदले हुएपन का पता लगाने के लिए फ़ाइल timestamp को आधार बनाया जाता है
  • अगर timestamp को मनमाने ढंग से बदल दिया जाए, तो व्यवहार इरादे से अलग हो सकता है

और उदाहरण

जुड़े हुए targets और dependencies का उदाहरण

blah: blah.o  
	cc blah.o -o blah   
  
blah.o: blah.c  
	cc -c blah.c -o blah.o   
  
blah.c:  
	echo "int main() { return 0; }" > blah.c   
  • dependency tree का अनुसरण करते हुए हर चरण की generation अपने-आप automate हो जाती है

हमेशा चलने वाले target का उदाहरण

some_file: other_file  
	echo "This will always run, and runs second"  
	touch some_file  
  
other_file:  
	echo "This will always run, and runs first"  
  • क्योंकि other_file वास्तव में फ़ाइल के रूप में नहीं बनता, इसलिए some_file का command हर बार चलता है

Make clean

  • clean target का उपयोग अक्सर build outputs हटाने के लिए किया जाता है
  • यह Make में कोई विशेष reserved word नहीं है, इसलिए इसे command के रूप में स्वयं define करना पड़ता है
  • अगर फ़ाइल का नाम clean हो, तो भ्रम हो सकता है, इसलिए .PHONY का उपयोग करने की सलाह दी जाती है

उदाहरण:

some_file:   
	touch some_file  
  
clean:  
	rm -f some_file  

variables को संभालना

  • variables हमेशा strings होते हैं.
  • आमतौर पर := की सलाह दी जाती है, जबकि =, ?=, += जैसी अलग-अलग assignment methods भी मौजूद हैं
  • उपयोग का उदाहरण:
files := file1 file2  
some_file: $(files)  
	echo "Look at this variable: " $(files)  
	touch some_file  
  
file1:  
	touch file1  
file2:  
	touch file2  
  
clean:  
	rm -f file1 file2 some_file  
  • variable reference का तरीका: $(variable) या ${variable}
  • Makefile में quotes का अपने-आप कोई अर्थ नहीं होता (हालाँकि shell commands में उनकी ज़रूरत हो सकती है)

target प्रबंधन

all target

  • कई targets को एक साथ चलाना हो, तो पहले (default) target को उसी तरह define किया जाता है
all: one two three  
  
one:  
	touch one  
two:  
	touch two  
three:  
	touch three  
  
clean:  
	rm -f one two three  

multiple targets और automatic variables

  • कई targets के लिए अलग-अलग commands चलाए जा सकते हैं। $@ में वर्तमान target का नाम होता है
all: f1.o f2.o  
  
f1.o f2.o:  
	echo $@  

automatic variables और wildcards

* wildcard

  • * फ़ाइल सिस्टम में नामों को सीधे खोजता है
  • इसे हमेशा wildcard function में wrap करके इस्तेमाल करने की सलाह दी जाती है
print: $(wildcard *.c)  
	ls -la  $?  
  • variable definition में सीधे * का उपयोग न करें
thing_wrong := *.o  
thing_right := $(wildcard *.o)  

% wildcard

  • इसका उपयोग मुख्यतः pattern rules में होता है, जहाँ तय pattern को निकालकर expand किया जा सकता है

Fancy Rules

implicit rules

  • Make में C/C++ build से जुड़ी कई छिपी हुई default rules पहले से मौजूद होती हैं
  • प्रमुख variables: CC, CXX, CFLAGS, CPPFLAGS, LDFLAGS आदि
  • C उदाहरण:
CC = gcc   
CFLAGS = -g   
  
blah: blah.o  
  
blah.c:  
	echo "int main() { return 0; }" > blah.c  
  
clean:  
	rm -f blah*  

Static Pattern Rules

  • एक ही pattern का पालन करने वाले कई rules को संक्षेप में लिखा जा सकता है
objects = foo.o bar.o all.o  
all: $(objects)  
	$(CC) $^ -o all  
  
$(objects): %.o: %.c  
	$(CC) -c $^ -o $@  
  
all.c:  
	echo "int main() { return 0; }" > all.c  
  
%.c:  
	touch $@  
  
clean:  
	rm -f *.c *.o all  

Static Pattern Rules + filter function

  • filter का उपयोग करने पर केवल वही targets चुने जा सकते हैं जो किसी खास extension pattern से मेल खाते हों
obj_files = foo.result bar.o lose.o  
src_files = foo.raw bar.c lose.c  
  
all: $(obj_files)  
.PHONY: all  
  
$(filter %.o,$(obj_files)): %.o: %.c  
	echo "target: $@ prereq: $

1 टिप्पणियां

 
GN⁺ 2025-06-21
Hacker News राय
  • 1985 में Boston University Graphics लैब में किसी को Makefile का इस्तेमाल करके animation के लिए 3D renderer बनाते हुए सीधे देखने का अनुभव साझा किया गया। वह व्यक्ति Lisp programmer था, शुरुआती procedural generation और 3D actor system पर काम कर रहा था, और उसने लगभग 10 लाइनों का बेहद शानदार Makefile बनाया था। सिर्फ़ file date dependencies के आधार पर सैकड़ों animations अपने-आप generate हो जाते थे। हर frame की 3D shape Lisp में बनती थी, और Make उन frames को generate करता था। 1985 में, आज की तरह 3D और animation को सामान्य नहीं माना जाता था, इसलिए सब लोग हैरान थे। याद है कि बाद में वही व्यक्ति Brian Gardner निकला, जिसने Iron Giant और Coraline के 3D renderer पर काम किया था

    • जिज्ञासा जताई गई कि क्या यह वही व्यक्ति है जो 3d-consultant.com/bio.html में दिखाई देता है

    • पुष्टि पूछी गई कि क्या बात फिल्म Coraline की ही हो रही है

  • Make इस्तेमाल करते समय कुछ कम-ज्ञात लेकिन उपयोगी flags बताए गए

    • --output-sync=recurse -j10: इसका मतलब है कि हर target का काम ख़त्म होने तक stdout/stderr को इकट्ठा करके output किया जाए; वरना logs आपस में मिल जाते हैं और analysis मुश्किल हो जाता है
    • busy systems या multi-user environments में -j की जगह --load-average का उपयोग करके parallel processing के दौरान system load नियंत्रित किया जा सकता है (make -j10 --load-average=10)
    • build target scheduling को randomize करने वाला --shuffle option, CI environment में Makefile की dependency समस्याएँ पकड़ने में उपयोगी है
    • यह विचार भी आया कि make के विभिन्न options को आधिकारिक रूप से text या documentation के रूप में program में शामिल किया जाए ताकि usability बढ़े

    • अक्सर इस्तेमाल किया जाने वाला option -B flag है, जो full forced build के लिए उपयोग होता है

    • make -j की वजह से DOS machine पर पैदा हुई समस्याएँ कई बार देखी गईं, इसलिए उसे bug की तरह माना गया

    • सवाल पूछा गया कि busy systems या multi-user environments में parallelization की समस्या क्या OS scheduler को संभालनी नहीं चाहिए

    • उपयोगी flags हैं, लेकिन ये options portable नहीं हैं, इसलिए केवल निजी projects के बाहर इनका इस्तेमाल न करने की सलाह दी गई

  • सिर्फ़ इसलिए कि .PHONY इस्तेमाल नहीं किया जाता, tutorial में उसे छोड़ देना एक कमज़ोर बहाना है। सही तरीका सिखाना चाहिए, ऐसा मत व्यक्त किया गया

    • टीम में Make को task runner की तरह इस्तेमाल करते हुए हर recipe में .PHONY जोड़ने और बनाए रखने को लेकर बहस हुई
    • Clark Grubb की Makefile style guide(clarkgrubb.com/makefile-style-guide) की सिफारिश की गई
    • .PHONY declaration हर recipe के साथ लिखना या file के ऊपर एक साथ रखना—इन अलग-अलग styles के अनुभव साझा किए गए, और इच्छा जताई गई कि इसे linter से enforce किया जा सके
    • पढ़ने पर यह ठीक document लगा, लेकिन कुछ बातों पर असहमति जताई गई
      • -o pipefail को आँख बंद करके लागू करना ठीक नहीं; pipe में grep वगैरह इस्तेमाल होने पर चीज़ें टूट सकती हैं, इसलिए स्थिति के अनुसार लागू करना बेहतर है
      • non-file targets को .PHONY mark करना technically सही है, लेकिन अक्सर अनावश्यक होता है और Makefile को लंबा बना देता है, इसलिए ज़रूरत पर ही इस्तेमाल करना बेहतर है
      • कई output files बनाने वाली recipes में पहले dummy file का इस्तेमाल होता था, लेकिन GNU Make 4.3 से grouped targets का आधिकारिक support उपलब्ध है (यहाँ देखें)
  • यह दावा सामने आया कि Make बड़े C codebase की build के लिए specialize किया गया tool है

    • किसी ने कहा कि वह इसे project-specific job runner के रूप में पसंद करता है, लेकिन Make job runner के रूप में उपयुक्त नहीं है और conditional जैसी चीज़ों को भी बेवजह कठिन बना देता है
    • Terraform जैसे tools को wrap करने की कोशिश में विफल रहने का अनुभव भी साझा किया गया
    • एक राय यह थी कि Make job runner से ज़्यादा linear shell scripts को declarative dependencies के रूप में बदलने वाला general-purpose shell tool है

    • यह भी कहा गया कि Make को सिर्फ़ C codebase build tool मानना अब सही नहीं है। पिछले 20 साल में ज़्यादा robust और स्पष्ट build systems विकसित हुए हैं। नज़रिए को अपडेट करने की ज़रूरत बताई गई

    • अच्छे job runner के बारे में पूछा गया। (साथ में यह मानते हुए माफ़ी भी जोड़ी गई कि शायद job runner का अर्थ खुद गलत समझा गया था)

  • Makefile की जटिलता वाले हिस्सों के modern replacement के रूप में just की सिफारिश की गई

    • just shell scripts की सूची के replacement के रूप में अच्छा है, लेकिन Make की मूल विशेषता—सिर्फ़ वही rules चलाना जिन्हें दोबारा चलाने की ज़रूरत है—का replacement नहीं कर सकता

    • अन्य विकल्पों के रूप में

    • वैकल्पिक tools खुद को Make replacement कहते हैं, लेकिन राय यह थी कि वे पूरी तरह अलग हैं और तुलना करना ही कठिन है। Make का मूल काम artifacts बनाना और जो पहले बन चुका है उसे दोबारा न बनाना है। वहीं just सिर्फ़ command runner की तरह काम करता है

    • command executor के रूप में Make का लाभ यह है कि यह लगभग हर जगह installed standard tool है। विकल्प शायद बेहतर बने हों, लेकिन अलग से install करने की ज़रूरत के कारण उन्हें इस्तेमाल करने की खास ज़रूरत महसूस नहीं होती

    • Task मेरे C में किए जाने वाले छोटे hobby projects के लिए अच्छा रहा है, लेकिन बड़े projects के लिए कितना उपयुक्त है, इस पर अभी राय पक्की नहीं है (Task आधिकारिक वेबसाइट)

  • यह दिलचस्प लगा कि हाल में CMake ने माना कि Makefile, C++20 modules support के लिए उपयुक्त नहीं है और इसलिए default के रूप में ninja चुना गया (CMake guide)

    • वास्तव में target dependencies को statically define करना लगभग असंभव है, इसलिए clang-scan-deps जैसे tools से dynamic analysis अपनाया जाता है (technical slides)
    • इसके जवाब में कहा गया कि यह सीमा असल में CMake का फ़ैसला हो सकता है या Makefile generator के लिए volunteers न होने की समस्या। ninja भी C++ modules को सीधे support नहीं करता (संबंधित issue), और यह भी बताया गया कि ninja में Make से कम features हैं तथा उसमें सभी dependencies को statically specify करना पड़ता है

    • modules की शुरुआत ही जटिल और भ्रमित करने वाली बताई गई

  • पूछा गया कि क्या किसी के पास tup इस्तेमाल करने का अनुभव है। (आधिकारिक दस्तावेज़)

    • tup file system access के आधार पर अपने-आप dependencies पता करने वाला build system है, जो किसी भी compiler/tool पर लागू हो सकता है
  • एक व्यक्ति ने अपना परिचय Task नामक Make alternative tool के creator और maintainer के रूप में दिया। वह 8 साल से अधिक समय से इस पर काम कर रहा है और tool लगातार विकसित हो रहा है

    • अगर कोई नया अनुभव चाहता है, तो उसे आज़माने की सलाह दी गई; सवाल हों तो कभी भी पूछने के लिए कहा गया
    • Task आधिकारिक वेबसाइट, GitHub repository लिंक दिए गए
    • just को भी एक और Make alternative के रूप में सुझाया गया (just GitHub)

    • एक दिलचस्प संयोग के तौर पर कहा गया कि मैं Task अक्सर इस्तेमाल करता हूँ, और आज सुबह भी issue दर्ज किया

  • इस tutorial में कुछ ख़तरनाक और सूक्ष्म समस्याएँ हैं

    • MAKEFLAGS से options parse करते समय, लंबे options या empty short options को संभालने के लिए इस तरह करना चाहिए
      ifneq (,$(findstring t,$(firstword -$(MAKEFLAGS))))
    • अगर OS X के default पुराने make के साथ compatibility चाहिए, तो कई features मौजूद नहीं हैं या सूक्ष्म रूप से अलग हैं
    • बाकी समस्याएँ ज़्यादातर typos या best style के उल्लंघन हैं, इसलिए छोड़ी गईं
    • संदर्भ के लिए, load, guile से ज़्यादा portable है, और cross-compilation environments में compiler specification सटीक होनी चाहिए
    • Paul’s Rules of Makefiles(यहाँ) और GNU make manual(यहाँ) तथा संबंधित manuals ज़रूर पढ़ने की सिफारिश की गई
    • एक साधारण demo Makefile project भी चलाया जा रहा है (demo github)
  • हर GitHub repo में हमेशा Makefile शामिल करने की आदत बताई गई

    • क्योंकि commands बार-बार भूलना आसान है, इसलिए उन्हें Makefile में सहेज देने से जटिल steps भी आसानी से जोड़े जा सकते हैं, और सिर्फ़ make चलाने पर बिना अलग से याद किए project के अपेक्षित actions तुरंत चलाए जा सकते हैं