Suppose this makefile snippet
$(cfstdlib):
svn export --force $(CF_REPO)/masterfiles/trunk/lib/$(VERSION)/
Where cfstdlib is a list of files, and the svn command, run only once, will create all the files the list. When I run make it executes svn for each file in the list. How can I make the svn command run only once?
$(cfstdlib): sentinel ;
.PHONY: phony
.ONESHELL:
sentinel: phony
temp=`mktemp -d`
svn export --force $(CF_REPO)/masterfiles/trunk/lib/$(VERSION)/ $$temp
if [ "`diff -r $(VERSION) $$temp 2>&1`" ]
then
rm -rf $(VERSION)
mv $$temp $(VERSION)
touch $#
else
rm -rf $$temp
fi
Related
I'm preparing some latex files and decided to make some makefile to help me to compile and clean de latex files. So I created the following makefile
aula=listaProb
all: compile clean
compile:
pdflatex $(aula).tex
clean:
rm -rf !(makefile|$(aula).tex|$(aula).pdf) -v
But when I execute "make" I get the following mistake
rm -rf !(makefile|listaProb.tex|listaProb.pdf) -v
/bin/sh: 1: Syntax error: "(" unexpected
makefile:8: recipe for target 'clean' failed
make: *** [clean] Error 2
But the command
rm -rf !(makefile|listaProb.tex|listaProb.pdf) -v
works fine on the terminal.
What is wrong? I can't find any mistake :/..
Ps. I use this way to remove the files because I want to delete all but the specified files. It needs the command
shopt -s extglob
before use it. If anyone knows how to do it without use extglob, it would be nice.
Thanks
The problem is recipe commands are passed to /bin/sh which cannot process that syntax. You can change your Makefile to say:
clean:
bash -O extglob -c "rm -rf !(makefile|$(aula).tex|$(aula).pdf) -v"
To force this command to be run in bash with extglob on.
Or define SHELL variable for your make e.g. by running:
make SHELL="/bin/bash -O extglob" clean
Or adding:
SHELL := /bin/bash -O extglob
To your make file. The former option only affects shell invocation of that one command, the latter will apply to all your recipes (commands).
I'm trying to uninstall a program by deleting all of the files the installer installed. This is the script I have tried, but it returns a "Too many arguments" error on line 6 (highlighted with **) when I try and run it.
This is to be deployed out to multiple machine through Apple Remote Desktop.
I would like to put it in a package to run, but as an executable script will also do the job. Am I going about this wrong? This is not the entire script but it follows the same pattern.
#!/bin/bash
## This will uninstall ETC Nomad v2.3.3.9.0.10.mpkg
## From Contents of ETCnomad Eos Mac 2.3.3.9.0.10.pkg
**if [ -d /Applications/Eos Family Welcome Screen.app ]; then**
/bin/rm -rf /Applications/Eos Family Welcome Screen.app
fi
if [ -f /tmp/Element_Hotkeys.pdf ]; then
/bin/rm -rf /tmp/Element_Hotkeys.pdf
fi
if [ -f /tmp/Eos_Hotkeys.pdf ]; then
/bin/rm -rf /tmp/Eos_Hotkeys.pdf
fi
if [ -f /tmp/FixtureReleaseNotes.pdf ]; then
/bin/rm -rf /tmp/FixtureReleaseNotes.pdf
fi
if [ -f usr/local/etc/DCIDTable ]; then
/bin/rm -rf usr/local/etc/DCIDTable
fi
exit 0
Answer
Use ' around path/filenames that contain spaces or else the shell will try to interpret the parts as different from the filename and get confused, hence the error message.
More comments
As jubobs points out, there's no use in testing whether the file exists before deleting it. Furthermore, you already use the -f option which ignores nonexistent files so the test becomes irrelevant.
Remove absolute paths from your commands to the keep your script portable. The shell's PATH environment variable is used to search for commands in the right places.
No need to remove files from /tmp/ because the OS does that for you.
Be careful when you tinker with system folders like /usr/ because every system upgrades overwrite them, and often times it's hard to tell all dependencies.
You can simplify your script:
#!/bin/bash
## This will uninstall ETC Nomad v2.3.3.9.0.10.mpkg
## From Contents of ETCnomad Eos Mac 2.3.3.9.0.10.pkg
rm -rf '/Applications/Eos Family Welcome Screen.app'
# rm -rf /tmp/Element_Hotkeys.pdf
# rm -rf /tmp/Eos_Hotkeys.pdf
# rm -rf /tmp/FixtureReleaseNotes.pdf
rm -rf /usr/local/etc/DCIDTable
exit 0
GNU Make includes a special target called .DELETE_ON_ERROR. If this is included in your Makefile, Make will delete any target whose build sequence completes with a non-zero return status. This is helpful so that in subsequent invocations Make does not assume that the target has been properly built.
Here's a dummy example.
.DELETE_ON_ERROR:
out.dat: in.dat
touch out.dat
false
Because false gives a non-zero return value, the build is considered failed and Make deletes the out.dat target. This is the advertised and expected behavior. However, this behavior does not seem to be preserved when the target is a directory. Consider another dummy example.
.DELETE_ON_ERROR:
outdir/: in.dat
mkdir outdir/
false
In this case, the build fails again but Make does not remove the outdir directory. Is there any way I can instruct Make to do this?
As noted in the comments, it is hard to use timestamps on directory. Few options:
proxy target (%.dir)
Atomic update using temporary folder.
Using proxy target, Makefile can be modified to incude a '%.done' target, which will embed the cleanup logic.
.PHONY: %.dir
outdir.dir:
$(MAKE) outdir ; if [ $? -ne 0 ] ; then echo CLEANUP $# ; rm -rf dir ; false ; fi
outdir: ... # as before
And use the outdir.dir as a dependency. Not elegant, but will get the work done. May be possible to to convert into a rule (disclaimer: I did not test this approach).
.PHONY %.dir
%.dir:
$(MAKE) $* ; if [ $? -ne 0 ] ; then echo CLEANUP $* ; rmd -rf $* ; false ; fi
Another variation is to change the outdir to add a "done" indicator file (if completed successfully), and use the proxy target to validate
%.dir:
$(MAKE) $* ; if [ ! -f $*.done ] ; then rm -rf $* ; false ; fi
outdir:
... commands, any can fail.
touch $*.done
As last resort (or first option, depending on your situation), consider, 'atomic' build for outdir - creating a temporary folder, and renaming it to outdir on success
outdir:
rm -rf $#.new $#
mkdir $#.new
# Command to create outdir.new here
mv $#.new $#
I'm using make to copy files to a DEST directory. I have the following rule
$(THUMBS): $(DEST)/% : %
mkdir -p $(dir $#)
cp $^ $#
The problem is that sometimes the source file may not exist. Rather than generating an error, I would rather copy a placeholder file instead.
I tried adding the placeholder as a dependence with the actual sources as intermediates. That kind of worked, but then if the placeholder is updated make overwrites all of the actual source files with it.
Is there an elegant way to accomplish this?
If the files in $(DEST) are being built externally (that is, not via a make recipe), then you can do this by embedding a little shell script in your recipe:
$(THUMBS):
mkdir -p $(#D)
for file in $(DEST_FILES); do\
if [[ -f $file ]]; then\
cp -f $file $#;\
else\
cp -f $(PLACEHOLDER_FILE) $#;\
fi;\
done
You aren't listing the files in $(DEST) as prerequisites, so make should never try to rebuild them. You will need to set PLACEHOLDER_FILE to the name of the placeholder file that you wish to use for missing files, and set DEST_FILES to the list of files that you expect to see in DEST. The downside is that without prerequisites, make won't know when it doesn't actually need to re-run this rule. You will run it unconditionally every time.
How about this:
$(DEST)/% : %
mkdir -p $(dir $#)
cp $^ $#
$(DEST)/% :
mkdir -p $(dir $#)
touch $#
I am very new to Makefiles, so I am probably not doing this the best way (your input is much appreciated, since I would like to learn how/why mine is bad). Anyway, here is my problem:
I have a Daemon that I wrote for a program of mine and I am trying to install it with the Makefile (target is "install"). What the "install" target is supposed to do is move the daemon binary to a location, then move the "service script" to either /etc/init.d/ or /etc/rc.d/ (since different distros have different folders...). Here is my makefile so far:
all:
#echo "Making Components"
#cd Daemon; make
#echo "Components Built"
install:
#echo "Installing Components"
#mkdir -p /usr/lib/
#cp Daemon/myprog_d /usr/lib/myprog_d
-#test -d /etc/init.d && cp Scripts/myprog /etc/init.d/
-#test -d /etc/rc.d && cp Scripts/myprog /etc/rc.d/
-#test ! -d /etc/init.d -a ! -d /etc/rc.d && echo " Warning: Couldn't install script. Manually install Scripts/myprog"
#mkdir -p /var/log/
#echo "Installed."
uninstall:
#echo "Uninstalling Components"
#./Scripts/myprog stop > /dev/null
#rm -f /usr/lib/myprog_d
#echo "Uninstall complete"
clean:
#echo "Cleaning Components"
#cd Daemon; make clean
#echo "Clean complete"
As you can see, the "install" target tests to see if those two directories exist and, if they do, copies the script into them (I haven't yet done it to "uninstall", don't worry).
My first question: Is this the right way to do this? The "all" and "clean" targets work (there is another makefile in "Daemon/", as you can deduce), but I want to know if there is a cleaner way of doing this.
Secondly, because the "test" function returns non-zero, I had to do "-" before it so the error would be ignored. Unfortunately, that results in the "make install" output being:
Installing Components
make: [install] Error 1 (ignored)
make: [install] Error 1 (ignored)
Installed.
Which is very ugly and probably not good practice. What can I do in this case? (I have tried both -# and #-, but # will not suppress the "ignored" output)
Sincerely,
Chris
I'd do it this way:
#if [ -d /etc/init.d ]; then cp Scripts/myprog /etc/init.d/ ; fi
#if [ -d /etc/rc.d ]; then cp Scripts/myprog /etc/rc.d/ ; fi
And I'm a little confused by your next line (-#test ! -d /etc/init.d -a !...) but you can probably do it the same way.
That takes care of the error messages, but if you wanted to keep the makefile as it is, you could suppress them by running make -s.