如何编写带有单独的源目录和头目录的Makefile? [英] How to write a Makefile with separate source and header directories?

查看:56
本文介绍了如何编写带有单独的源目录和头目录的Makefile?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

遵循本教程 ...

我有2个源文件和1个头文件.我想像教程中那样将它们放在单独的目录中.

I have 2 source files and 1 header file. I want to have them in separate directories like in the tutorial.

所以我设置了这个项目:

So I set this project up:

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

我生成的错误说:

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

发生了什么事?

推荐答案

您的教程提倡了古老的做法和不好的做法,您应该避免使用恕我直言.

Your tutorial promotes old and bad practices, you should avoid it IMHO.

您在这里的规则:

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

您要告诉make在它们实际位于 src 目录中时在当前目录中查找源,因此永远不会使用这种模式,并且您没有合适的模式.

You're telling make to look for sources in the current directory while they actually reside in the src directory, thus this pattern is never used and you have no suitable one.

确保像这样组织项目目录:

Make sure you organize your project directory like this :

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

然后,按照良好做法逐步进行操作.

Then let's take the process step by step, using good practices.

首先,如果不需要,请不要定义任何内容.Make具有许多预定义的变量和函数,您在尝试手动进行操作之前应使用这些变量和函数.实际上,他有这么多的东西,甚至在目录中根本没有Makefile的情况下,您都可以编译一个简单的文件!

Firstly, don't define anything if you don't need to. Make has a lot of predefined variables and functions that you should use before trying to do it manually. In fact, he has so many that you can compile a simple file without even having a Makefile in the directory at all!

  1. 列出您的源代码并构建输出目录:

  1. List your source and build output directories:

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

  • 命名您的最终目标,即您的可执行文件:

  • Name your final target, that is, your executable:

     EXE := $(BIN_DIR)/hellomake
    

  • 列出您的源文件:

  • List your source files:

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

  • 从源文件中,列出目标文件:

  • From the source files, list the object files:

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

  • 现在让我们来处理标志

  • Now let's handle the 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 代表此处的 C P re P ,而不是CPlusPlus!请使用 CXXFLAGS(用于C ++标志)和 CXX (用于C ++编译器).

    (CPP stands for C PreProcessor here, not CPlusPlus! Use CXXFLAGS for C++ flags and CXX for C++ compiler.)

    -MMD -MP 标志用于自动生成标头依赖项.稍后仅当标题更改时,我们将使用它来触发编译.

    The -MMD -MP flags are used to generate the header dependencies automatically. We will use this later on to trigger a compilation when only a header changes.

    好吧,现在我们的变量已正确填写,现在该轮一些食谱了.

    Ok, time to roll some recipes now that our variables are correctly filled.

    广泛传播,默认目标应称为 all ,并且它应该是Makefile中的第一个目标.当在命令行上仅编写 make 时,其先决条件应为您要构建的目标:

    It is widely spread that the default target should be called all and that it should be the first target in your Makefile. Its prerequisites shall be the target you want to build when writing only make on the command line:

    all: $(EXE)
    

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

    One problem though is Make will think we want to actually create a file or folder named all, so let's tell him this is not a real target:

    .PHONY: all
    

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

    Now list the prerequisites for building your executable, and fill its recipe to tell make what to do with these:

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

    ( CC 代表 C C 编译器.)

    请注意,您的 $(BIN_DIR)可能尚不存在,因此对编译器的调用可能会失败.让我们告诉make,您要它首先对其进行检查:

    Note that your $(BIN_DIR) might not exist yet so the call to the compiler might fail. Let's tell make that you want it to check for that first:

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

    一些快速的补充说明:

    • $(CC)是一个内置变量,已经包含在C中进行编译和链接时所需的内容
    • 为避免链接器错误,强烈建议在目标文件和 $(LDLIBS) 前放置 $(LDFLAGS) .>之后
    • $(CPPFLAGS) $(CFLAGS)在这里没用,编译阶段已经结束,这里是链接阶段
    • $(CC) is a built-in variable already containing what you need when compiling and linking in C
    • To avoid linker errors, it is strongly recommended to put $(LDFLAGS) before your object files and $(LDLIBS) after
    • $(CPPFLAGS) and $(CFLAGS) are useless here, the compilation phase is already over, it is the linking phase here

    下一步,由于您的源文件和目标文件不共享相同的前缀,因此您需要确切说明要执行的操作,因为其内置规则不能满足您的特定情况:

    Next step, since your source and object files don't share the same prefix, you need to tell make exactly what to do since its built-in rules don't cover your specific case:

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

    与以前相同的问题,您的 $(OBJ_DIR)可能尚不存在,因此对编译器的调用可能会失败.让我们更新规则:

    Same problem as before, your $(OBJ_DIR) might not exist yet so the call to the compiler might fail. Let's update the rules:

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

    好吧,现在可执行文件应该可以很好地构建了.我们想要一个简单的规则来清除构建工件:

    Ok, now the executable should build nicely. We want a simple rule to clean the build artifacts though:

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

    (同样,clean不是需要创建的目标,因此将其添加到 .PHONY 特殊目标中!)

    (Again, clean is not a target that needs to be created, so add it to the .PHONY special target!)

    最后一件事.还记得自动依赖项生成吗?GCC和Clang将创建与您的 .o 文件相对应的 .d 文件,其中包含供我们使用的Makefile规则,因此我们将其包含在此处:

    Last thing. Remember about the automatic dependency generation? GCC and Clang will create .d files corresponding to your .o files, which contains Makefile rules for us to use, so let's include that in here:

    -include $(OBJ:.o=.d) # The dash is used to silence errors if the files don't exist yet
    


    最终结果:


    Final result:

    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)
    

    这篇关于如何编写带有单独的源目录和头目录的Makefile?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆