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.
Related
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.
I have a number of makefiles that build and run tests. I would like to create a script that makes each one and notes whether the tests passed or failed. Though I can determine test status within each make file, I am having trouble finding a way to communicate that status to the caller of the make command.
My first thought is to somehow affect the return value of the make command, though this does not seem possible. Can I do this? Is there some other form of communication I can use to express the test status to the bash script that will be calling make? Perhaps by using environment variables?
Thanks
Edit: It seems that I cannot set the return code for make, so for the time being I will have to make the tests, run them in the calling script instead of the makefile, note the results, and then manually run a make clean. I appreciate everyone's assistance.
Make will only return one of the following according to the source
#define MAKE_SUCCESS 0
#define MAKE_TROUBLE 1
#define MAKE_FAILURE 2
MAKE_SUCCESS and MAKE_FAILURE should be self-explanatory; MAKE_TROUBLE is only returned when running make with the -q option.
That's pretty much all you get from make, there doesn't seem to be any way to set the return code.
The default behavior of make is to return failure and abandon any remaining targets if something failed.
for directory in */; do
if ( cd "$directory" && make ); then
echo "$0: Make in $directory succeeded" >&2
else
echo "$0: Make in $directory failed" >&2
fi
done
Simply ensure each test leaves its result in a file unique to that test. Least friction will be to create test.pass if thes test passes, otherwise create test.fail. At the end of the test run gather up all the files and generate a report.
This scheme has two advantages that I can see:
You can run the tests in parallel (You do us the -jn flag, don't you? (hint: it's the whole point of make))
You can use the result files to record whether the test needs to be re-run (standard culling of work (hint: this is nearly the whole point of make))
Assuming the tests are called test-blah where blah is any string, and that you have a list of tests in ${tests} (after all, you have just built them, so it's not an unreasonable assumption).
A sketch:
fail = ${#:%.pass=%.fail}
test-passes := $(addsuffix .pass,${tests})
${test-passes}: test-%.pass: test-%
rm -f ${fail}
touch $#
$* || mv $# ${fail}
.PHONY: all
all: ${test-passes}
all:
# Count the .pass files, and the .fail files
echo '$(words $(wildcard *.pass)) passes'
echo '$(words $(wildcard *.fail)) failures'
In more detail:
test-passes := $(addsuffix .pass,${tests})
If ${tests} contains test-1 test-2 (say), then ${test-passes} will be test-1.pass test-2.pass
${test-passes}: test-%.pass: test-%
You've just gotta love static pattern rules.
This says that the file test-1.pass depends on the file test-1. Similarly for test-2.pass.
If test-1.pass does not exist, or is older than the executable test-1, then make will run the recipe.
rm -f ${fail}
${fail} expands to the target with pass replaced by fail, or test-1.fail in this case. The -f ensures the rm returns no error in the case that the file does not exist.
touch $# — create the .pass file
$< || mv $# ${fail}
Here we run the executable
If it returns success, our work is finished
If it fails, the output file is deleted, and test-1.fail is put in its place
Either way, make sees no error
.PHONY: all — The all target is symbolic and is not a file
all: ${test-passes}
Before we run the recipe for all, we build and run all the tests
echo '$(words $(wildcard *.pass)) passes'
Before passing the text to the shell, make expands $(wildcard) into a list of pass files, and then counts the files with $(words). The shell gets the command echo 4 passes (say)
You run this with
$ make -j9 all
Make will keep 9 jobs running at once — lovely if you have 8 CPUs.
DEPRECATED_CHECK := $(shell grep "test454" tex/*.tex)
ifneq ($(DEPRECATED_CHECK), )
$(warning \test454 is deprecated. Use \test2 instead)
endif
When I run this I get:
../common/Makefile.include:133: \test454 is deprecated. Use \test2 instead
That's fine, but I'd quite like to have only:
\test454 is deprecated. Use \test2 instead
Is this possible? Some sort of awk function? I think I need something with:
#echo \text454 is deprecated ...
But I don't know how to get this working with the basic purpose of my MWE, as it keeps complaining about missing separators.
Many thanks
You could use $(info ...) instead of $(warning ...). info doesn't prepend the file and line number.
just an aside -- I usually try to do those sort of checks as part of a sanity rule, and make everything depend on that rule instead of doing it at the top level. It gives you more flexibility that way. For example, if you didn't want to run the check when building clean, it becomes simple, or if you wanted to fail the build if a check failed, it becomes simple as well.
EDIT (adding more detail on aside)
Instead of doing an ifneq at the top level of make, you could add a target as so:
sanity_check:
# ! grep -q "test454" tex/*.txt || echo "test454 is depricated"
.PHONY: sanity check
The add dependencies of your main targets to sanity check:
all maintarg1 maintarg2: sanity_check
This way the sanity check will be run before any of your main targets, and will output as desired. This is in my opinion, a cleaner way of doing the test. This way the test is only run if you are building any of your targets, and will not be run, if for example you are making clean, or if your makefile was included by a parent makefile, or in a bunch of other corner cases that might pop up in the future.
Just a quick note on the recipe syntax: the # is a make directive that tells make not to echo the command as it's run. The ! is bash syntax to inverse the return of grep (so ! grep returns false if the text is found, thereby causing the || part of the statement to be evaluated.). The .PHONY: sanity_check tells make to run the rule, even if a file called sanity_check already exists
I'm trying to get the exit code on the ifdef statement if the statement is not true, but I tried by using exit 1 and $(call exit 1)
when using the first on the following code I get "Makefile:11: * missing separator. Stop."
...
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
exit 1
endif
...
By using $(call exit 1) I get no error but the makefile still keeps executing.
What I'm trying to accomplish is to exit the Makefile on the else with the error code 1
Thanks
As geekosaur says you can't put a shell command like exit 1 as a makefile operation. Makefiles are not shell scripts, although they can contain shell scripts. Shell commands can only appear within a target recipe, and nowhere else.
If you have a sufficiently new version of GNU make you can use the $(error ...) function, like this:
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
Also note that ifdef will be true if the variable is defined, even if it's defined to be the empty string. You may prefer:
ifneq ($(PACKAGE),)
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
to ensure the variable is set to a non-empty value.
And, it's possible your version of GNU make is too old to support the $(error ...) function, although it's been around for a long time now.
You can't simply have bare code sticking somewhere in a Makefile; code (such as exit 1) is always associated with a build rule of some kind.
In this case you want the $(error) function. It may not be sufficient to drop it in the else, though, for the same reason that exit 1 itself won't work there; you may need to rephrase the whole thing as
PACKAGEDIR := $(if $(flavor PACKAGE),undefined,$(error PACKAGE must be defined!),$(HOME)/$(PACKAGE))
I'm trying to do something like it
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
But if i call in the next line the variable it don't work.
If i define it outside the if it works perfect.
Someone can help me?
As everyone is saying, you haven't given us enough information. But I'll make a guess. You want to set this variable conditionally, then use it elsewhere in the makefile, and in other makefiles which include this one.
The trouble is that you are trying to use shell syntax. In a command this will work (if the syntax is correct), but the value will apply only in that command. Outside commands, shell syntax is just wrong and will cause an error, malfunction, or be ignored depending on exactly what you do.
Try this in the makefile, outside of any rule (that is, not in the recipe for any particular target):
ifeq (1,1)
COMPILER_CMD = -fPic
endif
$(info $(COMPILER_CMD))
If that works, then you can try to adapt it to do whatever it is you're actually trying to do.
Each line in the Makefile is executed separately in a new shell process, so that's why changes you made to the environment are not propagated to next line.
You can combine both lines into one long one to achieve what you want. You probably have something like this in you Makefile:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
echo $COMPILER_CMD
You want to add the line continuation backslash to the line before echo:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi; \
echo $COMPILER_CMD
I'm assuming that the example you show is the recipe for some rule. By the syntax here it looks like you're trying to set a make variable COMPILER_CMD from within a recipe based on the value of some shell boolean test, which is of course impossible. You have to be very clear in your mind how make works: make is not interpreting the recipes you write, in any way. Make is simply passing those recipes to another program (the shell) and the other program is interpreting those commands. Thus, you can't change the behavior of make, including setting make variables, from within a recipe: that recipe is being run in a completely different program.
As others have said, you don't give enough information about what you REALLY want to do, at a higher level, for us to give a complete solution. Having a boolean like 1==1 doesn't give any hint whatsoever as to why you're doing this. Also your shell syntax contains syntax errors, so we can tell you didn't actually cut and paste this from a real, working example.
You can, as piokuc implies, use a shell variable COMPILER_CMD (you have to remove the whitespace around the = to make it a shell variable assignment) but that value takes effect only while that one recipe line is running. For the next recipe line a new shell is started and any values set in the previous shell are lost:
all:
# if [[ 1 == 1 ]]; then COMPILER_CMD=-fpic; fi; \
echo COMPILER_CMD=$$COMPILER_CMD
# echo COMPILER_CMD=$$COMPILER_CMD
will give:
COMPILER_CMD=-fpic
COMPILER_CMD=