Make tutorial

Από Κοινότητα Ελεύθερου Λογισμικού ΕΜΠ
Μετάβαση σε: πλοήγηση, αναζήτηση

Όταν έχουμε ένα μεγάλο project με πολλά αρχεία πηγαίου κώδικα είναι δύσκολο να το διαχειριστούμε από μόνοι μας. Χρειαζόμαστε κάποιο εργαλείο που θα αυτοματοποιεί κάπως την διαδικασία. Ένα τέτοιο εργαλείο είναι και το make. Αυτός ο οδηγός φιλοδοξεί να σας βοηθήσει να χειρίζεστε το make να γράφετε modular Makefiles.


Εισαγωγικά

Το make πρωτοδημιουργήθηκε από τον Stuart Feldman το 1977 στα Bell Labs. Εδώ θα παρουσιάσουμε μια πιο σύγχρονη εκδοχή του, το GNU Make που είναι μέρος του GNU toolchain. Το make είναι ένα εργαλείο αυτοματοποίησης μιας διαδικασίας πολλών βημάτων, σαν τα shell scripts ή τα batch files στα Windowz συστήματα. Η διαφορά του make και το σημείο στο οποίο υπερτερεί από τα προηγούμενα, είναι η χρήση των εξαρτήσεων. Αλλά ας προσφύγουμε σε ένα παράδειγμα για να γίνει πιο κατανοητό αυτό. Έστω ότι έχουμε ένα κλασικό project γραμμένο σε C που αποτελείται από τα εξής αρχεία:

  • compiler.c
  • tokens.h
  • lexer.l
  • parser.y
  • common.h
  • seman.c
  • symbol.h
  • symbol.c

Για να δημιουργηθεί το τελικό εκτελέσιμο, πρέπει να εκτελεστούν με την σειρά οι παρακάτω εντολές:

flex lexer.l -o lexer.c
bison parser.y -o parser.c
gcc -c lexer.c -o lexer.o
gcc -c parser.c -o parser.o
gcc -c seman.c -o seman.o
gcc -c symbol.c -o symbol.o
gcc -c compiler.c -o compiler.o
gcc -lfl lexel.o parser.o seman.o symbol.o compiler.o -o compiler

Δεν είναι και λίγες για να τις πληκτρολογούμε σε κάθε iteration του debugging μας, αν και είναι δυνατόν να συμπτυχθούν. Μια ιδέα θα ήταν να της βάλουμε όλες μαζί σε ένα script και να εκτελούμε κάθε φορά αυτό:

build.sh:

#!/bin/sh
flex lexer.l -o lexer.c
bison parser.y -o parser.c
gcc -c lexer.c -o lexer.o -Wall
gcc -c parser.c -o parser.o -Wall
gcc -c seman.c -o seman.o -Wall
gcc -c symbol.c -o symbol.o -Wall
gcc -c compiler.c -o compiler.o -Wall
gcc -lfl lexer.o parser.o seman.o symbol.o compiler.o -o compiler

Αυτό όμως έχει το μειονέκτημα ότι αν κάνουμε μια αλλαγή σε ένα μόνο αρχείο θα ξαναεκτελεστούν όλες οι εντολές από την αρχή και δεν θα αξιοποιηθεί η πληροφορία ότι μια αλλαγή στο compiler.c θα επηρεάσει μόνο το compiler.o και το τελικό compiler. Αυτό το κενό έρχεται να καλύψει το make και η χρήση των targets και των prerequisites.

Το make χρειάζεται ένα αρχείο ρυθμίσεων που περιέχει τις εντολές όπως το παραπάνω build.sh. Αυτό παραδοσιακά λέγεται Makefile και είναι οργανωμένο σε ενότητες που λέγονται rules. Κάθε rule αναφέρεται σε (τουλάχιστον) ένα target. Ένα target είναι ένας στόχος, συνήθως η δημιουργία ενός αρχείου, που πρέπει να πετύχει το make στην διαδικασία του build. Για την επίτευξη ενός στόχου πρέπει να εκτελεστούν κάποιες εντολές και πιθανόν να πρέπει να ικανοποιηθούν κάποιοι άλλοι στόχοι πρώτα, τα λεγόμενα prerequisites:

# Structure of the Makefile rule
target: prerequisite_1 prerequisites_2
        command 1
        command 2
        ...
        command Ν

Έτσι στο παράδειγμά μας για να δημιουργηθεί το τελικό εκτελέσιμο compiler φτιάχνουμε ένα rule με target με το ίδιο όνομα και ορίζουμε τα prerequisites και τις εντολές που πρέπει να εκτελεστούν ώστε να ικανοποιηθεί αυτός ο στόχος, δηλαδή η δημιουργία του compiler:

compiler: lexer.o parser.o seman.o symbol.o compiler.o
        gcc -lfl lexer.o parser.o seman.o symbol.o compiler.o -o compiler

Προσέξτε το υποχρεωτικό <tab> που υπάρχει στην αρχή κάθε εντολής. Έτσι ξεχωρίζουν οι εντολές που πρέπει να εκτελεστούν για κάθε rule. Με παρόμοιο τρόπο δημιουργούμε τα targets για τα υπόλοιπα αρχεία με τις εντολές που χρειάζεται το καθένα και τα prerequisites του. Το τελικό Makefile έχει ως εξής:

Makefile:

lexer.c: lexer.l
        flex lexer.l -o lexer.c

parser.c: parser.y
        bison parser.y -o parser.c

lexer.o: lexer.c tokens.h common.h
        gcc -c lexer.c -o lexer.o -Wall

parser.o: parser.c tokens.h common.h
        gcc -c parser.c -o parser.o -Wall

seman.o: seman.c common.h
        gcc -c seman.c -o seman.o -Wall

symbol.o: symbol.c symbol.h common.h
        gcc -c symbol.c -o symbol.o -Wall

compiler.o: compiler.c
        gcc -c compiler.c -o compiler.o -Wall

compiler: lexer.o parser.o seman.o symbol.o compiler.o
        gcc -lfl lexer.o parser.o seman.o symbol.o compiler.o -o compiler

Μπορούμε τώρα να εκτελέσουμε την εντολή make compiler και να δημιουργήσουμε το εκτελέσιμό μας. Βλέπουμε ότι έχουμε προσθέσει την επιπλέον πληροφορία των prerequisites και ότι έτσι μπορεί το make να ξέρει ότι όταν αλλάξει το αρχείο tokens.h δεν χρειάζεται να ξαναδημιουργήσει όλα τα αρχεία από την αρχή αλλά μόνο αυτά που εξαρτώνται, εμμέσως ή αμέσως, από αυτό (στην περίπτωσή μας θα ξαναεκτελεστούν οι κανόνες για τους στόχους lexer.o parser.o και compiler). Το make αναλαμβάνει να λύσει τον γράφο των εξαρτήσεων εικόνα, που πρέπει να είναι ακυκλικός, δηλαδή δεν μπορεί ένας στόχος να εξαρτάται άμεσα ή έμεσα από τον εαυτό του, για να βρει ποια rules πρέπει να εκτελέσει. Αυτό το κάνει βλέποντας τον χρόνο τελευταίας αλλαγής των αρχείων και εντοπίζοντας ποια έχουν αλλάξει και χρειάζονται update. Βλέπουμε ότι δεν χρειάζεται τα prerequisites να υπάρχουν σαν targets αρκεί να υπάρχουν σαν αρχεία.

Μεταβλητές

Γενικά στο προγραμματισμό είναι καλή πρακτική να γράφουμε την πληροφορία μια μόνο φορά. Την ίδια πρακτική ακολουθούμε και στα Makefiles. Βλέπουμε ότι την εντολή για τον C compiler gcc καθώς και το flag -Wall τα έχουμε γράψει πολλές φορές. Θα ήταν προτιμότερο να δηλώναμε κάπου ξεχωριστά αυτά τα δύο ώστε αν κάποια στιγμή αλλάξουν να μην χρειαστεί να αλλάξουμε το Makefile σε πολλά σημεία. Αυτό το πετυχαίνουμε με την χρήση μεταβλητών για αυτές τις δυο έννοιες:

CC = gcc
CFLAGS = -Wall

Για να χρησιμοποιήσουμε τις μεταβλητές του make αρκεί να χρησιμοποιήσουμε το ειδικό notation $(όνομα_μεταβλητής). Έτσι το $(CC) θα αντικατασταθεί με το gcc (ένα literal $ για τα commands γράφεται με $$). Το rule για το target compiler.o γίνεται:

compiler.o: compiler.c
        $(CC) -c compiler.c -o compiler.o $(CFLAGS)

Ακολουθώντας την ίδια διαδικασία δηλώνουμε σε μεταβλητές όσα θέλουμε να είναι εύκολη η αλλαγή τους και προκύπτει το παρακάτω Makefile:

Makefile:

CC = gcc
CFLAGS = -Wall
LDFLAGS = -lfl
LEX = flex
YACC = bison
LINK.c = gcc

OUT = compiler
OBJS = lexer.o parser.o seman.o symbol.o compiler.o

lexer.c: lexer.l
        $(LEX) lexer.l -o lexer.c

parser.c: parser.y
        $(YACC) parser.y -o parser.c

lexer.o: lexer.c tokens.h common.h
        $(CC) -c lexer.c -o lexer.o $(CFLAGS)

parser.o: parser.c tokens.h common.h
        $(CC) -c parser.c -o parser.o $(CFLAGS)

seman.o: seman.c common.h
        $(CC) -c seman.c -o seman.o $(CFLAGS)

symbol.o: symbol.c symbol.h common.h
        $(CC) -c symbol.c -o symbol.o $(CFLAGS)

compiler.o: compiler.c
        $(CC) -c compiler.c -o compiler.o $(CFLAGS)

$(OUT): $(OBJS)
        $(LINK.c) $(LDFLAGS) $(OBJS) -o $(OUT)

Pattern rules

Στην ίδια φιλοσοφία με πριν, το make μας δίνει ακόμα μια διευκόλυνση για το γράψιμο των κανόνων - τα pattern rules. Παρατηρούμε ότι οι κανόνες για το compile των αρχείων της C είναι παρόμοιοι και ότι αλλάζει κάθε φορά μόνο το όνομα του αρχείου που μεταφράζεται. Έτσι θα μπορούσαμε να ορίσουμε ένα σχήμα κανόνα που να εξηγεί στο make πώς θα κάνει compile τα αρχεία πηγαίου κώδικα της C:

%.o: %.c
        $(CC) -c ...

Με αυτό ορίζουμε ένα pattern για το compile C αρχείων όπου δηλώνουμε ότι για να δημιουργηθεί ένα XXX.o αρχείο αρκεί να δημιουργηθεί το αντίστοιχο XXX.c αρχείο (μπορεί απλά να υπάρχει αλλά μπαίνει σαν εξάρτηση). Με το ειδικό σύμβολο % δηλώνουμε το μεταβλητό κομμάτι του pattern και το make αναλαμβάνει να ταιριάξει (match) το pattern του κανόνα αυτού με τους υπόλοιπους στόχους. Τώρα αν δοκιμάσουμε να γράψουμε τις εντολές του κανόνα έχουμε πρόβλημα, αφού δεν ξέρουμε κάθε φορά ποια είναι τα αρχεία XXX.o και XXX.c. Η λύση δίνεται με τις αυτόματες μεταβλητές. Το $@ υπονοεί το όνομα του παρόντος στόχου, ενώ το $< το όνομα του πρώτου prerequisite. Άρα στην περίπτωσή μας το $@ ταυτίζεται με το XXX.o ενώ το $< με το XXX.c και έτσι μπορούμε να γράψουμε τον κανόνα:

%.o: %.c
        $(CC) -c $< -o $@ $(CFLAGS)

Γράφοντας αυτόν τον γενικό κανόνα δεν χρειάζεται να γράψουμε κανέναν από τους υπόλοιπους κανόνες για τα αρχεία C και όποτε χρειαστεί το make ένα object file θα το ταιριάξει σε αυτό το pattern και θα εκτελεστούν αυτά που χρειάζονται. Βλέπουμε ότι τους ορισμούς για αυτούς τους γενικούς κανόνες μπορούμε να τους χρησιμοποιήσουμε σε διαφορετικά Makefiles σαν ένα είδος βιβλιοθήκης και να τους κάνουμε include όπως θα δούμε παρακάτω.

Άλλες χρήσιμες αυτόματες μεταβλητές είναι οι ακόλουθες:

  • $^: Τα ονόματα όλων των prerequisites χωρισμένα με κόμμα.
  • $?: Τα ονόματα όλων των prerequisites που είναι πιο νέα από το target.

Υπάρχει και ένα άλλο σχήμα κανόνων που μοιάζει με τους pattern rules που λέγεται static pattern rules. Με αυτό δηλώνουμε ρητά ποιους στόχους θα εκτελέσει σε αυτό το pattern και χρησιμεύει σαν ένα είδος for-loop για τα Makefiles. Στην περίπτωσή μας θα είχαμε:

$(OBJS): %.o: %.c
        $(CC) -c $< -o $@ $(CFLAGS)

Κάνοντας γενικό τον κανόνα για το compile αρχείων C αναγκαστήκαμε να πετάξουμε τις εξαρτήσεις των αρχείων από τα header files. Αυτές μπορούμε να τις προσθέσουμε από κάτω σαν κανόνες χωρίς εντολές χωρίς κανένα πρόβλημα. Επίσης, φτιάχνουμε pattern rules για το flex και το bison και έτσι το τελικό Makefile έχει ως εξής:

Makefile:

CC = gcc
CFLAGS = -Wall
LDFLAGS = -lfl
LEX = flex
YACC = bison
LINK.c = gcc

OUT = compiler
OBJS = lexer.o parser.o seman.o symbol.o compiler.o

%.c: %.l
        $(LEX) $< -o $@

%.c: %.y
        $(YACC) $< -o $@

%.o: %.c
        $(CC) -c $< -o $@ $(CFLAGS)

lexer.o: tokens.h common.h

parser.o: tokens.h common.h

seman.o: common.h

symbol.o: symbol.h common.h

$(OUT): $(OBJS)
       $(LINK.c) $(LDFLAGS) $(OBJS) -o $(OUT)

Predefined implicit rules

Το make έχει έτοιμα pattern rules σαν τα παραπάνω για τις περισσότερες προγραμματιστικές ρουτίνες, όπως το compile διάφορων γλωσσών προγραμματισμού, αλλά και για άλλες συχνές εργασίες. Η επιλογή του κατάλληλου rule γίνεται με βάση την κατάληξη του target. Έτσι θα μπορούσαμε να παραλείψουμε τους pattern rules που έχουμε γράψει παραπάνω και να χρησιμοποιήσουμε τους predefined κανόνες από το make. Κάποιες βασικές μετατροπές που υποστηρίζονται από το make είναι οι εξής:

  • %.c → %.o  : C
  • %.cpp → %.o  : C++
  • %.p → %.o  : Pascal
  • %.f → %.o  : FORTRAN
  • %.s → %.o  : GNU Assembler
  • %.o → %  : Link from object files
  • %.l → %.c  : Lex
  • %.y → %.c  : Yacc
  • %.tex → %.dvi  : (La)TeX

Η παραμετροποίηση των εντολών στους predefined rules γίνεται μέσα από κάποιες δεδομένες μεταβλητές (implicit variables). Για παράδειγμα, η εντολή για compile αρχείων C ορίζεται από τη μεταβλητή $(CC), ενώ οι παράμετροι που περνιούνται στον compiler ορίζονται από την $(CFLAGS). Μερικές πολύ χρήσιμες implicit variables είναι οι εξής:

  • CC: C compiler
  • CXX: C++ compiler
  • PC: Pascal compiler
  • FC: FORTRAN compiler
  • LEX: Lexical generator
  • YACC: Parser generator
  • CFLAGS: Flags για τον C compiler
  • CPPFLAGS: Flags για τον C/C++ preprocessor
  • CXXFLAGS: Flags για τον C++ compiler
  • LDFLAGS: Flags για το linking
  • LFLAGS: Flags για τον lex
  • YFLAGS: Flags για τον yacc/bison
  • SHELL: Το shell με το οποίο εκτελούνται όλα τα commands

Να σημειώσουμε ότι και οι μεταβλητές περιβάλλοντος μετατρέπονται σε implicit variables στο Makefile. Επίσης, τις διάφορες μεταβλητές μπορούμε να (επανα)ορίσουμε και κατά την κλίση του make έτσι: make CFLAGS='-Ο3'. Το τελικό Makefile που προκύπτει είναι το εξής:

Makefile:

CC = gcc
CFLAGS = -Wall
LDFLAGS = -lfl
LEX = flex
YACC = bison
YFLAGS = --yacc  # make bison behave as yacc
LINK.c = gcc

OUT = compiler
OBJS = lexer.o parser.o seman.o symbol.o compiler.o

lexer.o: tokens.h common.h

parser.o: tokens.h common.h

seman.o: common.h

symbol.o: symbol.h common.h

$(OUT): $(OBJS)

Αυτόματα dependencies από τον compiler

Τα διάφορα αρχεία πηγαίου κώδικα συνήθως περιέχουν κρυμμένη την πληροφορία από ποια άλλα αρχεία εξαρτώνται. Αυτές τις εξαρτήσεις δεν χρειάζεται να τις γράψουμε μόνοι μας, όπως κάναμε παραπάνω, άλλα αν το υποστηρίζει ο compiler μπορούμε να τις εξάγουμε από αυτόν σε μορφή κανόνων για το make. Έτσι ο gcc με το flag -M εκτυπώνει στο stdout τα dependencies των αρχείων C. Παρόμοια το ocamldep εκτυπώνει τα dependencies για αρχεία κώδικα ocaml. Υπάρχουν αντίστοιχα εργαλεία για διάφορες γλώσσες. Μπορούμε αυτούς τους κανόνες να τους αποθηκεύσουμε σε ένα αρχείο το οποίο θα κάνουμε include μετά στο Makefile. Τη διαδικασία την αυτοματοποιούμε στο ίδιο το Makefile:

Makefile:

CC = gcc
CFLAGS = -Wall
LDFLAGS = -lfl
LEX = flex
YACC = bison
YFLAGS = --yacc  # make bison behave as yacc
LINK.c = gcc

OUT = compiler
OBJS = lexer.o parser.o seman.o symbol.o compiler.o

include .depend

depend:
	$(CC) -M *.c >.depend

.PHONY: depend

$(OUT): $(OBJS)

Αρκεί να τρέξουμε μια φορά την εντολή make depend ώστε να ανανεωθεί το αρχείο με τα dependencies και μετά μπορούμε απλά με make compiler να φτιάχνουμε το εκτελέσιμο. Αυτό, μέχρι να αλλάξουν ξανά τα dependencies και να χρειαστεί να το ξανατρέξουμε. Με .PHONY: depend δηλώνουμε ότι ο στόχος depend δεν είναι κάποιο πραγματικό αρχείο, αλλά μια "εντολή" του Makefile, ώστε να μην μπερδευτεί το make με πιθανό πραγματικό αρχείο depend.

Errors

Αν κατά την διάρκεια του build βρεθεί κάποιο λάθος στον κώδικα ή τερματίσει κάποια εντολή ανώμαλα, τότε το make σταματάει την εκτέλεση των εντολών και την διαδικασία του build εμφανίζοντας το κατάλληλο διαγνωστικό μήνυμα:

$ make compiler
cc     compiler.c   -o compiler.o
compiler.c: In function ‘main’:
compiler.c:1: error: ‘lala’ undeclared (first use in this function)
compiler.c:1: error: (Each undeclared identifier is reported only once
compiler.c:1: error: for each function it appears in.)
make: *** [compiler.o] Error 1

Αν για κάποιο λόγο θέλουμε να μην σταματήσει το make σε πιθανό λάθος κάποιας εντολής βάζουμε μια παύλα "-" μπροστά από την αντίστοιχη εντολή.

Functions

Το make διαθέτει μια μεγάλη βιβλιοθήκη από συναρτήσεις που επιτελούν διάφορες λειτουργίες. Για να καλέσουμε μια συνάρτηση η σύνταξη είναι: $(function para1,para2...paraN). Μερικές βασικές συναρτήσεις ακολουθούν:

  • $(shell command) : Εκτελεί μια εντολή στο shell και επιστρέφει την έξοδό της
  • $(subst FROM,TO,TEXT) : Αντικαθιστά όλες τις εμφανίσεις του FROM σε TO στο TEXT
  • $(VAR:FROM=TO) : Μια συντομογραφία του παραπάνω για μεταβλητές
  • $(word N,TEXT) : Η Ν-οστή λέξη του TEXT
  • $(dir NAMES...) : Η ιεραρχία φακέλων από ένα path χωρίς το αρχείο
  • $(notdir NAMES...) : Το αρχείο από ένα path
  • $(suffix NAMES...) : Κατάληξη ενός αρχείου
  • $(if CONDITION,THEN-PART[,ELSE-PART]) : Το CONDITION θεωρείται false όταν είναι κενό
  • $(or CONDITION1[,CONDITION2[,CONDITION3...]])
  • $(and CONDITION1[,CONDITION2[,CONDITION3...]]) : Τα προφανή (short-circuit)
  • $(foreach VAR,LIST,TEXT) : Μια παραλλαγή του for-loop

Με χρήση των functions το OBJS θα μπορούσε να γραφτεί:

OBJS = $(subst .c,.o,$(shell ls *.c)) $(subst .l,.o,$(shell ls *.l)) $(subst .y,.o,$(shell ls *.y))

αν και είναι μάλλον κακή τακτική αυτό.

Συμβάσεις των Makefiles

Τα περισσότερα Makefiles ακολουθούν μια βασική δομή, κατά σύμβαση, στους στόχους που προσφέρουν στο χρήστη τους. Αν κληθεί η εντολή make χωρίς ορίσματα, τότε ο πρώτος μη-ειδικός, μη-pattern κανόνας που θα βρεθεί εκτελείται για τον αντίστοιχο στόχο. Αυτός συνήθως λέγεται all και υπονοεί το χτίσιμο όλων τον εκτελέσιμων προγραμμάτων. Επίσης συνήθως υπάρχουν οι στόχοι install, uninstall, clean και distclean. Οι δυο πρώτοι χρειάζονται δικαιώματα root για να εκτελεστούν και οι δυο άλλοι διαγράφουν τα ενδιάμεσα αρχεία που παράγονται από το source code. Ειδικότερα, το distclean διαγράφει και τα τελικά εκτελέσιμα αφήνοντας μόνο όσα αρχεία υπήρχαν πριν κληθεί το make. Ακολουθώντας αυτήν την σύμβαση τροποποιούμε το Makefile στο ακόλουθο:

Makefile:

CC = gcc
CFLAGS = -Wall
LDFLAGS = -lfl
LEX = flex
YACC = bison
# make bison behave as yacc
YFLAGS = --yacc
LINK.c = gcc

OUT = compiler
OBJS = lexer.o parser.o seman.o symbol.o compiler.o
INSTALL_DIR = /usr/bin

all: $(OUT)

install: all
        mkdir -p $(INSTALL_DIR)
        cp -u $(OUT) $(INSTALL_DIR)/

uninstall:
        rm -f $(INSTALL_DIR)/$(OUT)

clean:
        -rm -f $(OBJS) lexer.c parser.c

distclean: clean
        -rm -f $(OUT)

include .depend

depend:
        $(CC) -M *.c >.depend

.PHONY: all install uninstall clean distclean depend

$(OUT): $(OBJS)

Έτσι μπορούμε να δώσουμε το Makefile μας μαζί με τον πηγαίο κώδικά μας σε κάποιον άλλο και αυτός, αφού εκτελέσει make depend; make, απλά θα εκτελέσει make install και θα εγκατασταθεί το εκτελέσιμο στο σύστημά του. Με make distclean θα διαγραφούν όλα τα παραγόμενα αρχεία για να μην πιάνουν χώρο.

Σχετικά εργαλεία

Το make χρησιμοποιείται και συνεργάζεται με μια πληθώρα από άλλα εργαλεία που κάνουν την ζωή ενός προγραμματιστή πιο εύκολη. Από διάφορα εργαλεία αυτοματοποίησης διαδικασιών μέχρι μεγάλα IDEs. Ίσως από τα γνωστότερα εργαλεία που χρησιμοποιούν το make είναι τα autotools του GNU toolchain. Αυτά χρησιμοποιούνται σε μεγάλα projects που χρειάζονται αυτοματοποιημένο τρόπο για την παραγωγή των Makefiles. Το χαρακτηριστικό όταν χρησιμοποιούνται τα autotools είναι η εκτέλεση της εντολής ./configure πριν από την make και make install η οποία δημιουργεί το Makefile που χρησιμοποιείται από το make.

Το make έχει πολλούς πιστούς αλλά και πολλούς αντιπάλους που το κατακρίνουν για διάφορα μειονεκτήματα που παρουσιάζει σε συγκεκριμένες περιπτώσεις. Υπάρχουν πολλές υλοποιήσεις του make (BSD make, dmake, Makepp, Mk, nmake) αλλά και πολλά άλλα εναλλακτικά προγράμματα για automatically building (CMake, OMake, MSBuild, cook, PyBuild, SCons, Waf). Ίσως από τα πιο γνωστά είναι το counterpart του make για την γλώσσα προγραμματισμού Java το Apache Ant. Το ant ονομάζει τα αντίστοιχα Makefiles αρχεία build.xml και χρησιμοποιεί την σύνταξη της XML για να δηλώσει τους διαφόρους στόχους. Ένα παράδειγμα build.xml:

<?xml version="1.0"?>
<project name="Hello" default="compile">
    <target name="clean" description="remove intermediate files">
        <delete dir="classes"/>
    </target>
    <target name="compile"
     description="compile the Java source code to class files">
        <mkdir dir="classes"/>
        <javac srcdir="." destdir="classes"/>
    </target>
    <target name="jar" depends="compile"
     description="create a Jar file for the application">
        <jar destfile="hello.jar">
            <fileset dir="classes" includes="**/*.class"/>
            <manifest>
                <attribute name="Main-Class" value="HelloProgram"/>
            </manifest>
        </jar>
    </target>
</project>

Όπως το make, έτσι και το ant δεν περιορίζεται σε μία γλώσσα προγραμματισμού αλλά μπορεί να προγραμματιστεί ώστε να δουλέψει σε project με διάφορες γλώσσες. Γενικότερα, δεν είναι απαραίτητο να χρησιμοποιηθούν καν για software building. Με αυτά τα εργαλεία μπορεί κανείς πολύ εύκολα να αυτοματοποιήσει διαδικασίες που βασίζονται στην δημιουργία ή την τροποποίηση αρχείων που έχουν εξαρτήσεις μεταξύ τους.

Για περισσότερες πληροφορίες μπορείτε να απευθυνθείτε στο manual και στο documentation του GNU make καθώς έχει πολύ χρηστικές σελίδες στο info.

Links