将Makefile从平面目录结构发展为子目录结构

时间:2019-02-14 14:56:55

标签: makefile

查看下面的更新


已完成研究:我发现很难学习如何将Makefile从一种情况发展到另一种情况。那里有很多问题和答案,但是很少有人真正显示出Makefile如何随着项目的变化而发展。他们似乎都使用了Makefile的各种不同技术和惯用法,因此,当您像我一样第一次学习Makefile时,在一个问题和另一个问题之间进行转换可能会很棘手。

问题::我的问题是我有一个项目,该项目开始时是平面目录结构,然后又迁移到带有子目录的结构。我不能做的就是随身携带我的Makefile。

首先,我将展示自己创建的作品,然后再展示我希望它如何发展以及如何失效。

平面目录结构,有效的Makefile

我有一个项目目录,其中包含我所有的C文件和一个头文件以及我的Makefile:

project
  Makefile
  c8_asm.c
  c8_dasm.c
  c8_terp.c
  chip8.h

这是我的Makefile(效果很好):

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

# Targets

all: c8_dasm c8_asm c8_terp

c8_dasm: c8_dasm.o
    $(CC) $(LDLIBS) c8_dasm.o -o $@

c8_asm: c8_asm.o
    $(CC) $(LDLIBS) c8_asm.o -o $@

c8_terp: c8_terp.o
    $(CC) $(LDLIBS) c8_terp.o -o $@

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

c8_dasm.o: chip8.h
c8_asm.o: chip8.h
c8_terp.o: chip8.h

.PHONY: clean
clean:
    rm c8_dasm c8_asm c8_terp c8_dasm.o c8_asm.o c8_terp.o

我获取了所有.o文件,并且可执行文件都在项目目录中创建。

发展项目

但是我想做的是将我的源文件(所有.c和.h)放在src目录中。我想建立一个obj目录,并使可执行文件进入bin目录。所以我的项目看起来像这样:

project
  src
    c8_asm.c
    c8_dasm.c
    c8_terp.c
    chip8.h
  Makefile

子目录结构,Makefile无法正常工作

为适应以上情况,我相应地更改了我的Makefile:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

MKDIR_P ?= mkdir -p

# Targets

all: $(BIN_DIR)/c8_dasm $(BIN_DIR)/c8_asm $(BIN_DIR)/c8_terp

$(BIN_DIR)/c8_dasm: $(OBJ_DIR)/c8_dasm.o
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_dasm.o -o $@

$(BIN_DIR)/c8_asm: $(OBJ_DIR)/c8_asm.o
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_asm.o -o $@

$(BIN_DIR)/c8_terp: $(OBJ_DIR)/c8_terp.o
    $(MKDIR_P) $(dir $@)
    $(CC) $(LDLIBS) $(OBJ_DIR)/c8_terp.o -o $@

$(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
    $(MKDIR_P) $(dir $@)
    $(CC) $< -o $(OBJ_DIR)/$@

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

$(OBJ_DIR)/c8_dasm.o: $(SRC_DIR)/chip8.h
$(OBJ_DIR)/c8_asm.o: $(SRC_DIR)/chip8.h
$(OBJ_DIR)/c8_terp.o: $(SRC_DIR)/chip8.h

.PHONY: clean
clean:
    rm -r $(BUILD_DIR)
    rm $(OBJECTS)

运行此命令后,我得到以下信息:

mkdir -p obj/obj/
gcc src/c8_dasm.c -o obj/c8_dasm.o
gcc -lm obj/c8_dasm.o -o bin/c8_dasm
ld: can't link with a main executable file 'obj/c8_dasm.o' for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [bin/c8_dasm] Error 1

我想在这里停下来并获得一些帮助,因为我担心我会使这个Makefile变得过于复杂,并且我试图避免养成不良习惯。

我希望能听到关于我在此处未正确概念化的观点。

首次更新

我设法一点一点地使用它,并使它大部分工作。这就是我最终得到的:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

# Directories.

SRC_DIR = src
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))

# Patterns for files.

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(SRC_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

# Targets

all: $(EXECUTABLES)

c8_dasm: $(SRC_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(SRC_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(SRC_DIR)/c8_terp.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Interpreter Built"

# Using implicit rules for updating an '.o' file from a correspondingly
# named '.c' file.

c8_dasm.o: $(SRC_DIR)/chip8.h
c8_asm.o: $(SRC_DIR)/chip8.h
c8_terp.o: $(SRC_DIR)/chip8.h

.PHONY: clean
clean:
    rm $(OBJECTS)
    rm -r $(BIN_DIR)

当然,正如我在Make中所发现的那样,这还会导致其他难以理解的问题。例如,这样做:

make
make clean

工作正常。这意味着将生成所有文件并清除文件,包括bin目录。

但是,如果我这样做:

make c8_dasm
make clean

这很好。但是清除操作无法删除bin目录(尽管确实删除了目标文件)。无论我尝试构建哪种可执行文件,都会发生这种情况。

大量搜索都无法帮助我找出原因。

第二次更新

我发现问题也解决了。只需在干净目标中对rm语句使用“ -f”。

第三次更新

要使目标文件目录部分正常工作,我尝试(从此:path include and src directory makefile)构造我的Makefile如下:

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))
$(shell mkdir -p $(OBJ_DIR))

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

all: $(EXECUTABLES)

c8_dasm: $(SRC_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(SRC_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(SRC_DIR)/c8_terp.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Interpreter Built"

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

.PHONY: clean
clean:
    rm -rf $(BIN_DIR)
    rm -f $(OBJECTS)

我能够使用chip8.h将原始的三行压缩为一个目标,但是我无法知道这是否正确。它至少可以编译。我还更改了OBJECTS行以反映我创建的新OBJ_DIR

但是,这不能将目标文件放在正确的位置。仍然会将它们放在src目录中,而不是obj目录中。

3 个答案:

答案 0 :(得分:1)

这就是为什么不对Makefile做任何复杂的事情是有道理的。只需在命令中输入实际目录名称即可。永远不要依赖通配符。

使用C和C ++以及使用Makefiles的人们花太多时间试图使它们工作,而不仅仅是实际完成工作。这就是为什么您会看到如此多的问题以及答案如此之大的原因。

在您的特定情况下,目标不一定总是包含目录,这就是问题的一部分。由于要在所有内容之前添加目录,因此生成的规则在文件中没有实际的目标。您必须考虑每个目标生成的内容:含义,即输出。因此,如果c8_dasm获得输出,那就是您的目标。该目录与此无关。因此,您需要删除所有不需要的目录替换。

但是在执行此操作之前,请先自问:如果您的第一个解决方案有效,为什么要更改它?最好不要在使用Make时执行目录。只需将所有内容都放在与开​​始时相同的目录中即可。您甚至可以看到,这使您的Makefile更加整洁。

答案 1 :(得分:1)

我相信我可能已经知道了。以下是我的Makefile。它似乎可以满足我的要求。它执行以下操作:

  • 将所有目标文件编译到obj目录中。
  • 编译并链接,以便在bin目录中生成可执行文件。
  • 识别是否有任何.c文件被更改并相应地重新编译。
  • 识别.h文件是否已更改,并重新编译引用该文件的所有C文件。

这似乎满足所有条件,但是我无法确定我是否已经将自己画在一个看不见的角落。

CC = gcc

CFLAGS += -c -Wall -std=c99
CFLAGS += -D_POSIX_C_SOURCE=200809L

LDLIBS += -lm

SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin

$(shell mkdir -p $(BIN_DIR))
$(shell mkdir -p $(OBJ_DIR))

SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

EXECUTABLES := c8_dasm c8_asm c8_terp

all: $(EXECUTABLES)

c8_dasm: $(OBJ_DIR)/c8_dasm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Disassembler Built"

c8_asm: $(OBJ_DIR)/c8_asm.o
    $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
    @echo "C8 Assembler Built"

c8_terp: $(OBJ_DIR)/c8_terp.o
   $(CC) $^ $(LDLIBS) -o $(BIN_DIR)/$@
   @echo "C8 Interpreter Built"

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

.PHONY: clean
clean:
    rm -rf $(BIN_DIR)
    rm -rf $(OBJ_DIR)

答案 2 :(得分:0)

Stackoverflow抱怨太多评论,因此我将其作为另一个“答案”。经过我们反复对我的原始评论,您的最后一条评论是正确的。那就是我希望你看到的。

了解您不能使用Make做您想做的事情。

因此,这实际上是答案:您不能创建多个可执行文件,并且在使用目录结构的同时仅将某些目标文件应用于每个文件并且。那个。

现在,您正以一种非预期的方式尝试使用Make,这就是为什么您遇到很多问题的原因。如果您不停地玩耍,将会遇到一系列“重复符号”错误,因为您将遵循大多数建议,为每个可执行文件多次编译每个文件。

查看此How can I create a Makefile for C projects with SRC, OBJ, and BIN subdirectories?以了解我的意思。之所以这样,是因为所有目标文件都用于创建单个可执行文件。但是正如您所说的,事实并非如此。这就是Make无法处理的。这就是为什么您找不到答案的原因。

虽然您的chip8.h文件现在不会在允许您进行编译方面引起问题,但是当chip8.h文件本身发生更改时,带有第三个更新的Makefile将无法识别。您将必须更改.c文件以强制重新编译,以便可以识别对.h的更改。因此,您要么必须坚持第二次更新,要么使用Make以外的其他功能。