How to create a directory in a makefile when mkdir -p is not available?

21,963

Solution 1

You can replace your forest of mkdirs with this:

$(Release_target_OBJDIR)/%.o: %.cpp
    $(foreach d,$(subst /, ,${@D}),mkdir $d && cd $d && ):
    ∶

This will create a shell command somethng like this:

mkdir projects && cd projects && mkdir htc && cd htc && mkdir arm && cd arm && :

This runs for every compile. Not very elegant. You could optimise this by using some sort of sentinel file. For instance:

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

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

.sentinel gets created once before all objects, and is make -j friendly. In fact you should do it this way even if mkdir -p works for you (in which case you would use mkdir -p rather than the $(foreach) hacksolution).

Solution 2

You can tell make to ignore any failure return code from a command using -:

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

(Note that this doesn't address the trailing slash problem.)

Share:
21,963
Simon Elliott
Author by

Simon Elliott

Updated on July 14, 2022

Comments

  • Simon Elliott
    Simon Elliott almost 2 years

    I have a makefile which does the usual directory creation:

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

    Unfortunately when I run this under scratchbox2 the mkdir -p command always fails silently.

    I attempted the following kludge which doesn't work:

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

    This outputs:

    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/  
    

    ... the trailing slash prevents the dir function from stripping the last directory in the way I wanted.

    Short of writing a script or small C app to replicate the "-p" functionality, does anyone have any ideas for creating the subdirectories within the makefile?

    Without the -p option mkdir will give an error when the makefile tries to create a directory which already exists. I can do mkdir blah 2> /dev/null but then I risk losing other error messages.

    Does anyone have any thoughts as to why mkdir -p doesn't work under scratchbox2?

    EDIT

    Based on suggestions by bobbogo I put this together. It looks fairly convoluted, but seems to work, even under 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 $@
    
  • Simon Elliott
    Simon Elliott over 13 years
    Thanks for this. One strange thing: I've added a sentinel at the link stage as well as at the compile stage, to create the output directory. This has (somehow) triggered the make file to generate an rm command for those sentinels, but not the compile time ones. I've added a "touch" to the sentinel rule to create the sentinel. The shell command isn't quite right but you've given me enough information to get something going.
  • bobbogo
    bobbogo over 13 years
    @Simon: Ah yes, I've added the touch so that I don't look too stupid. TVM.
  • bobbogo
    bobbogo over 13 years
    @Simon: You might need some combination of .PRECIOUS and .SECONDARY. You could also turn the sentinel pattern rule into a Static Pattern Rule (yay!).
  • Simon Elliott
    Simon Elliott over 13 years
    @ bobbogo: Thanks, .PRECIOUS did the business, and as a bonus led me to the right part of the gnu make docs to find out all about intermediate targets.
  • zeuxcg
    zeuxcg over 10 years
    @bobbogo Can you expand on 'make -j'-friendliness? I'm currently using mkdir -p in every compilation, but I got this to work: gist.github.com/zeux/8906196. I kinda liked the old approach better from the simplicity point.
  • bobbogo
    bobbogo over 10 years
    @Simon No, the old approach (by which I assume you mean "every compile does a mkdir") is a hack. Consider what happens if you use make -j with the old approach. You potentially get many mkdirs all running at once! Ugh, anything could happen. With the .sentinel formulation, the mkdir runs at most only once. I say "at most," because once the sentinal has been created, make will never feel the urge to run the mkdir again. Even on a second run.