C语言 如何在Makefile中管理目录

jmp7cifd  于 2023-04-11  发布在  其他
关注(0)|答案(1)|浏览(118)

下面的Makefile应该构建一个简单的项目

CC = gcc
INCDIRS = -Iinclude
CFLAGS = -Wno-implicit-function-declaration -g $(INCDIRS)

CFILES = char_list.c int_list.c main.c
OBJFILES = char_list.o int_list.o main.o

BINARY = main

BINDIR = bin/
OBJDIR = obj/

all: $(BINARY)

$(BINARY): $(OBJFILES)
    $(CC) -o $(TARGETDIR)$@ $(OBJDIR)$^

%.o: %.c
    $(CC) $(CFLAGS) -c -o $(OBJDIR)$@ $^

clean:
    @rm -rf $(BINARY) $(OBJFILES)

我刚刚开始第一次使用Makefile,所以在尝试更改项目文件夹结构并使其与Makefile一起工作时遇到了问题(文件夹结构在最后)。当我构建二进制文件时,规则要求所有.o,如图所示,但是因为我想更好地组织我的工作空间,所以我选择将所有.o文件放在obj文件夹中。(OBJDIR)会将其应用于每个OBJFILE,但我现在知道这不会发生,因为$(OBJDIR)和$^被类似地处理为字符串,所以这个命令只是将两者连接起来。
我怎样才能使OBJFILES中的每个单词都有正确的前缀?

.
├── bin
├── char_list.c
├── include
│   ├── char_list.h
│   └── int_list.h
├── int_list.c
├── main.c
├── Makefile
├── obj
│   ├── char_list.o
│   ├── int_list.o
│   └── main.o
└── parser_pseudo.txt

这就是错误,正如所说,只有第一个。o得到前缀

gcc -Wno-implicit-function-declaration -g -Iinclude -c -o obj/char_list.o char_list.c
gcc -Wno-implicit-function-declaration -g -Iinclude -c -o obj/int_list.o int_list.c
gcc -Wno-implicit-function-declaration -g -Iinclude -c -o obj/main.o main.c
gcc -o main obj/char_list.o int_list.o main.o
/usr/bin/ld: cannot find int_list.o: No such file or directory
/usr/bin/ld: cannot find main.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:16: main] Error 1
fcg9iug3

fcg9iug31#

首先,Makefile只做字符串连接。所以当你这样做时:

OBJFILES = char_list.o int_list.o main.o
OBJDIR = obj/

.PHONY: all

all:
    @echo $(OBJDIR)$(OBJFILES)

它将打印以下内容:

obj/char_list.o int_list.o main.o

为了向列表添加前缀,可以像这样使用$(addprefix prefix,names...)

OBJFILES = char_list.o int_list.o main.o
OBJDIR = obj/

.PHONY: all

all:
    @echo $(addprefix $(OBJDIR),$(OBJFILES))

但是第二,你的规则的目标和依赖项不再准确,因为你在命令中改变了输出文件的路径,但没有调整规则的其余部分。所以一般来说,我建议你在变量中已经有了完整的文件的实际路径。为了保存大量的输入和重复,你可以使用$(patsubst pattern,replacement,words...)

OBJDIR   := obj
CFILES   := char_list.c int_list.c main.c
OBJFILES := $(patsubst %.c,$(OBJDIR)/%.o,$(CFILES))

.PHONY: all

all:
    @echo $(OBJFILES)

假设您实际上希望所有C文件都在项目根目录中,那么您也可以使用$(wildcard *.c),而不是手动列出C文件。
我在这里做了两个修改:
1.使用:=而不是=。这意味着在赋值时而不是使用时计算右侧。对于常规的=,如果您在Makefile中写入$(OBJFILES) 20次,则$(patsubst ...)将执行20次,而:=只执行一次。IMO你应该在你定义的变量是最终的,在整个构建过程中任何时候都不会改变,并且在定义之前没有引用的时候使用它。

  1. .PHONY: all的使用。由于./all不是将被创建的文件,因此您应该通知make情况是这样的。从这里开始,我将使用.PHONY: all clean
    我想做的另一个改变是文件夹的创建和删除。原因很简单,就是git。如果你在一个git项目中创建了一个空文件夹obj/,那么你就没有办法提交它。你可以在里面创建一个占位符文件,但那很难看。相反,你可以修改%.o规则为$(OBJDIR)/%.o,并通过在末尾追加| $(OBJDIR)来添加一个仅订单依赖。然后你需要的就是一个构建规则,如下所示:
$(OBJDIR):
    @mkdir -p $@

现在,既然你已经在Makefile中声明了BINDIR,但在构建规则中使用了$(TARGETDIR),我假设你也希望你的二进制文件进入一个文件夹。所以总而言之,我会像这样重写你的Makefile:

CC       := gcc
INCDIRS  := -Iinclude
CFLAGS   := -Wno-implicit-function-declaration -g $(INCDIRS)

BINDIR   := bin
OBJDIR   := obj

BINARY   := $(BINDIR)/main

CFILES   := $(wildcard *.c)
OBJFILES := $(patsubst %.c,$(OBJDIR)/%.o,$(CFILES))

.PHONY: all clean

all: $(BINARY)

$(BINARY): $(OBJFILES) | $(BINDIR)
    $(CC) -o $@ $^

$(OBJDIR)/%.o: %.c | $(OBJDIR)
    $(CC) $(CFLAGS) -c -o $@ $<

$(BINDIR) $(OBJDIR):
    @mkdir -p $@

clean:
    @rm -rf $(BINDIR) $(OBJDIR)

注意:Stack Overflow的Markdown渲染器会将所有制表符转换为空格,因此如果您复制任何内容,请确保将它们转换回来。

相关问题