Makefile 只执行一次命令

Makefile executes the command only once

我正在使用MinGW(Windows 8.1,GCC 7.3.0)的Makefile构建一个中型项目,自动检测文件夹src下的所有源文件并编译所有目标文件进入 obj 文件夹,但不幸的是它只对第一个检测到的文件执行命令并在那里停止。

这是我第一次为一个源文件以外的任何东西编写 Makefile 脚本,也许我得到了一些错误的规则。提前致谢!

CC   := gcc

SRC  := src
OBJ  := obj

MAIN := main
PACK := libbundle

SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(patsubst $(SRC)/%.c,$(OBJ)/%.o, $(SOURCES))
CFLAGS := -I$(SRC)

$(OBJECTS): $(SOURCES)
    $(CC) $(CFLAGS) -c $< -o $@

# build:
#   ar rcs $(PACK).a $(OBJECTS)
#   $(CC) -shared -o $(PACK).so $(OBJECTS)
#   $(CC) -o $(MAIN).c $(PACK).so

输出:

gcc -Isrc -c src/firstsource.c -o obj/firstsource.o

...到此为止!

问题 - 具有多个目标的规则

你的规则

$(OBJECTS): $(SOURCES)
    $(CC) $(CFLAGS) -c $< -o $@

多个目标。我认为这在这里不合适。请参阅讨论 here 多目标规则的用处。

此外,此规则指定了多个先决条件 - 但 $< 表示 仅第一个先决条件 。您可以使用 $+ 来捕获 所有 先决条件 - 但随后您将失去使用 -o 选项的能力。如果您想使用多个先决条件,请参阅下文。

$(OBJECTS): $(SOURCES) 的详细含义

例如,假设您的 src/ 目录包含 firstsource.csecondsource.c。然后你的变量变成

$(SOURCES) -> src/firstsource.c src/secondsource.c
$(OBJECTS) -> obj/firstsource.o obj/secondsource.o

(实际上 - 并且有点不直观 - 第一个源将放置在 之后 第二个源,但为了简单起见,让我们忽略它。)

所以规则

$(OBJECTS): $(SOURCES)
    $(CC) $(CFLAGS) -c $< -o $@

等同于

obj/firstsource.o obj/secondsource.o: src/firstsource.c src/secondsource.c
    $(CC) $(CFLAGS) -c $< -o $@

这条规则反过来等同于两条规则(因为它有多个目标)——每个都有相同的先决条件:

obj/firstsource.o: src/firstsource.c src/secondsource.c
    $(CC) $(CFLAGS) -c $< -o $@

obj/secondsource.o: src/firstsource.c src/secondsource.c
    $(CC) $(CFLAGS) -c $< -o $@

你能看出这里的问题吗?

由于$<仅代表第一个先决条件,第一个规则的配方变为

    gcc -Isrc -c src/firstsource.c -o obj/firstsource.o

这对第一条规则没问题,但对第二条规则不起作用

    gcc -Isrc -c src/firstsource.c -o obj/secondsource.o

因为您使用了错误的输入文件。

顺便说一下...你提到过

unfortunately it [i.e. make] is only executing the command over the first detected file and stops there.

这是因为 - 当您不带任何参数调用 make 时 - 它会调用文件中的第一条规则,而不会再调用更多。

选项 1:使用多个规则

这里更适合的是多条规则——每条规则只有一个单个目标。所以尝试用以下内容替换上面的内容。

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

compile-only: $(OBJECTS)

您可以在这个修改后的 Makefile 上调用 make 作为

make -B compile-only

选项 2:具有多个先决条件的单个目标

如果您的目标中有多个先决条件,您可以使用特殊变量 $+ 在配方中引用它们。但是,在这种情况下您不能使用 -o 选项 - 因此将无法指定目标文件的输出目录。 (要解决这个问题,您可以在编译之前 cdobj 目录 - 但随后您需要调整 SOURCES 变量。)

CC := gcc
CFLAGS := -Isrc
SRC := src

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

myobjs: $(SOURCES)
    $(CC) $(CFLAGS) -c $+

这会将所有目标文件放在顶级目录中。如前所述,如果必须将目标文件放在单独的目录中,可以调整 SOURCEScd 目录 obj

旁白 - 模式规则的预定义配方

我理解像您所做的那样将构建输出放在单独目录中的基本原理,但是 - 如果您愿意将构建输出放在与源文件 - 您可以使用 makepredefined pattern rules.

简化您的 Makefile
SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(SOURCES:.c=.o)

compile: $(OBJECTS)

您应该在 Makefile 中使用 standard targets,最重要的是 "all"。它应该是 Makefile 中的第一个目标,以便 makemake all 做同样的事情。

all: $(OBJECTS)

使用 $(OBJECTS): $(SOURCES) 你告诉 make $(OBJECTS) 中的每个文件都依赖于 $(SOURCES) 中的每个文件,并且将执行下面的命令,因为任何对象未通过测试比任何来源都新。该命令将只执行一次并停止。

您需要指定每个目标文件都依赖于其对应的源文件。如我所见,您正在使用 GMAKE 语法,我将向您展示此类规则的 GNU make 语法:

$(OBJECTS): obj/%.o: src/%.c
    $(CC) $(CFLAGS) -c $< -o $@

这就好像每个 .o 文件都有一个规则,说明如何从其正确的源文件编译它。

您还需要说明哪些文件是您的默认目标,例如:

.PHONY: all
all: $(OBJECTS)
clean:
     $(RM) $(TOCLEAN)

把那个规则放在第一个,所以它会被默认选中。

这将使 all 成为您的默认目标。它会分解成你所有的目标文件,并且对于每个对象你都有一个规则来说明如何编译它(不是必需的,因为 gnu make 已经知道如何编译 C 程序,但在这里重复它并没有什么坏处)

你的最终 Makefile 是:

CC   := gcc

SRC  := src
OBJ  := obj

MAIN := main
PACK := libbundle
CFLAGS := -I$(SRC)
PICFLAGS := -fPIC


SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SOURCES))
TOCLEAN += $(OBJECTS)
PICOBJECTS := $(patsubst $(OBJ)/%.o, $(OBJ)/%.pic, $(OBJECTS))
TOCLEAN += $(PICOBJECTS)

.PHONY: all

.SUFFIXES: .c .o .pic

all: $(PACK).a $(MAIN)
clean:
    $(RM) $(TOCLEAN)

$(MAIN): $(MAIN).o $(PACK).so
    $(CC) $(LDFLAGS) -o $@ $+
TOCLEAN += $(MAIN)

$(PACK).a: $(OBJECTS)
    ar r $(PACK).a $(OBJECTS)
TOCLEAN += $(PACK).a

$(PACK).so: $(PICOBJECTS)
    $(LD) $(LDFLAGS) -shared -o $(PACK).so $(PICOBJECTS)
TOCLEAN += $(PACK).so

# this to create a normal .o file in $(OBJ) directory.
$(OBJECTS): $(OBJ)/%.o: $(SRC)/%.c
    $(CC) $(CFLAGS) -o $@ -c $<

# this to create a PIC (Position Independent Code) .pic object in $(OBJ) directory.
# (REQUIRED FOR .so SHARED OBJECT)
$(PICOBJECTS): $(OBJ)/%.pic: $(SRC)/%.c
    $(CC) $(CFLAGS) $(PICFLAGS) -o $@ -c $<