foobar
may create the output file even when it fails, so I need to delete it in that case.
I can do this:
foo: bar baz foobar $^ -o $@ || (rm -f $@ && exit 1)
But this does not propagate the same exit code returned by foobar
(which is then outputted by make
). Is there any way to catch the error in the Makefile rather than in the shell?
The Cleanup Rule clean: rm *.o prog3 This is an optional rule. It allows you to type 'make clean' at the command line to get rid of your object and executable files. Sometimes the compiler will link or compile files incorrectly and the only way to get a fresh start is to remove all the object and executable files.
To ignore errors in a recipe line, write a ' - ' at the beginning of the line's text (after the initial tab). The ' - ' is discarded before the line is passed to the shell for execution.
The $@ and $< are called automatic variables. The variable $@ represents the name of the target and $< represents the first prerequisite required to create the output file.
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files.
In case DELETE_ON_ERROR
doesn't cut it, and what you're looking is a bit like a tearDown
, @After
, or finally
in Java/JUnit, this is what you can do:
.ONESHELL:
to have all shell commands executed in a single shell.trap
for EXIT
to get your cleanup done.errexit
. While we're at it, we can also set pipefail
right away.For example, let's say you want to start a docker container, do a test, stop the docker contaner no matter what, but get the result of the test. This is how to do it:
export SHELL:=/bin/bash export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit .ONESHELL: .PHONY: test test: function tearDown { docker stop test-image } trap tearDown EXIT docker run --name test-image … testStep1… testStep2… testStep3… …
export SHELL
export tells GNU make to use bash
as shell, which has heavier footprint than the default sh
but way more features.export SHELLOPTS
sets the pipefail
and errexit
flags for the bash
shell. pipefail
ensures that the exit status of a pipe will not be the last command but the last with a non-zero exit status. So, false | true
would return 1
instead of 0
.errexit
ensures that the exit status of a command sequence will not be the last command but the last with a non-zero exit status, and that subsequent commands will not be executed. So, false ; true
would return 1
instead of 0
and true
would not be executed..ONESHELL:
tells GNU make to run all the commands in a single shell. That means, your recipe really is one shellscript now. (Requires GNU make 3.82 or later.)function tearDown { docker stop test-image }
defines a shell function named tearDown
. In this example, it will stop the docker container.trap tearDown EXIT
is the most crucial part of everything in this example. It tells the shell that was invoked for the recipe to run the tearDown
function on exit, that is, no matter whether the commands were successful or failed.This is analogous to finally
in Java. Reuse across multiple targets / tests is not possible. It is definitely not like @AfterClass
/ @AfterAll
or tearDown()
/ @After
/ @AfterEach
in JUnit.
But you could do that, in case you need it. Say, you want to run multiple tests on the same docker container, and tear it down no matter what. That would be analogous to @AfterClass
/ @AfterAll
in JUnit. Then that could look like this:
export SHELL:=/bin/bash export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit .ONESHELL: .PHONY: start start: docker run --name test-image … .PHONY: stop stop: docker stop test-image .PHONY: test test: start function tearDown { $(MAKE) stop } trap tearDown EXIT $(MAKE) -k testImpl .PHONY: testImpl testImpl: testCase1 testCase2 testCase3 .PHONY: testCase1 testCase1: … .PHONY: testCase2 testCase2: … .PHONY: testCase3 testCase3: …
This would now run all tests, even if the first one failed, clean up after all of them have finished, and report an error if any of the tests failed.
Disclaimer: This requires the .ONESHELL
feature of GNU make, which was introduced in GNU make 3.82. The current version of GNU make as of this edit is GNU make 4.2.1, and Mac OS X still ships with GNU make 3.81.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With