Search code examples
bashmakefileconditional-statements

How to use Makefile conditionals with bash commands as a preparation step for Makefile rules?


For some practical reasons, it would be useful for me to prepare the place for the Makefile rules in the working directory. Consider the hypothetical case that, if the $(VAR) value is true, I need to move the test.f file into the actual directory before compilation. I tried to use the ifeq conditional with the mv command, but it worked only when I closed it into a rule:

VAR=true

cond:
ifeq ($(VAR),true)
   @mv dir/test.f .
endif

test.x: test.o
   ifort -o test.x test.o

test.o: test.f
   ifort -c -o test.o test.f

In this case, however, only the cond rule is executed, but test.x and test.o not. Is there any way that

ifeq ($(VAR),true)
     @mv dir/test.f .
endif

should be always executed without blocking the execution of (other) rules in the Makefile?

For completeness, I am enclosing the real conditional block which I would like to treat:

ifeq ($(VAR),"./")
  @if [ -d mod ]; then \
     mv mod/* .; \
     rm -rf mod; \
     fi
  @if [ -d gen ]; then \
     mv gen/* .; \
     rm -rf gen; \
   fi
  @if [ -d extra ]; then \
     mv extra/* .; \
     rm -rf extra; \
     fi
  @if [ -d lnk ]; then \
     mv lnk/* .; \
     rm -rf lnk; \
     fi
else
   @if [ ! -d mod ]; then \
      mkdir -p mod; \
      mv mod_*.f* mod; \
   fi
   @if [ ! -d txt ]; then \
      mkdir -p txt; \
      mv *.txt txt; \
   fi
   @if [ ! -d gen ]; then \
      mkdir -p gen; \
      mv *.f* gen; \
   fi
   @if [ ! -d lnk ]; then \
      mkdir -p lnk; \
      mv *.o lnk; \
      mv *.mod lnk; \
   fi
endif

Solution

  • To be clear, make is not a shell and makefiles are not shell scripts. So you can't plop some shell scripting down anywhere in the makefile you want (whether it's prefixed with TAB or not) and have it work. make can invoke a shell to run some shell scripting, but it will only do that for shell operations that appear in specific places. Any text outside of those specific places, is considered makefile text (even if it's preceded by a TAB) and you'll get parser errors if it's not valid makefile syntax (which most shell scripting is not).

    One place a shell script can appear is in the recipe of a rule (this is where it's indented with TAB: the TAB tells make which lines are part of the recipe and which are not). The issue with this is that the recipe of a rule is only invoked if the target of the rule needs to be rebuilt. There are various answers here which give some thoughts about how to do this.

    The other place a shell script can appear (if you are using GNU Make) is in the $(shell ..) function. So one option to solve your problem would be this:

    VAR := true
    
    ifeq ($(VAR),true)
     __dummy := $(shell mv dir/test.f .)
    endif
    

    The __dummy assignment is there just in case the shell script generates some output to stdout.

    Of course if the script to be invoked is very complex it will be annoying to put it into a single invocation of $(shell ...) but it can be done: shell syntax is flexible enough for that. Or you can use multiple invocations. Or you can put all the commands into a separate shell script file and invoke that inside of $(shell ...).

    Ultimately I think that John's idea above where there's a separate operation that users need to invoke to "prep" the workspace is the best one. This operation can be a makefile target like make setup then you have:

    .PHONY: setup
    setup:
            mv dir/file.f .
    

    etc. Be sure that setup is not the first target in the makefile and it will only be run if the user runs make setup.