【问题标题】:Makefile executes the command only onceMakefile 只执行一次命令
【发布时间】:2023-03-12 14:36:01
【问题描述】:

我正在使用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

...然后停在那里!

【问题讨论】:

  • 您缺少 makefile 中的链接步骤。启用build
  • 默认情况下,make 在 makefile 中创建第一个目标。在你的例子中,第一个目标是firstsource.o——所以它完全按照它的指示去做。如果你想让它做更多的事情,告诉它你想要在当前第一个目标之前使用诸如all: ${OBJECTS} 之类的规则的所有对象。或者让all 目标依赖于您尝试创建的程序或共享库,然后该程序或库依赖于目标文件,而目标文件依赖于源文件。

标签: c gcc makefile gnu-make


【解决方案1】:

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

你的规则

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

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

此外,此规则指定了多个先决条件 - 但$&lt;仅代表第一个先决条件。您可以使用$+ 来捕获所有先决条件 - 但随后您将无法使用-o 选项。如果您想使用多个先决条件,请参阅下文。

$(OBJECTS): $(SOURCES)具体是什么意思

例如,假设您的src/ 目录包含firstsource.csecondsource.c。那么你的变量就变成了

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

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

所以规则

$(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 $@

你能看出这里的问题吗?

由于$&lt;仅代表第一个先决条件,因此第一个规则的配方变为

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

第一个规则没问题,第二个规则就不行了

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

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

顺便说一句……你提到了

不幸的是 [i.e. make] 只对第一个检测到的文件执行命令并停在那里。

这是因为 - 当您在不带任何参数的情况下调用 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 $+

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

除此之外 - 模式规则的预定义配方

我理解将构建输出放在单独目录中的理由,但是 - 如果您愿意将构建输出与源文件放在同一目录中 - 您可以使用makepredefined pattern rules 简化您的Makefile。

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

compile: $(OBJECTS)

【讨论】:

  • 这个OBJECTS := $(SOURCES:.c=.o)会将对象放入SRC目录中。
  • @shawnhcorey:我知道 - 我在回答中提出了警告。我只是提出一种用户可以简化 Makefile 的方法如果他们愿意接受这个警告。
  • 天哪! 选项 1 起到了作用,但我不太了解那里发生了什么。我不知道我是否做对了,所以让我试着解释一下我从中理解了什么。 1.首先你写了一个规则,只要涉及的“目标”被调用,就可以最终执行? 2. 然后你编写一个规则(shell 命令,compile-only),指向OBJECTS 的先决条件列表 3. 然后,该规则迭代对象文件列表,应用前面所述的规则(对于@987654358 中的对象文件@文件夹)我的逻辑正确吗?而Makefile调用中的-B标志是什么意思?
  • @phtdacosta:-B 选项只是指示make 构建目标,即使它已经构建。关于你的其他观点......很难在评论中回应他们。如果时间允许,我将在未来几天内尝试更新我的答案。
  • @phtdacosta:我已经更新了答案,更详细地说明了$(OBJECTS): $(SOURCES) 的实际含义(以及为什么它不能按预期工作)。希望这有助于澄清而不是混淆!关于“选项 1”...您的逻辑基本上是正确的,是的。make 手册中的这些 few paragraphs 也可能有所帮助。
【解决方案2】:

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

all: $(OBJECTS)

【讨论】:

  • OP 的 Makefile 表明它们有一个单独的链接阶段目标 - 因此在这种情况下使用编译阶段的默认目标可能不起作用。此外,您展示的示例无法解决 OP 的主要问题 - 目标 $(OBJECTS): $(SOURCES) 没有达到他们的预期。
【解决方案3】:

使用$(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 $<

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多