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.c
和 secondsource.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
选项 - 因此将无法指定目标文件的输出目录。 (要解决这个问题,您可以在编译之前 cd
到 obj
目录 - 但随后您需要调整 SOURCES
变量。)
CC := gcc
CFLAGS := -Isrc
SRC := src
SOURCES := $(wildcard $(SRC)/*.c)
myobjs: $(SOURCES)
$(CC) $(CFLAGS) -c $+
这会将所有目标文件放在顶级目录中。如前所述,如果必须将目标文件放在单独的目录中,可以调整 SOURCES
和 cd
目录 obj
。
旁白 - 模式规则的预定义配方
我理解像您所做的那样将构建输出放在单独目录中的基本原理,但是 - 如果您愿意将构建输出放在与源文件 - 您可以使用 make
的 predefined pattern rules.
简化您的 Makefile
SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(SOURCES:.c=.o)
compile: $(OBJECTS)
您应该在 Makefile 中使用 standard targets,最重要的是 "all"。它应该是 Makefile 中的第一个目标,以便 make
和 make 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 $<
我正在使用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.c
和 secondsource.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
选项 - 因此将无法指定目标文件的输出目录。 (要解决这个问题,您可以在编译之前 cd
到 obj
目录 - 但随后您需要调整 SOURCES
变量。)
CC := gcc
CFLAGS := -Isrc
SRC := src
SOURCES := $(wildcard $(SRC)/*.c)
myobjs: $(SOURCES)
$(CC) $(CFLAGS) -c $+
这会将所有目标文件放在顶级目录中。如前所述,如果必须将目标文件放在单独的目录中,可以调整 SOURCES
和 cd
目录 obj
。
旁白 - 模式规则的预定义配方
我理解像您所做的那样将构建输出放在单独目录中的基本原理,但是 - 如果您愿意将构建输出放在与源文件 - 您可以使用 make
的 predefined pattern rules.
SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(SOURCES:.c=.o)
compile: $(OBJECTS)
您应该在 Makefile 中使用 standard targets,最重要的是 "all"。它应该是 Makefile 中的第一个目标,以便 make
和 make 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 $<