6 पॉइंट द्वारा GN⁺ 2025-06-21 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Makefile फ़ाइल में हुए बदलाव के आधार पर सिर्फ़ ज़रूरी build steps को दोबारा चलाता है, और मुख्यतः बड़े प्रोजेक्ट्स में C/C++ compilation को automate करने के लिए इस्तेमाल होता है
  • Make target और dependency के filesystem timestamps की तुलना करता है, और command तभी चलाता है जब target मौजूद न हो या dependency उससे नई हो
  • बुनियादी नियम targets: prerequisites और tab से indented command से मिलकर बनता है; spaces से indentation करने पर make fail हो सकता है
  • variables, wildcards, automatic variables, pattern rules, conditionals, functions, recursive make, include, .PHONY, .DELETE_ON_ERROR के ज़रिए build flow को बढ़ाया जा सकता है
  • Cookbook example src/ के C/C++ source से build/ outputs बनाता है और -MMD -MP तथा -include के साथ automatic dependency management सेट करता है

Makefile की ज़रूरत कब पड़ती है

  • Makefile बड़े प्रोग्राम्स में यह तय करने के लिए इस्तेमाल होता है कि किन फ़ाइलों को दोबारा compile करना है
  • ट्यूटोरियल का मुख्य उदाहरण C/C++ files compile करना है
  • फ़ाइल में बदलाव के हिसाब से commands की एक श्रृंखला चलानी हो, तो Make को compilation के अलावा भी इस्तेमाल किया जा सकता है
  • C/C++ के दूसरे build tools के रूप में SCons, CMake, Bazel, Ninja का परिचय दिया गया है
  • Java के लिए Ant, Maven, Gradle हैं, और Go, Rust, TypeScript जैसी भाषाओं के अपने build tools होते हैं
  • Python, Ruby, और pure JavaScript जैसी interpreted languages में फ़ाइल बदलने पर दोबारा compile करने की ज़रूरत नहीं होती, इसलिए Makefile जैसे tools अनिवार्य नहीं होते

Make का बुनियादी execution model

  • उदाहरण make installed terminal में Makefile फ़ाइल बनाकर, उस directory में make चलाने के तरीके से काम करते हैं
  • बुनियादी नियम नीचे दिए गए रूप का पालन करता है
    • targets: prerequisites
    • tab से शुरू होने वाला command
  • target आम तौर पर एक फ़ाइल नाम होता है, और command उस target को बनाने का step होता है
  • prerequisites वे फ़ाइलें हैं जो command चलने से पहले मौजूद होनी चाहिए; इन्हें dependencies भी कहा जाता है
  • Makefile की command lines को अनिवार्य रूप से tab character से indent करना चाहिए; spaces इस्तेमाल करने पर make fail हो जाएगा

target और dependency के समय की तुलना

  • make hello hello target से जुड़ी command चलाता है, लेकिन अगर hello फ़ाइल पहले से मौजूद है तो command नहीं चलती
  • target और फ़ाइल नाम सीधे जुड़े होते हैं, और आम तौर पर target की command उसी नाम की फ़ाइल बनाती है
  • blah: blah.c rule में compilation command तभी चलती है जब blah मौजूद न हो या blah.c, blah से नया हो
  • Make बदलावों का इतिहास सीधे track नहीं करता, बल्कि filesystem timestamps को heuristic की तरह इस्तेमाल करता है
  • अगर फ़ाइल को modify करने के बाद उसका modified time अतीत में बदल दिया जाए, तो Make ग़लत तरीके से मान सकता है कि फ़ाइल नहीं बदली

default target और clean

  • अगर make में कोई target न दिया जाए, तो Makefile का पहला target default target की तरह चलता है
  • all target कई targets को एक साथ बनाने के लिए इस्तेमाल होता है, और इसे पहला rule बना देने पर सिर्फ़ make चलाने से पूरा build हो जाता है
  • clean आम तौर पर build outputs हटाने के लिए इस्तेमाल होने वाला target है, लेकिन यह Make का कोई विशेष reserved word नहीं है
  • अगर clean न तो पहला target हो और न किसी दूसरे target की dependency, तो इसे make clean की तरह explicitly चलाना होगा
  • अगर वास्तव में clean नाम की फ़ाइल मौजूद हो, तो clean target नहीं चल सकता; इसे आगे .PHONY से हल किया जाता है

variables और string processing

  • Make variables सिर्फ़ strings रख सकते हैं, और ट्यूटोरियल आम तौर पर := इस्तेमाल करने की सलाह देता है
  • variable reference के लिए $(name) या ${name} format इस्तेमाल होता है
  • single quotes और double quotes का Make में अपने-आप कोई विशेष अर्थ नहीं होता; वे variable के भीतर सिर्फ़ characters हैं
  • variable definition के दो मुख्य तरीके हैं
    • =: recursive variable, जो इस्तेमाल के समय variables को expand करता है
    • :=: simply expanded variable, जो definition के समय सिर्फ़ पहले से defined values को expand करता है
  • ?= सिर्फ़ उन variables के लिए value सेट करता है जो अभी set नहीं हुए हैं
  • += variable में value जोड़ता है
  • undefined variables को empty string माना जाता है
  • line ending पर spaces हटाए नहीं जाते; एक-character space variable बनाने के लिए $(nullstring) जैसी तकनीक इस्तेमाल की जा सकती है

wildcards और automatic variables

  • Make में * और % दोनों को wildcard कहा जाता है, लेकिन इनके अर्थ अलग हैं
  • * filesystem में matching filenames ढूँढता है, और इसे सीधे variable definition में रखने के बजाय $(wildcard *.o) जैसी wildcard function में wrap करना ज़्यादा सुरक्षित है
  • अगर * किसी फ़ाइल से match नहीं करता, तो wildcard function के बाहर वह ज्यों का त्यों रह सकता है
  • % rules और कुछ functions में इस्तेमाल होता है, और match हुआ हिस्सा stem कहलाता है
  • अक्सर इस्तेमाल होने वाले automatic variables ये हैं
    • $@: target का नाम
    • $?: target से नई dependencies
    • $^: सभी dependencies
    • $<: पहली dependency

implicit rules और pattern rules

  • Make के पास C compilation के लिए implicit rules होते हैं, इसलिए compilation command को explicitly लिखे बिना भी build चल सकता है
  • प्रमुख implicit rules का रूप इस तरह है
    • n.o को n.c से $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@ के रूप में बनाया जाता है
    • n.o को n.cc या n.cpp से $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@ के रूप में बनाया जाता है
    • n को n.o से linker command द्वारा बनाया जाता है
  • implicit rules में महत्वपूर्ण variables हैं CC, CXX, CFLAGS, CXXFLAGS, CPPFLAGS, LDFLAGS
  • static pattern rule का रूप targets...: target-pattern: prereq-patterns ... होता है, और target में % से match हुए stem को dependency pattern में डाला जाता है
  • pattern rule में target में % शामिल होता है, जैसे %.o: %.c, और इसे अपने बनाए implicit rule की तरह इस्तेमाल किया जा सकता है
  • double-colon rule :: एक ही target के लिए कई rules define कर सकता है, लेकिन इसका इस्तेमाल कम होता है

command execution और error handling

  • command के आगे @ लगाने से वह command खुद print नहीं होती
  • make -s चलाने पर यह हर line के आगे @ लगे होने जैसा व्यवहार करता है
  • हर command line नए shell में चलने जैसा प्रभाव देती है, इसलिए एक line में cd .. अगली line को प्रभावित नहीं करता
  • वही shell state बनाए रखने के लिए cd ..; echo \pwd`` की तरह एक line में लिखना या backslash से जोड़ना चाहिए
  • default shell /bin/sh होता है, जिसे SHELL=/bin/bash की तरह बदला जा सकता है
  • shell variable का $ Makefile में लिखने के लिए $$ इस्तेमाल करना होता है
  • error handling execution mode के अनुसार बदलती है
    • make -k: error होने पर भी संभव दूसरे काम जारी रखता है
    • command के आगे -: उस command की error को ignore करता है
    • make -i: सभी commands पर error ignore लागू करता है
  • ctrl+c से Make रोकने पर, Make अभी-अभी बनाया गया सबसे नया target हटा देता है

recursive make और environment variables

  • Makefile के भीतर दोबारा make बुलाते समय make की जगह special variable $(MAKE) इस्तेमाल करना चाहिए
  • $(MAKE) make flags आगे भेजता है और flags के प्रभाव को सही तरीके से संभालता है
  • Make शुरू होते ही execution के समय set environment variables अपने-आप Make variables के रूप में बन जाते हैं
  • export directive variables को सभी recipe shell commands के environment में सेट करता है
  • child make में भी variable इस्तेमाल करना हो तो उस variable को export करना होगा
  • .EXPORT_ALL_VARIABLES इस्तेमाल करने पर सभी variables export किए जा सकते हैं
  • make clean run test की तरह कई targets एक साथ देने पर वे clean, run, test क्रम में चलते हैं

conditionals और functions

  • conditionals ifeq, ifneq, ifdef, ifndef, else, endif से बनते हैं
  • ifdef variable reference को expand नहीं करता, बल्कि सिर्फ़ यह देखता है कि variable defined है या नहीं
  • $(MAKEFLAGS) और findstring का इस्तेमाल करके make -i जैसे flags के pass होने की जाँच की जा सकती है
  • Make functions मुख्यतः text processing के लिए इस्तेमाल होती हैं, और इन्हें $(fn, arguments) या ${fn, arguments} रूप में call किया जाता है
  • प्रमुख functions ये हैं
    • subst: string replacement
    • patsubst: pattern के अनुसार word replacement
    • foreach: space-separated words की list को transform करना
    • if: पहला argument खाली न हो तो दूसरा argument, नहीं तो तीसरा argument
    • call: variable को base function की तरह call करना और $(1), $(2) जैसे parameters इस्तेमाल करना
    • shell: shell को call करता है, लेकिन newlines को spaces में बदल देता है
    • filter: list में से किसी खास pattern से match करने वाले elements चुनता है
    • filter-out: किसी खास pattern से match न करने वाले elements चुनता है

include, vpath, .PHONY, .DELETE_ON_ERROR

  • include filenames... एक या अधिक दूसरे Makefile पढ़ने देता है
  • include तब उपयोगी होता है जब gcc के -M जैसे options से generated Makefile पढ़ना हो
  • vpath <pattern> <directories> बताता है कि किसी खास dependency file को किन directories में ढूँढना है
  • VPATH variable से इसी तरह का search path behavior global रूप से सेट किया जा सकता है
  • backslash \ लंबी commands को कई lines में बाँटकर लिखने के लिए इस्तेमाल होता है
  • किसी target को .PHONY में जोड़ने पर Make उसे उसी नाम की फ़ाइल के साथ confuse नहीं करता
  • .DELETE_ON_ERROR rule चलते समय अगर command non-zero exit status लौटाए, तो उस rule का target हटा देता है
  • .DELETE_ON_ERROR PHONY की तरह सिर्फ़ ठीक पहले वाले target पर लागू नहीं होता, बल्कि सभी targets पर लागू होता है

Makefile Cookbook उदाहरण

  • Cookbook example मध्यम आकार के प्रोजेक्ट्स के लिए Makefile template देता है
  • src/ फ़ोल्डर में C/C++ files रखने पर Makefile compile targets और dependencies अपने-आप तय कर लेता है
  • मुख्य variable configuration इस प्रकार है
    • TARGET_EXEC := final_program
    • BUILD_DIR := ./build
    • SRC_DIRS := ./src
  • SRCS find और $(shell ...) का इस्तेमाल करके *.cpp, *.c, *.s files ढूँढता है
  • OBJS हर source file के आगे BUILD_DIR जोड़कर और .o लगाकर build output path बनाता है
  • DEPS .o को .d में बदलकर dependency Makefile की list बनाता है
  • INC_DIRS src/ के नीचे की सभी directories ढूँढता है, और INC_FLAGS हर directory के आगे -I prefix जोड़ता है
  • CPPFLAGS := $(INC_FLAGS) -MMD -MP GCC को .d dependency files बनाने के लिए configure करता है
  • C और C++ source के लिए अलग-अलग pattern rules build/ के नीचे .o files बनाते हैं, और ज़रूरी directories mkdir -p $(dir $@) से बनाई जाती हैं
  • clean को .PHONY घोषित किया जाता है और rm -r $(BUILD_DIR) से build directory हटाई जाती है
  • -include $(DEPS) .d files को include करता है, लेकिन पहली build की तरह file न होने पर आने वाली error को दबा देता है

1 टिप्पणियां

 
GN⁺ 2025-06-21
Hacker News की राय
  • make के कुछ कम-ज्ञात लेकिन उपयोगी flags हैं। Output synchronization target खत्म होने के बाद ही stdout/stderr दिखाता है, जिससे parallel build के दौरान logs आपस में मिलकर पढ़ने में मुश्किल होने की समस्या कम होती है
    make --output-sync=recurse -j10
    व्यस्त multi-user system में सिर्फ -j jobs की संख्या काफी नहीं हो सकती, इसलिए load average के आधार पर parallelism सीमित भी किया जा सकता है: make -j10 --load-average=10
    target scheduling order को randomize करने वाला --shuffle भी CI में Makefile की छूटी हुई dependencies पकड़ने में उपयोगी है: make --shuffle # or --shuffle=seed/reverse

    • अगर ये “कम-ज्ञात” हैं, तो अच्छा होगा कि make के लेखक options की सूची कहीं संकलित करके program के साथ distribute करें। अगर यह users के पढ़ने लायक text file या typesetting language जैसे रूप में हो, तो ज्ञान तक पहुंच बहुत बेहतर हो जाएगी
    • मैं सबसे ज्यादा -B इस्तेमाल करता हूं, यह option बिना शर्त सब कुछ फिर से build कराता है
    • मैंने make -j को कई बार machine को DoS state में डालते देखा है, इसलिए इसे लगभग bug जैसा मानता हूं
    • अगर यह व्यस्त multi-user system है, तो क्या operating system scheduler इसे handle नहीं कर सकता?
    • उपयोगी तो है, लेकिन portable नहीं है। ऐसे निजी toy projects के बाहर, जिन्हें distribute नहीं करना है, इसे न इस्तेमाल करना बेहतर है
  • 1985 के आसपास Boston University Graphics lab में मैंने किसी को Makefile से animation के लिए 3D renderer generation चलाते देखा था। वे Lisp side के व्यक्ति थे और शुरुआती procedural generation और 3D actor systems पर काम कर रहे थे; Makefile कुल मिलाकर करीब 10 lines का था और बहुत elegant था
    सिर्फ simple file date dependencies से सैकड़ों animations generate किए जाते थे; Lisp हर frame का 3D form बनाता था और Make frames generate करता था
    1985 में, आज 3D और animation में जिन चीजों को हम obvious मानते हैं, वे लगभग मौजूद नहीं थीं, इसलिए इसने सबको चौंका दिया। बाद में उन्होंने Iron Giant का 3D renderer लिखा और, जहां तक मुझे याद है, Coraline में भी key role निभाया। वे Brian Gardner थे

    • क्या यह वही व्यक्ति हैं? http://3d-consultant.com/bio.html
    • आपका मतलब Coraline से ही है, सही?
  • Make उन tools में से है जिन्हें career की शुरुआत में सीख लेने पर मुझे सच में खुशी होती है। आजकल मैं इसे ज्यादा इस्तेमाल नहीं करता, लेकिन इसने imperative systems की तुलना में declarative systems की ताकत दिखाई
    मुझे एहसास हुआ कि यह idea दूसरे कामों तक भी स्वाभाविक रूप से फैलता है। इस site के ऊपर की image देखकर लगता है कि author भी कुछ ऐसा ही महसूस करते हैं: traditional recipe की तरह script-style command sequence में लिखने के बजाय, Makefile की तरह declarative रूप में देखने पर cooking recipes ज्यादा अच्छी तरह समझ आती हैं
    संबंधित लेख: https://blog.gpkb.org/posts/cooking-with-make/
    मैं recipes को हमेशा Makefile की तरह पढ़ने लायक लिखकर kitchen में ले जाता हूं, और सोचता हूं कि क्या किसी ने उन्हें इस तरह typeset या display किया है। नई recipe पढ़ते समय script को दिमाग में Makefile में बदलना न पड़े, तो काफी समय बचेगा

    • इस approach की अच्छी बात यह है कि user असल में dependency graph को खुद interpret करता है और executor बन जाता है, इसलिए उसके पास ज्यादा control होता है। cooking example में declarative structure उन points को बेहतर दिखाता है जहां आगे क्या करना है इसके options खुले होते हैं, और बर्तन धोना आसान हो ऐसे order जैसी बाहरी constraints को भी reflect करने की स्वतंत्रता देता है, जो Makefile में formal रूप से नहीं लिखी जातीं
      बेशक इसकी कीमत यह है कि dependency graph खुद solve करना पड़ता है। जब आप बस पहले से serialized sequential steps follow करना चाहते हैं, तो आपके ऊपर ज्यादा काम आ जाता है
  • लेख में कहा गया है कि ज्यादातर recipes को .PHONY के रूप में mark नहीं किया जाता, और इसे tutorial में अलग से न समझाने की वजह जैसा इस्तेमाल किया गया है, लेकिन यह कमजोर बहाना है। tools को सही usage सिखाना चाहिए
    हमारी team में make को task runner की तरह इस्तेमाल किया जाता है, इसलिए हर recipe में .PHONY जोड़ने और maintain करने पर मुझे टोका गया था
    Clark Grubb की Makefile style guide अच्छी है: https://clarkgrubb.com/makefile-style-guide
    सोचता हूं कि क्या कोई इस style guide का इस्तेमाल करता है। यह भी जानना चाहूंगा कि phony recipes को declaration point पर mark करते हैं या file के top पर एक बड़ी list में रखते हैं। अच्छा होगा अगर इसे enforce करने वाला कोई linter हो

    • पढ़ा, और कुल मिलाकर अच्छा document है। हालांकि कुछ बातों से सहमत नहीं हूं
      -o pipefail को आदतन इस्तेमाल करना मुझे ठीक नहीं लगता। Pipefail उपयोगी है, लेकिन pipelines में लोग जो सबसे आम काम करते हैं—grep से output filter करना—उसे तोड़ देता है। बेहतर है कि इसे सिर्फ उन recipes में अलग से लगाया जाए जहां जरूरत हो
      non-file targets को .PHONY के रूप में mark करना strict sense में सही है, लेकिन आमतौर पर जरूरी नहीं होता। targets ज्यादा हों तो Makefile में अनावश्यक verbosity बढ़ती है, इसलिए मेरे हिसाब से जरूरत पड़ने पर ही जोड़ना बेहतर है
      कई output files बनाने वाली recipes में, पहले जब pattern rules fit नहीं होते थे तो dummy files/flag files standard तरीका थे, लेकिन GNU Make 4.3 से group targets का native support है। यह Ubuntu 22.04 LTS में भी शामिल है: https://www.gnu.org/software/make/manual/html_node/Multiple-...
  • हाल में दिलचस्प बात यह है कि CMake ने तय किया कि C++20 modules इस्तेमाल करने वाले projects के लिए Makefile उपयुक्त नहीं है और Ninja सही है। https://cmake.org/cmake/help/latest/manual/cmake-cxxmodules....
    मूल रूप से लगता है कि target dependencies को statically define करना बहुत कठिन या असंभव है। अब इसे clang-scan-deps जैसे tools से dynamically handle किया जाता है: https://llvm.org/devmtg/2019-04/slides/TechTalk-Lorenz-clang...

    • अगर मैं गलत हूं तो सुधार दें, लेकिन Makefile से जुड़ी यह limitation पूरी तरह CMake की पसंद लगती है, या कम से कम Makefile generator में support जोड़ने वाले volunteers की कमी का नतीजा। Ninja खुद भी C++ modules support नहीं करता: https://github.com/ninja-build/ninja/issues/2457
      दरअसल Ninja में सभी dependencies statically define करनी पड़ती हैं, इसलिए इस मामले में यह सामान्य Make से कम सक्षम है
    • सच कहूं तो modules लगभग एक आपदा हैं
  • बड़े C codebase के build tool के रूप में Make की अपनी जगह है। लेकिन इसे अक्सर general-purpose “project-specific task runner” की तरह इस्तेमाल किया जाता है, और वहां यह ज्यादा फिट नहीं बैठता। क्योंकि साधारण conditionals भी कठिन हैं
    उदाहरण के लिए Terraform को Make से wrap करने की नेकनीयत कोशिशें मैंने कई बार देखी हैं, लेकिन उनका अंत अच्छा नहीं हुआ

    • Make कोई general-purpose task runner नहीं है। यह linear shell scripts को declarative dependencies में बदलने का एक general तरीका ज्यादा है। Shell के लिए एक सामान्य tool है
    • मुझे लगता है कि बड़े C codebase के build tool वाली बात भी अब सही नहीं है। पिछले 20 सालों में ज्यादा मजबूत और अच्छी तरह defined build systems बन चुके हैं, इसलिए update करने का समय है
    • क्या कोई अच्छा general-purpose task runner है? Edit: लगता है मैंने “task runner” शब्द को पूरी तरह गलत समझ लिया था
  • Makefile के messy हिस्सों की जगह लेने के लिए एक बेहतरीन modern tool just है: https://github.com/casey/just

    • यह Make के “छोटी shell scripts की list” वाले पहलू को replace करता है, लेकिन असल में उपयोगी हिस्से—“सिर्फ वही rules चलाना जिन्हें फिर से run करने की जरूरत है”—को replace नहीं करता
    • दूसरे विकल्प भी हैं: Task(Go) https://github.com/go-task/task, Cake(C#) https://github.com/cake-build/cake, Rake(Ruby) https://github.com/ruby/rake
      बिल्कुल अलग concept के तौर पर Makedown भी है, जिसे 8 महीने पहले HN पर cover किया गया था: https://news.ycombinator.com/item?id=41825344
    • ये tools खुद को make के alternatives के रूप में position करते हैं, लेकिन निजी तौर पर मुझे वे पूरी तरह अलग लगते हैं और comparison के योग्य भी नहीं। make का focus artifacts generate करने और जो पहले से बना है उसे दोबारा build न करने पर है, जबकि just एक command runner है
    • Make को command runner के रूप में इस्तेमाल करने का मुख्य फायदा यह है कि यह “हर जगह” installed standard tool है। Alternative tools इस्तेमाल में बेहतर दिखते हैं, फिर भी मुझे कभी ऐसा नहीं लगा कि वे अतिरिक्त tool install करने को justify करने जितना बड़ा फायदा देते हैं
    • Task भी एक और alternative है। हालांकि मैंने personally इसे सिर्फ सरल C hobby projects में इस्तेमाल किया है, इसलिए बड़ा scale होने पर यह कितना टिकेगा, कहना मुश्किल है: https://taskfile.dev/
  • Makefile शानदार हैं, लेकिन उनमें बहुत ज्यादा डूबना ठीक नहीं। कुछ साल पहले pure GNU Make framework बनाने की कोशिश करते हुए मुझे एहसास हुआ कि मैं असल में autoconf को फिर से invent कर रहा हूं, और तभी समझ आया कि GNU autotools क्यों बनाए गए थे
    Makefile अजीब तरह से Lisp जैसी Turing tarpit है। GNU Make में metaprogramming features तक हैं, इसलिए Makefile के अंदर कोई अपवित्र system metaprogram करने की इच्छा रोकना मुश्किल है। GNU Make का इतना व्यापक रूप से installed होना भी इस लालच को बढ़ाता है

  • मैं Make के alternative Task का creator और maintainers में से एक हूं। यह 8 साल से ज्यादा समय से मौजूद है और लगातार विकसित हो रहा है, इसलिए अगर आप कुछ नया खोज रहे हैं तो इसे आजमा कर देखना अच्छा होगा। सवाल भी बेझिझक पूछ सकते हैं
    https://taskfile.dev/
    https://github.com/go-task/task

  • सोच रहा हूं कि क्या किसी ने tup इस्तेमाल किया है: https://gittup.org/tup/ex_dependencies.html
    यह file system access के आधार पर dependencies अपने आप identify करने वाला build system है, इसलिए किसी भी compiler या tool के साथ इस्तेमाल किया जा सकता है