Συντομη επισκοπηση του εργαλειου Make

Εισαγωγη

Το εργαλειο make χρησιμοποιειται για τη διαχειριση μεγαλων εργασιων με πολλα αρχεια. Το make ελεγχει ποια αρχεια εχουν αλλαξει και τα μεταγλωττιζει αυτοματα οταν χρειαζεται.

Makefiles

Οι πληροφοριες για τον τροπο που το make μεταγλωττιζει τα διαφορα αρχεια περιεχονται σε ενα αρχειο που ονομαζεται (συνηθως) Makefile. Το Makefile μπορει να περιεχει κανονες, μεταβλητες, σχολια, γενικους κανονες και οδηγιες (στο παρον κειμενο δεν θα ασχοληθουμε με τις οδηγιες ενω με τους γενικους κανονες θα ασχοληθουμε μονο επιφανειακα). Απο τα παραπανω μονο οι κανονες ειναι απολυτως απαραιτητοι.

Κανονες

Οι κανονες ειναι ο κορμος του Makefile. Η μορφη καθε κανονα ειναι η παρακατω :

<target> : <dependencies>
	<action1>
	<action2>...
Ο κανονας οριζει τρια πραγματα. Το ονομα του αρχειου που πρεπει να ανανεωθει (στοχος - target), ποτε πρεπει να ανανεωθει (εξαρτησεις - dependencies) και πως θα ανανεωθει (ενεργειες - actions). Οι εξαρτησεις (dependencies) ειναι ονοματα αρχειων απο τα οποια εξαρταται ο στοχος (target) και μπορει να ειναι και ονοματα στοχων απο αλλους κανονες. Το make θα εκτελεσει τις ενεργειες (actions) με τη σειρα που οριζονται οταν καποιο απο τα αρχεια που αναφερονται στις εξαρτησεις εχει νεοτερη ημερομηνια απο το στοχο. Οι ενεργειες (actions) μπορει να ειναι οποιεσδηποτε εντολες και πρεπει υποχρεωτικα να ξεκινουν με ΤΑΒ. Η παραλειψη του αρχικου TAB σε καθε γραμμη ενεργειας ειναι και το συνηθεστερο λαθος στην κατασκευη του Makefile.

Εστω για παραδειγμα οτι σε μια εργασια του μαθηματος εχουμε τα αρχεια list.C και list.h . Ενας κανονας για να δημιουργουμε το list.o ειναι:

list.o : list.C list.h
	g++ -c list.C
Αν το εκτελεσιμο της εργασιας ονομαζεται test_data_structures και περιεχει ενα επιπλεον αρχειο main.C, τοτε για να το δημιουργησουμε αρκει να προσθεσουμε στην αρχη του αρχειου Makefile τους κανονες:
test_data_structures : list.o main.o
	g++ -o test_data_structures list.o main.o

main.o : main.C
	g++ -c main.C
Ετσι οταν δινουμε την εντολη make, θα μεταγλωττιζεται μονο οποιο αρχειο ειναι νεοτερο απο το εκτελεσιμο.

Σχολια

Τα σχολια στο Makefile εισαγονται με τη χρηση του χαρακτηρα #: ο χαρακτηρας # και ολοι οι χαρακτηρες μεχρι το τελος της γραμμης αγνοουνται. Τα σχολια μπορουν να εμφανιζονται οπουδηποτε στο Makefile. Παραδειγμα:
LEDA_LIBS = -lL -lm             # Link with the LEDA library of basic data types

Ειδικοι κανονες και ορισματα γραμμης εντολων

Το ονομα ενος στοχου (target) δεν ειναι αναγκη να ειναι καποιο αρχειο. Μπορουμε να ορισουμε εναν ειδικο κανονα που οταν κληθει θα κανει μια εργασια διαφορετικη απο μεταγλωττιση. Ο συνηθεστερος τετοιος κανονας ειναι αυτος που οριζουμε για να διαγραφουν ορισμενα αρχεια οταν δεν χρειαζονται :
clean : 
	rm -f *.o test_data_structures core 
Τετοιους κανονες τους καλουμε δινοντας το ονομα του στοχου σαν ορισμα στη γραμμη εντολων αμεσως μετα το make, π.χ. :
make clean
Το make δεχεται διαφορα ορισματα στη γραμμη εντολων. Το πιο συνηθισμενο ειναι να δοθει σαν ορισμα το ονομα ενος στοχου (οπως το make clean που εχουμε δει παραπανω). Αν δεν υπαρχει ονομα στοχου στη γραμμη εντολων το make επεξεργαζεται τον πρωτο στοχο του Makefile. Αλλο ενα συνηθες ορισμα ειναι το -f <Makefile> που λεει στο make να χρησιμοποιησει καποιο αλλο αρχειο σαν Makefile. Παραδειγμα:
make -f Makefile2

Μεταβλητες

Στην αρχη καθε Makefile μπορουμε να ορισουμε μεταβλητες για να αποφευγουμε περιττες επαναληψεις και να το κανουμε πιο ευαναγνωστο. Οι ορισμοι των μεταβλητων ειναι της μορφης:
<ονομα_μεταβλητης> = <κειμενο>
Αν υπαρχουν κενα στην αρχη του κειμενου αγνοουνται. Οι μεταβλητες μπορουν να χρησιμοποιηθουν οπουδηποτε (ακομη και για τον ορισμο αλλων μεταβλητων). Στην τιμη μιας μεταβλητης αναφερομαστε με τη συνταξη:
$(<ονομα>) η ${<ονομα>}
Εστω για παραδειγμα οτι εχουμε την εργασια που περιγραψαμε παραπανω και εστω οτι θελουμε να καλεσουμε τον μεταγλωττιστη με την επιλογη -g (για debugging). Μπορουμε στην αρχη του Makefile να ορισουμε :
CXXFLAGS = -g
και να τροποποιησουμε τους κανονες ως εξης:
test_data_structures : list.o main.o
	g++ -o $(CXXFLAGS) test_data_structures list.o main.o

list.o : list.C list.h
	g++ -c ${CXXFLAGS} list.C

main.o : main.C
	g++ -c $(CXXFLAGS) main.C
Ετσι αν θελουμε να αλλαξουμε τα ορισματα του compiler π.χ. απο -g σε -O (για βελτιστοποιηση), αρκει να αλλαξουμε μονο τον ορισμο της CXXFLAGS σε :
CXXFLAGS = -O
Η ιδια τακτικη μπορει να χρησιμοποιηθει και για αλλα ορισματα, π.χ. βιβλιοθηκες. Για παραδειγμα αν για την παραπανω εργασια πρεπει να χρησιμοποιησουμε και τη βιβλιοθηκη βασικων τυπων της LEDA θα προσθεσουμε στο Makefile :
LEDA_LIBS = -lL -lm
και θα τροποποιησουμε τον κανονα για το test_data_structures ως εξης:
test_data_structures : list.o main.o
	g++ -o $(CFLAGS) test_data_structures list.o main.o $(LEDA_LIBS) 

Ενσωματωμενοι κανονες

Ειναι κανονες μεταγλωττισης οι οποιοι υποθετουν προεπιλεγμενες (default) τιμες σε συγκεκριμενες μεταβλητες. Αυτο διευκολυνει στο να μην χρειαζεται να οριζουμε κανονες για συνηθισμενες λειτουργιες μεταγλωττισης (παραγωγη object κωδικα, η εκτελεσιμου κωδικα). Για παραδειγμα, εστω οτι ενα αρχειο foo.o παραγεται απο ενα αρχειο foo.c (C κωδικας) μεσω του μεταγλωττιστη gcc (για κωδικα C) με τη χρηση του κανονα:
$(CC) -c $(CPPFLAGS) $(CFLAGS)
οπου η μεταβλητη CC εχει προεπιλεγμενη τιμη gcc, ενω οι μεταβλητες CPPFLAGS και CXXFLAGS θεωρουνται οτι εχουν κενη προεπιλεγμενη τιμη.

Απο την αλλη πλευρα, θα μπορουσε το ιδιο αρχειο foo.o να παραγεται απο ενα αρχειο foo.C η foo.cc (C++ αρχειο) μεσω του μεταγλωττιστη g++ (για κωδικα C++) με τη χρηση του κανονα:

$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
οπου η μεταβλητη CXX εχει προεπιλεγμενη τιμη g++, ενω οι μεταβλητες CPPFLAGS και CXXFLAGS θεωρουνται οτι εχουν κενη προεπιλεγμενη τιμη. (Υπαρχουν τετοιοι κανονες για τους περισσοτερους τυπους αρχειων).

Για να χρησιμοποιησουμε εναν τετοιο κανονα πρεπει ειτε να ορισουμε εναν κανονα χωρις ενεργειες (actions) η να μην ορισουμε κανενα κανονα για καποιο αρχειο. Για παραδειγμα για την εργασια που εχουμε προαναφερει ο κανονας που δημιουργει το main.o μπορει να παραλειφθει. Οταν το make χρειαστει το main.o θα ελεγξει αν υπαρχει το main.C και αν ναι, θα χρησιμοποιησει τον αντιστοιχο ενσωματωμενο κανονα για C++. Οταν θελουμε να ορισουμε επιπλεον εξαρτησεις (dependecies), τοτε οριζουμε ενα σχετικο κανονα αλλα δεν οριζουμε ενεργειες (actions). Στην παραπανω εργασια ο κανονας για το list.o μπορει να τροποποιηθει ως εξης:

list.o : list.h
Η συμπεριφορα των ενσωματωμενων κανονων μπορει να τροποποιηθει με το να δωσουμε διαφορετικες τιμες στις μεταβλητες που χρησιμοποιουν. Ετσι αν θελουμε, μπορουμε να προσθεσουμε στo Makefile :
CXXFLAGS = -g 
για να τροποποιησουμε τη συμπεριφορα του ενσωματωμενου κανονα. (Φυσικα δεν ειναι αναγκαιο να χρησιμοποιησουμε τους ενσωματωμενους κανονες). Στο τελος του κειμενου υπαρχει ενα ολοκληρωμενο παραδειγμα.

Δημιουργια βιβλιοθηκων

Το make μπορει να χρησιμοποιηθει και για την αυτοματοποιηση της δημιουργιας βιβλιοθηκων. Μια βιβλιοθηκη δημιουργειται απο αρχεια object (.o) με τη χρηση των εντολων ar και ranlib. Μπορουμε επομενως να ορισουμε εναν κανονα που θα εκτελει τις παραπανω εντολες. Για παραδειγμα εστω οτι στην εργασια που εχουμε αναφερει υπαρχει και ενα τριτο αρχειο, το stack.C, και θελουμε περα απο το εκτελεσιμο της εργασιας να δημιουργησουμε μια βιβλιοθηκη my_lib.a για μελλοντικη χρηση. Οριζουμε τοτε στο Makefile τον κανονα :
my_lib : list.o stack.o
	ar crv my_lib.a list.o stack.o
	ranlib my_lib.a
(Τα ορισματα της ar σημαινουν τα εξης : c : να δημιουργησει - create - το αρχειο my_lib.a, να προσθεσει - add with replacement - τα αρχεια list.o και stack.o και να αναφερει αναλυτικα - verbose - τις ενεργειες καθως εκτελειται). Για το stack.o δε χρειαζεται να ορισουμε τιποτα αφου θα δημιουργηθει απο το stack.C με τη χρηση ενσωματωμενου κανονα. Για να δημιουργησουμε τη βιβλιοθηκη πρεπει να δωσουμε στη γραμμη εντολων το ονομα του σχετικου στοχου σαν ορισμα, δηλαδη :
make my_lib
Ακολουθει ενα ολοκληρωμενο παραδειγμα.

Παραδειγμα

Εστω οτι εχουμε την παραπανω εργασια που χρησιμοποιει και τη βιβλιοθηκη LEDA που υποθετουμε οτι βρισκεται στο /usr/local/leda. Ενα Makefile για τη συγκεκριμενη εργασια ειναι το παρακατω:
CXX = /usr/local/gcc2.95/bin/g++ # use of g++-2.95 compiler

OBJS = main.o list.o stack.o      # project files
LIB_OBJS = list.o stack.o          # my_lib files

LEDA_PATH = /usr/local/leda     # directory where LEDA libraries are stored
LEDA_LIBS = -lL -lm                  # Link with the LEDA library of basic data types

CXXFLAGS =  -g                        # for debugging; can be changed or omitted later
CPPFLAGS = -I$(LEDA_PATH)

all : test_data_structures my_lib  # rule to create everything

test_data_structures : $(OBJS)   # rule to create executable
	$(CXX) -o test_data_structures $(OBJS) -L$(LEDA_PATH) $(LEDA_LIBS) 

my_lib : $(LIB_OBJS)                 # rule to create my_lib.a from list.o and stack.o
	ar crv my_lib.a list.o stack.o
	ranlib my_lib.a

# we write this rule only to state the dependency of list.o from list.h
list.o : list.h             

#rules for main.o and stack.o are not necessary

clean : 			 # rule to delete unnecessary files
	rm -f *.o test_data_structures core

Επιστροφη στη σελιδα του εργαστηριου.