web-dev-qa-db-fra.com

Comment créer un projet Makefile for C avec des sous-répertoires SRC, OBJ et BIN?

Il y a quelques mois, j'ai proposé le générique suivant Makefile pour les travaux 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 va essentiellement compiler chaque .c et .h fichier pour générer .o fichiers et l'exécutable projectname tous dans le même dossier.

Maintenant, j'aimerais pousser ceci un peu. 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, j'aimerais avoir un Makefile qui compile les sources C à partir de ./src/ en ./obj/ puis liez tout pour créer le fichier 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 de projet ci-dessus; au lieu de cela, le projet ne parvient pas à compiler avec toutes sortes d'erreurs. Bien sûr, je pourrais utiliser à part entière IDE (Monodevelop, Anjuta, etc.), mais honnêtement, je préfère rester avec gEdit et le bon vieux terminal.

Y at-il un gourou qui peut me donner une solution de travail, ou des informations claires sur la façon dont cela peut être fait? Merci!

** PDATE (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!"
77
Yanick Rochon

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

  1. c'est un peu aveugle, rendant tous les conditions préalables de chaque objet,
  2. il utilise souvent la mauvaise source (comme vous l'avez découvert avec file1.o et 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 règle $(TARGET) a le même problème que le nom de la cible ne décrit pas réellement ce que la règle génère. Pour cette raison, si vous tapez make plusieurs fois, Make reconstruira la cible à chaque fois, même s’il n’ya 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 de la dépendance plus sophistiquée; Si vous modifiez l'un des fichiers d'en-tête, ce fichier make ne saura pas quels objets/exécutables doivent être reconstruits. Mais cela peut attendre un autre jour.

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

28
Beta

Vous pouvez ajouter le -I drapeau sur les drapeaux du compilateur (CFLAGS) pour indiquer où le compilateur doit chercher les fichiers source, et le drapeau -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!"

Pour déposer les fichiers objet dans le répertoire obj, utilisez le fichier -o option lors de la compilation. Aussi, regardez le $@ et $<variables automatiques .

Par exemple, considérons 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 réalise que vous utilisez le -o drapeau. Bien. Continuez à l’utiliser, mais ajoutez une variable de répertoire cible pour indiquer où le fichier de sortie doit être écrit.

5
Tom

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 un support pour la maintenabilité/plusieurs projets dans votre arbre de compilation, regardez ce qui suit -

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

0
Kamath