346 votes

Comment créer un Makefile C++ SIMPLE

Nous sommes tenus d'utiliser un Makefile pour rassembler tous les éléments de notre projet, mais notre professeur ne nous a jamais montré comment faire.

Je n'ai que un fichier, a3driver.cpp . Le conducteur importe une classe à partir d'un emplacement, "/user/cse232/Examples/example32.sequence.cpp" .

C'est tout. Tout le reste est contenu dans le .cpp .

Comment faire pour créer un Makefile simple qui crée un exécutable appelé a3a.exe ?

14 votes

.EXE, il s'agit donc bien de Windows. En y réfléchissant bien, le chemin d'accès est de type Unix. Il utilise probablement Mingw-32.

3 votes

Soupir. Je suppose qu'il faut apprendre les bases de chaque métier, même si on ne les utilisera jamais. Il faut juste comprendre comment les choses fonctionnent. Il y a de bonnes chances que vous développiez toujours dans un IDE, comme Eclipse. Vous obtiendrez une réponse ici pour votre cas simple d'une ligne et il y a beaucoup de tutoriels sur le web, mais si vous voulez des connaissances approfondies, vous ne pouvez pas battre le livre d'O'reilly (même chose pour la plupart des sujets s/w). amazon.com/Managing-Projects-Make-Nutshell-Handbooks/dp/ Choisissez un exemplaire d'occasion sur amazon, half.com, betterworldbooks eBay

3 votes

Le lien posté par @Dennis est maintenant mort, mais le même matériel peut être trouvé dans ceci archive.org page .

641voto

dmckee Points 50318

Puisqu'il s'agit d'Unix, les exécutables n'ont pas d'extensions.

Il convient de noter que root-config est un utilitaire qui fournit les bons drapeaux de compilation et d'édition de liens, ainsi que les bonnes bibliothèques pour construire des applications avec Root. Ce n'est qu'un détail lié à l'audience initiale de ce document.

Make Me Baby

ou On n'oublie jamais la première fois qu'on s'est fait avoir

Une introduction à make et à l'écriture d'un fichier makefile simple.

Qu'est-ce que Make ? Et pourquoi devrais-je m'en préoccuper ?

L'outil appelé Faire est un gestionnaire de dépendances de construction. C'est-à-dire qu'il s'occupe de savoir quelles commandes doivent être exécutées dans quel ordre pour prendre votre projet logiciel à partir d'une collection de fichiers sources, de fichiers objets, de bibliothèques, d'en-têtes, etc., etc. -- dont certains peuvent avoir changé récemment -- et de les transformer en une version correcte et à jour du programme.

En fait, vous pouvez 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és être compilés dans un programme appelé tool et supposons que vous ayez travaillé sur les fichiers source (ce qui signifie que les fichiers tool est désormais obsolète) et souhaite compiler le programme.

Pour ce faire, vous pouvez

  1. Vérifier si l'un ou l'autre support.cc o support.hh est plus récent que support.o et si c'est le cas, lancez une commande comme

    g++ -g -c -pthread -I/sw/include/root support.cc
  2. Vérifier si l'un ou l'autre support.hh o tool.cc sont plus récents que tool.o et si c'est le cas, lancez une commande comme

    g++ -g  -c -pthread -I/sw/include/root tool.cc
  3. Vérifier si tool.o est plus récent que tool et si c'est le cas, lancez 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

Ouf ! Quelle galère ! Il y a beaucoup de choses à retenir et plusieurs occasions de faire des erreurs. (BTW-- les particularités des lignes de commande présentées ici dépendent de notre environnement logiciel. Celles-ci fonctionnent sur mon ordinateur).

Bien entendu, vous pouvez exécuter les trois commandes à chaque fois. Cela fonctionnerait, mais n'est pas adapté à un logiciel important (comme DOGS qui prend plus de 15 minutes à compiler sur mon MacBook).

Au lieu de cela, vous pouvez écrire un fichier appelé 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 simplement make à la ligne de commande. Ce dernier effectuera automatiquement les trois étapes décrites ci-dessus.

Les lignes non indentées ont la forme suivante "cible : dépendances" et indiquer à Make que les commandes associées (lignes en retrait) doivent être exécutées si l'une des dépendances est plus récente que la cible. En d'autres termes, les lignes de dépendances décrivent la logique de ce qui doit être reconstruit pour prendre en compte les changements dans les différents fichiers. Si les dépendances sont plus récentes que la cible, les commandes associées doivent être exécutées. support.cc change, ce qui signifie que support.o doit être reconstruite, mais tool.o peuvent être laissés tranquilles. Quand support.o changements tool doit être reconstruit.

Les commandes associées à chaque ligne de dépendance sont séparées par un onglet (voir ci-dessous) et doivent modifier la cible (ou au moins la toucher pour mettre à jour l'heure de modification).

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

A ce stade, notre makefile se souvient simplement du travail à faire, mais nous devons encore trouver et taper chaque commande nécessaire dans son intégralité. Il n'est pas nécessaire de procéder ainsi : Make est un langage puissant avec des variables, des fonctions de manipulation de texte, et toute une série 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 la suivante $(VAR) .

La syntaxe de l'affectation à 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 comme 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

qui est un peu plus lisible, mais qui nécessite encore beaucoup de saisie

Faire des fonctions

GNU make supporte une variété de fonctions pour accéder aux informations du système de fichiers ou à d'autres commandes sur le système. Dans le cas présent, nous nous intéressons à $(shell ...) qui s'étend à la sortie du ou des arguments, et $(subst opat,npat,text) qui remplace toutes les instances de opat con npat dans le texte.

En tirant parti de cette situation, nous pouvons

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.

Notez que

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

Règles implicites et modèles

Nous nous attendons généralement à ce que tous les fichiers sources C++ soient traités de la même manière, et Make fournit trois façons de l'indiquer :

  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élisation

Des règles implicites sont intégrées, et quelques-unes d'entre elles seront examinées ci-dessous. Les règles de modèle sont spécifiées sous une forme telle que

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

ce qui signifie que les fichiers objets sont générés à partir des fichiers sources C en exécutant la commande indiquée, où la variable "automatique" est $< se développe jusqu'au nom de la première dépendance.

Règles intégrées

Make possède toute une série de règles intégrées qui font que très souvent, un projet peut être compilé par un makefile très simple.

La règle intégrée de 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 telle que $(CXX) -c $(CPPFLAGS) $(CFLAGS) .

Les fichiers d'objets individuels sont liés à l'aide de $(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS) mais cela ne fonctionnera pas dans notre cas, car nous voulons lier plusieurs fichiers d'objets.

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 (comme l'emplacement des fichiers Root include) sans avoir à réécrire toutes les règles. Les plus susceptibles de nous intéresser sont les suivantes :

  • CC -- le compilateur C à utiliser
  • CXX -- le compilateur C++ à utiliser
  • LD -- l'éditeur de liens à utiliser
  • CFLAGS -- drapeau de compilation pour les fichiers sources C
  • CXXFLAGS -- drapeaux de compilation pour les fichiers sources C
  • CPPFLAGS -- drapeaux pour le préprocesseur c (incluent typiquement les chemins de 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 profitant des règles intégrées, nous pouvons simplifier notre fichier makefile :

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 (comme le nettoyage du répertoire source).

Notez que lorsque make est invoqué sans argument, il utilise la première cible trouvée dans le fichier (dans ce cas, all), mais vous pouvez également nommer la cible à obtenir, ce qui permet à make d'utiliser la fonction make clean supprimer les fichiers objets dans ce cas.

Toutes les dépendances sont encore codées en dur.

Des 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

Notez 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 le faites make puis ls -A vous voyez un fichier nommé .depend qui contient des éléments qui ressemblent à des lignes de dépendance make

Autre lecture

Connaître les bogues et les notes historiques

Le langage d'entrée de Make est sensible aux espaces blancs. En particulier, les lignes d'action qui suivent les dépendances doivent commencer par une tabulation . Mais une série d'espaces peut avoir la même apparence (et il existe en effet des éditeurs qui convertissent silencieusement les tabulations en espaces ou vice versa), ce qui donne un fichier Make qui a l'air correct mais qui ne fonctionne toujours pas. Ceci a été identifié comme un bogue très tôt, mais ( l'histoire est la suivante ), il n'a pas été corrigé, car il y avait déjà 10 utilisateurs.

(Ceci a été copié à partir d'un billet wiki que j'ai écrit pour les étudiants diplômés en physique).

11 votes

Cette méthode de génération des dépendances est obsolète et même nuisible. Voir Génération avancée d'auto-dépendances .

5 votes

-pthread causes du drapeau gcc pour définir les macros nécessaires, -D_REENTRANT n'est pas nécessaire.

1 votes

@MaximYegorushkin : Pourquoi cette méthode est-elle nuisible ?

68voto

Brendan Long Points 24372

J'ai toujours pensé qu'il était plus facile d'apprendre à l'aide d'un exemple détaillé, voici donc comment je conçois les makefiles. Pour chaque section, vous avez une ligne qui n'est pas indentée et qui montre le nom de la section suivi des dépendances. Les dépendances peuvent être d'autres sections (qui seront exécutées avant la section courante) ou des fichiers (qui, s'ils sont mis à jour, entraîneront l'exécution de la section courante lors de la prochaine exécution de make ).

Voici un exemple rapide (gardez à l'esprit que j'utilise 4 espaces là où je devrais utiliser une tabulation, Stack Overflow ne me permet pas d'utiliser des tabulations) :

a3driver: a3driver.o
    g++ -o a3driver a3driver.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

Lorsque vous tapez make a3driver dépend de a3driver.o, il ira donc dans cette section. a3driver.o dépend de a3driver.cpp, il ne s'exécutera donc que si a3driver.cpp a été modifié depuis la dernière fois qu'il a été exécuté. En supposant que ce soit le cas (ou qu'il n'ait jamais été exécuté), il compilera a3driver.cpp en un fichier .o, puis retournera à a3driver et compilera l'exécutable final.

Puisqu'il n'y a qu'un seul dossier, on pourrait même le réduire :

a3driver: a3driver.cpp
    g++ -o a3driver a3driver.cpp

La raison pour laquelle j'ai montré le premier exemple est qu'il montre la puissance de makefiles. Si vous avez besoin de compiler un autre fichier, il vous suffit d'ajouter une autre section. Voici un exemple avec un secondFile.cpp (qui charge 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 recompilez, seul secondFile.cpp sera recompilé (et non a3driver.cpp). Ou alternativement, si vous changez quelque chose dans a3driver.cpp, cela ne recompilera pas secondFile.cpp.

Si vous avez des questions à ce sujet, n'hésitez pas à m'en faire part.

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 construction" comme 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'avais pas remarqué que vous étiez sous Windows. Je pense que la seule différence est de changer le -o a3driver a -o a3driver.exe .

0 votes

Le code absolu que j'essaie d'utiliser est : p4a.exe : p4driver.cpp g++ -o p4a p4driver.cpp MAIS, il me dit "missing separator". J'utilise TAB, mais il me dit toujours cela. Une idée ?

2 votes

Pour autant que je sache, ce message d'erreur n'apparaît que si vous avez des espaces. Assurez-vous que vous n'avez pas de lignes commençant par des espaces (espace + tabulation provoquent cette erreur). C'est la seule chose à laquelle je peux penser

0 votes

Note aux futurs rédacteurs : StackOverflow ne peut pas afficher les onglets même si vous les éditez dans la réponse, donc n'essayez pas de "réparer" ma note à ce sujet.

40voto

friedmud Points 569

Pourquoi tout le monde aime-t-il énumérer les fichiers sources ? Une simple commande find peut s'en charger facilement.

Voici un exemple de Makefile C++ très simple. Il suffit de le déposer dans un répertoire contenant .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

15voto

No one Points 21

Deux possibilités s'offrent à vous.

Option 1 : makefile le plus simple = 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.

Option 2 : un fichier makefile de 2 lignes.

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj

3 votes

Cette réponse serait excellente si elle ne présupposait pas tant de choses sur les détails de l'environnement de l'OP. Oui, ils sont sous Windows, mais cela ne veut pas dire qu'ils utilisent nmake . Les link semble également très spécifique à un compilateur particulier, et devrait au moins indiquer lequel.

12voto

VectorVortec Points 16

J'ai utilisé Réponse de friedmud . Je me suis penché sur la question pendant un certain temps, et il semble que ce soit une bonne façon 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 fait des changements pour que cela fonctionne dans mon environnement, Ubuntu et g++. Des exemples plus concrets 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 il générait une erreur liée au fait que les bibliothèques g++ n'étaient pas liées. Cette configuration a résolu ce problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X