当mkdir -p不可用时,在makefile中如何创建目录?

8

我有一个Makefile,它会做通常的目录创建:

$(Release_target_OBJDIR)/%.o: %.cpp
     mkdir -p $(dir $@)
     $(COMPILE.cpp) $< $(CFLAGS) $(INCLUDES) -o $@

很不幸,当我在scratchbox2下运行时,mkdir -p命令总是悄无声息地失败。
我尝试了以下方法,但没有成功:
$(Release_target_OBJDIR)/%.o: %.cpp
    mkdir $(dir $(dir $(dir $@)))
    mkdir $(dir $(dir $@))
    mkdir $(dir $@)
    $(COMPILE.cpp) $< $(CFLAGS) $(INCLUDES) -o $@

这将输出:

mkdir -p /home/foo/projects/htc/arm/obj/cbar/release/                  
mkdir -p /home/foo/projects/htc/arm/obj/cbar/release/                  
mkdir -p /home/foo/projects/htc/arm/obj/cbar/release/  

...在我想要的方式中,尾随斜杠防止dir函数剥离最后一个目录。

除了编写脚本或小型C应用程序来复制“-p”功能外,是否有任何想法可以在makefile中创建子目录?

如果没有“-p”选项,当makefile尝试创建已经存在的目录时,mkdir将会出现错误。我可以执行mkdir blah 2> /dev/null,但这样会有丢失其他错误消息的风险。

是否有人对为什么mkdir -p在scratchbox2下不起作用有想法?

编辑

根据bobbogo的建议,我整理了这个代码。它看起来相当复杂,但似乎工作正常,甚至在scratchbox2下也是如此。

# Generic variables for use in functions
comma:= ,
empty:=
space:= $(empty) $(empty)

# Make directory function
forlooprange = $(wordlist 1,$(words $1),1 2 3 4 5 6 7 8 9 10)
forloop = $(foreach n,$(call forlooprange,$1),$(call $2,$n,$3))
mkdirfunc0 = test -d $1 || mkdir $1;
mkdirfunc1 = $(call mkdirfunc0,/$(subst $(space),/,$(foreach n,$(wordlist 1,$1,$2),$n)))
mkdirfunc2 = $(call forloop,$1,mkdirfunc1,$1)
mkdirmain = $(call mkdirfunc2,$(subst /, ,$1))

.PRECIOUS: %/.sentinel  
%/.sentinel:
    $(call mkdirmain,$*)
    touch $@
2个回答

4
你可以用以下代码替换你的一堆mkdir命令:
$(Release_target_OBJDIR)/%.o: %.cpp
    $(foreach d,$(subst /, ,${@D}),mkdir $d && cd $d && ):
    ∶

这将创建一个类似于以下内容的shell命令:
mkdir projects && cd projects && mkdir htc && cd htc && mkdir arm && cd arm && :

这个在每次编译时都会运行。不太优雅。你可以通过使用某种哨兵文件来进行优化。例如:

$(Release_target_OBJDIR)/%.o: %.cpp ${Release_target_OBJDIR}/.sentinel
    ∶

%/.sentinel:
    $(foreach d,$(subst /, ,$*),mkdir $d && cd $d && ):
    touch $@

.sentinel会在所有对象之前创建,并且支持make -j。实际上,即使mkdir -p对您有效(在这种情况下,您将使用mkdir -p而不是$(foreach)的方法),您也应该按照这种方式操作。


谢谢这个。有一件奇怪的事情:我在链接阶段和编译阶段都添加了一个哨兵来创建输出目录。这(不知何故)触发了make文件为那些哨兵生成rm命令,但没有为编译时的哨兵生成。我已经在哨兵规则中添加了“touch”以创建哨兵。Shell命令不是很正确,但你给了我足够的信息来让我开始做些什么。 - Simon Elliott
@Simon:啊,是的,我加了 touch,这样我就不会看起来太蠢了。TVM。 - bobbogo
@Simon:你可能需要一些.PRECIOUS.SECONDARY的组合。你也可以将哨兵模式规则转换为静态模式规则(耶!)。 - bobbogo
@ bobbogo:谢谢,.PRECIOUS解决了问题,并且额外地帮我找到了GNU make文档中有关中间目标的所有信息。 - Simon Elliott
@bobbogo,你能详细说明一下“make -j”友好性吗?我目前在每次编译时都使用mkdir -p,但我已经让它工作了:https://gist.github.com/zeux/8906196。从简单性的角度来看,我有点喜欢旧方法。 - zeuxcg
@Simon 不,旧方法(我想你指的是“每次编译都要进行mkdir”)是一种hack。考虑一下如果你使用旧方法和make -j会发生什么。你可能会同时运行许多mkdir!糟糕,任何事情都可能发生。使用.sentinel的方式,mkdir最多只会运行一次。我说“最多”,因为一旦sentinal被创建,即使第二次运行,_make_也不会再次运行mkdir - bobbogo

2

您可以使用-来告诉make忽略命令的任何失败返回代码:

$(Release_target_OBJDIR)/%.o: %.cpp
    -mkdir $(dir $(dir $(dir $@)))
    -mkdir $(dir $(dir $@))
    -mkdir $(dir $@)
    $(COMPILE.cpp) $< $(CFLAGS) $(INCLUDES) -o $@

(请注意,这并未解决尾部斜杠的问题。)

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