web-dev-qa-db-fra.com

Comment faire un Makefile C ++ simple?

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?

275
Befall

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.

Fais moi bébé

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: tooltool.cctool.osupport.ccsupport.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).

Variables, règles intégrées et autres goodies

À 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

  1. Nous continuons d'indiquer explicitement les dépendances pour chaque fichier objet et l'exécutable final
  2. Nous avons dû taper explicitement la règle de compilation pour les deux fichiers source

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.

  1. règles de suffixe (considérées comme obsolètes dans GNU make, mais conservées pour des raisons de compatibilité ascendante)
  2. règles implicites
  3. règles de modèle

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 à utiliser
  • CXX - le compilateur c ++ à utiliser
  • LD - l'éditeur de liens à utiliser
  • CFLAGS - indicateur de compilation pour les fichiers source c
  • CXXFLAGS - 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 liens
  • LDLIBS - bibliothèques à relier

Un 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

  1. Il n'y a plus de lignes de dépendance pour les fichiers source!?!
  2. Il existe une magie étrange liée à .depend et depend
  3. Si vous faites 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.

514
dmckee

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.

51
Brendan Long

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
33
friedmud

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à.

12
No one

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
6
John Knoeller

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
5
Jezz

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.

5
VectorVortec