Conditional statements depending on successful compilation within a Makefile - makefile

For a makefile, I am trying to make it run a block of code in case of successful compilation, or an else block otherwise.
I have tried something like this
default:
ifeq ($(gcc -obuild main.c), 0)
echo "successful"
else
echo "you fail lol"
endif
But I cannot get the compilation command to be evaluated as my code suggests that I want. I thought that it could work like Bash but it seems that not, or I am missing something I dont know.
How can I accomplish this task?

Do it with the shell, not with Make:
default:
if gcc -obuild main.c ; then \
echo "sucessful" ; \
else echo "you fail" ; fi
Note that only the first line of the commands (if...) starts with a tab.

Related

Makefile: exit on conditional

I want to check that an environment variable is set before executing some code in a Makefile. If it's not set I want to throw an error with a simple error message:
run:
[ -z "$(MY_APP)" ] && echo "MY_APP must be set" && exit 1
echo "MY_APP is set. Yay!"
echo "Let's continue on with the command..."
When MY_APP is not set I get the following error, which is desired:
[ -z "" ] && echo "MY_APP must be set" && exit 1
MY_APP must be set
make: *** [run] Error 1
However, when MY_APP is set I get the following error:
[ -z "EXAMPLE_NAME" ] && echo "MY_APP must be set" && exit 1
make: *** [run] Error 1
Any idea what I'm doing wrong? And is there a better way to do this?
Recall that the && condition require that all conditions must be TRUE to pass. Since the first condition fail, the whole command will return a status of 1 (-> false), effectively stopping the make
You can use the following, so that the test will fail only when MY_APP is missing.
Note that I'm using false instead of exit 1. Also better to use "${MY_APP}", which make it easier to copy/paste from Make to shell prompt/script.
run:
{ [ -z "$(MY_APP)" ] && echo "MY_APP must be set" && false } || true
...
# Or just if-Then-Else
if [ -z "${MY_APP}" ] ; then echo "MY_APP must be set" ; false ; fi
...
You can test environment variables with Makefile conditional syntax, like this:
sometarget:
ifndef MY_APP
#echo "MY_APP environment variable missing"
exit 1
endif
somecommand to_run_if_my_app_is_set
Note that ifndef/ifdef operate on the name of the variable, not the variable itself.
It seems that you are trying to use a Makefile to run commands which are not building targets (the target name run is a giveaway). You already got bitten by one of Makefile and shells caveats. Makefile caveat: exit status is inspected after each line and if not zero abort immediately. Shell caveat: the test command ([) returns a non zero exit status so the entire line returns non zero.
The rule of thumb is: a recipe of a rule should create a filename named like the target of the rule.
Here is a rule (to clarify the terms):
target:
recipe command lines
should create file named target
There are some exceptions to this rule of thumb. Most notably make clean and make install. Both typically do not create files named clean or install. One can argue that make run maybe also be an exception to this rule of thumb.
If your run is as simple as a typical clean then I might agree about making an exception. But usually commands are run with command line arguments. Before long you will want make run accept arguments. And making make accept custom command line arguments is not fun at all.
You tried to manipulate the behaviour using environment variables which is somewhat less problematic than command line arguments. But still problematic enough to make you trip over a caveat.
My suggestion for a fix:
Put complex recipes in a shell script. There you have all the power and flexibility of a shell script without the awkwardness of makefiles. For example as explained here: Basic if else statement in Makefile
In case of a typical run target write a wrapper shell script around the makefile which lets the makefile rebuild the target and then run the target. For exampe as explained here: Passing arguments to "make run"
You can conditionally exit the Makefile using error control function, at least in the GNU version.
This snippet is a helpful condition to put into the head of the Makefile. It exits with a message of help, if make was not called from within the directory of the Makefile.
MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ifneq (${MAKEFILE_DIR}, $(shell pwd))
INVALID_LOCATION:=`make` must be called from within ${MAKEFILE_DIR} (or with option -C ${MAKEFILE_DIR})
$(error ERROR: $(INVALID_LOCATION))
endif
See: https://www.gnu.org/software/make/manual/html_node/Make-Control-Functions.html
Useful in case your paths are relative to the Makefile and you don't want them to prefix with a base.

How to abort makefile without error message

How can I abort makefile from continuing other targets, if certain condition is met in current target.
For example:
Step01:
## Do something
Step02: Step01_Output
## Check that Step01_output meet certain condition, otherwise, abort
Step03: Step02
## Do somethings (of course if did not abort in Step02)
# And so on
I tried using "exit" with status 0 ==> But it continues nevertheless!
I tried using "exit 1" or other exist status ==> It aborts, but gives error message at output.
I want to abort, but still not to give an error message at make calling shell.
I tried also to set env variable from Step02 and surround Step03 and after within if check like this:
ifneq ($(ToAbort),1)
Step03:
...
StepN
endif
Unfortunately, it seems that make did not even look at the condition or the variable value has not been transferred between targets.
Any ideas? May be through adding additional target or so?
I think make exits only when all the targets are created or any error arises.
By the way, commands are run in sub shells so that using`exit' will not cause make to exit.
Not sure your thinking is correct on this one.
Leave the culling of work up to make.
However,
you can express what you requested with a smattering of shell and recursive make (sorry).
You want to express "if a completes with/without error, then we are done, else carry on with b."
Step01:
command1
command2
command3 && ${MAKE} Step02
command4
Step02:
cmd5
cmd6 && ${MAKE} Step03
⋮
Note those ampersands.
if command3; then ${MAKE} Step02; fi does not do what you want.
If the make fails,
you do not want to continue into command4.
P.S. Don't forget to mark those steps as PHONY if they aren't real files.

Bad comportement while check error during makefile

I want to make automatically the documentation of my project with my makefile.
I also create a target doc (and a variable DOC_DIRECTORY = ../doc) to specify the directory of the documentation. In my doxygen file, I added a log file name "doxyLog.log" in the ../doc/ directory.
Here is my target definition :
#Creation of the Doxygen documentation
doc: $(DOC_DIRECTORY)/path_finder_doc
doxygen $(DOC_DIRECTORY)/path_finder_doc
#echo $(shell test -s ../doc/doxyLog.log; echo $$?)
ifeq ($(shell test -s ../doc/doxyLog.log; echo $$?),1)
#echo "Generation of the doxygen documentation done"
else
#echo "Error during the creation of the documentation, please check $(DOC_DIRECTORY)/doxyLog.log"
endif
To test if my check is working, I manually introduce an error in my documentation (a bad command like \retufjdkshrn instead of \return). But, when I launch the make doc, this error appears after the second time :
First make doc (with an error in the doc ) --> Generation of the doxygen documentation done
Second make doc (always the error in the doc) --> Error during the creation of the documentation, please check ../doc/doxyLog.log
I don't understand why, can someone help me please?
There appear to be two things wrong here, so parts of this answer must be guesswork.
First:
ifeq ($(shell test -s ../doc/doxyLog.log; echo $$?),1)
#echo "Generation of the doxygen documentation done"
As I understand test, it will return 0 if the file exists and 1 if the file does not exist. I suspect that you didn't test this before putting it into your makefile.
Second, you are confusing shell commands with Make commands. This:
ifeq ($(shell test -s ../doc/doxyLog.log; echo $$?),1)
#echo "Generation of the doxygen documentation done"
else
#echo "Error..."
endif
is a Make conditional. Make will evaluate it before running any rule. Since the log file does not yet exist, the shell command will return 1 (see First), the conditional will evaluate to true and the entire if-then-else statement will become
#echo "Generation of the doxygen documentation done"
This will become part of the rule before the rule is executed. On the next pass, the file already exists, the shell command returns 0 and the the statement becomes
#echo "Error..."
This explains why you're getting strange results.
If you want Make to report on the results of the attempt it's just made, you must put a shell conditional in a command in the rule:
doc: $(DOC_DIRECTORY)/path_finder_doc
doxygen $(DOC_DIRECTORY)/path_finder_doc
#if [ -s ../doc/doxyLog.log ]; then echo Log done; else echo error...; fi

Makefile conditional error

I am attempting to do a make statement to check the architecture. I am very close to getting it to work:
test:
ifeq ("$(shell arch)", "armv7l")
echo "This is an arm system"
else
echo "This is not an arm system."
endif
I have one issue: although this seems to resolve to ifeq ("i386", "armv7l") which should be false, I get the following error:
$ make
ifeq ("i386", "armv7l")
/bin/sh: -c: line 0: syntax error near unexpected token `"i386",'
/bin/sh: -c: line 0: `ifeq ("i386", "armv7l")'
make: *** [test] Error 2
So, it is resolving to two strings comparing to each other, but there is a syntax error. What's wrong here?
You cannot use make statements like ifeq inside a recipe. Recipes (the lines that begin with TAB) are passed to the shell. The shell doesn't understand ifeq; that's a make construct.
You'll have to use shell if-statements inside a recipe. And, you don't have to use $(shell ...) in a recipe, because you're already in a shell.
test:
if [ `arch` = armv7l ]; then \
echo "This is an arm system"; \
else \
echo "This is not an arm system."; \
fi
This is likely not the best way to handle this, but since you didn't provide any info on what you're really trying to do with this it's all we can say.
As MadScientist said, make is passing the ifeq lines to the shell, but if you write it properly, you can definitely mix make constructs like ifeq with commands within a recipe. You just need to understand how make parses a Makefile:
If a line begins with a TAB, it is considered a command for the shell regardless of where the line is within the file.
If it doesn't begin with a TAB, make interprets it as part of its own language.
So, to fix your file, just avoid starting the make conditionals with a TAB:
test:
ifeq ("$(shell arch)", "armv7l")
echo "This is an arm system"
else
echo "This is not an arm system."
endif

Using conditional rules in a makefile

I capture the intent of the Makefile in pseudo code, then indicate the issues I have. I'm looking for a Makefile which is more user friendly in a test environment. The correct usage of the Makefile is one of the below.
make CATEGORY=parser TEST=basic.
make ALL
If a user gives "JUST" the commands as indicated below, it should print a message saying "CATEGORY defined TEST undefined" and vice-versa
make CATEGORY=parser
make TEST=basic
I tried writing the Makefile in following ways, but it errors out:
help:
echo"Usage: make CATEGORY=<advanced|basic> TEST=<test-case>
echo" make ALL
ifdef CATEGORY
ifdef TEST
CATEGORY_TEST_DEFINED = 1
else
echo "TEST not defined"
else
echo "CATEGORY not defined"
endif
ifeq ($(CATEGORY_TEST_DEFINED), 1)
$(CATEGORY):
cd $(PROJ)/$(CATEGORY)
make -f test.mk $(TEST)
endif
ifdef ALL
$(ALL):
for i in `ls`
cd $$(i)
make all
endif
The questions I have are:
Whether the rules in a Makefile can be selective (using ifdef to select the rules and targets).
echo doesn't work. echo should help the user with correct usage.
The problem is that echo belongs to the shell; Make can pass it to the shell in a command, but Make cannot execute it. Use info instead:
ifdef CATEGORY
$(info CATEGORY defined)
else
$(info CATEGORY undefined)
endif
If you want the rules to be conditional:
ifdef CATEGORY
ifdef TEST
$(CATEGORY):
whatever
else
$(info TEST not defined)
else
$(info CATEGORY not defined)
endif
The biggest issue here is that all ifdef/ifndef/ifeq/... statements must be at column 0 or they it will results in an error. The echo is a minor issue compared with the indentation issue.
These lines are dubious:
help:
echo"Usage: make CATEGORY=<advanced|basic> TEST=<test-case>
echo" make ALL
You need a space between echo and the string, and the string needs to be terminated:
help:
echo "Usage: make CATEGORY=<advanced|basic> TEST=<test-case>"
echo " make ALL"
These lines are dubious:
ifdef CATEGORY
ifdef TEST
CATEGORY_TEST_DEFINED = 1
else
echo "TEST not defined"
else
echo "CATEGORY not defined"
endif
Surely you need an endif before the second else? (Even if it is not syntactically mandatory, I'd recommend it.)
ifdef CATEGORY
ifdef TEST
CATEGORY_TEST_DEFINED = 1
else
echo "TEST not defined"
endif
else
echo "CATEGORY not defined"
endif
Additionally, make only executes commands such as echo is supposed to be when processing a target (rule). It won't execute echo there; it will simply object that you cannot define commands without them being actions for a target. Despite everything that GNU Make adds to a makefile, the language in a makefile is a declarative language and not a procedural language.
Another way of handling this is to define default values for the macros:
CATEGORY = advanced
TEST = all
Define default values that do something semi-reasonable; let the user override the default if they want to. You can have a rule such as:
${CATEGORY}/${TEST}: ...dependencies...
...actions...
You can leave help as the first rule. I have some directories where the first rule is:
null:
#echo "You must specify a target with this makefile!"
This is equivalent to what you have (except that make does not echo the command before running it, so I only see the message instead of the echo command line and the message; that's the # at work). The makefile this comes from also has a rule all which is otherwise usually the most sensible first (default) rule.

Resources