Conditionally do make distclean - bash

As part of a bash script, which has set -e, I do make distclean which, obviously, fails with the following error, if I have run distclean before calling the script:
make: *** No rule to make target 'distclean'. Stop
Is there way to do distclean and not fail if there's nothing to clean?

If you're okay with the error message being printed, and just want to ignore the failure and keep going, a common idiom is to append || :.
The general syntax here is cmd1 || cmd2. If cmd1 fails then it runs cmd2. : is the (unusual) name of a command that always succeeds, so || : has the effect of ignoring the first command's exit code.
make distclean || :
If you would rather not see an error message at all, you could check if the Makefile exists first:
if [[ -e Makefile ]]; then make distclean; fi

You could do a
make distclean || echo "(Error from make ignored)"
to make it explicit what's happening.

Related

How to derive the meaning of error produced by make?

Having this simple makefile rule:
exe:
for i in *; do [ -x "$$i" ] && echo "$$i"; done
Will output:
for i in *; do [ -x "$i" ] && echo "$i"; done
executablefile
make: *** [makefile:6: exe] Error 1
So it does, what I want, but even then, error with no other message. But not only for this particular example (which I still do not get), I would like to know, how to get some more info from bugs in makefile (is there a makefile debugger?). From makefile manual the *** is for fatal error, which ends compilation, but yet it outputs the executablefile (so it did compiled to that point). Apart from fatal error, - warnings give more info, so why do not do fatal errors as well?
explanation of this example
some advices how to debug makefile scripts
This is not an error from make, that's why there's no other information.
Make runs a shell and gives the shell your recipe to invoke. If the shell exits with success (exit code 0) then make assumes that the command it ran worked. If the shell exits with failure (any exit code other than 0), then make assumes the command it ran failed. Make doesn't know why it failed, make assumes that whatever command failed will have printed some information about why. All make knows is the exit code, so that's all make can tell you:
make: *** [makefile:6: exe] Error 1
This means that make ran the recipe for target exe at makefile line number 6, and that command exited with an error code 1 (which is not 0, hence a failure).
Why did this happen? Let's look at your shell script:
for i in *; do [ -x "$$i" ] && echo "$$i"; done
Let's suppose the last file matching * (so the last time we go through the loop) the file is not executable. That means the test of the last file [ -x "$$i" ] will fail. Since that's the last command that the shell runs before it exits, that will be the exit code of the shell, and you have a failure.
You need to be sure that the shell exits with success. One way to do that is ensure the last command the shell runs is always success; maybe something like this:
for i in *; do [ -x "$$i" ] && echo "$$i"; done; true

Makefile check if a block of commands succeeded

I have a target such as this:
.PHONY: do-symlink
do-symlink:
ln -s $(HERE_ONE_FILE) $(THERE_ONE_DIR)/
ln -s $(HERE_TWO_FILE) $(THERE_ONE_DIR)/
# check if those succeed or failed. I want to make sure both have passed
if succeeded:
do_something
else:
do_something_else
How can i check that they are both succeed? and if so, do something based on it?
So first of all, if a make recipe line fails, make fails, and stops executing the recipe. So if you put a recipe line at the end #echo "succeeded", then this will on run if all previous lines worked. As far as printing if something specific if one fails, you can use bash || for that
all:
#command1 || { echo "command1 failed"; exit 1 }
#command2 || { echo "command2 failed"; exit 1 }
#echo "command1 and command2 passed"
Notice the exit 1's in there. Normally, false || echo "false" would have a return status of 0 (pass), because the exit status is taken from the last command run (echo "false"), which will always succeed. This would cause make to always continue running the next recipe line. You may want this, however, you can preserve the failure by compounding the statement and doing an exit 1 at the end.
For running both commands regardless of exit status, and then handling the exits after, prefix the recipe lines with -. This will cause make to not stop running if the command fails. Notice however that each recipe line is run in its own shell, so one recipe line cannot directly access the return code from another line. You could output to a file and then access that file in a later recipe line:
all:
-#command1; echo $? > .commands.result
-#command2; echo $? >> .commands.result
#if grep -q "^00$" .commands.result; then \
echo both commands passed; \
else \
echo "at least one command failed"
(or you could always concatenate all the recipes lines into a single bash command and pass the variables around as well).

How to use shell command in GNU Make to echo string

I have the following lines in my makefile:
.PHONY : clean
clean:
#echo "Running Clean"
$(shell if [ -e exe ]; then rm exe; else echo "no files"; fi)
When I run:
make clean
I get the following output on the shell
Running Clean
no files
make: no: Command not found
Makefile:22: recipe for target 'clean' failed
make: *** [clean] Error 127
Any suggestions?
The problem is the use of $(shell ...). What you want is:
.PHONY : clean
clean:
#echo "Running Clean"
#if [ -e exe ]; then rm exe; else echo "no files"; fi
As far as an explanation of what's going wrong -- when you first run the clean target, make will expand all make variables and functions in the recipes before it starts running them -- because $(shell ...) only has one $, this is considered a make function. Make runs the command, which outputs no files to stdout, and replaces the call with that string, and then starts executing the recipes... So now make sees the following:
clean:
#echo "Running Clean"
no files
When it tries to run no files, due to the lack of a #, it echos the line to the screen, and then passes the command to the shell. Because the shell doesn't recognize the keyword no it outputs the error you're seeing. Make itself then fails because the shell returned an error.
Hey all I'm the same guy who asked this question but I found an answer right after I posted this, I think I'll leave this up (unless this is against stackoverflow etiquette) in case someone else has the same problems. My solution was echoing the string to stdout.
$(shell if [ -e exe ]; then rm exe; else echo "no files" >&2; fi)

Why make '--dry-run' with $(MAKE) in a recipe result in an error?

When I run make --dry-run on
all:
false # $(MAKE)
using GNU Make 4.2.1, I get back the following error. Why?
false # make all
make: *** [Makefile:2: all] Error 1
https://www.gnu.org/software/make/manual/make.html#Instead-of-Execution:
The -n, -t, and -q options do not affect recipe lines that begin with + characters or contain the strings $(MAKE) or ${MAKE}.
(--dry-run is an alias for -n.)
https://www.gnu.org/software/make/manual/make.html#MAKE-Variable:
subsystem:
cd subdir && $(MAKE)
[...]
As a special feature, using the variable MAKE in the recipe of a rule alters the effects of the -t (--touch), -n (--just-print), or -q (--question) option. Using the MAKE variable has the same effect as using a + character at the beginning of the recipe line.
[...]
Consider the command make -t in the above example. (The -t option marks targets as up to date without actually running any recipes; see Instead of Execution.) Following the usual definition of -t, a make -t command in the example would create a file named subsystem and do nothing else. What you really want it to do is run cd subdir && make -t; but that would require executing the recipe, and -t says not to execute recipes.
The special feature makes this do what you want: whenever a recipe line of a rule contains the variable MAKE, the flags -t, -n and -q do not apply to that line. Recipe lines containing MAKE are executed normally despite the presence of a flag that causes most recipes not to be run.
Your recipe contains $(MAKE), so it gets executed despite --dry-run. false returns an exit status of 1, which is considered an error by make.

`2>/dev/null` does not work inside a Makefile

I tried to suppress an error from rm command by writing
Makefile:
...
clean: $(wildcard *.mod)
-rm $^ 2>/dev/null
...
I ran:
$ make clean
rm 2>/dev/null
make: [clean] Error 64 (ignored)
I still had gotten an error.
Anyway, when I tried
$ rm [some non-existent files] 2>/dev/null
on the bash shell, it just works fine.
How can I use 2>/dev/null inside a makefile?
2>dev/null will redirect the error output so you don't see it, it will not prevent the shell to raise the error level. And the - sign in front of your shell command will tell GNU make to continue even if the error level is raised but it will not either prevent the shell to raise it.
What you want is the shell not to raise the error level and this can be done like this :
Unix (credits to this answer)
-rm $^ 2>/dev/null ; true
Windows
-rm $^ 2>NUL || true
or if you don't have rm on Windows
-del /F /Q $^ 2>NUL || true
The message make: [clean] Error 64 (ignored) is being printed by make after it sees that your shell command has failed.
It will therefore not be affected by any redirection that you use in the recipe.
Two fixes:
Use the -f rm flag. rm -f never returns an error.
(Well, hardly ever anyway, and if it does you probably want to know about it!)
Stop the shell command returning an error: simply append || : to the command.
Say what? Well if the rm succeeds your job is done and make is happy. OTOH if rm fails, the shell runs the second command in the or.
: is a shell built-in that always succeeds, and is much preferable to true IMHO.
The first of these is best in this case,
though the second is a general, if somewhat less efficient, pattern.
.PHONY: clean
clean: ; rm -rf *.mod

Resources