Makefile-一次编译多个C文件

Makefile-一次编译多个C文件,makefile,gnu-make,Makefile,Gnu Make,这个问题与上一个问题不同,因为我有一个额外的要求:我想将所有对象文件重定向到一个单独的目录中 以下是设置: 我在一个目录中有多个源,比如src/mylib 我希望对象文件以build/mylib结束 还请注意,mylib下有子目录 第一次尝试如下: sources = $(shell find src/ -name ".c") objects_dirs = $(subst src/, build/, $(dir $(sources)) # This variable is used by the

这个问题与上一个问题不同,因为我有一个额外的要求:我想将所有对象文件重定向到一个单独的目录中

以下是设置:

我在一个目录中有多个源,比如
src/mylib

我希望对象文件以
build/mylib
结束
还请注意,
mylib
下有子目录

第一次尝试如下:

sources = $(shell find src/ -name ".c")
objects_dirs = $(subst src/, build/, $(dir $(sources)) # This variable is used by the build rule to create directories for objects files prior to compilation
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory

# This is where things aren't working as expected
$(objects): build $(sources)
    $(cc) $(cflags) -o $@ $(word 2, $^))

build:
    $(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))
对于上面的makefile,只生成了一个对象文件。我猜这可能与GCC一次只能生成一个对象文件有关。不管怎样,在
$(objects)
目标中检查
$@
$(word 2,$^)
的值表明,即使我有多个文件,也只考虑一个文件

因此,我将makefile更改为以下内容:

sources = $(shell find src/ -name ".c")
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory

# This works as expected but it appears to me like make is generating all the objects files even though source files did not change. This can be seen by checking the timestamps on new object files after running make again.
$(objects): build $(sources)
    $(foreach source, $(sources), $(shell $(cc) $(cflags) -o $(subst src/,build/, $(patsubst %.o,%.c,$(source))) $(source)))

build:
    $(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))
第二个makefile按预期工作,但对象文件正在重新生成,这与使用make的另一个目的背道而驰:仅重新编译上次编译时更改的源文件

因此我的问题是:如何一次在单独的目录中生成所有对象文件(我的意思是在一个规则中执行所有源文件的编译),同时确保如果源文件没有更改,则不应重新生成关联的对象文件

我不是在加速编译。我所寻求的是一个规则,它将生成所有对象文件,以便只重新编译更新的源文件。

最后一个makefile完成了这项工作,但是重新编译了所有源文件,这破坏了使用make的另一个目的:只应重新编译更改后的源文件

编辑 在阅读评论之后,我似乎没有正确地表达我的问题。由于我所拥有的细节已经呈现,我保留问题,并在下面提供更多细节

上面源代码中的第二个makefile确实有效。但它只完成了一半的工作。
build
目录有效地镜像了
src
目录。
因此,如果我将一个文件命名为
src/mylib/point/point.c
,则生成
build/mylib/point/point.o
。这是第一部分。
第二部分是,如果
point.c
没有更改,
build/mylib/point/
目录中的
point.o
不能重新生成。但是在检查了对象文件上的时间戳之后,我可以看出在再次运行
make
之后,新的对象文件替换了旧的对象文件。这并不好,因为对于大型项目,编译时间仍然是
O(n)
,而
n
是要编译的源文件数

因此,这个问题是关于如何在不重新生成对象文件的情况下保留第二个makefile。

从我从评论中收集到的信息来看,我对
make
的要求太高了。但是,如果有人知道如何实现这一点,我就不提这个问题了。

如果您只需要一条规则来处理所有对象文件,而不必“一次编译”,那么您就可以有如下功能:

BUILD_DIR = build
SOURCES = ...
TARGET  = ...
OBJECTS = $(SOURCES:%.c=$(BUILD_DIR)/%.o)

default: target

target: $(TARGET)

$(TARGET): $(OBJECTS)
    $(LD) -o $@ $(LDFLAGS) $^ $(LIBS)

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

$(BUILD_DIR):
    -mkdir $@
BUILD\u DIR=BUILD
来源=。。。
目标=。。。
对象=$(源:%.c=$(构建目录)/%.o)
默认值:target
目标:$(目标)
$(目标):$(对象)
$(LD)-o$@$(LDFLAGS)$^$(LIBS)
$(构建目录)/%.o:%.c
$(CC)-c-o$@$<$(CFLAGS)
$(构建目录):
-mkdir$@

[注意:这是从内存中编写的,无需测试。]

如果您只需要一条规则来处理所有对象文件,而不必“一次编译所有对象”,那么您可以使用以下内容:

BUILD_DIR = build
SOURCES = ...
TARGET  = ...
OBJECTS = $(SOURCES:%.c=$(BUILD_DIR)/%.o)

default: target

target: $(TARGET)

$(TARGET): $(OBJECTS)
    $(LD) -o $@ $(LDFLAGS) $^ $(LIBS)

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

$(BUILD_DIR):
    -mkdir $@
BUILD\u DIR=BUILD
来源=。。。
目标=。。。
对象=$(源:%.c=$(构建目录)/%.o)
默认值:target
目标:$(目标)
$(目标):$(对象)
$(LD)-o$@$(LDFLAGS)$^$(LIBS)
$(构建目录)/%.o:%.c
$(CC)-c-o$@$<$(CFLAGS)
$(构建目录):
-mkdir$@

[注意:这是从内存编写的,没有经过测试。]

再次阅读GNU make手册后,这里有一个解决第二个问题的解决方案

第一次尝试是正确的路径。第二次尝试在先决条件中有
$(sources)
,但在命令中没有使用它,这很愚蠢

因此工作的makefile如下。它将对象文件放在一个单独的目录中,并且只编译已更改的文件

sources = $(shell find src/ -name ".c")
$objects_dirs = $(subst src/, build/, $(dir $(sources)) # This variable is used by the build rule to create directories for objects files prior to compilation
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory

# This should now work as expected: object files go into their designated directories under "build/" and only updated files will be recompiled.
$(objects): build $(sources)
# After running say "make clean", make will figure out the need to run the first prerequisite.
# If we are doing a clean build, the number of prerequisites will equal the number of new prerequisites.
ifeq ($(words $?), $(words $^))
    # Note the use of "$?" instead of "$^". $? is used since it holds prerequisites that are newer than the target while $^ will holds all prerequisites whether they are new or not.
    $(foreach source, $(wordlist 2, $(words $?), $?), $(shell $(cc) $(cflags) -o $(subst src/,build, $(patsubst %.c,%.o, $(source))) $(source)))
else
    # If we have a few new targets, no need to exclude "build" from prerequisites because the first prerequisite will be a file that changed.
    $(foreach source, $?, $(shell $(cc) $(cflags) -o $(subst src/,build, $(patsubst %.c,%.o, $(source))) $(source)))
endif

.PHONY: build
build:
    $(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))

.PHONY: clean
clean:
    @rm -rf build/
makefile被大量注释,其中包含使其工作的更改。最重要的变化是:

  • 使用
    $(foreach)
    按照GCC的要求单独编译每个文件
  • 使用
    $?
    仅处理比目标更新的先决条件
  • 使用conditional可检测第一个先决条件是否已根据情况更改。如果我们有一个干净的构建(第一次运行
    make
    ,或者在运行
    make clean
    )后运行),更新的先决条件的数量将与与目标相比的更新的先决条件的数量相同。换句话说,
    $(words$?)==$(words$^)
    将为真。因此,我们使用这一事实从要传递给GCC的文件列表中排除列出的第一个先决条件(
    build
另外,在从对象文件构建可执行文件时,请确保在选择先决条件时使用
$^
而不是
$?
,否则,可执行文件中将只包含较新的文件,并且不会运行

target = bin/mylib.a

.PHONY: all
all: $(target)

$(target): $(objects)
    ar -cvq $@ $^ # Notice that we're not using $? else only updated object files will end up in the archive.

在再次阅读GNU make手册之后,这里有一个解决第二个问题的解决方案

第一次尝试是正确的路径。第二次尝试在先决条件中有
$(sources)
,但在命令中没有使用它,这很愚蠢

因此工作的makefile如下。它将对象文件放在一个单独的目录中,并且只编译
all:
clean:

src_root := src
src_subdirs := foo foo/bar foo/bar/buz
build_root := build

o_suffix := .o

# Build list of sources. Iterate every subfolder from $(src_subdirs) list 
# and fetch all existing files with suffixes matching the list.
source_suffixes := .c .cpp .cxx
sources := $(foreach d,$(addprefix $(src_root)/,$(src_subdirs)),$(wildcard $(addprefix $d/*,$(source_suffixes))))

# If src_subdirs make variable is unset, use 'find' command to build list of sources.
# Note that we use the same list of suffixes but tweak them for use with 'find'
ifeq ($(src_subdirs),)
  sources := $(shell find $(src_root) -type f $(foreach s,$(source_suffixes),$(if $(findstring $s,$(firstword $(source_suffixes))),,-o) -name '*$s'))
endif

$(info sources=$(sources))

# Build source -> object file mapping.
# We want map $(src_root) -> $(build_root) and copy directory structure 
# of source tree but populated with object files.
objects := $(addsuffix $(o_suffix),$(basename $(patsubst $(src_root)%,$(build_root)%,$(sources))))
$(info objects=$(objects))

# Generate rules for every .o file to depend exactly on corresponding source file.
$(foreach s,$(sources),$(foreach o,$(filter %$(basename $(notdir $s)).o,$(objects)),$(info New rule: $o: $s)$(eval $o: $s)))

# This is how we compile sources:
# First check if directory for the target file exists. 
# If it doesn't run 'mkdir' command.
$(objects): ; $(if $(wildcard $(@D)),,mkdir -p $(@D) &&) g++ -c $< -o $@

# Compile all sources.
all: $(objects)
clean: ; rm -rf $(build_root)

.PHONY: clean all
$ find
.
./src
./src/foo
./src/foo/bar
./src/foo/bar/bar.cxx
./src/foo/bar/buz
./src/foo/bar/buz/buz.c
./src/foo/bar/foo.c
./src/foo/foo.cpp
$ make -f /cygdrive/c/stackoverflow/Makefile.sample -j
sources=src/foo/bar/bar.cxx src/foo/bar/buz/buz.c src/foo/bar/foo.c src/foo/foo.cpp
objects=build/foo/bar/bar.o build/foo/bar/buz/buz.o build/foo/bar/foo.o build/foo/foo.o
New rule: build/foo/bar/bar.o: src/foo/bar/bar.cxx
New rule: build/foo/bar/buz/buz.o: src/foo/bar/buz/buz.c
New rule: build/foo/bar/foo.o: src/foo/bar/foo.c
New rule: build/foo/foo.o: src/foo/bar/foo.c
New rule: build/foo/bar/foo.o: src/foo/foo.cpp
New rule: build/foo/foo.o: src/foo/foo.cpp
mkdir -p build/foo/bar && g++ -c src/foo/bar/bar.cxx -o build/foo/bar/bar.o
mkdir -p build/foo/bar/buz && g++ -c src/foo/bar/buz/buz.c -o build/foo/bar/buz/buz.o
mkdir -p build/foo/bar && g++ -c src/foo/bar/foo.c -o build/foo/bar/foo.o
mkdir -p build/foo && g++ -c src/foo/bar/foo.c -o build/foo/foo.o
$ find
.
./build
./build/foo
./build/foo/bar
./build/foo/bar/bar.o
./build/foo/bar/buz
./build/foo/bar/buz/buz.o
./build/foo/bar/foo.o
./build/foo/foo.o
./src
./src/foo
./src/foo/bar
./src/foo/bar/bar.cxx
./src/foo/bar/buz
./src/foo/bar/buz/buz.c
./src/foo/bar/foo.c
./src/foo/foo.cpp