bridging shell and make

Recently at work I was tasked with updating our build system to be more stable and maintainable in the future. We had previously built a cobbled together Makefile based project that worked for a few specific types of builds but it was very fragile and interdependant. For example in order to build the version of our product that worked with the eclipse plugin we would have to run:

make ECLIPSE=true

In order to build for the visualstudio extension:

make VS=true WINDOWS=true

The problem came when we wanted to support windows versions of the eclipse platform. This required that we be able to run:

make ECLIPSE=true WINDOWS=true

But there was a snag, since some of the functionality for the visual studio build was flagged as being “windows” and some of it as being “vs”, this did not mix well with the “eclipse” functionality. At the time we hacked together a new target:


And we felt dirty ever since.

Come around to the next time we needed a new target (this time would be a CMDLINE_ON_WINDOWS=true abomination) and I said no. I am going to fix this situation. I went for orthogonal as much as I could, so that it would be possible to build all different configurations, such as a x64-Linux-VS build. I then wrote some error checking and the makefile was happy. In addition we have some shell scripts which are affected by the behaviour of the makefile, and so I thought it would be pertinent to try to make them both speak the same language.

In order achieve this I needed to figure out the semantics of Makefile variables/arguments.

  • If a variable is defined in the environment, you need to use make -e to get it into a makefile variable
  • If a variable is defined on the commandline, then it will overwrite the definition in the makefile.
  • If a variable is not defined in either of the above, and it is in the file, use that definition.

So the desired order of evaluation goes:

  • in file
  • environment
  • commandline

I start off by processing all the default values for the given variables:

for line in `cat defaults`; do
  if [[ "$line" == \#* ]]; then
    continue # skip comments

the defaults file is a series of lines which look like VAR=val. These variable value pairs are extracted below

  LINE_VAR=`echo $line | sed -e "s/=.*//"`
  LINE_VAL=`echo $line | sed -e "s/$LINE_VAR=//"`

I need to be able to see if the variable has been set before, so here I use an eval to see what is in the current variable, and if it has, use that value and export the pair

  # previously exported values overwrite defaults
  if [ "$LINE_VAR_VAL" != "" ]; then
  export $LINE_VAR=$LINE_VAL

Finally I go through the arguments array and overwrite any environment variables that are specified. The script that uses this one will have to refer to ALL_ARGS after they have been processed here.

# arguments that are VAR=something overwrite
for ARG in "$@"
  if [[ "$ARG" =~ ^[A-Z].*=.* ]]; then
    eval $ARG

This simplish hack has allowed me to standardise on a set of variables which control the logic of building, while maintaining orthogonality.