Make jest programem automatyzującym proces kompilacji programów, na które składa się wiele zależnych od siebie plików. Make przetwarza plik reguł Makefile, analizuje daty aktualizacji poszczególnych plików i wywołuje kompilacje tylko tych plików, które są zależne od plików które uległy zmianie od ostatniej kompilacji, co zaoszczędza wiele czasu przy tworzeniu programu, dzięki czemu nie ma potrzeby kompilacji całego projektu co pozwala oszczędzić czas. Ponadto umiejetnie zastosowany upraszcza i przyśpiesza proces instalacji i sprawia iż jest on bardziej elastyczny
Istnieje kilka wersji make, m.in:
Format pliku reguł różni się w zależności od implementacji programu make, ale podstawowe reguły są takie same dla wszystkich odmian make.
Po uruchomieniu, make w bieżącym katalogu próbuje otworzyć plik o nazwie „Makefile„, a jeżeli go nie ma, to próbuje odczytać plik „makefile„. Jeżeli chcemy aby wywołał plik o innej nazwie, to podajemy ją korzystając z opcji -f:
make -f moja_nazwa_pliku
Zanim przejdziemy do tego jak pisać pliki Makefile, warto wspomnieć jeszcze, że jeżeli program jest złożony z jednego pliku plik.c, to wystarczy
make plik
aby go skompilować — make skorzysta z odpowiedniego szablonu przetwarzania, pamiętając o CFLAGS/CXXFLAGS
i odpowiednim celu.
Należy też wspomnieć o parametrach -M i -MM, kompilatora cc/gcc:
gcc -MM *.c
wydrukuje gotową listę zależności dla make’a.
Składnia pliku makefile:
# komentarz... cel : zależność1 zależność2 zależność3 ... zależnośćN komendy ... komendy ... ...
Każda linia komend musi zaczynać się znakiem tabulacji. Dla ustalenia uwagi (dydaktyczny) przykładowy plik makefile mógł by wyglądać tak:
program: program.cpp nagluwek.h g++ program.cpp -o program.cpp
Jeśli chcemy aby napisany makefile był maksymalnie przenośny, elastyczny i zgodny dobrymi praktykami i wytycznymi, to Makefile nawet dla podstawowego jednoplikowego programu powinien być znacznie bardziej rozbudowany.
Szablon maksymalnie przenośnego, elastycznego i zgodnego z dobrymi praktykami i wytycznymi pliku makefile
Jeśli chcemy zaoszczędzić sobie lub innym czasu i problemów w przyszłości, to dobrze od początki i nawet dla najprostszych programów tworzyć pliki Makefile zgodne z ogólnie przyjętymi normami, standardami, wytycznymi i sprawdzonymi rozwiązaniami. W tym celu za bazę możecie przyjąć przygotowany przeze mnie szablon:
# # Podpis i kontakt do autora, licencja # Copyright (C) 2010-2014 Arkadiusz Ćwiek # SHELL = /bin/sh ### Start of configuration section ### srcdir = . prefix = /usr/local # ewentualnie /opt/nazwa-pakietu exec_prefix = $(prefix) bindir = $(exec_prefix)/bin sbindir = $(exec_prefix)/sbin libexecdir = $(exec_prefix)/libexec libdir = $(exec_prefix)/libdir sysconfdir = $(prefix)/etc sharedstatedir = $(prefix)/com localstatedir = $(prefix)/var runstatedir = $(localstatedir)/run includedir = $(prefix)/include datarootdir = $(prefix)/share datadir = $(datarootdir) docdir = $(datarootdir)/doc/nazwa-pakietu infodir = $(datarootdir)/info localedir = $(datarootdir)/locale mandir = $(datarootdir)/man INSTALL = install -C INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_DATA = $(INSTALL) -m 644 INSTALL_MKDIR = $(INSTALL) -d -m 755 CD = cd RMDIR = rmdir CXXFLAGS += -O3 -Wall LFLAGS += ### End of configuration section ### EXEC = program1 program2 compile: $(EXEC) install: $(INSTALL_MKDIR) -d $(bindir)/ && $(INSTALL_PROGRAM) $(EXEC) $(bindir)/ uinstall: $(CD) $$(bindir) && $(RM) $(EXEC) all: compile install clean: $(RM) *~ *.bak *.o *.so *.a $(EXEC) program1: ... ... program2: ... ... ...
Szablon zawiera wszystkie standardowe zmienne które powinny być używane przy definiowaniu katalogów. Natomiast cześć z regułami została ograniczona do niezbędnego minimum. Uważam, że nazwy wszystkich zmiennych są na tyle klarowne, że nie wymagają dodatkowego komentarza.
Warto podkreślić, że tak jak widać w powyższym szablonie do instalacji programów należy używać programu install
, a nie cp
, co jest często występującym błędem.
Ponad żadnego z programów konsolowych tj. np. awk cat cmp cp diff echo egrep expr false grep install-info ln ls mkdir mv printf pwd rm rmdir sed sleep sort tar test touch tr true
nie należy używać bezpośrednio. Zamiast tego należy utworzyć odpowiednią zmienną i używać zewnętrznych programów poprzez te zmienne tak jak to zrobiono w powyższym szablonie dla programów rmdir
, cd
, install
. Ponadto dla części programów istnieją wbudowane reprezentujące je zmienne, np. RM
która odpowiada rm -f
.
Jak NIE pisać makefile!
Na koniec jedna fatalna konstrukcja którą kiedyś widziałem (przez grzeczność nie linkuje do autorów) i której powielać NIE należy — czyli jak NIE tworzyć katalogów i jak NIE instalować programów i bibliotek:
install: all sh -c "if [ ! -d $(DESTDIR) ] ; then mkdir $(DESTDIR) ; fi" cp $(EXEC) $(DESTDIR)/$(EXEC)
Niektórzy pewnie zapytają co w tym złego? Oto odpowiedzi:
- Do instalacji programów i bibliotek należy używać programu/polecenia install a nie cp! Więcej na ten temat napisałem tu.
- Jaki jest sens sprawdzać czy podana ścieżka jest katalogiem — przecież można kazać go po prostu utworzyć — jak istnieje to nic złego się nie stanie.
- Skoro sprawdzamy czy taki katalog przypadkiem nie istnieje przed próbą utworzenia to&dlaczego nie sprawdzamy czy istnieje plik o tej nazwie przecież to stanowiło by realny problem.
- I na koniec — jest sprawdzenie czy katalog istnieje przed utworzeniem (co jest zbędne) ale nie ma sprawdzenia czy został utworzony przed próbą kopiowania do niego pliku.
Podsumowując — czy można być bardziej nie konsekwentnym?
A więc pojawi się pytanie jak to zrobić właściwie? Oto odpowiedź:
install: compile install -D -m 755 $(EXEC) $(bindir)/$(EXEC)
lub jeżeli zmienna ${EXEC} zwiera więcej niż jedną nazwę pliku:
install: compile install -d -m 755 $(bindir) && install -m 755 $(EXEC) $(bindir)