Nous sommes obligés d'utiliser un Makefile pour tout réunir pour notre projet mais notre professeur ne nous a jamais montré comment.
Je n'ai qu'un seul fichier, a3driver.cpp
. Le pilote importe une classe à partir d'un emplacement "/user/cse232/Examples/example32.sequence.cpp"
.
Ça y est, tout le reste est contenu dans le .cpp
.
Comment pourrais-je créer un Makefile simple qui crée un exécutable appelé a3a.exe
?
Copié d'un article wiki que j'ai écrit pour les étudiants en physique.
Comme cela concerne unix, les exécutables n’ont pas d’extension.
Une chose à noter est que root-config
est un utilitaire qui fournit la bonne compilation et des indicateurs de liaison; et les bonnes bibliothèques pour construire des applications contre root. C'est juste un détail lié au public d'origine de ce document.
ou vous n'oublierez jamais la première fois où vous vous êtes fait
Une discussion introductive sur make et comment écrire un makefile simple
Qu'est-ce que Make? Et pourquoi devrais-je m'en soucier?
L'outil appelé make est un gestionnaire de dépendances de construction. En d’autres termes, il s’occupe de savoir quelles commandes doivent être exécutées dans quel ordre pour extraire votre projet de logiciel d’une collection de fichiers sources, de fichiers objets, de bibliothèques, d’en-têtes, etc., etc., dont certaines peuvent avoir été modifiées récemment --- et en les transformant en une version correcte et à jour du programme.
En fait, vous pouvez aussi utiliser make pour d'autres choses, mais je ne vais pas en parler.
Un Makefile trivial
Supposons que vous ayez un répertoire contenant: tool
tool.cc
tool.o
support.cc
support.hh
et support.o
qui dépendent de root
et sont censé être compilé dans un programme appelé tool
, et supposons que vous avez piraté les fichiers source (ce qui signifie que le tool
existant est maintenant obsolète) et que vous voulez compiler le programme.
Pour le faire vous-même, vous pourriez
1) vérifiez si support.cc
ou support.hh
est plus récent que support.o
et, le cas échéant, exécutez une commande comme
g++ -g -c -pthread -I/sw/include/root support.cc
2) vérifiez si support.hh
ou tool.cc
sont plus récents que tool.o
et, le cas échéant, exécutez une commande du type
g++ -g -c -pthread -I/sw/include/root tool.cc
3) vérifiez si tool.o
est plus récent que tool
, et si c'est le cas, exécutez une commande comme
g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
Phew! Quelle galère! Il y a beaucoup à retenir et plusieurs occasions de faire des erreurs. (BTW - Les détails des lignes de commande présentées ici dépendent de notre environnement logiciel. Celles-ci fonctionnent sur mon ordinateur.)
Bien sûr, vous pouvez simplement exécuter les trois commandes à chaque fois. Cela fonctionnerait, mais ne correspondrait pas à un logiciel important (comme DOGS, qui nécessite plus de 15 minutes pour compiler à partir de zéro sur mon MacBook).
Au lieu de cela, vous pouvez écrire un fichier nommé makefile
comme ceci:
tool: tool.o support.o
g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
tool.o: tool.cc support.hh
g++ -g -c -pthread -I/sw/include/root tool.cc
support.o: support.hh support.cc
g++ -g -c -pthread -I/sw/include/root support.cc
et tapez make
sur la ligne de commande. qui effectuera automatiquement les trois étapes indiquées ci-dessus.
Les lignes non mises en retrait ici ont la forme "cible: dépendances" et indiquent à make que les commandes associées (lignes mises en retrait) doivent être exécutées si l'une des dépendances est plus récente que la cible. . C'est-à-dire que les lignes de dépendance décrivent la logique de ce qui doit être reconstruit pour prendre en compte les modifications apportées dans divers fichiers. Si support.cc
change, cela signifie que support.o
doit être reconstruit, mais tool.o
peut être laissé seul. Lorsque support.o
change _, vous devez reconstruire tool
.
Les commandes associées à chaque ligne de dépendance sont définies avec un onglet (voir ci-dessous) qui doit modifier la cible (ou au moins la toucher pour mettre à jour l'heure de modification).
À ce stade, notre fichier makefile se souvient simplement du travail à effectuer, mais nous devions encore trouver et saisir chaque commande nécessaire dans son intégralité. Ce n'est pas obligatoirement le cas: make est un langage puissant avec des variables, des fonctions de manipulation de texte et tout un ensemble de règles intégrées qui peuvent nous faciliter la tâche.
Créer des variables
La syntaxe pour accéder à une variable make est $(VAR)
.
La syntaxe à utiliser pour affecter une variable make est la suivante: VAR = A text value of some kind
(ou VAR := A different text value but ignore this for the moment
).
Vous pouvez utiliser des variables dans des règles telles que cette version améliorée de notre makefile:
CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
-Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
-lm -ldl
tool: tool.o support.o
g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
ce qui est un peu plus lisible, mais nécessite encore beaucoup de dactylographie
Construire des fonctions
GNU make prend en charge diverses fonctions d’accès aux informations du système de fichiers ou d’autres commandes du système. Dans ce cas, nous nous intéressons à $(Shell ...)
qui se développe en sortie du ou des arguments, et à $(subst opat,npat,text)
qui remplace toutes les occurrences de opat
par npat
dans le texte.
Profitant de cela nous donne:
CPPFLAGS=-g $(Shell root-config --cflags)
LDFLAGS=-g $(Shell root-config --ldflags)
LDLIBS=$(Shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
tool: $(OBJS)
g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
qui est plus facile à taper et beaucoup plus lisible.
Remarquerez que
Règles implicites et modèles
Nous nous attendions généralement à ce que tous les fichiers sources c ++ soient traités de la même manière et nous fournissons trois façons de le dire.
Des règles implicites sont intégrées, et quelques-unes seront discutées ci-dessous. Les règles de modèle sont spécifiées sous la forme suivante:
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
ce qui signifie que les fichiers objet sont générés à partir de fichiers source c en exécutant la commande indiquée, dans laquelle la variable "automatique" $<
se développe jusqu'au nom de la première dépendance.
Règles intégrées
Make a toute une foule de règles intégrées qui signifient que très souvent, un projet peut être compilé avec un makefile très simple.
La règle intégrée GNU make pour les fichiers source c est celle présentée ci-dessus. De même, nous créons des fichiers objets à partir de fichiers sources c ++ avec une règle comme $(CXX) -c $(CPPFLAGS) $(CFLAGS)
Les fichiers objet unique sont liés à l'aide de $(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
, mais cela ne fonctionnera pas dans notre cas car nous souhaitons lier plusieurs fichiers objet.
Variables utilisées par les règles intégrées
Les règles intégrées utilisent un ensemble de variables standard qui vous permettent de spécifier des informations sur l’environnement local (par exemple, où trouver les fichiers inclus ROOT) sans réécrire toutes les règles. Les plus susceptibles de nous intéresser sont:
CC
- le compilateur c à utiliserCXX
- le compilateur c ++ à utiliserLD
- l'éditeur de liens à utiliserCFLAGS
- indicateur de compilation pour les fichiers source cCXXFLAGS
- drapeaux de compilation pour les fichiers source c ++CPPFLAGS
- indicateurs pour le pré-processeur c (généralement inclure les chemins d'accès aux fichiers et les symboles définis sur la ligne de commande), utilisés par c et c ++LDFLAGS
- drapeaux de l'éditeur de liensLDLIBS
- bibliothèques à relierUn Makefile de base
En tirant parti des règles intégrées, nous pouvons simplifier notre makefile pour:
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(Shell root-config --cflags)
LDFLAGS=-g $(Shell root-config --ldflags)
LDLIBS=$(Shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
tool.o: tool.cc support.hh
support.o: support.hh support.cc
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) tool
Nous avons également ajouté plusieurs cibles standard qui effectuent des actions spéciales (telles que le nettoyage du répertoire source).
Notez que lorsque make est appelé sans argument, il utilise la première cible trouvée dans le fichier (dans ce cas, tout), mais vous pouvez également nommer la cible pour obtenir ce qui fait que make clean
supprime les fichiers objet de cette Cas.
Nous avons toujours toutes les dépendances codées en dur.
Quelques améliorations mystérieuses
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(Shell root-config --cflags)
LDFLAGS=-g $(Shell root-config --ldflags)
LDLIBS=$(Shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
depend: .depend
.depend: $(SRCS)
$(RM) ./.depend
$(CXX) $(CPPFLAGS) -MM $^>>./.depend;
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) *~ .depend
include .depend
Remarquerez que
make
puis ls -A
, vous voyez un fichier nommé .depend
contenant des éléments qui ressemblent à des lignes de dépendance.Autre lecture
Connaître les bugs et les notes historiques
La langue de saisie de make est sensible aux espaces. En particulier , les lignes d'action qui suivent les dépendances doivent commencer par un onglet . Mais une série d'espaces peut avoir le même aspect (et il existe en effet des éditeurs qui convertiront des onglets en espaces ou inversement), ce qui donne un fichier de création qui semble correct et qui ne fonctionne toujours pas. Cela a été identifié tôt comme un bug, mais ( l'histoire raconte ) n'a pas été corrigé car il y avait déjà 10 utilisateurs.
J'ai toujours pensé que c'était plus facile à apprendre avec un exemple détaillé, alors voici comment je pense aux makefiles. Pour chaque section, vous avez une ligne non en retrait qui affiche le nom de la section suivi de dépendances. Les dépendances peuvent être soit d'autres sections (qui seront exécutées avant la section actuelle), soit des fichiers (qui, s'ils sont mis à jour, entraîneront la réexécution de la section actuelle à la prochaine exécution de make
).
Voici un exemple rapide (n'oubliez pas que j'utilise 4 espaces où je devrais utiliser un onglet, Stack Overflow ne me permet pas d'utiliser des onglets):
a3driver: a3driver.o
g++ -o a3driver a3driver.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
Lorsque vous tapez make
, il choisira la première section (a3driver). a3driver dépend de a3driver.o, il ira donc à cette section. a3driver.o dépend de a3driver.cpp, il ne s'exécutera donc que si a3driver.cpp a changé depuis la dernière exécution. En supposant qu'il ait été exécuté (ou n'ait jamais été exécuté), il compilera a3driver.cpp dans un fichier .o, puis reviendra dans a3driver et compilera l'exécutable final.
Puisqu'il n'y a qu'un seul fichier, il pourrait même être réduit à:
a3driver: a3driver.cpp
g++ -o a3driver a3driver.cpp
La raison pour laquelle j'ai montré le premier exemple est qu'il montre le pouvoir des makefiles. Si vous avez besoin de compiler un autre fichier, vous pouvez simplement ajouter une autre section. Voici un exemple avec secondFile.cpp (qui se charge dans un en-tête nommé secondFile.h):
a3driver: a3driver.o secondFile.o
g++ -o a3driver a3driver.o secondFile.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
secondFile.o: secondFile.cpp secondFile.h
g++ -c secondFile.cpp
Ainsi, si vous modifiez quelque chose dans secondFile.cpp ou secondFile.h et que vous le recompilez, il ne recompilera que secondFile.cpp (et non a3driver.cpp). Ou bien, si vous modifiez quelque chose dans a3driver.cpp, il ne recompilera pas secondFile.cpp.
Faites-moi savoir si vous avez des questions à ce sujet.
Il est également traditionnel d'inclure une section nommée "all" et une section nommée "clean". "all" construira généralement tous les exécutables, et "clean" supprimera les "artefacts de génération" tels que les fichiers .o et les exécutables
all: a3driver ;
clean:
# -f so this will succeed even if the files don't exist
rm -f a3driver a3driver.o
EDIT: Je n'ai pas remarqué que vous êtes sur Windows. Je pense que la seule différence est de changer le -o a3driver
en -o a3driver.exe
.
Pourquoi tout le monde aime-t-il lister les fichiers sources? Une simple commande de recherche peut s’occuper de cela facilement.
Voici un exemple de fichier de compilation C++ très simple. Déposez-le simplement dans un répertoire contenant les fichiers .C
, puis tapez make
...
appname := myapp
CXX := clang++
CXXFLAGS := -std=c++11
srcfiles := $(Shell find . -name "*.C")
objects := $(patsubst %.C, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
Vieille question, je sais, mais pour la postérité. Vous avez eu deux options.
Option 1: le plus simple makefile = NO MAKEFILE.
Renommez "a3driver.cpp" en "a3a.cpp", puis écrivez sur la ligne de commande:
nmake a3a.exe
Et c'est tout. Si vous utilisez gnu-make, utilisez "make" ou "gmake" ou autre chose.
Option 2: un fichier makefile de 2 lignes.
a3a.exe: a3driver.obj
link /out:a3a.exe a3driver.obj
Voilà.
Votre fichier make aura une ou deux règles de dépendance, selon que vous compilez et liez avec une seule commande ou avec une commande pour la compilation et une pour le lien.
Les dépendances sont un arbre de règles ressemblant à ceci:
main_target : source1 source2 etc
command to build main_target from sources
source1 : dependents for source1
command to build source1
Il doit être une ligne vide après les commandes d'une cible, et il doit ne pas soit une ligne vierge avant les commandes. La première cible dans le makefile est la cible globale, les autres cibles ne sont construites que si la première cible en dépend.
Donc, votre fichier make ressemblera à quelque chose comme ça.
a3a.exe : a3driver.obj
link /out:a3a.exe a3driver.obj
a3driver.obj : a3driver.cpp
cc a3driver.cpp
Je suggère:
tool: tool.o file1.o file2.o
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
ou
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_Arch)
tool: tool.o file1.o file2.o
Cette dernière suggestion est légèrement meilleure car elle réutilise GNU créer des règles implicites. Cependant, pour fonctionner, un fichier source doit avoir le même nom que l'exécutable final (c'est-à-dire: tool.c
et tool
).
Notez qu'il n'est pas nécessaire de déclarer les sources. Les fichiers objets intermédiaires sont générés à l'aide d'une règle implicite. En conséquence, ceci Makefile
fonctionne pour C et C++ (ainsi que pour Fortran, etc ...).
Notez également que Makefile utilise par défaut $(CC)
comme éditeur de liens. $(CC)
ne fonctionne pas pour lier des objets C++. Nous modifions LINK.o
uniquement pour cette raison. Si vous voulez compiler du code C, vous n'avez pas à forcer la valeur LINK.o
.
Bien sûr, vous pouvez également ajouter vos drapeaux de compilation avec la variable CFLAGS
et ajouter vos bibliothèques dans LDLIBS
. Par exemple:
CFLAGS = -Wall
LDLIBS = -lm
Remarque un côté: si vous devez utiliser des bibliothèques externes, je suggère d'utiliser tilisez pkg-config afin de définir correctement CFLAGS
et LDLIBS
:
CFLAGS += $(Shell pkg-config --cflags libssl)
LDLIBS += $(Shell pkg-config --libs libssl)
Le lecteur attentif remarque que cette Makefile
ne se reconstruit pas correctement si un en-tête est modifié. Ajoutez ces lignes pour résoudre le problème:
override CPPFLAGS += -MMD
include $(wildcard *.d)
-MMD
permet de construire des fichiers .d contenant des fragments de Makefile sur les dépendances des en-têtes. La deuxième ligne vient de les utiliser.
Bien sûr, un Makefile bien écrit devrait aussi inclure les règles clean
et distclean
:
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
Remarquez que $(RM)
est équivalent à rm -f
mais il est recommandé d’appeler directement rm
.
La règle all
est également appréciée. Pour fonctionner, cela devrait être la première règle de votre fichier:
all: tool
Vous pouvez également ajouter la règle install
:
PREFIX = /usr/local
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
DESTDIR
est vide par défaut. L'utilisateur peut le configurer pour installer votre programme sur un autre système (obligatoire pour le processus de compilation croisée). Les mainteneurs de paquets pour plusieurs distributions peuvent également changer PREFIX
afin d’installer votre paquet dans /usr
.
Un dernier mot, ne placez pas les fichiers source dans des sous-répertoires. Si vous voulez vraiment faire cela, conservez ce Makefile
dans le répertoire racine et utilisez des chemins complets pour identifier vos fichiers (c'est-à-dire. subdir/file.o
).
Donc pour résumer, votre Makefile complet devrait ressembler à ceci:
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_Arch)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)
all: tool
tool: tool.o file1.o file2.o
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
J'ai utilisé la réponse de friedmud. Je me suis penché sur la question pendant un moment et cela semble être un bon moyen de commencer. Cette solution a également une méthode bien définie pour ajouter des drapeaux de compilateur. J'ai répondu à nouveau parce que j'ai apporté des modifications pour le faire fonctionner dans mon environnement, Ubuntu et g ++. Plusieurs exemples de travail sont parfois les meilleurs enseignants.
appname := myapp
CXX := g++
CXXFLAGS := -Wall -g
srcfiles := $(Shell find . -maxdepth 1 -name "*.cpp")
objects := $(patsubst %.cpp, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
les makefiles semblent être très complexes. J'en utilisais un, mais cela générait une erreur liée à l'absence de liaison dans les bibliothèques g ++. Cette configuration a résolu ce problème.