सबसे बेहतरीन उदाहरणों के साथ Makefile सीखें
(makefiletutorial.com)- 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 करने का बुनियादी उदाहरण
blah.cफ़ाइल बनाएँ (int main() { return 0; }सामग्री के साथ)- नीचे दिया गया Makefile लिखें
blah:
cc blah.c -o blah
makeचलाने पर, अगरblahtarget मौजूद नहीं है तो compilation चलेगा औरblahफ़ाइल बनाई जाएगीblah.cबदलने के बाद भी automatic recompilation नहीं होगा → dependency जोड़नी होगी
dependency जोड़ने का तरीका
blah: blah.c
cc blah.c -o blah
- अब अगर
blah.cनया बदला है, तोblahtarget फिर से 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
cleantarget का उपयोग अक्सर 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
*फ़ाइल सिस्टम में नामों को सीधे खोजता है- इसे हमेशा
wildcardfunction में 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 टिप्पणियां
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 मुश्किल हो जाता है-jकी जगह--load-averageका उपयोग करके parallel processing के दौरान system load नियंत्रित किया जा सकता है (make -j10 --load-average=10)--shuffleoption, CI environment में Makefile की dependency समस्याएँ पकड़ने में उपयोगी हैयह विचार भी आया कि make के विभिन्न options को आधिकारिक रूप से text या documentation के रूप में program में शामिल किया जाए ताकि usability बढ़े
अक्सर इस्तेमाल किया जाने वाला option
-Bflag है, जो full forced build के लिए उपयोग होता हैmake -jकी वजह से DOS machine पर पैदा हुई समस्याएँ कई बार देखी गईं, इसलिए उसे bug की तरह माना गयासवाल पूछा गया कि busy systems या multi-user environments में parallelization की समस्या क्या OS scheduler को संभालनी नहीं चाहिए
उपयोगी flags हैं, लेकिन ये options portable नहीं हैं, इसलिए केवल निजी projects के बाहर इनका इस्तेमाल न करने की सलाह दी गई
सिर्फ़ इसलिए कि
.PHONYइस्तेमाल नहीं किया जाता, tutorial में उसे छोड़ देना एक कमज़ोर बहाना है। सही तरीका सिखाना चाहिए, ऐसा मत व्यक्त किया गया.PHONYजोड़ने और बनाए रखने को लेकर बहस हुई.PHONYdeclaration हर recipe के साथ लिखना या file के ऊपर एक साथ रखना—इन अलग-अलग styles के अनुभव साझा किए गए, और इच्छा जताई गई कि इसे linter से enforce किया जा सके-o pipefailको आँख बंद करके लागू करना ठीक नहीं; pipe मेंgrepवगैरह इस्तेमाल होने पर चीज़ें टूट सकती हैं, इसलिए स्थिति के अनुसार लागू करना बेहतर है.PHONYmark करना technically सही है, लेकिन अक्सर अनावश्यक होता है और Makefile को लंबा बना देता है, इसलिए ज़रूरत पर ही इस्तेमाल करना बेहतर हैयह दावा सामने आया कि Make बड़े C codebase की build के लिए specialize किया गया tool है
एक राय यह थी कि 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)
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 इस्तेमाल करने का अनुभव है। (आधिकारिक दस्तावेज़)
एक व्यक्ति ने अपना परिचय Task नामक Make alternative tool के creator और maintainer के रूप में दिया। वह 8 साल से अधिक समय से इस पर काम कर रहा है और tool लगातार विकसित हो रहा है
just को भी एक और Make alternative के रूप में सुझाया गया (just GitHub)
एक दिलचस्प संयोग के तौर पर कहा गया कि मैं Task अक्सर इस्तेमाल करता हूँ, और आज सुबह भी issue दर्ज किया
इस tutorial में कुछ ख़तरनाक और सूक्ष्म समस्याएँ हैं
ifneq (,$(findstring t,$(firstword -$(MAKEFLAGS))))हर GitHub repo में हमेशा Makefile शामिल करने की आदत बताई गई
makeचलाने पर बिना अलग से याद किए project के अपेक्षित actions तुरंत चलाए जा सकते हैं