How to ignore mv error? - makefile

I'm making a Makefile that moves an output file (foo.o) to a different directory (baz).
The output file moves as desired to the directory. However since make won't recompile the output file if I type make again, mv gets an error when it tries to move the non-existent empty file to the directory baz.
So this is what I have defined in my rule make all after all compilation:
-test -e "foo.o" || mv -f foo.o ../baz
Unfortunately, I'm still getting errors.

Errors in Recipes (from TFM)
To ignore errors in a recipe line, write a - at the beginning of the
line's text (after the initial tab).
So the target would be something like:
moveit:
-mv foo.o ../baz

I notice nobody has actually answered the original question itself yet, specifically how to ignore errors (all the answers are currently concerned with only calling the command if it won't cause an error).
To actually ignore errors, you can simply do:
mv -f foo.o ../baz 2>/dev/null; true
This will redirect stderr output to null, and follow the command with true (which always returns 0, causing make to believe the command succeeded regardless of what actually happened), allowing program flow to continue.

+#[ -d $(dir $#) ] || mkdir -p $(dir $#)
is what I use to silently create a folder if it does not exist. For your problem something like this should work
-#[ -e "foo.o" ] && mv -f foo.o ../baz

-test -e "foo.o" || if [ -f foo.o ]; then mv -f foo.o ../baz; fi;
That should work

Something like
test -e "foo.o" && mv -f foo.o ../baz
should work: the operator should be && instead of ||.
You can experiment with this by trying these commands:
test -e testfile && echo "going to move the file"
test -e testfile || echo "going to move the file"

I faced the same problem and as I am generating files, they always have different time. Workaround is set the same time to the files: touch -d '1 June 2018 11:02' file. In that case, gzip generates the same output and same md5sum. In my scenario, I don't need the time for the files.

Related

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

Makefile remove files

For a schoolproject I try around with makefiles. First I create the files with
install: main.c
gcc -asve-temps main.c
#if test ! -d bin/; then mkdir bin; else : fi
mv a.out $(shell pwd)/bin/
chmod 555 ./bin/a.out
Now I want to clear the project:
clear:
#if test -d *.[osia]; then rm *.[osia] else : ; fi
#if test -d a.out then rm a.out; else: ; fi
Running make install works fine. Running make clear produces the error code:
/bin/sh: 1: test: main.i: unexpected operator
and does not remove the requested files. I want to delete all the *.o *.s *.i and *.a files by running the make clear target using the pattern given above avoiding the error cannot remove ... : no such file or directory
test expects a single argument; when you pass it a glob, it's getting a bunch of them. Something like find will work in this case:
find . -maxdepth 1 -name '*.[osia]' -delete
Or, why check if the file exists at all?
rm -f *.[osia]
Couple of other notes: if you don't have an else clause in your if statement, don't include it. Read up on the test command; you certainly don't want to be using -d if you're looking for files. And, you can use the variable $PWD in place of running a subshell to get it.

Modifying file extensions using Makefiles

I'm new to Makefiles and I want to modify the extension of a set of files. The following command works on the shell:
for file in path/*.ext1; do j=`echo $file | cut -d . -f 1`;j=$j".ext2";echo mv $file $j; done
However, I'm not sure how to run this in a Makefile. I tried running
$(shell for file in path/*.ext1; do j=`echo $file | cut -d . -f 1`;j=$j".ext2";echo mv $file $j; done)
But this never did what I needed it to do. What do I need to do to make this work on the Makefile? How do I call it in a section?
The immediate answer to your question is that the $ character is special to make: it introduces a make variable. If you want to pass a $ to the shell, you'll have to write two of them: $$.
So, your shell function invocation would have to be written as:
$(shell for file in path/*.ext1; do j=`echo $$file | cut -d . -f 1`;j=$$j".ext2";echo mv $$file $$j; done)
However, this is almost certainly not a good way to do what you want. You don't really describe clearly what you want to do, however. If you just want to have a target in a makefile that can be invoked to make this change, you can use:
fixext:
for file in path/*.ext1; do \
j=`echo $$file | cut -d . -f 1`; \
j=$$j".ext2"; \
echo mv $$file $$j; \
done
Or, taking advantage of some useful shell shortcuts, you could just run:
fixext:
for file in path/*.ext1; do \
echo mv $$file $${file%.*}.ext2; \
done
Now if you run make fixext it will perform those steps.
But, a much more make-like way to do it would be to write a single rule that knows how to rename one file, then use prerequisites to have them all renamed:
TARGETS = $(patsubst %.ext1,%.ext2,$(wildcard path/*.ext1))
fixext: $(TARGETS)
%.ext2 : %.ext1
mv $< $#
Now you can even run make -j5 and do 5 of the move commands in parallel...
you can also add rename blocks at the top of your file eg to change a suffix
output := $(input:.mov=.mp4)
but this won't work inside a make command as far as I can see
check:
output := $(input:.mov=.mp4)
gives
$ input=walkthrough.mov make check
output := walkthrough.mp4
make: output: No such file or directory
make: *** [check] Error 1

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.)

gnu make reloads includes but doesn't update the targets

I'm trying to create a Makefile that will download and process file a file to generate targets, this is a simplified version:
default: all
.PHONY: all clean filelist.d
clean:
#rm -fv *.date *.d
#The actual list comes from a FTP file, but let's simplify things a bit
filelist.d:
#echo "Getting updated filelist..."
#echo "LIST=$(shell date +\%M)1.date $(shell date +\%M)2.date" > $#
#echo 'all: $$(LIST)' >> $#
%.date:
touch $#
-include filelist.d
Unfortunately the target all doesn't get updated properly on the first run, it needs to be run again to get the files. This is the output I get from it:
$ make
Getting updated filelist...
make: Nothing to be done for `default'.
$ make
Getting updated filelist...
touch 141.date
touch 142.date
touch 143.date
I'm using GNU Make 3.81 whose documentation states that it reloads the whole thing if the included files get changed. What is going wrong?
You have specified filelist.d as a .PHONY target, so make believes making that target doesn't actually update the specified file. However, it does, and the new contents are used on the next run. For the first run, the missing file isn't an error because include is prefixed with the dash.
Remove filelist.d from .PHONY. However, remember it won't be regenerated again until you delete it (as it doesn't depend on anything).
By the same token, you should include "default" in .PHONY.
I wrote a shell script rather than lump all this in the makefile:
#!/bin/bash
# Check whether file $1 is less than $2 days old.
[ $# -eq 2 ] || {
echo "Usage: $0 FILE DAYS" >&2
exit 2
}
FILE="$1"
DAYS="$2"
[ -f "$FILE" ] || exit 1 # doesn't exist or not a file
TODAY=$(date +%s)
TARGET=$(($TODAY - ($DAYS * 24 * 60 * 60)))
MODIFIED=$(date -r "$FILE" +%s)
(($TARGET < $MODIFIED))
Replace X with the max number of days that can pass before filelist.d is downloaded again:
filelist.d: force-make
./less-than-days $# X || command-to-update
.PHONY: force-make
force-make:
Now filelist.d depends on a .PHONY target, without being a phony itself. This means filelist.d is always out of date (phony targets are always "new"), but its recipe only updates the file periodically.
Unfortunately, this requires you to write the update command as a single command, and space may be a problem if it is long. In that case, I would put it in a separate script as well.

Resources