如何在Makefile中针对特定目标使用"include"指令

22

我希望只针对特定目标使用include指令,不需要时不想运行其他makefiles,因为这意味着无谓地生成了makefiles。

所以,有没有一种可以根据目标条件地使用include指令的方法?或者将include指令作为目标的先决条件。

以下是我目前的代码:

# Flags

INCDIR = $(CURDIR)/include
CFLAGS = -Wall -Wno-overflow -Wno-uninitialized -pedantic -std=c99 -I$(INCDIR) -O3
LFLAGS = -flat_namespace -dynamiclib -undefined dynamic_lookup

# Directory names

# Set vpath search paths

vpath %.h include
vpath %.c src
vpath %.o build
vpath %.d build

# Get files for the core library

CORE_FILES = $(wildcard src/*.c)
CORE_OBJS = $(patsubst src/%.c, build/%.o, $(CORE_FILES))
CORE_DEPS = $(CORE_OBJS:.o=.d)

# Core library target linking

core : $(CORE_OBJS) | bin
    $(CC) $(LFLAGS) -o bin/libcbitcoin.2.0.dylib $(CORE_OBJS)

# Include header prerequisites (How to do only for "core" target?)

include $(CORE_DEPS)

# Makefiles for header dependencies. 

$(CORE_DEPS): build/%.d: src/%.c | build
    rm -f $@; \
    $(CC) -I$(INCDIR) -MM $< -MT '$(@:.d=.o) $@' > $@

# Objects depend on directory

$(CORE_OBS) : | build

# Create build directory

build:
    mkdir build

# Create bin directory

bin:
    mkdir bin

# Core Compilation

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@

# Depencies require include/CBDependencies.h as a prerequisite

build/CBOpenSSLCrypto.o: include/CBDependencies.h

# Crypto library target linking

crypto : build/CBOpenSSLCrypto.o -lcrypto -lssl | bin
    $(CC) $(LFLAGS) -o bin/libcbitcoin-crypto.2.0.dylib build/CBOpenSSLCrypto.o -lcrypto -lssl

# Crypto library compile

build/CBOpenSSLCrypto.o: dependencies/crypto/CBOpenSSLCrypto.c
    $(CC) -c $(CFLAGS) $< -o $@

#Clean

clean:
    rm -f $(CORE_OBJS) $(CORE_DEPS) build/CBOpenSSLCrypto.o

正如你所能看到的,对于“crypto”,我不需要包括“.d”文件,但是对于“core”(默认目标),我需要。

感谢任何帮助。


可以这样做,但你不应该。这违背了Make的本意,并且有更好的方法。你想要快速简单的方法还是高级强大的方法? - Beta
1
那我可以问一下:什么是“make的粒度”和这些“更好的方法”?只要易于维护即可。这就是我想要的。我不想手动添加头文件先决条件。 - Matthew Mitchell
3个回答

21

Make不是一种过程式语言,因此将其视为一种会逆反常规;你的makefiles将很难扩展,并且可能导致难以察觉的错误。

Tom Tromey有一种更好的方法,它干净、高效且可扩展。诀窍在于意识到你可以在生成目标文件的同一步骤中构建依赖文件。这些依赖关系只是告诉Make何时重建对象;当你第一次构建对象时,不需要它们,因为Make知道必须构建该对象。如果依赖性发生变化,那么只能是源代码或旧依赖项中的某个东西发生了变化,因此Make还是知道必须重建对象。(这并不明显,所以可能需要一点思考。)

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<

-include build/*.d

还有一个问题:如果你修改代码以删除某个依赖项,并同时删除该文件,那么你将无法重新构建,因为旧的依赖列表仍然需要一个找不到的文件。解决方案是处理依赖文件,使每个前提条件(例如标头)都成为自己的目标,没有任何命令,这样可以在需要时假定其已重建:

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<
    @cp build/$*.d build/$*.P
    @sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
            -e '/^$$/ d' -e 's/$$/ :/' < build/$*.P >> build/$*.d;
    @rm build/$*.P

一种不太精细但几乎同样可靠的方法是为标题和来源设置通配规则:

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<

%.cc %.h:

编辑:

解释新命令:

-MM 选项告诉gcc生成一个make规则的对象文件,而不是预处理或编译。默认情况下,将规则发送到将预处理输出发送到的位置,通常是标准输出。

-MF 选项与-MM一起使用,指定输出文件。因此,-MM -MF build/$*.d会将规则放在我们想要的位置。

因此,以下两个命令(几乎总是)等效:

    $(CC) -MM -MF build/$*.d $<

    $(CC) -MM $< > build/$*.d

(我省略了-I$(...)和使用-MMD选项的可能性,因为两者都有点复杂,并不是问题的重点。)


好的,谢谢你。我不得不将 $(CC) -MM -MF build/$*.d $< 更改为 $(CC) -I$(INCDIR) -MM -MF build/$*.d $< > build/$*.d 才能使其正常工作,但在我的系统上,MF标志不受支持。我应该怎么做来替换MF的功能? - Matthew Mitchell
1
请注意,现在 -MP 选项允许使用 sed 跳过最后一步。 - Norswap
@Norswap:是的,好事成双。现在我可以推荐一个更简单的Makefile了(http://stackoverflow.com/questions/21871426/example-makefile-for-building-simple-c-project-recompiling-when-headers-change/21873648#21873648)。 - Beta
@Beta:你能否解释一下那个可怕的长 sed 命令是做什么的 :) - bhavesh
@Beta:我尝试使用“cruder”方法。虽然它能够生成依赖关系,但当头文件更改时无法重新编译。 - bhavesh
显示剩余3条评论

3
您可以使用MAKECMDGOALS。
ifeq (core,$(MAKECMDGOALS))
include $(CORE_DEPS)
endif

如果有多个目标,当然可以使用ifneq (,$(findstring core,$(MAKECMDGOALS)))

注意:这是一种“快速而肮脏”的解决方案--我同意Beta的看法,你不应该把它变成常规做法(如果在很多makefile中都这样做,可能会变得混乱)。

John


谢谢。我猜这是一种方法,但Beta的替代方法似乎更理想。我猜使用automake也可以实现类似的目标。 - Matthew Mitchell

1

我无法遵守好答案的准则。

在我看来,对于原始问题的回答是:不,您不能包含依赖于目标的规则 - 所有规则都在考虑目标之前处理。这是make的限制(我猜)。但是,确实有MAKECMDGOALS,但这不仅仅是make实用程序本身的一个hack吗?

Beta的答案足够合理和正统,但即使它是最好的解决方案,也不能称其为干净的解决方案。如果make尚未处理特定目标并且适当的build / * .d依赖项文件不存在,则它将无法工作。


2
你误解了。我所描述的方法(我并没有发明)在Make尚未构建目标且相应的.d文件不存在时完全有效。 - Beta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接