Comment puis-je créer un Makefile pour les projets C avec les sous-répertoires SRC, OBJ et BIN?

95

Il y a quelques mois, j'ai proposé le générique suivant Makefilepour les devoirs scolaires:

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

Cela compilera essentiellement tous les fichiers .cet .hpour générer les .ofichiers et l'exécutable projectnamedans le même dossier.

Maintenant, j'aimerais pousser un peu cela. Comment puis-je écrire un Makefile pour compiler un projet C avec la structure de répertoires suivante?

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

En d' autres termes, je voudrais avoir un Makefile qui compile les sources C de ./src/dans ./obj/, puis tout lien pour créer l'exécutable dans ./bin/.

J'ai essayé de lire différents Makefiles, mais je ne peux tout simplement pas les faire fonctionner pour la structure du projet ci-dessus; au lieu de cela, le projet ne parvient pas à se compiler avec toutes sortes d'erreurs. Bien sûr, je pourrais utiliser un IDE complet (Monodevelop, Anjuta, etc.), mais je préfère honnêtement m'en tenir à gEdit et au bon vieux terminal.

Y a-t-il un gourou qui peut me donner une solution de travail ou des informations claires sur la façon dont cela peut être fait? Je vous remercie!

** MISE À JOUR (v4) **

La solution finale :

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"
Yanick Rochon
la source
Quelle est la question précise ici?
Oliver Charlesworth
Je ne suis pas sûr de comprendre ce que vous voulez faire.
Tom
Mise à jour du Makefile. Je me rapproche, mais j'ai des problèmes avec les variables automatiques, donc il semble quand même
Yanick Rochon
Je viens de trouver une solution. Si quelqu'un souhaite trouver quelque chose de mieux, le Makefile peut encore être amélioré.
Yanick Rochon
2
@YanickRochon Je ne voulais pas critiquer vos compétences en anglais. Mais pour que les cibles PHONY aient un sens, vous ne pouvez certainement pas écrire BANANA;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

Réponses:

34

Premièrement, votre $(OBJECTS)règle est problématique, car:

  1. c'est un peu aveugle, rendant toutes les sources préalables à chaque objet,
  2. il utilise souvent la mauvaise source (comme vous l'avez découvert avec file1.oet file2.o)
  3. il essaie de construire des exécutables au lieu de s'arrêter aux objets, et
  4. le nom de la cible ( foo.o) n'est pas ce que la règle produira réellement ( obj/foo.o).

Je suggère ce qui suit:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

La $(TARGET)règle présente le même problème que le nom de la cible ne décrit pas réellement ce que la règle construit. Pour cette raison, si vous tapez makeplusieurs fois, Make reconstruira la cible à chaque fois, même s'il n'y a aucune raison de le faire. Un petit changement corrige cela:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Une fois que tout est en ordre, vous pouvez envisager une gestion des dépendances plus sophistiquée; si vous modifiez l'un des fichiers d'en-tête, ce makefile ne saura pas quels objets / exécutables doivent être reconstruits. Mais cela peut attendre un autre jour.

EDIT:
Désolé, j'ai omis une partie de la $(OBJECTS)règle ci-dessus; Je l'ai corrigé. (J'aimerais pouvoir utiliser "grève" dans un exemple de code.)

Bêta
la source
avec vos suggestions de modifications, j'obtiens:obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here
Yanick Rochon
@Yanick Rochon: Avez-vous plusieurs mainfonctions? Peut-être un dans file1.cet un dans main.c? Si tel est le cas, vous ne pourrez pas lier ces objets; il ne peut y en avoir qu'un maindans un exécutable.
Bêta du
Non, je ne. Tout fonctionne bien avec la dernière version que j'ai publiée dans la question. Quand je change mon Makefile pour ce que vous suggérez (et je comprends les avantages de ce que vous dites) c'est ce que j'obtiens. Je viens de coller file1.cmais cela donne le même message à tous les fichiers du projet. Et main.cest le seul avec une fonction principale ... et main.cimporte file1.het file2.h(il n'y a pas de relation entre file1.cet file2.c), mais je doute que le problème vienne de là.
Yanick Rochon
@Yanick Rochon: j'ai fait une erreur en collant la première ligne de ma $(OBJECTS)règle; Je l'ai édité. Avec la mauvaise ligne, j'ai eu une erreur, mais pas celle que vous avez ...
Bêta
6

Vous pouvez ajouter l' -Iindicateur aux indicateurs du compilateur (CFLAGS) pour indiquer où le compilateur doit rechercher les fichiers source, et l'indicateur -o pour indiquer où le binaire doit être laissé:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Afin de déposer les fichiers objets dans le objrépertoire, utilisez l' -ooption lors de la compilation. Regardez également les variables automatiques$@ et .$<

Par exemple, considérez ce Makefile simple

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

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

Mettre à jour>

En regardant votre makefile, je me rends compte que vous utilisez le -odrapeau. Bien. Continuez à l'utiliser, mais ajoutez une variable de répertoire cible pour indiquer où le fichier de sortie doit être écrit.

À M
la source
Pourriez-vous être plus précis? Voulez-vous dire ajouter -l ...au CFLAGSet ... il y a déjà l' -oargument du lien ( LINKER)
Yanick Rochon
Oui, le CFLAGS, et oui, continuez à utiliser -o, ajoutez simplement la variable TARGETPATH.
Tom
Merci, j'ai fait les modifications, mais il me semble qu'il me manque encore quelque chose (voir la mise à jour sur la question)
Yanick Rochon
juste make, d'où se trouve le Makefile
Yanick Rochon
Vous ne pouvez pas lire la commande en cours d'exécution? par exemple gcc -c yadayada. À peu près sûr, il y a une variable qui ne contient pas ce que vous attendez
Tom
-1

J'ai arrêté d'écrire des makefiles ces jours-ci, si votre intention est d'apprendre, allez-y, sinon vous avez un bon générateur de makefile fourni avec eclipse CDT. Si vous voulez une certaine maintenabilité / prise en charge de plusieurs projets dans votre arborescence de construction, jetez un œil à ce qui suit -

https://github.com/dmoulding/boilermake J'ai trouvé ça plutôt bien ..!

Kamath
la source
3
Basé sur l'opinion. Ne répond pas à la question d'OP. Suppose l'environnement Eclipse.
Nathaniel Johnson