如何在GNU make中将目标设为“私有”仅供内部使用?或者:如何最好地执行特定于目标的变量值?

时间:2014-09-26 16:09:57

标签: makefile gnu-make

我在makefile中有一些辅助目标,我想在makefile中限制内部或“私人”使用(仅限)。也就是说,我希望能够在makefile中将这些目标指定为依赖项,但我想阻止将目标从命令行指定为构建目标。有点类似于OOP中的private函数:目标是单独构建的有害(或根本没有意义)。

我希望有一个特殊目标.HIDDEN.PRIVATE或类似的东西,类似于.PHONY对非文件目标的作用,但我不认为这存在。 private关键字仅适用于变量。

保护目标仅供内部/私人使用的优秀/一般/优雅方式是什么?

我能提出的最佳解决方法是检查$(MAKECMDGOALS)是否有“不可接受的”目标,如果指定则会出错;这似乎不优雅。我确信makefile可以被重写以避免这种情况 - 也许是一个更好的解决方案 - 但这在这里不实用。


在切割线下面......这是一个人为的例子。

虽然我正在寻找一般解决方案,但作为个人/主要目标有害的目标的一个例子是继承特定于目标的变量值:

override CFLAGS += -Wall
all : debug
%.o : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
debug   : CFLAGS += -g3 -O0
release : CFLAGS +=     -O3
debug   : CPPFLAGS += -DDEBUG
release : CPPFLAGS += -DRELEASE
debug release : foo.o bar.o main.o
    $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
    -rm -f *.o debug release
.PHONY: all clean

隐含规则重复(不必要)用于说明。以debugrelease为目标,foo.o和其他人将继承相应的CFLAGSCPPFLAGS - 如果有人make clean debug所有对象将始终如一。但是,例如,如果有人单独构建foo.o,它将无法继承适当的标志;例如,make clean foo.o debug您将使用默认foo.o构建CFLAGS;然后在构建debug时不需要更新它,因此它将与具有不同优化或不同宏设置的其他对象链接。它可能适用于这种情况,但它不是预期的。将foo.o等标记为非法目标会阻止这种情况。


编辑:

很明显,我的例子(上面)对于我的更一般的问题不是一个好的选择:隐藏目标不是解决我的示例问题的最佳方法。这是一个修改过的示例,说明了修改后的问题“如何强制执行特定于目标的值?” - 它建立在@Michael,@ Bed,@ Ross下面的评论的基础上,并允许冒充并回答这个更有限的场景。

如下面的回答所述,在这种情况下,更好的想法是创建在不同位置具有不同构建标志的对象。例如,

bin_debug/%.o   : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
bin_release/%.o : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
DEBUG_OBJS   = $(addprefix bin_debug/,$OBJS)
RELEASE_OBJS = $(addprefix bin_release/,$OBJS)
debug   : $(DEBUG_OBJS)
release : $(RELEASE_OBJS)
debug release :
    $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)

模式规则重复,因为我认为它必须是(多个“模式目标”(%)说服make所有目标都是使用一个配方同时构建的;请参阅SO问题thisthis)。

现在,添加特定于目标的标志:

debug   : CPPFLAGS += -DDEBUG
release : CPPFLAGS += -DRELEASE

但这仍然受到影响:

make bin_debug/foo.o

不会从CPPFLAGS获取debug。我接受了@ Michael的答案,因为它让我以更有帮助的方式思考这个问题,但也回答了我自己的一些修辞问题。

5 个答案:

答案 0 :(得分:10)

你试图解决的问题是合法的,但你正朝着更糟糕的方向前进去解决它。

声明私人目标没有任何意义

当我们编写Makefile时,我们正在根据目标,来源和配方来描述编译工作。这项工作的进展由已经建立的一组目标来描述。现在你准确地观察了序列

make clean
make foo.o
make debug

将生成格式与foo.o不一致的对象,从而使您的构建目录处于不一致状态。但是推断用户不应该明确地构造foo.o是非常错误的。请考虑以下顺序:

make clean
# Wait for foo.o being compiles and
#  interrupt the build job with a signal
make debug

由于make看到foo.o它将恢复其所在的任务,并且在编译具有不同标志的后续单元时保持foo.o不变,使构建目录保持与在第一个场景中。

因此,如果我们可以在Makefile中实现私有目标,那么这将是无效的并且可能传达错误的安全感,这甚至比不安全感本身更糟糕。您想象的解决方案也消除了使用Makefiles而不是shell脚本的最重要优势之一: Make可以轻松地继续执行中断任务。

我记录了使用Makefiles与my answer to the question “What is the purpose of linking object files separately in a Makefile?”中已构建的目标集相关的其他一些方面。

您问题的另一种解决方案

为了解决编译标志不一致的问题,我们可以安排将构建的目标存储到特殊目录中,具体取决于使用的编译标志。实现这一点可以解决问题,而不会强迫我们在恢复中断的编译工作时轻松辞职。

以下是实施路线图:

  1. 识别构建配置文件,您可以releasebuild
  2. 选择要用于每个构建配置文件的编译。
  3. 选择存储每个构建配置文件的构建目标的目录。
  4. 编写Makefile,以便将构建的目标存储在您选择的目录中。请参阅Gnu make - how to get object files in separate subdirectory
  5. 注意。在我看来,make的BSD变体对于在特殊目录中编写目标有 更好的支持,请参阅{{3} }。一般来说,我更喜欢make的BSD变体,因为它的文档很简短,而且它有很多有用的高级示例,因为在BSD世界中构建的操作系统构建和端口是由该程序编排的。

答案 1 :(得分:6)

你可以通过用两个连字符开头来定义私人目标。

--private-target:
    @echo private

public-target: --private-target
    @echo public

您可以致电make public-target,但make --private-target会抱怨未知选项:

$ make public-target
private
public

$ make --private-target
/Library/Developer/CommandLineTools/usr/bin/make: unrecognized option `--private-target'
$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0

答案 2 :(得分:2)

我认为没有任何“优雅”的方式让目标以某种方式私有化。我认为唯一可以称为优雅的解决方案是重写你的makefile,以便用户调用的目标无关紧要,正如Beta建议的那样。它还具有使makefile更易于维护和易于理解的优势。

将目标设置为“私有”的不那么优雅但相当简单的方法是将makefile重命名为默认名称之外的其他名称。然后在其中放置一个新的makefile,调用“private”makefile来完成它的工作。类似的东西:

.SUFFIXES:

PUBLIC_TARGETS = all debug release clean
REAL_MAKEFILE = private.mak

define invoke_make
 $(1): $(REAL_MAKEFILE)
    $(MAKE) -f $(REAL_MAKEFILE) $(1)
endef

$(foreach target, $(PUBLIC_TARGETS), $(eval $(call invoke_make,$(target))))

.PHONY: $(PUBLIC_TARGETS)

显然,这并不能阻止确定的用户调用“私有”目标,但希望它能说明他们不应该这样做。无论如何,这都是面向对象语言中私有化的东西。足够坚定的用户总是可以绕过它。

答案 3 :(得分:2)

问题的一个解决方案是将CPPFLAGS迁移到模式规则(例如bin_debug/%.o: CPPFLAGS...)而不是常规规则(debug: CPPFLAGS...),最终结果:

bin_debug/%.o   : CPPFLAGS += -DDEBUG
bin_debug/%.o   : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
bin_release/%.o : CPPFLAGS += -DRELEASE
bin_release/%.o : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
DEBUG_OBJS   = $(addprefix bin_debug/,$OBJS)
RELEASE_OBJS = $(addprefix bin_release/,$OBJS)
debug   : $(DEBUG_OBJS)
release : $(RELEASE_OBJS)
debug release :
    $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)

因此make bin_debug/foo.oCPPFLAGS包括-DDEBUG

现在,假设您有&gt;&gt; 2个规则:debug,release,config01,config02,config03,...每个都有自己的CPPFLAGS

一种方法可能是继续重复所有模式规则,但如果有任何必须改变,这会很烦人。此外,它不可能在foreach中使用。这看起来很方便:

debug    : CPPFLAGS+=-DDEBUG
release  : CPPFLAGS+=-DRELEASE
config01 : CPPFLAGS+=-DSOMETHING
config02 : CPPFLAGS+=-DSOMETHINGELSE
TARGETS = debug release config01 config02
OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
define TARGET_template
 bin_$(1)/%.o : %.c
    $$(CC) $$(CFLAGS) $$(CPPFLAGS) -c -o $@ $<
 $(1): $(addprefix bin_$(1)/,$(OBJS))
 # other TARGET-specific stuff here
endef
$(foreach tgt,$(TARGETS),$(eval $(call TARGET_template,$(tgt))))

但仍然没有解决make bin_debug/foo.o的情况 - 仍然没有得到CPPFLAGS

因此,您可以拥有一个特定于目标的变量,而不是像debug: CPPFLAGS+=...那样创建特定于目标的变量值,而不是{{1然后添加到每个规则:

CPPFLAGS_debug

当心;以上可能需要更多CPPFLAGS_debug = -DDEBUG CPPFLAGS_release = -DRELEASE CPPFLAGS_config01 = -DSOMETHING CPPFLAGS_config02 = -DSOMETHINGELSE TARGETS = debug release config01 config02 OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c) define TARGET_template bin_$(1)/%.o : CPPFLAGS+=$$(CPPFLAGS_$(1)) bin_$(1)/%.o : %.c $$(CC) $$(CFLAGS) $$(CPPFLAGS) -c -o $$@ $$< $(1): $(addprefix bin_$(1)/,$(OBJS)) # other TARGET-specific stuff here endef $(foreach tgt,$(TARGETS),$(eval $(call TARGET_template,$(tgt)))) s,未经测试。

有问题吗?更好的方式?

答案 4 :(得分:0)

考虑到这一点并尝试以下方法:

TEST := $(shell echo $$RANDOM)

test : $(TEST)

$(TEST):
<tab>@echo tada $(TEST)

然后在命令行上执行make test似乎工作,我认为如果不使用测试目标就很难得到结果。也许这条路可以帮忙吗?