52

I am writing a Makefile with a lot of repetitive stuff, e.g.

debug_ifort_Linux:
        if [ $(UNAME) = Linux ]; then                           \
          $(MAKE) FC=ifort FFLAGS=$(difort) PETSC_FFLAGS="..."  \
                  TARGET=$@ LEXT="ifort_$(UNAME)" -e syst;      \
        else                                                    \
          echo $(err_arch);                                     \
          exit 1;                                               \
        fi

where the target 'syst' is defined, the variable 'UNAME' is defined (and is usually Linux, but might also by Cygwin or OSF1) and the variables 'difort' and 'err_arch' are also defined. This block of code is used very many times for different compiler targets (using a name convention of ''). Since this is a huge amount of redundant code, I would like to be able to write it in a more simple manner. E.g., I would like to do something like this:

debug_ifort_Linux:
        compile(uname,compiler,flags,petsc_flags,target,lext)

where compile could be a function doing the code above based on the arguments. Does anyone have any idea how I could accomplish this?

Rob Wells
  • 36,220
  • 13
  • 81
  • 146
Karl Yngve Lervåg
  • 1,714
  • 3
  • 18
  • 34

2 Answers2

40

There are 3 related concepts:

  1. call function
  2. multi-line variables
  3. conditionals

The refactored result could look like this:

ifeq ($(UNAME),Linux)
    compile = $(MAKE) FC=$(1) FFLAGS=$(2) PETSC_FFLAGS=$(3) \
                      TARGET=$@ LEXT="$(1)_$(UNAME)" -e syst
else
    define compile =
        echo $(err_arch)
        exit 1
    endef
endif
        

debug_ifort:
        $(call compile,ifort,$(difort),"...")

That one \ that is left is to continue the $(MAKE) line for the shell. No multi-line variable is necessary here, because it is just one line of shell code. Multi-line variables are only used in the else block.

If you don't need parameters you can use := assignment and just expand the method with $(compile) (see canned recipes)

Note: Using make prior to version 3.82, the = was not recognized at the end of the define statement for me. I fixed this by using define compile instead.

Zoe
  • 27,060
  • 21
  • 118
  • 148
JonnyJD
  • 2,593
  • 1
  • 28
  • 44
40

You're looking for the call function.

compile =                                                 \
        if [ $(UNAME) = $(1) ]; then                      \
          $(MAKE) FC=$(2) FFLAGS=$(3) PETSC_FFLAGS="..."  \
                  TARGET=$@ LEXT="$(4)_$(UNAME)" -e syst; \
        else                                              \
          echo $(err_arch);                               \
          exit 1;                                         \
        fi

debug_ifort_Linux:
        $(call compile,Linux,ifort,$(difort),ifort)

If you can restructure your Makefile a bit, though, you should see if you can use make's conditionals instead of sh's.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 1
    Thanks, this worked! :) I do not see directly how I can use make's conditionals to do the same as what I want. At least not without a _lot_ of restructuring. Of course, this might just be because I'm not very experienced with writing Makefiles... – Karl Yngve Lervåg Feb 03 '09 at 16:10
  • This is a prime example where you should use [multi-line variables](http://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html). – JonnyJD Dec 23 '12 at 15:21