c++ 如何编写一个具有独立源目录和头目录的Makefile?

uurity8g  于 2023-01-22  发布在  其他
关注(0)|答案(5)|浏览(110)

this tutorial之后...
我有2个源文件和1个头文件。我想让他们在单独的目录一样,在教程。
所以我设立了这个项目:

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

生成文件:

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)/*~

我生成的错误如下:

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

发生什么事了?

mrphzbgm

mrphzbgm1#

你的教程推广旧的和坏的做法,你应该避免它IMHO。
在你的统治下:

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

您告诉make在当前目录中查找源代码,而它们实际上驻留在src目录中,因此这个模式从未使用过,您也没有合适的模式。
确保按如下方式组织项目目录:

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

然后,让我们使用良好的实践一步一步地执行该过程。
首先,如果你不需要的话,不要定义任何东西。Make有很多预定义的变量和函数,你应该在尝试手工做之前使用它们。事实上,他有这么多,你甚至可以编译一个简单的文件,而不在目录中有一个Makefile!
1.列出源目录和生成输出目录:

SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin # or . if you want it in the current directory

1.命名最终目标,即可执行文件:

EXE := $(BIN_DIR)/hellomake

1.列出源文件:

SRC := $(wildcard $(SRC_DIR)/*.c)

1.在源文件中,列出目标文件:

OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# You can also do it like that
OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))

1.现在我们来处理旗子

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在这里代表C*PreP**处理器,而不是CPlusPlus!使用CXXFLAGS表示C标志,使用CXX表示C编译器。)
-MMD -MP标志用于自动生成头文件依赖项,稍后我们将使用它在只有头文件更改时触发编译。
好了,现在我们的变量已经正确填充,是时候推出一些配方了。
默认的目标应该被称为all,并且它应该是Makefile中的第一个目标,这是一种广泛传播的观点。它的先决条件应该是在命令行中只写make时要构建的目标:

all: $(EXE)

但有一个问题是Make会认为我们实际上想创建一个名为all的文件或文件夹,所以让我们告诉他这不是一个真实的的目标:

.PHONY: all

现在列出构建可执行文件的先决条件,并填写其配方以告诉make如何处理这些先决条件:

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

CC代表C*C**编译器。)
请注意,您的$(BIN_DIR)可能还不存在,因此对编译器的调用可能会失败,让我们告诉make您希望它首先检查它是否存在:

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

$(BIN_DIR):
    mkdir -p $@

一些简短的补充说明:

  • $(CC)是一个内置变量,已经包含了在C中编译和链接时所需的内容
  • 为避免链接器错误,强烈建议将$(LDFLAGS)放在目标文件之前,将$(LDLIBS)放在之后
  • $(CPPFLAGS)$(CFLAGS)在这里无用,编译阶段已经结束,这里是链接阶段

下一步,由于源文件和目标文件不共享相同的前缀,您需要告诉make具体要做什么,因为它的内置规则并不适用于您的特定情况:

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

同样的问题,你的$(OBJ_DIR)可能还不存在,所以对编译器的调用可能会失败。

$(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) # The @ disables the echoing of the command

(同样,clean不是需要创建的目标,因此将其添加到.PHONY特殊目标中!)
最后一件事。还记得自动依赖项生成吗?GCC和Clang会创建与您的.o文件相对应的.d文件,其中包含供我们使用的Makefile规则,所以让我们将其包含在这里:

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

最终结果:

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)
3phpmpom

3phpmpom2#

make实用程序,没有特定的'target',将创建文件中的第一个目标。
第一个目标通常命名为“all”
对于提交的文件,将生成目标文件,并且在命令行中未给定目标时,将不继续生成可执行文件
建议如下:

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
u0sqgete

u0sqgete3#

简单的Makefile定义在你的问题中看起来还不错。试着在文件名之前指定编译器选项:

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

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

您需要从源目录运行make

bcs8qyzn

bcs8qyzn4#

当您收到此错误时”

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

“,这意味着您没有对象文件,
只需查看Makefile中行“* ${OBJS} := *“。
嗨,兄弟!
如果您的项目“helloFunc“的架构是这样的:

helloFunc

|

|__Makefile

|__build

|__include

|     |__hellomake.h

|__src

     |__hellofunc.cpp
 
     |__hellomake.cpp

您Makefile应该如下所示:

# 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

将该Makefile更改为所需的Linux版本:

# (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

您需要注意的是,“Makefile“文件与包含文件和源文件位于同一目录,
所以你需要在你的“Makefile“中把你的“IDIR:=../include“改为“IDIR:=./include”。
结束!

nnvyjq4y

nnvyjq4y5#

这是我在我的windows安装使用:

CC = g++
CFLAGS   = -Wall -std=c++20

SRCDIR   = src
HEADDIR  = include
OBJDIR   = build
BINDIR   = bin
# where the executable will be stored
EXECUTABLE := $(BINDIR)/main

# list of all source files
SOURCES  := $(wildcard $(SRCDIR)/*.cpp)
# list of all header files
INCLUDES := $(wildcard $(HEADDIR)/*.h)
# from the list of all source files, create a list of all object files
OBJECTS  := $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)

# all: clean $(EXECUTABLE)
all: $(EXECUTABLE)

# Link: Generate executable file from object file
$(EXECUTABLE): $(OBJECTS)
    @echo LINKING..... $(CC) -o $@ $(OBJECTS)
    @$(CC) -o $@ $(OBJECTS)
    @echo RUNNING: $(EXECUTABLE)
    @$(EXECUTABLE)
# Compile: Generate object files from source files
# $@ := {EXECUTABLE}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(OBJDIR)/%.o : $(SRCDIR)/%.cpp | makedirs
    @echo COMPILING... $(CC) $(CFLAGS) -c "$<" -o "$@"
    @$(CC) $(CFLAGS) -c $< -o $@

# `|` is order-only-prerequisites
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html
makedirs:
# check if the file exists; if not, create it
# mkdir -p $(OBJDIR) in linux
    @if not exist "$(OBJDIR)" mkdir $(OBJDIR)
    @if not exist "$(BINDIR)" mkdir $(BINDIR)

#Delete dependence files, object files, and the EXECUTABLE file
clean:
    @echo CLEANING UP
# check if the directories exist; if so, delete them
    @if exist "$(OBJDIR)" rmdir /s /q $(OBJDIR)
    @if exist "$(BINDIR)" rmdir /s /q $(BINDIR)

相关问题