Разбирање на Make
August 1, 2026 · – посетители
make се појавува во речиси секој embedded и системски проект што сум го допрел — Yocto рецепти, kernel build-ови, U-Boot, мали firmware репозиториуми. Тоа е и една од оние алатки што луѓето ги користат со години преку имитација, копирајќи Makefile од претходниот проект без никогаш целосно да го разберат моделот под него. Еве го тој модел, директно изложен.
Што е Make?
Make е алатка за автоматизација на build процесот: чита датотека (Makefile) што опишува множество цели (targets), предусловите (prerequisites) од кои зависи секоја цел, и рецептот (shell команди) потребен за да се произведе таа цел од нејзините предуслови. Со таков опис, make открива кои цели се застарени и извршува само рецептите потребни да ги ажурира — ништо повеќе.
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c
gcc -c main.c
Кога ќе извршиш make, тој го гради main.o само ако main.c е понов од main.o (или main.o сè уште не постои), а потоа го линкува main само ако main.o или utils.o се промениле. Ништо не се превзема непотребно.
За кого е наменет?
Make е напишан од Stuart Feldman во Bell Labs во април 1976. Според неговото сопствено сведоштво, поттикот бил колега — Steve Johnson, авторот на yacc — кој изгубил цело утро дебагирајќи програма што всушност била исправна; бинарната датотека едноставно не била повторно линкувана по промена во изворниот код. Feldman за тоа ја добил наградата ACM Software System Award во 2003. Алатката произлезена од тоа попладне — следи ги зависностите еднаш, остави ја машината да открие што треба повторно да се направи — стана една од најшироко копираните идеи во софтверските алатки. GNU Make, верзијата што речиси сите ја користат денес, е бесплатна реимплементација од GNU Project (1) (5) (6).
Во пракса, наменета е за секого што составува нетривијален производ — компајлиран бинарен, документ, множество генерирани датотеки — од помали делови, каде повторното градење сè од нула при секоја промена би било бесмислено или бавно. Тоа опфаќа C/C++ проекти, kernel и bootloader build-ови, LaTeX документи, и многу ad-hoc автоматизации кои воопшто немаат врска со компајлирање код.
Кога треба да ја користиш?
Make се исплатува кога три работи важат истовремено:
- Постои вистински граф на зависности — некои излези зависат од други, а повторното градење не е едноставно „регенерирај сè“.
- Повторните градења се доволно скапи за да е важно да се прескокнат непотребните (компајлирање од две секунди не му треба ова; kernel build од дваесет минути му треба).
- Правилата се разумно стабилни — моделот на Make базиран на временски печати на датотеки не се справува со екстремно динамични pipeline-ови толку елегантно колку task runner со експлицитна инвалидација.
За неколку датотеки без меѓусебни зависности, shell скрипта e честo поедноставна и поискрена. Make почнува да се исплатува кога прашањето „дали ова навистина требаше повторно да се гради?“ станува прашање што вреди да се поставува автоматски, а не рачно.
Каде се вклопува во синџирот на алатки?
Make обично седи еден слој над компајлерот и еден слој под тоа што го извршува човек. Во типичен C проект: изворни датотеки → (компајлер, управуван од Make) → објектни датотеки → (linker, управуван од Make) → финален бинарен. Самиот Make не компајлира ниту линкува ништо — ги повикува алатките што тоа го прават, како shell команди во секој рецепт.
Исто така обично седи под build-конфигурациски алатки од повисоко ниво. CMake, Autotools, и BitBake на Yocto, на пример, не го заменуваат Make туку генерираат Makefile-ови (или управуваат со еквивалент) како нивен вистински извршен backend. Значи дури и проекти што никогаш директно не ти покажуваат Makefile честo некаде во pipeline-от извршуваат еден.
Зошто функционира вака?
Основната дизајнерска одлука — споредување на времињата на модификација на датотеките за да се одлучи што е застарено — е тоа што го прави Make брз кај големи дрва: проверката на временски печат е евтина, па повторното градење на огромен проект по промена од еден ред може да прескокне речиси сè и сепак да биде исправно, под услов графот на зависности што му е даден да е точен.
Тој последен дел е изворот на речиси секоја поплака „Make е расипан“ на која луѓето наидуваат, и вреди да се задржиш на неа: Make е само толку точен колку графот што му е кажан. Ако .c датотека прави #include на заглавие кое не е наведено како предуслов на соодветната .o датотека, менувањето на тоа заглавие нема да предизвика повторно градење — не затоа што Make е збунет, туку затоа што никој никогаш не му кажал дека таа зависност постои. Ова е причината зошто вистинските Makefile-ови речиси секогаш се комбинираат со чекор за зависности генериран од компајлерот (-MMD -MP кај GCC/Clang) наместо рачно напишани листи на заглавија — компајлерот веќе го знае вистинскиот include граф, па тоа е единствениот сигурен извор за него (4).
Како всушност функционира?
Цели, предуслови, рецепти. Основната единица е:
target: prerequisite1 prerequisite2
recipe line 1
recipe line 2
Редовите на рецептот мора да бидат навлечени со tab, не со празни места — познат историски недостаток што и денес им прави проблеми на луѓето.
Автоматските променливи ја намалуваат повторливоста:
%.o: %.c
gcc -c $< -o $@
$@ е целта, $< е првиот предуслов — само ова pattern правило може да гради секој .o од соодветната .c датотека без по еден ред за секоја датотека.
.PHONY целите означуваат имиња кои не одговараат на вистински датотеки — clean, all, test — за Make да не се збуни ако случајно постои датотека со тоа име, и секогаш да го извршува рецептот наместо да проверува временски печати: (2)
.PHONY: clean
clean:
rm -f *.o main
Променливите избегнуваат повторување на flag-ови насекаде и го олеснуваат повторното користење меѓу проекти:
CC = gcc
CFLAGS = -Wall -O2
main.o: main.c
$(CC) $(CFLAGS) -c main.c
Автоматското генерирање на зависности ја затвора празнината опишана погоре:
CFLAGS = -Wall -O2 -MMD -MP
-include $(wildcard *.d)
%.o: %.c
$(CC) $(CFLAGS) -c $<
-MMD го тера компајлерот да произведе .d датотека по објект, наведувајќи го секое заглавие што навистина е вклучено во тоа компајлирање. -include ги враќа тие датотеки назад во Makefile-от за графот на зависности да остане точен како што заглавијата се менуваат — без некој рачно да го одржува (3).
Заклучна мисла
Најголемиот дел од тоа што изгледа како чудачки особини на Make — споредбите на временски печати, инсистирањето на tab-ови, начинот на кој недостасувачка зависност пропаѓа тивко наместо гласно — произлегува од една дизајнерска одлука донесена во 1976: моделот да остане едноставен а проверката евтина, и да се верува дека Makefile-от точно ја опишува реалноста. Штом ќе се гледа низ таа призма, повеќето моменти во кои „Make е чуден“ всушност се „графот што му го дадов бил нецелосен“ — а тоа е многу полесно да се поправи.
Поврзани текстови
- Git: бришење гранки, Merge и враќање лош Push — секојдневните команди за гранки и push
Референци
- Feldman, S. I. (1979). Make — A Program for Maintaining Computer Programs. Software: Practice and Experience, 9(4), 255–265. — сопствената статија на Feldman во која го претставува Make, вклучувајќи ја оригиналната мотивација.
- GNU Make Manual — целосната, авторитативна референца за синтаксата и семантиката на GNU Make.
- GNU Make Manual: Automatic Prerequisites — шемата
-MMD/-includeсо зборовите на самите одржувачи. - GCC: Preprocessor Options (
-Mfamily) — што всушност прават-MMDи-MPво позадина. - Make (software) — Wikipedia — општа историја и потекло на имплементациите на Make (BSD Make, GNU Make, NMAKE, итн.).
- Stuart Feldman — Wikipedia — позадина за работата на Feldman во Bell Labs и наградата ACM Software System Award од 2003 година.
Коментари