'make run clean' shell script that properly handles interupts - makefile

My first jab at writing a shell script that would run my makefile, run the executable, and then remove the executable looked like:
make
if [ -e a.out ]; then
./a.out
rm a.out
fi
after realizing that the script didn't clean up a.out when I pressed ^C, I tried modifying it like so:
onintr foo
make
if [ -e a.out ]; then
./a.out
rm a.out
fi
foo:
rm a.out
but it did not solve my problem. If any of you know how I can accomplish what I'm trying to do that would be very helpful.

Add an infinite loop at the top of your script, print out a line in your onintr routine and test that you hit it when you break the loop with Ctrl-C. Remove the extra ouput and the infinite loop. Read your man pages on make, you probably don't have much control over what happens when it gets interrupted. That's why so many make files have a Clean target in them, so you can rerun make to clean-up all of its outputs.

Related

Using Make, how to build multiple configuration in one make command

Thanks for all your time and response -
Currently, we are using the nested build, multiple Makefiles, and individual subdirectories having their own Makefile, all are connected with a top-level Makefile. We are running
make xxxxx_yyyy_defconfig
make
this builds and creates an output file which is xxxxx.elf file. --- Till here everything works fine.
Now we are having multiple def-configs(around 50), I want to build all configurations using one "make all" command. is that possible?
This is not a simple case where we can put all "all: prog01 prog02 prog03" as every program needs to have a different configuration. Configuration can be achieved by using "make xxxxx_yyyy_defconfig". The output of "make config" is the .config file, which is used during the "make" command.
Based on .config file many variables are exported which is used at the subdirectory level.
So How can I build multiple configurations using a single "make all" command?
Environment - Ubuntu, Cross compile for ARM, output file xxxx.elf.
With the use of script and make file I am able to solve, But I have to solve only using Makefile.
in Makefile add one PHONY target
all:
./build_all.sh #shell script calling.
Created one shell script like this
#! /usr/bin/bash
echo "Make All"
for entry in `ls conf`; do
make $entry
wait
make
if [ $? -eq 0 ] ; then
for xxxfile in `ls xxx*_*` ; do
xxxdir=$(echo $xxxfile | cut -b yy-zz)
mkdir -p $xxxdir
mv $xxxfile $xxxdir/
done
else
break
fi
done
If you want to build several configurations you must do this out of tree in separate build directories (make O=/tmp/builds/foo foo_defconfig; make -C /tmp/builds/foo) to avoid conflicts. A shell script could do this as well as a Makefile but if you insist on using a Makefile you could try the following that assumes your source tree is in /src/kernel and you want to build configuration foo in /tmp/builds/foo; adapt to your needs:
$ pwd
/tmp/builds
$ cat Makefile
CONFIGS := uuuu_vvvv xxxx_yyyy ...
BUILD := /tmp/build
KERNEL := /src/kernel
.PHONY: $(CONFIGS) all
all: $(CONFIGS)
$(CONFIGS):
rm -rf $#
mkdir -p $(BUILD)/$#
$(MAKE) -C $(KERNEL) O=$(BUILD)/$# $#_defconfig
$(MAKE) -C $(BUILD)/$#
$ make

How to delete the target file in case of error in Makefile?

In the following example I would like foo to be deleted in case of error. Unfortunately it doesn't work.
foo:
perl -e 'die()' > $# || [rm $# -a true]
What is it wrong?
GNU make can do that for you.
Special Built-in Target Names:
.DELETE_ON_ERROR
If .DELETE_ON_ERROR is mentioned as a target anywhere in the makefile, then make will delete the target of a rule if it has changed and its recipe exits with a nonzero exit status, just as it does when it receives a signal.
It is a general problem that creating a file is a non-atomic operation. And not always you can delete an incomplete or corrupted file on termination, for example, when the program is killed with SIGKILL or by the OOM-killer. In other words, all solutions involving removing the file are prone to failures.
The robust generic solution is:
Create the file with a temporary filename.
Once the file is complete and have correct permissions, rename it to the final name. Renaming a file is an atomic operations in UNIX, as long as the file stays in the same filesystem.
E.g.:
foo:
perl -e 'die()' > $#~
mv --force $#~ $#
This works for me…
foo:
#perl -e 'die()' > $# || { echo "removing $# because exit code was $${?}"; rm $#; }
Output
Died at -e line 1.
removing foo because exit code was 255

Make doesn't finish when run with entr

I'm trying to use entr to recompile as soon as I change a C file with the following command:
$ echo ex8.c | entr make ex8 && ./ex8
When I run it I get the cc output but then nothing happends
$ echo ex8.c | entr make ex8 && ./ex8
cc -Wall -g ex8.c -o ex8
If I just write it manually it works great
$ make ex8 && ./ex8
How should I write it with entr for it to work?
The man page wasn't quite detailed enough, but I installed it and tried it. Note your command is actually three distinct shell commands: echo ex8.c, entr make ex8, and ./ex8. These are connected by a pipe (the first two) and the && operator (the final two). The two commands in the pipeline are both started together in parallel. The final command will not be invoked until the pipeline completes, then if the exit code is success it be run.
This means that the final command ./ex8 will not be started until after the entr make ex8 command finishes. But, entr does not exit after it runs make one time: its entire point is to continue to watch the source file and run make every time it changes. That's why the final command is never invoked: entr never exits.
There are multiple ways to fix this but the simplest way is to add a rule to your makefile that will build AND RUN the command, then call that with make; add this to your makefile:
.PHONY: run-ex8
run-ex8: ex8
./$<
Now use entr like this:
echo ex8.c | entr make run-ex8

equivalent of pipefail in GNU make?

Say I have the following files:
buggy_program:
#!/bin/sh
echo "wops, some bug made me exit with failure"
exit 1
Makefile:
file.gz:
buggy_program | gzip -9 -c >$#
Now if I type make, GNU make will happily build file.gz even though buggy_program exited with non-zero status.
In bash I could do set -o pipefail to make a pipeline exit with failure if at least one program in the pipeline exits with failure. Is there a similar method in GNU make? Or some workaround that doesn't involve temporary files? (The reason for gzipping here is precisely to avoid a huge temporary file.)
Try this
SHELL=/bin/bash -o pipefail
file.gz:
buggy_program | gzip -9 -c >$#
You could do:
SHELL=/bin/bash
.DELETE_ON_ERROR:
file.gz:
set -o pipefail; buggy_program | gzip -9 -c >$#
but this only work with bash.
Here's a possible solution that doesn't require bash. Imagine you have two programs thisworks and thisfails that fail or work fine, respectively. Then the following will only leave you with work.gz, deleting fail.gz, ie. create the gzipped make target if and only if the program executed correctly:
all: fail.gz work.gz
work.gz:
( thisworks && touch $#.ok ) | gzip -c -9 >$#
rm $#.ok || rm $#
fail.gz:
( thisfails && touch $#.ok ) | gzip -c -9 >$#
rm $#.ok || rm $#
Explanation:
In the first line of the work.gz rule, thisworks will exit with success, and a file work.gz.ok will be created, and all stdout goes through gzip into work.gz. Then in the second line, because work.gz.ok exists, the first rm command also exits with success – and since || is short-circuiting, the second rm does not get run and so work.gz is not deleted.
OTOH, in the first line of the fail.gz rule, thisfails will exit with failure, and fail.gz.ok will not be created. All stdout still goes through gzip into fail.gz. Then in the second line, because fail.gz.ok does not exist, the first rm command exits with failure, so || tries the second rm command which deletes the fail.gz file.
To easily check that this works as it should, simply replace thisworks and thisfails with the commands true and false, respectively, put it in a Makefile and type make.
(Thanks to the kind people in #autotools for helping me with this.)

How do I get the exit status from the first command in a pipe within make?

I'm trying to pipe to the output of the compiler to the tee command in windows, but I've ran into an issue where if the compiler fails within make it'll continue compiling the next file when I want to it to stop. Is there a way to have the exit status of the first command be the exit status of the second command?
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $# $< $(CFLAGS) 2>&1 | tee build_log.txt
First off, I would leave any logging to the caller of make. Second off, this sort of piping is untidy in make. Third off, not a fan of losing the stderr stream inside make.
That said, this is a shell question. If you are using bash then see pipefail in the manual. Unfortunately I think it's quite tricky to turn on. (Yeah, I know you said windows, but I assume you aren't using the execrable cmd.)
SHELL := /bin/bash
passes:
(exit 1) |& cat
fails:
bash -c 'set -o pipefail; (exit 1) |& cat'
After Struggling a lot, I came to this solution ...
.ONESHELL:
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $# $< $(CFLAGS) 2> temp_err_file
set EXIT_STATUS=%ERRORLEVEL%
type temp_err_file >> build_log.txt
type temp_err_file 1>&2
del /q temp_err_file
exit /b %EXIT_STATUS%
Here .ONESHELL allows make to run entire recipe in the single shell command instead of running each line in separate cmd and collect return status of each separately. Overall exit status depends on the main compilation command so in end it is necessary to exit with the status of compilation.
I know its not a clean solution involving the temp_err_file and if something goes wrong after compilation command, make would not be able to catch it but this I think is the best I can find to work with windows without losing stderr stream and logging.
A method that is shell independent and may be feasible in some cases is the following:
assume you have a recipe:
target:
try_making_target |& tee target.log
What I did was convert it to:
target:
(try_making_target || rm -f $#) |& tee target.log
test -e $#
The the piped command fails, the "fallback" (command after ||) will delete the goal file, and the final test will fail. Note that this example assumes the OS is Linux ('rm' for deletions) and that your shell supports the || operator.
This assumes that you are not interested in partial results when try_making_target fails. If you want to keep partial result in 'target', you can use other "marker" files to designate the success or failure of try_making_target. Something like this may work:
target:
touch $#.succeeded # Assume success
(try_making_target || rm -f $#.succeeded) |& tee target.log # Delete to mark failure
test -e $#.succeeded # Fail if marker
rm $#.succeeded # Remove unneeded marker
This last code would also work for phony goals, although you really should minimize the use of these (I try only using them as mnemonics for real files, which may have longish names).

Resources