Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Basic if else statement in Makefile

I'm trying to execute a simple if else statement in a Makefile:

check:
  if [ -z "$(APP_NAME)" ]; then \
    echo "Empty" \
  else \
    echo "Not empty" \
  fi

When I execute make check I get the following error:

if [ -z "" ]; then
/bin/bash: -c: line 1: syntax error: unexpected end of file
make: *** [check] Error 2

Any idea what I'm doing wrong?

I know I can use the following, but I have a lot of logic after the echos so I need to spread it out across multiple lines:

check:
  [ -z "$(PATH)" ] && echo "Empty" || echo "Not empty"
like image 657
Johnny Metz Avatar asked Oct 29 '19 07:10

Johnny Metz


2 Answers

Change your make target to this (adding semicolons):

check:
    if [ -z "$(APP_NAME)" ]; then \
        echo "Empty"; \
    else \
        echo "Not empty"; \
    fi

For evaluating a statement in a shell without newlines (newlines get eaten by the backslash \) you need to properly end it with a semicolon. You cannot use real newlines in a Makefile for conditional shell-script code (see Make-specific background)

[ -z "$(APP_NAME)" ], echo "Empty", echo "Not empty" are all statements that need to be evaluated (similar to pressing enter in terminal after you typed in a command).

Make-specific background

make spawns a new shell for each command on a line, so you cannot use true multi line shell code as you would e.g. in a script-file.

Taking it to an extreme, the following would be possible in a shell script file, because the newline acts as command-evaluation (like in a terminal hitting enter is a newline-feed that evaluates the entered command):

if
[ 0 ]
then
echo "Foo"
fi

Listing 1

If you would write this in a Makefile though, if would be evaluated in its own shell (changing the shell-state to if) after which technically the condition [ 0 ] would be evaluated in its own shell again, without any connection to the previous if. However, make will not even get past the first if, because it expects an exit code to go on with the next statement, which it will not get from just changing the shell's state to if.

In other words, if two commands in a make-target are completely independent of each other (no conditions what so ever), you could just perfectly fine separate them solely by a normal newline and let them execute each in its own shell.

So, in order to make make evaluate multi line conditional shell scripts correctly, you need to evaluate the whole shell script code in one line (so it all is evaluated in the same shell).

Hence, for evaluating the code in Listing 1 inside a Makefile, it needs to be translated to:

if \
[ 0 ]; \
then \
echo "Foo"; \
fi

The last command fi does not need the backslash because that's where we don't need to keep the spawned shell open anymore.

like image 153
Jan Avatar answered Sep 19 '22 07:09

Jan


Other answers already pointed out that the problem is combination of makefile design and shell syntax. The design of Makefiles make it really cumbersome to write complex recipes. Often it is better to rethink the process and either rewrite parts of the makefile or put the complexity in a shell script.

Here is example of your recipe put in a shell script:

check:
  sh check.sh "$(APP_NAME)"

and the script:

if [ -z "$1" ]; then
  echo "Empty"
else
  echo "Not empty"
fi

advantage: you have all the power and flexibility of a shell script without any of the makefile awkwardness. You just need to pass the right arguments.

disadvantage: you have aditional files for your build process and your makefile recipes is spread across multiple files.

If the condition is "simple" you might use the conditional construct from make itself. In your case I would argue that it is just barely simple enough to tolerate, but any more complexity and it will go in a shell script.

Here is how to write conditional recipes using makefile features:

check:
ifdef APP_NAME
  echo "Empty"
else
  echo "Not empty"
endif

again with annotation

check: # target name
ifdef APP_NAME # makefile conditional syntax
  echo "Empty" # recipe if condition true
else # makefile conditional syntax
  echo "Not empty" # recipe if condition false
endif # makefile conditional syntax

For example if APP_NAME is defined the rule will effectively look like this during execution:

check:
  echo "Empty"

This specific example is probably semantically equivalent to your makefile. I cannot say for sure because I did not test thoroughly.

It is important to know that this conditional is evaluated before the recipe is executed. That means the value of variables that get computed values might be different.

advantage: all build commands in one place.

disadvantage: headaches trying to figure out when makefile does variable assignment and evaluation if the conditional does not behave like you expected.

read here for more info:

  • https://www.gnu.org/software/make/manual/html_node/Conditional-Example.html
  • https://www.gnu.org/software/make/manual/html_node/Conditional-Syntax.html
  • https://www.gnu.org/software/make/manual/html_node/Reading-Makefiles.html

see also

  • Passing arguments to "make run"
like image 27
Lesmana Avatar answered Sep 17 '22 07:09

Lesmana