Wie kann ich ein Makefile für C-Projekte mit SRC-, OBJ- und BIN-Unterverzeichnissen erstellen?

94

Vor einigen Monaten habe ich mir das folgende Generikum Makefilefür Schulaufgaben ausgedacht:

# ------------------------------------------------
# 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!"

Dies kompiliert grundsätzlich jede .cund .hDatei zu erzeugen , .oDateien und die ausführbare Datei projectnamealle im selben Ordner.

Jetzt möchte ich das ein wenig vorantreiben. Wie kann ich ein Makefile schreiben, um ein C-Projekt mit der folgenden Verzeichnisstruktur zu kompilieren?

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

Mit anderen Worten, ich hätte gerne ein Makefile, das C-Quellen aus ./src/in kompiliert ./obj/und dann alles verknüpft, um die ausführbare Datei in zu erstellen ./bin/.

Ich habe versucht, verschiedene Makefiles zu lesen, aber ich kann sie einfach nicht für die obige Projektstruktur verwenden. Stattdessen kann das Projekt nicht mit allen möglichen Fehlern kompiliert werden. Sicher, ich könnte eine ausgewachsene IDE (Monodevelop, Anjuta usw.) verwenden, aber ich ziehe es ehrlich gesagt vor, bei gEdit und dem guten alten Terminal zu bleiben.

Gibt es einen Guru, der mir eine funktionierende Lösung geben kann oder klare Informationen darüber, wie dies getan werden kann? Danke dir!

** UPDATE (v4) **

Das finale Resultat :

# ------------------------------------------------
# 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
quelle
Was ist hier die spezifische Frage?
Oliver Charlesworth
Ich bin mir nicht sicher, ob ich verstehe, was du tun willst.
Tom
Aktualisiert die Makefile. Ich komme näher, aber ich habe Probleme mit den automatischen Variablen, also scheint es trotzdem
Yanick Rochon
Ich habe gerade eine Lösung gefunden. Wenn jemand etwas Besseres finden möchte, kann das Makefile noch verbessert werden.
Yanick Rochon
2
@YanickRochon Ich wollte deine Englischkenntnisse nicht kritisieren. Aber damit die PHONY-Ziele einen Sinn ergeben, können Sie BANANA definitiv nicht schreiben;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

Antworten:

34

Erstens ist Ihre $(OBJECTS)Regel problematisch, weil:

  1. Es ist irgendwie wahllos und macht alle Quellen zu Voraussetzungen für jedes Objekt.
  2. es wird oft die falsche Quelle verwendet (wie Sie mit file1.ound entdeckt haben file2.o)
  3. Es wird versucht, ausführbare Dateien zu erstellen, anstatt bei Objekten anzuhalten
  4. Der Name des Ziels ( foo.o) entspricht nicht dem, was die Regel tatsächlich erzeugt ( obj/foo.o).

Ich schlage folgendes vor:

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

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

Die $(TARGET)Regel hat das gleiche Problem, dass der Zielname nicht beschreibt, was die Regel erstellt. Wenn Sie makemehrmals eingeben, erstellt Make das Ziel aus diesem Grund jedes Mal neu, auch wenn es keinen Grund dafür gibt. Eine kleine Änderung behebt Folgendes:

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

Sobald alles in Ordnung ist, können Sie eine komplexere Behandlung von Abhängigkeiten in Betracht ziehen. Wenn Sie eine der Header-Dateien ändern, weiß dieses Makefile nicht, welche Objekte / ausführbaren Dateien neu erstellt werden müssen. Aber das kann auf einen anderen Tag warten.

EDIT:
Entschuldigung, ich habe einen Teil der $(OBJECTS)obigen Regel weggelassen . Ich habe es korrigiert. (Ich wünschte, ich könnte "Strike" in einem Codebeispiel verwenden.)

Beta
quelle
Mit Ihren Änderungsvorschlägen bekomme ich: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: Haben Sie mehrere mainFunktionen? Vielleicht eins rein file1.cund eins rein main.c? In diesem Fall können Sie diese Objekte nicht verknüpfen. Es kann nur eine mainin einer ausführbaren Datei geben.
Beta
Nein, tue ich nicht. Alles funktioniert gut mit der letzten Version, die ich in der Frage gepostet habe. Wenn ich mein Makefile auf das ändere, was Sie vorschlagen (und ich verstehe die Vorteile dessen, was Sie sagen), bekomme ich das. Ich habe gerade eingefügt, file1.caber es gibt die gleiche Nachricht für jede einzelne Datei des Projekts. Und main.cist der einzige mit einer Hauptfunktion ... und main.cimportiert file1.hund file2.h(es gibt keine Beziehung zwischen file1.cund file2.c), aber ich bezweifle, dass das Problem von dort kommt.
Yanick Rochon
@ Yanick Rochon: Ich habe einen Fehler gemacht, als ich die erste Zeile meiner $(OBJECTS)Regel eingefügt habe . Ich habe es bearbeitet. Mit der schlechten Linie habe ich einen Fehler bekommen, aber nicht den, den du bekommen hast ...
Beta
6

Sie können das -IFlag zu den Compiler-Flags (CFLAGS) hinzufügen, um anzugeben, wo der Compiler nach Quelldateien suchen soll, und das Flag -o, um anzugeben, wo die Binärdatei verbleiben soll:

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

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

objVerwenden Sie -obeim Kompilieren die Option , um die Objektdateien im Verzeichnis abzulegen. Schauen Sie sich auch die $@und $< automatische Variablen an .

Betrachten Sie beispielsweise dieses einfache Makefile

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

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

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

Update>

Wenn ich mir dein Makefile ansehe, merke ich, dass du die -oFlagge benutzt. Gut. Verwenden Sie es weiterhin, fügen Sie jedoch eine Zielverzeichnisvariable hinzu, um anzugeben, wo die Ausgabedatei geschrieben werden soll.

Tom
quelle
Könnten Sie genauer sein? Meinen Sie das Hinzufügen -l ...zu CFLAGSund ... es gibt bereits das -oArgument zum Linker ( LINKER)
Yanick Rochon
Ja, die CFLAGS, und ja, verwenden Sie weiterhin -o. Fügen Sie einfach die Variable TARGETPATH ​​hinzu.
Tom
Vielen Dank, ich habe die Änderungen vorgenommen, aber es scheint, dass mir noch etwas fehlt (siehe das Update zu der Frage)
Yanick Rochon
nur make, von wo das Makefile sitzt
Yanick Rochon
Können Sie den ausgeführten Befehl nicht lesen? zum Beispiel gcc -c yadayada. Ziemlich sicher, dass es eine Variable gibt, die nicht das enthält, was Sie erwarten
Tom
-1

Ich habe in diesen Tagen aufgehört, Makefiles zu schreiben. Wenn Sie lernen möchten, machen Sie weiter, sonst haben Sie einen guten Makefile-Generator, der mit Eclipse CDT geliefert wird. Wenn Sie in Ihrem Build-Baum eine gewisse Wartbarkeit / Unterstützung für mehrere Projekte wünschen, sehen Sie sich Folgendes an:

https://github.com/dmoulding/boilermake Ich fand das ziemlich gut ..!

Kamath
quelle
3
Meinungsbasiert. Beantwortet die Frage von OP nicht. Nimmt eine Eclipse-Umgebung an.
Nathaniel Johnson