gnu make - 用于保持已安装的文件版本与文件的主版本

时间:2016-07-06 11:33:59

标签: makefile gnu

所以这是一个Makefile来安装foo.conf,基于一个名为foo.conf.master的主副本。它将它安装到当前目录而不是/ etc,仅用于测试目的:

all: foo.conf.copied

foo.conf.copied: foo.conf.master foo.conf
        cp foo.conf.master foo.conf
        touch $@

#  Recipe to tell make that it is okay for foo.conf not to exist beforehand. 
foo.conf:

然后创建foo.conf.master:

$ touch foo.conf.master
$ 

你已经准备好测试了:

$ make
cp foo.conf.master foo.conf
touch foo.conf.copied
$ 

重点是,如果我(使用我的“可信”系统管理员帽子)修改foo.conf.master然后make(可能由cron调用)将推出更新:

$ touch foo.conf.master
$ make
cp foo.conf.master foo.conf
touch foo.conf.copied
$ 

但同样重要的是:如果我(使用我的“流氓”系统管理员帽子)修改已安装的版本,那么make将退出更新:

$ touch foo.conf
$ make
cp foo.conf.master foo.conf
touch foo.conf.copied
$ 

哇噢。

好的,现在问题是:显然foo.conf并不是我想要的唯一文件,因此我需要将静态规则更改为模式规则。好的,这很简单:在目标和依赖项中用%替换foo.conf,在命令中用$替换foo.conf,然后对最后一个配方(否则只会变成'%:')做一个小修改,这样就可以了看起来我不想取消内置模式规则。

所以清理并创建这个Makefile:

all: foo.conf.copied

%.copied: %.master %
        cp $*.master $*
        touch $@

#  Recipe to tell make that it is okay for foo.conf not to exist beforehand. 
#  Nop tells make that I'm not *cancelling* a pattern rule here
#  (see http://stackoverflow.com/questions/34315150/make-implicit-rules-dont-work-without-command).
%: ;

但这不起作用:

$ make
make: *** No rule to make target `foo.conf.copied', needed by `all'.  Stop.
$ 

错误消息具有误导性;它真的是foo.conf,它不知道如何制作,这可以通过在Makefile底部添加以下内容来证明:

foo.conf:
        touch $@

但那时再次是静态规则,我不想要。

还有一些我想要满足的要求,上面的例子没有说明。这些是:

  • foo.conf应该可以安装在文件系统的任何地方(例如/etc/foo/server/foo.conf)
  • foo.conf.master应位于所有主版本的中央目录或其子目录中,最好不要使用'.master'扩展名(例如〜/ poor-mans-puppet / master-files / etc / foo / foo.conf)
  • foo.conf.copied应该在一个中心目录中,而不是在与foo.conf相同的目录中(例如〜/ poor-mans-puppet / timestamp-files / etc / foo / foo.conf)
经过多次谷歌搜索,拔毛,我在这里问!有什么想法吗? (PS:如果从这里复制Makefile,请记得将缩进更改回标签。)

下面的疯狂科学家提出了一个优雅的静态规则,但我真的需要它作为一个模式规则。原因是我需要在使用规则时挂钩额外的依赖关系:

all: <new-dependency>

而不是使用变量挂钩:

STUFF_ALL_SHOULD_DEPEND_ON += <new-dependency>

此要求的原因是为了与我的大型Makefile中处理其他(非复制的)目标的方式保持一致。

然而,根据Mad Scientist的想法,我尝试了以下,但没有奏效,但也许有人帮助我:

all: foo.conf.copied

%.copied: %.master %
    $(eval FILES_FOR_WHICH_AN_EMPTY_RECIPE_ARE_NEEDED += $$*)
    cp $*.master $*
    touch $@

define GENERATE_STATIC_EMPTY_RULE
$(1):
endef

$(foreach X,$(FILES_FOR_WHICH_AN_EMPTY_RECIPE_ARE_NEEDED),$(eval $(call GENERATE_STATIC_EMPTY_RULE,$(X))))

2 个答案:

答案 0 :(得分:0)

你能解释为什么你要使用这个额外的“.copied”文件吗?你为什么不用它:

%: %.master ; cp $< $@

无论如何,你正在违反make与match-anything规则相关的特殊规则(像%那样可以构建所有内容的模式规则)。如果你改变你的模式,那么它就不匹配 - 比如%.conf: ;,那么它就可以了。但是,您可能不希望假设所有文件都以.conf结尾。

或者,您可以使用静态模式规则,如下所示:

FILES_TO_COPY = foo.conf bar.conf biz.baz

all: $(FILES_TO_COPY:%=%.copied)

$(FILES_TO_COPY:%=%.copied): %.copied : %.master %
        cp $*.master $*
        touch $@

并且您不需要额外的模式规则。

答案 1 :(得分:0)

最后,我动态生成了静态规则。以下伪代码有望使实际的Makefile更容易理解:

if flag not set                   #  flag won't be set on first call
    prepare static rules
    set flag                      #  prevent running this clause again
    recurse!                      #  make invokes make
else
    include static rules
    do the normal thing
endif

这是真正的Makefile:

ifeq ($(MAKELEVEL),0)
all:
        for X in $(patsubst %.copied,%,$^); do \
            echo "$$X.copied: $$X.master $$X"; \
            echo "      cp $$X.master $$X"; \
            echo "      touch \$$@"; \
            echo "$$X: ;"; \
        done > Makefile.include
        $(MAKE)

#  The real dependencies on all are defined below, but while creating
#  Makefile.include, we don't want make to digress and start making
#  the dependencies; this pattern rule will stop it from doing that.
%.copied: ;
else
include Makefile.include
endif

all: foo.conf.copied

使用.SILENT--no-print-directory选项(为清晰起见,上面未显示)可以使外部品牌保持沉默。

这是输出:

$ touch foo.conf.master
$ make
cp foo.conf.master foo.conf
touch foo.conf.copied
$ touch foo.conf
$ make
cp foo.conf.master foo.conf
touch foo.conf.copied
$