CE121 Προγραµµατισµός 2 Εισαγωγή σε Makefiles 1
Η διαδικασία µεταγλώττισης myprog.c gcc myprog 2
Η διαδικασία µεταγλώττισης myprog.c preprocessor (cc1) /tmp/cczxt.i assembler (as) compiler (cc1) /tmp/cczxt.o /tmp/cczxt.s linker (ld) myprog 3
Η διαδικασία µεταγλώττισης! Δοκιµάστε: gcc -E test.c > test.i gcc -S test.i (παράγει αρχείο.s) gcc -c test.s (παράγει αρχείο.o) gcc test.o -o test 4
Separate compilation! Στις περισσότερες περιπτώσεις, ο κώδικας ενός προγράµµατος είναι µοιρασµένος σε διάφορα αρχεία. Γιατί? Πώς?.h files: ορισµοί τύπων, prototypes συναρτήσεων, καθολικές µεταβλητές.c files: υλοποιήσεις συναρτήσεων Ξεχωριστά γκρουπ από.c/.h αρχεία για ξεχωριστές ενότητες του προγράµµατος. 5
Separate compilation! Τι χρειάζεται να ξέρει ο compiler για να παράγει object code? Ορισµούς τύπων prototypes συναρτήσεων που καλούνται ΔΕ χρειάζεται να ξέρει την υλοποίησή τους!! Άρα: Μπορούµε να κάνουµε compile κάθε ένα από τα αρχεία C ξεχωριστά ώστε να παράγουµε object files και στο τέλος να συνδέσουµε όλα τα object files µαζί για να παράγουµε ένα εκτελέσιµο. 6
Separate compilation! Μια λύση για το hw1 util.h : prototypes βοηθητικών συναρτήσεων, ορισµοί #define util.c : υλοποιήσεις των συναρτήσεων του util.h db.h : prototypes συναρτήσεων βάσης (import, delete, export κτλ.) db.c: υλοποιήσεις των συναρτήσεων του db.h καλούνται και συναρτήσεις που δηλώθηκαν στο util.h main.c: main και wrappers για τις βασικές λειτουργίες. καλούνται οι συναρτήσεις που δηλώθηκαν στο db.h και κάποιες από το util.h 7
make! Τι είναι το make? Ένα εργαλείο που χρησιµοποιείται για τη διαχείριση εργασιών µε πολλά αρχεία πηγαίου κώδικα. Το make: ελέγχει ποια από τα αρχεία έχουν αλλαχθεί από την τελευταία µεταγλώττιση, και µεταγλωττίζει ξανά µόνο αυτά καθώς και όσα άλλα αρχεία της εργασίας εξαρτώνται από αυτά. Για να λειτουργήσει χρειάζεται ένα Makefile 8
Makefile! Τι είναι Makefile? Ένα αρχείο στο οποίο καταγράφουµε: πληροφορίες για τις σχέσεις εξάρτησης (include) ανάµεσα στα αρχεία της εργασίας µας, και οδηγίες για το πώς θέλουµε να γίνει η µεταγλώττιση (και όχι µόνο) των αρχείων. Το εργαλείο make διαβάζει ένα Makefile και µεταγλωττίζει όσα αρχεία χρειάζεται µε βάση τις πληροφορίες και οδηγίες που βρίσκει σε αυτό. Το Makefile πρέπει να έχει όνοµα είτε makefile είτε Makefile (συνηθέστερο). 9
Γιατί χρησιµοποιούµε Makefiles?! Κάθε φορά που αλλάζει ένα αρχείο, δε χρειάζεται να µεταγλωττίζονται όλα.! Πιο εύκολο να γράψουµε make αντί να µεταγλωττίζουµε ένα-ένα αρχείο χωριστά. Ειδικά αν υπάρχουν πολλαπλά flags! Το Makefile µπορεί να περιέχει επιπλέον εντολές που θέλουµε να εκτελούνται κάθε φορά (πχ. να σβήνει κάποιο αρχείο) 10
Στοιχεία ενός Makefile! Μεταβλητές Για ονόµατα αρχείων, προγραµµάτων, flags κτλ. που εµφανίζονται σε πολλά σηµεία! Κανόνες Αποτελούνται από το όνοµα ενός στόχου (τι θέλουµε να δηµιουργήσουµε) και σχετικές εντολές (πώς να το δηµιουργήσουµε) Οι εντολές είναι εντολές κελύφους (shell commands)! Σχόλια! και πολλά άλλα. Δείτε το manual. 11
Χρήση makefile! Σύνταξη: make < όνομα στόχου> Το εργαλείο make ψάχνει στο τρέχον directory να βρει ένα αρχείο µε όνοµα Makefile (ή makefile) και εκτελεί τις εντολές για το συγκεκριµένο στόχο (αφού πρώτα εκτελέσει ότι εντολές αντιστοιχούν σε προαπαιτούµενους στόχους)! Σύνταξη: make Οµοίως µε πριν, αλλά εκτελεί τις εντολές για τον πρώτο στόχο που εµφανίζεται στο Makefile, εφόσον δεν έχουµε προσδιορίσει κάποιον. 12
Κανόνες <target>: <dependencies> <command1> <command2>...! target (στόχος): Αρχείο προς κατασκευή (συνήθως κάποιο εκτελέσιµο) ή ενέργεια προς ολοκλήρωση! dependencies (εξαρτήσεις): ένα ή περισσότερα αρχεία ή ενέργειες από τις οποίες εξαρτάται ο στόχος! commands: εντολές που το make στέλνει προς το shell και θα έχουν ως αποτέλεσµα το να δηµιουργηθεί ο στόχος. Το σύνολο των εντολών για ένα στόχο λέγεται και συνταγή. ΠΡΟΣΟΧΗ: Πρέπει να υπάρχει ένα tab πριν κάθε εντολή Εντολές µεγάλου µήκους µπορούν να συνεχίσουν στην επόµενη γραµµή αν η προηγούµενη τελειώνει σε \ 13
Παράδειγµα #include "util.h" int main (int argc, char *argv[]) { parsecmd(argv);... main.c } #ifndef UTIL_H #define UTIL_H util.h void parsecmd(char *[]); compile, link, exec στην κονσόλα: > gcc -Wall -c util.c > gcc -Wall -c main.c > gcc main.o util.o -o all >./all input 13 #include "util.h" #include<stdio.h> #include<string.h> util.c void parsecmd (char *cmd[]) { char *word;... } 14
Παράδειγµα #include "util.h" int main (int argc, char *argv[]) { parsecmd(argv);... main.c } #ifndef UTIL_H #define UTIL_H util.h void parsecmd(char *[]); main.o all util.o εκτελέσιµο (τελικός στόχος) εξαρτήσεις αρχείων #include "util.h" #include<stdio.h> #include<string.h> util.c void parsecmd (char *cmd[]) { char *word;... } main.c util.c util.h 15
Παράδειγµα all main.o util.o main.c util.c util.h all: main.o util.o gcc -Wall -g main.o util.o -o all main.o: main.c util.h gcc -Wall -g -c main.c Makefile util.o: util.c util.h gcc -Wall -g -c util.c 16
Μεταβλητές <name> = <value>! name (όνοµα): Το όνοµα της µεταβλητής! value (τιµή): ένα ή περισσότερα αρχεία ή ενέργειες από τις οποίες εξαρτάται ο στόχος! Το όνοµα χρησιµοποιείται ως συντοµογραφία για την τιµή! Για να χρησιµοποιηθεί το όνοµα, περικλείεται σε $( ) 17
Παράδειγµα CC = gcc CFLAGS = -Wall -g OBJ = main.o util.o Makefile all: $(OBJ) $(CC) main.o util.o -o all main.o: main.c util.h $(CC) $(CFLAGS) -c main.c util.o: parce.c util.h $(CC) $(CFLAGS) -c util.c 18
Σχόλια! Ξεκινούν από # και τελειώνουν στο τέλος της γραµµής! Μπορούν να τοποθετηθούν (σχεδόν) οπουδήποτε 19
Παράδειγµα CC = gcc CFLAGS = -Wall -g OBJ = main.o util.o #all object files #rule to create everything all: $(OBJ) $(CC) $(OBJ) -o all main.o: main.c util.h $(CC) $(CFLAGS) -c main.c util.o: parce.c util.h $(CC) $(CFLAGS) -c util.c Makefile 20
Patterns και αυτόµατες µεταβλητές! Το σύµβολο % µπορεί να χρησιµοποιηθεί στο στόχο ή/και στις εξαρτήσεις) για να αναπαραστήσει µέρος του ονόµατος ενός αρχείου.! Το σύµβολο $* αναπαριστά το κοµµάτι του ονόµατος που αντιστοιχεί στο %! Το σύµβολο $< αναπαριστά το πρώτο αρχείο στη λίστα εξαρτήσεων.! Το σύµβολο $@ αναπαριστά το όνοµα του στόχου. CC = gcc OBJ = foo.o bar.o all: $(OBJ) $(CC) $(OBJ) -o all foo.o: foo.c foo.h $(CC) -c foo.c CC = gcc OBJ = foo.o bar.o all: $(OBJ) $(CC) $(OBJ) -o $@ %.o: %.c %.h $(CC) -c $< bar.o: bar.c bar.h $(CC) -c bar.c 21
Στόχοι που δεν είναι αρχεία! Μπορούµε να δηµιουργήσουµε ένα κανόνα που δεν έχει ως αποτέλεσµα τη µεταγλώττιση αρχείων, αλλά κάποια άλλη πράξη.! Σε αυτή την περίπτωση, ο στόχος δεν είναι όνοµα αρχείου.! Συνήθως χρησιµοποιούµε το χαρακτηρισµό.phony για να αποφύγουµε "συγκρούσεις" µε αρχεία που πιθανώς έχουν ίδιο όνοµα µε το στόχο.! Τυπική εφαρµογή: κανόνας για το σβήσιµο περιττών αρχείων µετά την ολοκλήρωση της µεταγλώττισης..phony: clean clean: rm -rf *.o *.dsym 22
Κανόνες χωρίς εντολές! Ένας κανόνας µπορεί να αποτελείται µόνο από το στόχο και τις εξαρτήσεις. test.%s: test.% a.out./a.out test.$* test: test.1s test.2s test.3s test.4s 23
Σειρά εκτέλεσης all: target1 @echo zero target1: target2 target3 @echo one target2: @echo two Με ποια σειρά θα εµφανιστούν τα µηνύµατα? Οι στόχοι target2 και target3 πρέπει να χτιστούν πριν τον στόχο target1 και ο target1 πριν τον all target3: @echo three ΠΡΟΣΟΧΗ: Δεν υπάρχει κάποιος κανόνας για τη σειρά µε την οποία θα χτιστούν τα target2, target3 24
Παράδειγµα all: target1 @echo zero target1: target2 target3 @echo one Πιθανή έξοδος : two target2: @echo two target3: @echo three 25
Παράδειγµα all: target1 @echo zero target1: target2 target3 @echo one Πιθανή έξοδος: two three target2: @echo two target3: @echo three 26
Παράδειγµα all: target1 @echo zero target1: target2 target3 @echo one Πιθανή έξοδος: two three one target2: @echo two target3: @echo three 27
Παράδειγµα all: target1 @echo zero target1: target2 target3 @echo one target2: @echo two target3: @echo three Πιθανή έξοδος: two three one zero Εναλλακτική έξοδος: three two one zero 28
Εύρεση εξαρτήσεων! gcc -MM <αρχεία> Επιστρέφει µια λίστα εξαρτήσεων για τα δεδοµένα αρχεία, χωρίς να περιλαµβάνονται system headers Χρησιµοποιήστε -Μ για να συµπεριληφθούν system headers 29
Συχνά λάθη! Δεν υπάρχει tab στην αρχή εντολής (ελέγξτε µήπως βάλατε κενά κατά λάθος ή µήπως ο editor που χρησιµοποιείτε αντικατέστησε το tab µε κενά): Makefile:4: *** missing separator. Stop.! Δεν υπάρχει κάποιος στόχος: make: *** No rule to make target 'all'. Stop. 30
Συχνά λάθη! Πολλαπλές "συνταγές" για τον ίδιο στόχο: Makefile:7: warning: overriding commands for target `test1' Makefile:4: warning: ignoring old commands for target `test1' Σε αυτή την περίπτωση εκτελείται όποια συνταγή είναι τελευταία. Σηµείωση: Επιτρέπεται να υπάρχουν πολλαπλοί κανόνες για τον ίδιο στόχο (µε εξαρτήσεις µόνο) Δείτε το manual για περισσότερες πληροφορίες. 31
Μία εργασία σε πολλά αρχεία! Οι my_read, my_write, my_dup2 κτλ. θα χρησιµοποιηθούν από περισσότερα από ένα προγράµµατα. Μπορείτε να τις βάλετε σε ξεχωριστό αρχείο. Ένα αρχείο µε κατάληξη.h περιέχει µόνο τα prototypes αυτών των συναρτήσεων (ποτέ υλοποιήσεις) Ένα αρχείο µε κατάληξη.c (και ίδιο όνοµα) περιέχει τις υλοποιήσεις τους. Όποιο πρόγραµµα θέλει να χρησιµοποιήσει αυτές τις συναρτήσεις κάνει #include το.h αρχείο. Προσοχή: Χρησιµοποιήστε " " και όχι < > γύρω από το όνοµα. Παρόµοια λογική µε το παράδειγµα του slide 14. 32
Μία εργασία σε πολλά αρχεία! Πιθανό πρόβληµα: Το test1.h περιέχει κάποιες δηλώσεις τύπων. To test2.h κάνει #include το test1.h και περιέχει µερικές ακόµη δηλώσεις/prototypes. Το test.c κάνει #include τα test1.h, test2.h Πρόβληµα: οι δηλώσεις του test1.h περιέχονται δύο φορές και ο compiler διαµαρτύρεται! Πώς αποφεύγουµε το "διπλό" #include? Με ειδικά directives 33
Μία εργασία σε πολλά αρχεία! Η µορφή του.h αρχείου είναι: #ifndef NAME_H #define NAME_H Αν δεν έχει οριστεί η σταθερά NAME_H, όρισέ τη, /* prototypes κτλ. */ #endif δες αυτόν τον κώδικα (πχ. κάνε τον copy+paste όπως ζητά το #include) µέχρι εδώ! Την πρώτη φορά που θα γίνει #include ένα header file, δε θα είναι αρχικά ορισµένη η σταθερά, θα οριστεί, και θα γίνει copy+paste ο κώδικας.! Τη δεύτερη φορά θα έχει οριστεί η σταθερά, οπότε θα αγνοηθεί ο κώδικας µέχρι το #endif! Τυπικά το όνοµα της σταθεράς είναι παρόµοιο µε του αρχείου. 34