Wie schreibe ich ein Makefile mit separaten Quell- und Header-Verzeichnissen?

Lesezeit: 13 Minuten

Benutzer-Avatar
iBeyondPower

Folgend dieses Tutorial

Ich habe 2 Quelldateien und 1 Header-Datei. Ich möchte sie in separaten Verzeichnissen wie im Tutorial haben.

Also habe ich dieses Projekt aufgesetzt:

.
├── include
│   └── hellomake.h
├── Makefile
└── src
    ├── hellofunc.c
    └── hellomake.c

Makefile:

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o 
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))

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

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)

.PHONY: clean

clean:
    rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ 

Der Fehler, den ich generiere, sagt:

gcc -o hellomake  -I../include
gcc: fatal error: no input files
compilation terminated.
make: *** [hellomake] Error 4

Was ist los?

  • Poste deinen eigentlichen Code und formatiere ihn so, dass er lesbar ist

    – Eregrith

    1. Juni 2015 um 12:39 Uhr


  • Ich glaube nicht, dass ich gcc eine Datei zur Verfügung stellen sollte, alles ist im Makefile enthalten und alles, was ich aufrufe, ist make im Verzeichnis, da alle 3 Dateien bereits im Makefile enthalten sind

    – iBeyondPower

    1. Juni 2015 um 12:43 Uhr


  • Ein Trick, den ich beim Debuggen einer Make-Datei nützlich finde, besteht darin, ein “show” -Ziel (oder einen beliebigen Namen) ohne Abhängigkeiten einzurichten, das nur Ihre Variablen druckt, dh: @echo "OBJ=$(OBJ)" (eine Zeile für jede Variable). Dann mit make show Sie werden deutlich sehen, ob sie den richtigen Inhalt enthalten.

    – Kebs

    1. Juni 2015 um 12:49 Uhr

  • Der Zweck eines Makefiles besteht darin, Befehle aufzurufen. Einer davon ist gcc zum Beispiel. Wenn Sie nicht liefern gcc mit den zu verknüpfenden Dateien wird es sie nicht erfinden.

    – Eregrith

    1. Juni 2015 um 12:52 Uhr

  • $info kann fast überall in Ihrem Makefile platziert werden, wohingegen echo kann nur in der Variablendeklaration oder im Regeltext platziert werden.

    – Chnossos

    2. Juni 2015 um 21:19 Uhr


Benutzer-Avatar
Chnossos

Ihr Tutorial fördert alte und schlechte Praktiken, Sie sollten es meiner Meinung nach vermeiden.

In Ihrer Regel hier:

$(ODIR)/%.o: %.c $(DEPS)

Sie sagen make, dass es im aktuellen Verzeichnis nach Quellen suchen soll, während sie sich tatsächlich im befinden src Verzeichnis, daher wird dieses Muster nie verwendet und Sie haben kein passendes.


Stellen Sie sicher, dass Sie Ihr Projektverzeichnis wie folgt organisieren:

root
├── include/
│   └── all .h files here
├── lib/
│   └── all third-party library files (.a/.so files) here
├── src/
│   └── all .c files here
└── Makefile

Lassen Sie uns dann den Prozess Schritt für Schritt durchgehen und bewährte Verfahren anwenden.

Erstens: Definieren Sie nichts, wenn Sie es nicht müssen. Make hat viele vordefinierte Variablen und Funktionen, die Sie verwenden sollten, bevor Sie versuchen, es manuell zu tun. Tatsächlich hat er so viele, dass Sie eine einfache Datei kompilieren können, ohne überhaupt ein Makefile im Verzeichnis zu haben!

  1. Listen Sie Ihre Quell- und Build-Ausgabeverzeichnisse auf:

     SRC_DIR := src
     OBJ_DIR := obj
     BIN_DIR := bin # or . if you want it in the current directory
    
  2. Nennen Sie Ihr endgültiges Ziel, d. h. Ihre ausführbare Datei:

     EXE := $(BIN_DIR)/hellomake
    
  3. Listen Sie Ihre Quelldateien auf:

     SRC := $(wildcard $(SRC_DIR)/*.c)
    
  4. Listen Sie in den Quelldateien die Objektdateien auf:

     OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
     # You can also do it like that
     OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))
    
  5. Jetzt kümmern wir uns um die Flags

     CPPFLAGS := -Iinclude -MMD -MP # -I is a preprocessor flag, not a compiler flag
     CFLAGS   := -Wall              # some warnings about bad code
     LDFLAGS  := -Llib              # -L is a linker flag
     LDLIBS   := -lm                # Left empty if no libs are needed
    

(CPP steht für C PbetreffendPProzessor hier, nicht CPlusPlus! Verwenden CXXFLAGS für C++-Flags und CXX für C++-Compiler.)

Das -MMD -MP Flags werden verwendet, um die Header-Abhängigkeiten automatisch zu generieren. Wir werden dies später verwenden, um eine Kompilierung auszulösen, wenn sich nur ein Header ändert.

Ok, jetzt, da unsere Variablen korrekt gefüllt sind, ist es an der Zeit, ein paar Rezepte zu erstellen.

Es ist weit verbreitet, dass das Standardziel aufgerufen werden soll all und dass es das erste Ziel in Ihrem Makefile sein sollte. Seine Voraussetzungen sollten das Ziel sein, das Sie nur beim Schreiben erstellen möchten make auf der Kommandozeile:

all: $(EXE)

Ein Problem ist jedoch, dass Make denkt, dass wir tatsächlich eine Datei oder einen Ordner mit dem Namen erstellen möchten allsagen wir ihm also, dass dies kein echtes Ziel ist:

.PHONY: all

Listen Sie nun die Voraussetzungen zum Erstellen Ihrer ausführbaren Datei auf und füllen Sie das Rezept aus, um make mitzuteilen, was damit zu tun ist:

$(EXE): $(OBJ)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

(CC steht für C CCompiler.)

Beachten Sie, dass Ihre $(BIN_DIR) möglicherweise noch nicht vorhanden, sodass der Aufruf des Compilers möglicherweise fehlschlägt. Sagen wir make, dass es zuerst danach suchen soll:

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(BIN_DIR):
    mkdir -p $@

Einige schnelle zusätzliche Anmerkungen:

  • $(CC) ist eine eingebaute Variable, die bereits alles enthält, was Sie beim Kompilieren und Linken in C benötigen
  • Um Linkerfehler zu vermeiden, wird dringend empfohlen, put $(LDFLAGS) Vor Ihre Objektdateien und $(LDLIBS) nach
  • $(CPPFLAGS) und $(CFLAGS) sind nicht zu gebrauchen hier ist die Kompilationsphase schon vorbei, hier ist die Verlinkungsphase

Da Ihre Quell- und Objektdateien nicht dasselbe Präfix haben, müssen Sie im nächsten Schritt make genau sagen, was zu tun ist, da die integrierten Regeln Ihren speziellen Fall nicht abdecken:

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

Gleiches Problem wie zuvor, Ihr $(OBJ_DIR) möglicherweise noch nicht vorhanden, sodass der Aufruf des Compilers möglicherweise fehlschlägt. Aktualisieren wir die Regeln:

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

Ok, jetzt sollte die ausführbare Datei schön gebaut werden. Wir wollen jedoch eine einfache Regel, um die Build-Artefakte zu bereinigen:

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) # The @ disables the echoing of the command

(Auch hier ist clean kein Ziel, das erstellt werden muss, also fügen Sie es der .PHONY Sonderziel!)

Letztes Ding. Erinnern Sie sich an die automatische Generierung von Abhängigkeiten? GCC und Clang erstellen .d Dateien, die Ihren entsprechen .o files, die Makefile-Regeln enthält, die wir verwenden können, also fügen wir das hier ein:

-include $(OBJ:.o=.d) # The dash silences errors when files don't exist (yet)

Endergebnis:

SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin

EXE := $(BIN_DIR)/hellomake
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

CPPFLAGS := -Iinclude -MMD -MP
CFLAGS   := -Wall
LDFLAGS  := -Llib
LDLIBS   := -lm

.PHONY: all clean

all: $(EXE)

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR)

-include $(OBJ:.o=.d)

  • Makefiles brauchen Tabulatoren, keine Leerzeichen. Beachten Sie dies beim Kopieren von hier.

    – Benedikt S. Vogler

    19. August 2020 um 18:23 Uhr

  • Unter Linux ist es möglich, die Leerzeichen und Tabulatoren mit der Syntax cat -e -t -v Makefile zu überprüfen

    – giammi56

    27. April 2021 um 21:40 Uhr

  • Ich möchte Ihr Makefile um die Möglichkeit erweitern, Release- und Debug-Versionen zu erstellen.

    – Homunculus Reticulli

    6. Oktober 2021 um 15:35 Uhr


  • @HomunculusReticulli Danke! Ich habe mit vielen Möglichkeiten gespielt, Release-/Debug-Builds hinzuzufügen, aber das Ergebnis hat mir nie gefallen. Es gab immer seltsame Macken und Eckfälle, und der Code war viel komplexer. Ich würde dringend empfehlen, stattdessen über Modern CMake zu lesen (hier und hier).

    – Chnossos

    6. Oktober 2021 um 15:43 Uhr

Das Dienstprogramm make erstellt ohne spezifisches ‘Ziel’ das erste Ziel in der Datei.

Das erste Ziel heißt normalerweise “alle”.

Für die bereitgestellte Datei werden die Objektdateien erstellt und die ausführbare Datei wird nicht fortgesetzt, wenn das Ziel nicht in der Befehlszeile angegeben wird

Schlagen Sie Folgendes vor:

SHELL := /bin/sh

# following so could define executable name on command line
# using the '-D' parameter
#ifndef $(NAME)
    NAME := hellomake
#endif

# use ':=' so macros only evaluated once


MAKE    :=  /usr/bin/make
CC      :=  /usr/bin/gcc

CFLAGS  := -g -Wall -Wextra -pedantic
LFLAGS  :=

ODIR    := obj
IDIR    := ../include
LIBS    :=
LIBPATH := ../lib

DEPS    := $(wildcard $(IDIR)/*.h)
SRCS    := $(wildcard *.c)
OBJS    := $(SRCS:.c=.o)

.PHONY: all
all: $(NAME) $(OBJS)

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) $(CFLAGS) -c -o $@ $< -I$(DEPS)

$(NAME): $(OBJS)
    $(CC) $(LFLAGS) -o $@ $^ -L$(LIBPATH) -l$(LIBS)

.PHONY: clean
clean:
    rm -f $(ODIR)/*.o
    rm -f $(NAME)


however, in your proposed project,
not every source file needs every header file
so should use either gcc or sed to generate the dependency files
then use makefile rules similar to the following,
which may need a little 'tweaking' for your project
because the include files are not in the same directory
as the source files:

DEP := $(SRCS:.c=.d)

#
#create dependency files
#
%.d: %.c 
    # 
    # ========= START $< TO $@ =========
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$;                      \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;     \
    rm -f $@.$$$$
    # ========= END $< TO $@ =========

# 
# compile the .c files into .o files using the compiler flags
#
%.o: %.c %.d 
     # 
     # ========= START $< TO $@ =========
     $(CC) $(CCFLAGS) -c $< -o $@ -I$(IDIR) 
     # ========= END $< TO $@ =========
     # 

# include the contents of all the .d files
# note: the .d files contain:
# <filename>.o:<filename>.c plus all the dependencies for that .c file 
# I.E. the #include'd header files
# wrap with ifneg... so will not rebuild *.d files when goal is 'clean'
#
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEP)
endif

  • Ihr Kommentar ist nicht korrekt. Musterregeln und Suffixregeln (implizite Regeln) können nicht als Standardziele gelten. Die erste explizite Regel im obigen Makefile ist helloworld und das ist das Ziel, das erstellt wird, wenn kein anderes Ziel auf der Befehlszeile angegeben wird. Obwohl das Hinzufügen einer all target ist keine schlechte Idee, es wird nicht benötigt. Auch Ihr Kommentar zur Verwendung -D ist falsch: make verwendet nicht -D Variablen zu definieren. Und Sie können keine Präprozessoranweisungen wie schreiben #ifdef in einem Makefile (das ist nur ein Kommentar, zu machen). Auch hier gibt es andere problematische Dinge.

    – Verrückter Wissenschaftler

    2. Juni 2015 um 4:39 Uhr

Die einfachen Makefile-Definitionen erscheinen mir in Ordnung, wie sie in Ihrer Frage erscheinen. Versuchen Sie, die Compileroptionen vor den Dateinamen anzugeben:

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

hellomake: $(OBJ)
    gcc $(CFLAGS) -o $@ $^

Du musst laufen make aus dem Quellverzeichnis.

  • Das Ändern der Reihenfolge der Flags hilft nicht … das Problem ist, dass die Objektdateien tatsächlich in der Befehlszeile fehlen.

    – Verrückter Wissenschaftler

    2. Juni 2015 um 4:40 Uhr

  • @MadScientist: Das Umschalten der Optionen ist nicht die Lösung, aber es ist ratsam. Warum $(OBJ) leer ist, verwendet das OP entweder Gnu make nicht oder etwas fehlt in der Frage.

    – chqrlie

    3. Juni 2015 um 5:19 Uhr

Benutzer-Avatar
Vittore Marcas

Wenn Sie diesen Fehler erhalten haben”

*gcc: fatal error: no input files
compilation terminated.*

“, das heißt, Sie haben keine Objektdateien,

schau dir einfach diese Zeile an “${OBJS} := ” im Makefile.

Hi Bruder!

Wenn Ihr Projekt “helloFunc” ‘s Architektur gefällt einfach so:

helloFunc

|

|__Makefile

|__build

|__include

|     |__hellomake.h

|__src

     |__hellofunc.cpp
 
     |__hellomake.cpp
     

Ihr Makefile sollte genau so aussehen:

# This is a Makefile for separated multiple sources to build with VSCode on mac
# Thanks, Job Vranish.
# (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
# Reference: Makefile Tutorial
# (https://makefiletutorial.com/)
# Reference: @yagiyuki from Qiita
# (https://qiita.com/yagiyuki/items/ff343d381d9477e89f3b)
# Reference: simonsso from Github
# (https://github.com/simonsso/empty-cpp-project/blob/master/Makefile)
# Reference: Chinese Website blogger CDNS
# (https://blog.csdn.net/qq_22073849/article/details/88893201)

# (1)Compiler
# clang++
CXX = clang++
# (2)Compile options
# -Wall -Wextra -std=c++11 -g
CXX_FLAGS = -Wall -Wextra -std=c++11 -g
# (3)Build task directory path
# I do care about out-of-source builds
# ./build
BUILD_DIR ?= ./build
# (4)Source files directory path
# ./src
SRC_DIRS ?= ./src
# (5)Library files directory path
LIBDIR := 
# (6)Add library files
LIBS :=
# (7)Target file, excutable file.
# main
TARGET ?= main
# (8)Source files(code), to be compiled
# Find source files we want to compile 
# *expression must around by single quotos
# ./src/bank.cpp ./src/main.cpp
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# (9)Object files
# String substituion for every C/C++ file
# e.g: ./src/bank.cpp turns into ./build/bank.cpp.o
# ./build/bank.cpp.o  ./build/main.cpp.o
OBJS := $(patsubst %.cpp, ${BUILD_DIR}/%.cpp.o, $(notdir $(SRCS)))
# (10)Dependency files
# which will generate a .d file next to the .o file. Then to use the .d files,
# you just need to find them all:
# 
DEPS := $(OBJS:.o=.d)
# (11)Include files directory path
# Every folder in ./src find include files to be passed via clang 
# ./include
INC_DIRS := ./include
# (12)Include files add together a prefix, clang make sense that -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
# (13)Make Makefiles output Dependency files
# That -MMD and -MP flags together to generate Makefiles 
# That generated Makefiles will take .o as .d to the output
# That "-MMD" and "-MP" To generate the dependency files, all you have to do is
# add some flags to the compile command (supported by both Clang and GCC):
CPP_FLAGS ?= $(INC_FLAGS) -MMD -MP
# (14)Link: Generate executable file from object file
# make your target depend on the objects files:
${BUILD_DIR}/${TARGET} : $(OBJS)
    $(CXX) $(OBJS) -o $@ 
# (15)Compile: Generate object files from source files
# $@ := {TARGET}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(BUILD_DIR)/%.cpp.o: $(SRC_DIRS)/%.cpp
    $(MKDIR_P) $(dir $@)
    $(CXX) $(CPP_FLAGS) $(CXX_FLAGS) -c $< -o $@

#(16)Delete dependence files, object files, and the target file
.PHONY: all clean 
all: ${BUILD_DIR}/${TARGET}


clean:
    $(RM) $(DEPS) $(OBJS) ${BUILD_DIR}/${TARGET}

-include $(DEPS)

MKDIR_P ?= mkdir -p

Ändern Sie dieses Makefile in Ihre benötigte Linux-Version:

# (1)Compiler
# g++
CXX = g++
# (2)Compile options
# -Wall -Wextra -std=c++11 -g
CXX_FLAGS = -Wall -Wextra -std=c++11 -g
# (3)Build task directory path
# I do care about out-of-source builds
# ./build
BUILD_DIR ?= ./build
# (4)Source files directory path
# ./src
SRC_DIRS ?= ./src
# (5)Library files directory path
LIBDIR := 
# (6)Add library files
LIBS :=
# (7)Target file, excutable file.
# main
TARGET ?= main
# (8)Source files(code), to be compiled
# Find source files we want to compile 
# *expression must around by single quotos
# ./src/bank.cpp ./src/main.cpp
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# (9)Object files
# String substituion for every C/C++ file
# e.g: ./src/bank.cpp turns into ./build/bank.cpp.o
# ./build/bank.cpp.o  ./build/main.cpp.o
OBJS := $(patsubst %.cpp, ${BUILD_DIR}/%.cpp.o, $(notdir $(SRCS)))
# (10)Dependency files
# which will generate a .d file next to the .o file. Then to use the .d files,
# you just need to find them all:
# 
DEPS := $(OBJS:.o=.d)
# (11)Include files directory path
# Every folder in ./src find include files to be passed via clang 
# ./include
INC_DIRS := ./include
# (12)Include files add together a prefix, gcc make sense that -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
# (13)Make Makefiles output Dependency files
# That -MMD and -MP flags together to generate Makefiles 
# That generated Makefiles will take .o as .d to the output
# That "-MMD" and "-MP" To generate the dependency files, all you have to do is
# add some flags to the compile command (supported by both Clang and GCC):
CPP_FLAGS ?= $(INC_FLAGS) -MMD -MP
# (14)Link: Generate executable file from object file
# make your target depend on the objects files:
${BUILD_DIR}/${TARGET} : $(OBJS)
    $(CXX) $(OBJS) -o $@ 
# (15)Compile: Generate object files from source files
# $@ := {TARGET}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(BUILD_DIR)/%.cpp.o: $(SRC_DIRS)/%.cpp
    $(MKDIR_P) $(dir $@)
    $(CXX) $(CPP_FLAGS) $(CXX_FLAGS) -c $< -o $@

#(16)Delete dependency files, object files and the target file
.PHONY: all clean 
all: ${BUILD_DIR}/${TARGET} 
clean:
    $(RM) $(DEPS) $(OBJS) ${BUILD_DIR}/${TARGET}

-include $(DEPS)

MKDIR_P ?= mkdir -p

Was Sie beachten müssen, ist, dass Ihr “Makefile” Datei ist das gleiche Verzeichnis der Include-Dateien und Quelldateien,

Sie müssen also Ihre ändern “IDIR:=../include” in “IDIR:=./include” in Ihrem “Makefile“.

ENDE!

1345220cookie-checkWie schreibe ich ein Makefile mit separaten Quell- und Header-Verzeichnissen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy