Na przykładzie narzędzia Make przedstawimy automatyzację zarządzania złożonymi programami. Cel:
Zasady są następujące:
make
na podstawie reguł steruje pracą kompilatorów,
linkerów itp. narzędzi programisty.
Wywołanie:
$ makepowoduje zbudowanie domyślnego obiektu (zob. poniżej), natomiast
$ make prog1powoduje zbudowanie obiektu
prog1
.
Oba wywołania korzystają z domyślnego pliku reguł o nazwie
makefile
. Można to zmienić korzystając z opcji -f
:
$ make -f moje.reguly $ make -f moje.reguly prog1Inna użyteczna opcja to:
Składowe pliku makefile
:
Reguły jawne określają zależności pomiędzy obiektem docelowym (produktem) a obiektami, wymaganymi do jego skonstruowania (surowcami):
prog1.o: prog1.c stale.h
prog1.o
wymagane jest istnienie plików prog1.c
oraz stale.h
.
Poznana reguła opisywała tylko zależności między plikami, nie określała natomiast sposobu regeneracji pliku docelowego.
prog1.o: prog1.c stale.h cc -c prog1.c
Polecenia występujące w regułach mogą być poprzedzane znakami specjalnymi. Poprzedzenie polecenia znakiem specjalnym @ zapobiega wyświetleniu polecenia podczas wykonania
clean: @echo Usuwamy zbędne już pliki rm zbedny.o rm niepotrzebny.o @echo nawet jeśli ich nie ma.
Polecenia w treści reguły wykonuje się w nowym shellu --- normalnie jest
to /bin/sh
, ale można na początku pliku określić inny,
nadając wartość zmiennej SHELL
.
Makrodefinicje służą do zwięzłego nazywania ciągów symboli. Zdefiniowane nazwy, tzw. makra, mogą następnie być wystąpić w regułach i są wtedy zastępowane odpowiadającymi im ciągami symboli. Możemy np. zdefiniować
# # Definicja makra INCLUDE # INCLUDE=stale.h prototypy.h
Makra wołane w regułach poprzedza się znakiem $ (dolar) i otacza nawiasami (nie dotyczy to jednoznakowych makr systemowych). Tak więc zdefiniowane przez nas makro może być następnie użyte w regule w następujący sposób
prog1.o: prog1.c $(INCLUDE)
Reguła ta po rozwinięciu makra przyjmie postać
prog1.: prog1.c stale.h prototypy.h
Typowe makra:
AR | Program do budowy bibliotek (ar) |
AS | Asembler (as) |
CC | Kompilator języka C (cc) |
CFLAGS | Flagi dla kompilatora C |
CXX | Kompilator języka C++ (g++) |
CXXFLAGS | Flagi dla kompilatora C++ |
LDFLAGS | Flagi dla linkera ld |
Dodatkowo w makrowołaniu można użyć podstawienia.
Podstawienie zastępuje podany po znaku :
ciąg znaków innym,
podanym po znaku =
, ale jedynie na końcu symboli
SRCS=glowny.c proc1.c proc2.c OBJS=$(SRCS:.c=.o)
Obiekt docelowy w regule nie musi być plikiem. Takie obiekty określa się jako phony. Ponieważ w bieżącym katalogu może przypadkowo znaleźć się plik o takiej samej nazwie, obiekty takie można (i warto) deklarować
.PHONY: clean
Typowe obiekty, którym nie odpowiadają pliki, to:
clean
, ale dodatkowo usuwa pliki konfiguracyjne.gcc-1.40
.
/usr/bin
oraz pliki pomocnicze w katalogach,
w których powinny być przechowywane (np. /usr/lib
). W przypadku
braku odpowiednich katalogów tworzy je.Program MAKE wywołany bez argumentu próbuje zbudować obiekt domyślny --- docelowy obiekt pierwszej napotkanej reguły jawnej. Obiekt docelowe pozostałych reguł są budowane jedynie w miarę potrzeby.
Często jako pierwszą regułę jawną podaje się regułę
all: prog1 prog2 prog3aby domyślnie zbudować wszystkie wymienione obiekty. Można też oczywiście wywołać MAKE, podając mu jawnie (jako argument) obiekt do zbudowania.
Normalnie program MAKE przerywa pracę po napotkaniu pierwszego polecenia,
którego wykonanie zakończy się błędem.
Poprzedzenie polecenia przedrostkiem -
zapobiega sprawdzaniu
jego poprawności.
Inaczej mówiąc, niezależnie od wyniku wykonania polecenia MAKE kontynuuje pracę
prog1.o: prog1.c stale.h @echo Teraz będziemy kompilować -cc -c prog1.c
objects=main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit: $(objects) cc -o edit $(objects) main.o: defs.h kbd.o: defs.h command.h command.o: defs.h command.h display.o: defs.h buffer.h insert.o: defs.h buffer.h search.o: defs.h buffer.h files.o: defs.h buffer.h command.h utils.o: defs.h clean : -rm edit $(objects)
Często sposób regeneracji objektu nie zależy od konkretnych plików, lecz jest wspólny dla wszystkich plików tego samego typu. Można wtedy skorzystać z reguły domyślnej.
Opisuje ona, jak plik o pewnym rozszerzeniu otrzymuje się z pliku o tej samej nazwie, różniącego się tylko rozszerzeniem. Oba rozszerzenia podaje się wtedy przed dwukropkiem, np.
.c.o: cc -c $<
W regule powyższej wystąpiło systemowe makro $<
, zastępowane
podczas użycia reguły (pełną) nazwą pliku źródłowego.
Istnieją również inne predefiniowane makra systemowe, których nazwami są pojedyncze znaki przestankowe. Ich znaczenie podaje poniższa tabelka:
Makro | Znaczenie |
---|---|
$* | Bazowa nazwa pliku docelowego (bez rozszerzenia) |
$< | Pełna nazwa pliku |
$: |
Katalog zawierający plik (odcięta ostatnia część pełnej nazwy, tzn. nazwa właściwa i rozszerzenie) |
$. |
Właściwa nazwa pliku wraz z rozszerzeniem |
$& |
Sama nazwa pliku (bez katalogu/ścieżki ani rozszerzenia) |
Jeśli pełną nazwą pliku jest /home/pjotr/projekt/prog1.c
, to
poszczególne makra oznaczać będą:
$* /home/pjotr/projekt/prog1 $< /home/pjotr/projekt/prog1.c $: /home/pjotr/projekt/ $. prog1.c $& prog1
Gdy nasz program ma działać na wielu platformach (np. Linux i MS Windows), warto wydzielić w osobne pliki fragmenty zależne od środowiska.
Dla prostego programu w pliku głównym pozostałoby tylko
# Główny plik makefile all = program$(EXE) include environ all : program$(OBJ) $(CC) $@ program$(OBJ) $(LIBS)natomiast zależny od platformy plik
environ
dla UNIXA miałby postać
# Makra make specyficzne dla UNIXA OBJ=.o EXE= CC=cc -g -o LIBS=-lX11 -lma dla MS Windows
# Makra make specyficzne dla MS Windows OBJ=.obj EXE=.exe CC=gcc -g -o LIBS=-lm
Warto poprzedzać wszystkie odwołania do plików źródłowych prefiksem
``$(SRCDIR)
/'', na przykład
${SRCDIR}/parser.tab.c: ${SRCDIR}/parser.y ${YACC} -d ${SRCDIR}/parser.y mv y.tab.c ${SRCDIR}/parser.tab.c mv y.tab.h ${SRCDIR}/parser.tab.h
Dzięki temu można wywołać make
z katalogu innego niż żródłowy.
Zwróćmy uwagę na plik parser.tab.c
--- plik w języku C
generowany przez $(YACC)
. Po utworzeniu przenosimy go do
katalogu źródłowego. Podobnie dzieje się z plikiem y.tab.h
.
Program makedepend
w Unixie (pół)automatycznie generuje
zależności. Ale to samo daje kompilator gcc wywołany z opcją -MM
[zbyszek@katastrofa4 ch12.molecule]$ gcc -MM *.c createmenu.o: createmenu.c filesel.o: filesel.c frontend.o: frontend.c matrix3d.o: matrix3d.c atom.h matrix3d.h misc.o: misc.c molecule.o: molecule.c atom.h matrix3d.h
Pora na większy przykład -- oryginalny makefile dla programu tar
w wersji GNU. Domyślnym obiektem docelowym jest all
.
# Generated automatically from Makefile.in by configure. # Un*x Makefile for GNU tar program. # Copyright (C) 1991 Free Software Foundation, Inc. # This program is free software; you can redistribute # it and/or modify it under the terms of the GNU # General Public License ... ... ... SHELL = /bin/sh #### Start of system configuration section. #### srcdir = . # If you use gcc, you should either run the # fixincludes script that comes with it or else use # gcc with the -traditional option. Otherwise ioctl # calls will be compiled incorrectly on some systems. CC = gcc -O YACC = bison -y INSTALL = /usr/local/bin/install -c INSTALLDATA = /usr/local/bin/install -c -m 644 # Things you might add to DEFS: # -DSTDC_HEADERS If you have ANSI C headers and # libraries. # -DPOSIX If you have POSIX.1 headers and # libraries. # -DBSD42 If you have sys/dir.h (unless # you use -DPOSIX), sys/file.h, # and st_blocks in `struct stat'. # -DUSG If you have System V/ANSI C # string and memory functions # and headers, sys/sysmacros.h, # fcntl.h, getcwd, no valloc, # and ndir.h (unless # you use -DDIRENT). # -DNO_MEMORY_H If USG or STDC_HEADERS but do not # include memory.h. # -DDIRENT If USG and you have dirent.h # instead of ndir.h. # -DSIGTYPE=int If your signal handlers # return int, not void. # -DNO_MTIO If you lack sys/mtio.h # (magtape ioctls). # -DNO_REMOTE If you do not have a remote shell # or rexec. # -DUSE_REXEC To use rexec for remote tape # operations instead of # forking rsh or remsh. # -DVPRINTF_MISSING If you lack vprintf function # (but have _doprnt). # -DDOPRNT_MISSING If you lack _doprnt function. # Also need to define # -DVPRINTF_MISSING. # -DFTIME_MISSING If you lack ftime system call. # -DSTRSTR_MISSING If you lack strstr function. # -DVALLOC_MISSING If you lack valloc function. # -DMKDIR_MISSING If you lack mkdir and # rmdir system calls. # -DRENAME_MISSING If you lack rename system call. # -DFTRUNCATE_MISSING If you lack ftruncate # system call. # -DV7 On Version 7 Unix (not # tested in a long time). # -DEMUL_OPEN3 If you lack a 3-argument version # of open, and want to emulate it # with system calls you do have. # -DNO_OPEN3 If you lack the 3-argument open # and want to disable the tar -k # option instead of emulating open. # -DXENIX If you have sys/inode.h # and need it 94 to be included. DEFS = -DSIGTYPE=int -DDIRENT -DSTRSTR_MISSING \ -DVPRINTF_MISSING -DBSD42 # Set this to rtapelib.o unless you defined NO_REMOTE, # in which case make it empty. RTAPELIB = rtapelib.o LIBS = DEF_AR_FILE = /dev/rmt8 DEFBLOCKING = 20 CDEBUG = -g CFLAGS = $(CDEBUG) -I. -I$(srcdir) $(DEFS) \ -DDEF_AR_FILE="$(DEF_AR_FILE)" \ -DDEFBLOCKING=$(DEFBLOCKING) LDFLAGS = -g prefix = /usr/local # Prefix for each installed program, # normally empty or `g'. binprefix = # The directory to install tar in. bindir = $(prefix)/bin # The directory to install the info files in. infodir = $(prefix)/info #### End of system configuration section. #### SRC1 = tar.c create.c extract.c buffer.c \ getoldopt.c update.c gnu.c mangle.c SRC2 = version.c list.c names.c diffarch.c \ port.c wildmat.c getopt.c SRC3 = getopt1.c regex.c getdate.y SRCS = $(SRC1) $(SRC2) $(SRC3) OBJ1 = tar.o create.o extract.o buffer.o \ getoldopt.o update.o gnu.o mangle.o OBJ2 = version.o list.o names.o diffarch.o \ port.o wildmat.o getopt.o OBJ3 = getopt1.o regex.o getdate.o $(RTAPELIB) OBJS = $(OBJ1) $(OBJ2) $(OBJ3) AUX = README COPYING ChangeLog Makefile.in \ makefile.pc configure configure.in \ tar.texinfo tar.info* texinfo.tex \ tar.h port.h open3.h getopt.h regex.h \ rmt.h rmt.c rtapelib.c alloca.c \ msd_dir.h msd_dir.c tcexparg.c \ level-0 level-1 backup-specs testpad.c all: tar rmt tar.info tar: $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) rmt: rmt.c $(CC) $(CFLAGS) $(LDFLAGS) -o $@ rmt.c tar.info: tar.texinfo makeinfo tar.texinfo install: all $(INSTALL) tar $(bindir)/$(binprefix)tar -test ! -f rmt || $(INSTALL) rmt /etc/rmt $(INSTALLDATA) $(srcdir)/tar.info* $(infodir) $(OBJS): tar.h port.h testpad.h regex.o buffer.o tar.o: regex.h # getdate.y has 8 shift/reduce conflicts. testpad.h: testpad ./testpad testpad: testpad.o $(CC) -o $@ testpad.o TAGS: $(SRCS) etags $(SRCS) clean: rm -f *.o tar rmt testpad testpad.h core distclean: clean rm -f TAGS Makefile config.status realclean: distclean rm -f tar.info* shar: $(SRCS) $(AUX) shar $(SRCS) $(AUX) | compress \ > tar-`sed -e '/version_string/!d' \ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \ -e q version.c`.shar.Z dist: $(SRCS) $(AUX) echo tar-`sed \ -e '/version_string/!d' \ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \ -e q version.c` > .fname -rm -rf `cat .fname` mkdir `cat .fname` ln $(SRCS) $(AUX) `cat .fname` -rm -rf `cat .fname` .fname tar chZf `cat .fname`.tar.Z `cat .fname` tar.zoo: $(SRCS) $(AUX) -rm -rf tmp.dir -mkdir tmp.dir -rm tar.zoo for X in $(SRCS) $(AUX) ; do \ echo $$X ; \ sed 's/$$/^M/' $$X \ > tmp.dir/$$X ; done cd tmp.dir ; zoo aM ../tar.zoo * -rm -rf tmp.dirZbigniew Jurkiewicz, Instytut Informatyki UW