Linux: Kniha kouzel, vanilková příchuť 1.17 (27. února 2022)
Veškerá moc příkazové řádky/příkazového řádku přehledně, pro začátečníky i pokročilé

9. Make

Řada 1.x vanilkové příchuti Linuxu: Knihy kouzel je od 1. října 2020 do 1. března 2023 ve stavu dlouhodobé pasivní údržby; nahlášené chyby budou opravovány, ale aktivní vývoj se již věnuje novější vývojové řadě určené pro novější verze operačního systému a programů. Pokud nejste vázáni na starší verze programů, doporučuji vyhledat novou verzi z aktivně vyvíjené vývojové řady.
program programování

1. Úvod

GNU make je nástroj k automatizaci procesu sestavování určitých cílových souborů ze souborů zdrojových. Vykonává podobnou úlohu jako např. skript napsaný v bashi, ale na rozdíl od něj umí:

Vztahy mezi zdrojovými a cílovými soubory a kompilační příkazy jsou popsány v tzv. Makefilu (souboru jménem „Makefile“), jemuž se bude věnovat většina této kapitoly.

2. Definice

3. Zaklínadla (v souboru Makefile)

3/1 Nastavení proměnných

nastavit proměnnou (expandovat v místě definice)#1
NÁZEV_PROMĚNNÉ := hodnota včetně mezer
připojit obsah na konec proměnné (expanze stejně jako v původní definici)#2
NÁZEV_PROMĚNNÉ += hodnota včetně mezer
nastavit proměnnou (expandovat v každém místě použití)#3
NÁZEV_PROMĚNNÉ = hodnota včetně mezer
přiřadit do proměnné mezeru (trik)#4
PRAZDNA :=
NÁZEV_PROMĚNNÉ := $(PRAZDNA) $(PRAZDNA)
nastavit/připojit víceřádkový obsah#5 (1)
define NÁZEV_PROMĚNNÉ [operátor]
víceřádkový obsah
endef
nastavit proměnnou prostředí pro všechny podprocesy#6
export NÁZEV_PROMĚNNÉ := hodnota včetně mezer

3/2 Rozvoj proměnných

rozvinout proměnnou#1
$(NÁZEV_PROMĚNNÉ)
při rozvinutí proměnné nahradit v každém slově prefix/suffix/obojí#2 (2)
$(NÁZEV_PROMĚNNÉ:[původní-prefix]%=[nový-prefix]%)
$(NÁZEV_PROMĚNNÉ:%[původní-suffix]=%[nový-suffix])
$(NÁZEV_PROMĚNNÉ:[původní-prefix]%[původní-suffix]=[nový-prefix]%[nový-suffix])
rozvinout proměnnou prostředí či příkazového interpretu (ne proměnnou Makefilu)(alternativy)#3
$$NÁZEV_PROMĚNNÉ
$${NÁZEV_PROMĚNNÉ}
rozvinout proměnnou, jejíž název je uložený v jiné proměnné#4
$($(NÁZEV_PROMĚNNÉ))
příklad záměny při rozvoji – vypíše: bbeceda.cpp hlavicka.h bbakus.cpp ostatni.cc#5
TEST := abeceda.cc hlavicka.h abakus.cc ostatni.cc
all:
@echo $(TEST:a%.cc=b%.cpp)

3/3 Automatické a předdefinované proměnné

cíl pravidla (alternativy)#1
$@
$(@)
první zdroj pravidla (alternativy)#2
$<
$(<)
všechny zdroje (alternativy)#3
$^
$(^)
mazání souborů (typicky „rm -f“)#4
$(RM)
program make#5
$(MAKE)
v generovaném a implicitním pravidle posloupnost znaků odpovídající znaku % v %-vzoru cíle#6

3/4 Obecný tvar pravidel

normální (pevné) pravidlo#1
cíle oddělené mezerami: [zdroje oddělené mezerami] [;příkaz]
[ [prefix-příkazu]příkaz]
zobecněné (generované) pravidlo (zdroje lze odvodit od cíle)#2
cíle oddělené mezerami: %-vzor-pro-cíle: cesta-nebo-%-vzor-zdroje [;příkaz]
[ [prefix-příkazu]příkaz]
obecné (implicitní) pravidlo (zdroje lze odvodit od cíle)#3 (3)
%-vzor cíle: cesta-nebo-%-vzor-zdroje [;příkaz]
[ [prefix-příkazu]příkaz]
označit, že určité cíle jsou akce, ne soubory#4
.PHONY: [akce oddělené mezerami]
přeložit soubory uvedené v proměnné ZDROJE, které se nacházejí v adresáři „kod“ a jeho podadresářích a mají příponu „.cc“, na objektové soubory do adresáře obj#5 (4)
$(patsubst kod/%.cc,obj/%.o,$(filter kod/%.cc,$(ZDROJE))): obj/%.o: kod/%.cc
$(CXX) $(CXXFLAGS) -c -o $@ $<
cíl závisí na zdroji, jen pokud zdroj existuje (nepovinný zdroj)#6 (5)
cíle oddělené mezerami: $(wildcard nepovinný-zdroj)
[ [prefix-příkazu]příkaz]
v případě chyby odstranit cílový soubor#7
.DELETE_ON_ERROR: cíl [další-cíl]
v případě chyby pokračovat dál, jako by nenastala#8
.IGNORE: cíl [další-cíl]

3/5 Textové funkce

připojit text před/za každé slovo v řetězci/v rozvoji proměnné#1 (6)
$(addprefix text-před,řetězec slov)
$(addsuffix text-za,řetězec slov)
$(proměnná:%=text-před%text-za)
provést náhradu (záměnu) ve slovech pomocí %-vzoru#2
$(patsubst co-nahradit-%-vzor,čím-nahradit-%-vzor,řetězec slov)
vybrat slova odpovídající/neodpovídající kterémukoliv ze zadaných %-vzorů#3
$(filter %-vzory oddělené mezerou,řetězec slov)
$(filter-out %-vzory oddělené mezerou,řetězec slov)
nahradit všechny výskyty podřetězce#4
$(subst co nahradit,čím nahradit,původní text)
normalizovat bílé znaky (posloupnosti nahradit jednou mezerou, na začátku a konci odstranit)#5
$(strip řetězec)
získat počet slov v řetězci#6
$(words řetězec)
první/poslední/n-té slovo z řetězce#7
$(firstword řetězec)
$(lastword řetězec)
$(word n,řetězec)
předposlední/před-předposlední slovo z řetězce#8
$(if $(word 2,řetězec),$(word $(shell expr $(words řetězec ) - 1),řetězec),)
$(if $(word 3,řetězec),$(word $(shell expr $(words řetězec ) - 2),řetězec),)
seřadit slova a odstranit duplicity#9
$(sort řetězec)
obrátit pořadí slov v řetězci#10
$(shell printf %s\\n '$(strip řetězec slov)' | tr ' ' \\n | tac)

3/6 Analýza adresářových cest (pro každé slovo zvlášť)

získat adresářovou cestu (např. „../a/“)#1 (7)
$(dir řetězec slov)
۵získat samotný název souboru včetně přípony/bez přípony (např. „b.o“, resp. „b“)#2
$(notdir řetězec slov)
$(basename $(notdir řetězec slov))
získat příponu souboru (např. „.o“)#3 (8)
$(suffix řetězec slov)
získat adresářovou cestu (je-li uvedena) + název souboru bez přípony (např. „../a/b“)#4
$(basename řetězec slov)
získat úplnou kanonickou cestu existujících souborů a adresářů (např. „/home/elli/test/a/b.o“)#5
$(realpath řetězec slov)
získat seznam existujících souborů a adresářů odpovídajících vzorku interpretu#6 (9)
$(wildcard vzorek)

3/7 Příkazy v pravidlech

vykonat příkaz bez vypsání#1
@příkaz
vykonat příkaz a ignorovat návratovou hodnotu#2
-příkaz
u určitých cílů nevypisovat příkazy před vykonáním#3
.SILENT: cíl [další-cíl]

3/8 Logické funkce

podmíněný výraz#1
$(if podmínkový řetězec,je-li neprázdný[,jinak])
získat první neprázdný řetězec#2
$(or řetězec[,další řetězec])
získat první prázdný řetězec, nebo jsou-li všechny neprázdné, poslední z nich#3
$(and řetězec[,další řetězec])
pro každé slovo ze seznamu toto slovo nastavit do proměnné a rozvinout podvýraz#4
$(foreach proměnná,seznam,podvýraz)
vrátí podřetězec, pokud se vyskytuje v řetězci; jinak vrátí prázdný řetězec#5
$(findstring podřetězec,řetězec)
příklad – vrátí: aa bb cc cc bb aa#6
$(foreach PROM,a b c c b a,$(PROM)$(PROM))

3/9 Podmíněný překlad a include

podmíněný překlad#1 (10)
ifeq "výraz 1" "výraz 2"
první alternativa
[else ifeq "výraz 1" "výraz 2"
další alternativa]
[else
poslední alternativa]
endif
načíst kód Makefilu z jiných souborů, jako by byl zde#2
include soubory

3/10 Ostatní funkce

vykonat příkaz a rozvinout se na jeho výstup; konce řádků se nahradí mezerami#1
$(shell příkaz shellu)
vyvolat chybu a ukončit zpracování Makefile#2 (11)
$(error popis chyby)
vypsat varování/zprávu#3 (12)
$(warning popis varování)
$(info zpráva)

4. Parametry příkazů

make#1
make [parametry] [cíl]
$(MAKE) [parametry] [cíl]

5. Instalace na Ubuntu

sudo apt-get install make

6. Ukázka

/home/elli/test/Makefile:#1
# Komentář
SHELL := /bin/sh
CXX := g++
CXXFLAGS := -Wall \
  -pedantic -DHOME=\"$$HOME\"
OBJS := main.o second.o
OBJS += hello.o
SOURCES := $(OBJS:%.o=%.cc)
.PHONY: all clean
all: main
clean:
$(MAKE) -C lib clean
$(RM) $(OBJS)
main: $(OBJS)
@echo "Koncové sestavení:"
$(CXX) $(CXXFLAGS) -o $@ $^
main.o second.o: %.o: %.cc lib/hello.h
$(CXX) $(CXXFLAGS) -c -o $@ $<
hello.o:
$(MAKE) -C lib hello.o
cp lib/hello.o $@
/home/elli/test/lib/Makefile:#2
.PHONY: clean
hello.o: hello.cc hello.h
g++ -o $@ -c -Wall -pedantic hello.cc
clean:
$(RM) hello.o

7. Tipy a zkušenosti

8. Další zdroje informací

9. Snímek obrazovky

(snímek obrazovky)
1 „endef“ musí být na samostatném řádku; operátor může být „=“, „:=“ nebo „+=“.
2 Třetí uvedená varianta provede náhradu jen ve slovech, kde odpovídá prefix i suffix. Slova, u kterých odpovídá jen prefix nebo jen suffix, projdou bez náhrady.
3 Obecné pravidlo má nižší prioritu než všechna pevná a generovaná pravidla. Navíc je tiše ignorováno, pokud chybí nekterý ze zdrojů. Má-li uvedeno víc cílů, považují se po jeho provedení všechny uvedené cíle za vygenerované, a tedy se pravidlo nevolá pro překlad dalších zdrojů znovu.
4 Uvedený příklad předpokládá předdefinované proměnné CXX a CXXFLAGS, které GNU make předdefinovává, takže je nemusíte sami nastavovat.
5 Poznámka: nepovinné zdroje se zadávají jako vzorky interpretu oddělené mezerami, což znamená, že „?“, „*“ a „ “ jsou v nich znaky se speciální významem.
6 Při použití poslední uvedené varianty nesmějí text-před a text-za obsahovat znak %.
7 Neobsahuje-li slovo žádné „/“, vrací pro něj $(dir) „./“.
8 Pozor! Slova, která takovou příponu neobsahují, budou touto funkcí vynechána bez náhrady, což sníží počet slov ve výsledném řetězci.
9 Vzorek příkazového interpretu může obsahovat znaky ? a * s významem obvyklým v bashi. Pokud vzorku neodpovídá žádný soubor ani adresář, vzorek se potichu přeskočí. Toho je možno použít k vynechání neexistujících souborů z proměnné.
10 Vybere první z alternativ, kde jsou si uvedené výrazy po rozvinutí rovny. Doporučuji alternativy v Makefilu odsadit, ale není to vyžadováno.
11 Tip: Popis chyby v parametru funkce $(error) může obsahovat rozvoj proměnných a volání funkcí.
12 Výsledkem expanze funkcí $(warning) a $(info) je prázdný řetězec.
Regulární výrazySedSoubory a adresáře