Makefile multi line variable, single command - makefile

I would like to have a makefile which compiles all my .svg files, one variable into command line at a time. What if have tried so far is:
FIGS_SVG= \
fault_region.svg \
fault_region2.svg
all:
inkscape -z -D --file=$(FIGS_SVG) --export-pdf=$(FIGS_SVG:.svg=.pdf) --export-latex
When this is run the variable is set into the command one after the other as so:
inkscape -z -D --file=fault_region.svg fault_region2.svg --export-pdf=fault_region.pdf fault_region2.pdf --export-latex

Usually with Make, you give a rule for turning one kind of file into another, then tell it the files you want it to build, and Make takes care of it for you:
FIGS_SVG = \
fault_region.svg \
fault_region2.svg
all: $(FIGS_SVG:.svg=.pdf)
%.pdf %.pdf_tex: %.svg
inkscape -z -D --file=$< --export-pdf=$# --export-latex
The special variables $# and $< are documented in the Automatic Variables section of the manual.
This will run the two commands
inkscape -z -D --file=fault_region.svg --export-pdf=fault_region.pdf --export-latex
inkscape -z -D --file=fault_region2.svg --export-pdf=fault_region2.pdf --export-latex
but only if the .pdf file is nonexistent or has an older file modification time than the corresponding .svg file.
Note that Make has been told that a tex file is created too, so that if you delete one of those, Make will know how to regenerate it.

Related

what does #D mean in shell script

i have a code as
echo -e "\\n" "===== Making: $(#D)\n";\
if [ ! -d $(#D) ]; then \
mkdir $(#D); \
else \
if [ -e $(#D)\PackageBuild.error ]; then \
rm $(#D)\PackageBuild.error;\
fi; \
i am not sure what this #D is doing.
can someone help me out here
Usually $(command) executes command and replaces $(command) with the output of command.
So there must be a file named #D which is executable and located in the search path.
But if this is not a shell script but a make file it means:
$(#D)
The directory part of the file name of the target, with the trailing
slash removed. If the value of $# is dir/foo.o then $(#D) is
dir. This value is . if $# does not contain a slash.
The bash does not have any notion of a syntax like #D. I guess you are in a special context here, I guess again in a Makefile. make is a special program which does not verbatim execute the scripts in the Makefile. Instead it preprocesses the scripts.
$(x) is evaluated prior to calling the scripts and replaced by variables set in the Makefile. So I guess somewhere in the Makefile you have a variable called #D set to a specific value. (And there is a predefined one, as D. Mika found out ;-)
The documentation https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables mention that
‘$(#D)’: The directory part of the file name of the target, with the trailing slash removed [...]
But also
‘$(#F)’: The file-within-directory part of the file name of the target[...]
So if ‘$#’ is dir/foo.o then ‘$(#D)’ is dir and '$(#F)' is foo.o

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

Bash Script to do operations based on file extensions where the input is the path to the file. MacOSX

EDIT2:
THIS POST IS SOLVED (if you would like to see the product that tis post resulted in please visit the site http://www.nasmagic.com )
here is my plan firstly
i am using Platypus (http://sveinbjorn.org/platypus) a program for OSX that lets you create little "droplets" that basically wrap a bash script in a fancy drag and drop GUI.
now im using this as a nice easy way of making myself a drag and drop Nasm assembler.
i have made a few of these "droplets" with simple bash scripts inside them, one example would be the folowing:-
#!/bin/bash
chmod u+x $1
this as you can see makes my scripts executable,... very handy.
The way they work is they take one variable only and that is the path to the file eg.
/Users/MiRAGE/Desktop/example.sh
now here is my conundrum for the day
i need to do the following command:-
/usr/local/bin/nasm -f macho example.asm && ld -macosx_version_min 10.7.0 -o example example.o
this is one command that works fine with the non variable filenames. however in the droplet context it has a problem.
it can execute each of these commands in two seperate droplets but without changing the command it will not find the outputed file of the first command as it is not 'cd'd into the directory where the file is outputed so it doesent know what to do.
now as i say at the moment i have successfully got it to compile with one droplet with this command:-
/usr/local/bin/nasm -f macho $1
which i drag the file into first and it spits out a ".o" file
then i drop that ".o" file into droplet2 which has this command inside:-
ld -macosx_version_min 10.7.0 -lSystem -o $1.5y $1
this command is much less elegant than the first.
the only way i could get it to compile the file is to append my made up extention otherwise it would just not work. the problem with this method is that while it does output the compiled binary it ends up looking like this "exampleFilename.o.5y".
now i could go in and delete that and i would, i guess be where i need to be. but its very messy. 2 droplets, renaming files..... not what i want i want a drag and drop hassle free assembler.
so heres the code i have in mind except this is not real and clearly doesn't work.
if [filename $1 == ".asm"] # if the file extension is ".asm"
then # then do next step
/usr/local/bin/nasm -f macho $1 # make mach-o file
fi # end
else if [filename $1 == ".o"] # else if the file extension is ".o"
then # then do this step
ld -macosx_version_min 10.7.0 -lSystem -o $1 $1.o # take the file ".o" and outfile with no extention but the same name.
fi #end
this way i can drag it in once, it will spit out the ".o" file, then i drag that in, and it then converts it to the executable binary. PERFECT
but i cant for the life of me find out how to write this properly if anyone can help i would be very very appreciative
many thanks in advance
MiRAGE
If my understanding is correct, you want to be able to drag the .asm file to the droplet, then drag the resulting .o file to the droplet to produce an executable, in two steps.
In which case, after downloading platypus and recent nasm, I find this script works for me:
#!/bin/bash
# Get the path to the input file, and enter that directory
pathname=$(dirname "$1")
cd $pathname
# attempt strip the .asm extension off the input filename (if there is one)
filestem=$(basename -s .asm "$1")
# If the input file was a .asm file, then assemble it:
if [ "${pathname}/${filestem}.asm" == "$1" ]; then
/usr/local/bin/nasm -f macho "$1"
fi
# attempt strip the .o extension off the input filename (if there is one)
filestem=$(basename -s .o "$1")
# If the input file was a .o file, then link it:
if [ "${pathname}/${filestem}.o" == "$1" ]; then
ld -macosx_version_min 10.7.0 -o "$filestem" "$1"
fi
This script takes care to make sure output files are placed into the same directory as the input files.
Conversely, if you want a script to assemble and link in one shot, this works for me:
#!/bin/bash
# Get the path to the input file, and enter that directory
pathname=$(dirname "$1")
cd $pathname
# attempt strip the .asm extension off the input filename (if there is one)
filestem=$(basename -s .asm "$1")
# If the input file was an .asm file, then assemble and link it:
if [ "${pathname}/${filestem}.asm" == "$1" ]; then
/usr/local/bin/nasm -f macho "$1" && ld -macosx_version_min 10.7.0 -o "${pathname}/$filestem" "${pathname}/${filestem}.o"
fi
Note that what you have here basically replicates a lot of make or similar build systems do for you.
Note also that third-party software like playtpus is not strictly needed for the drag-n-drop part. You can use the built-in automator application to create similar applications. e.g I created one that you just drag example.asm onto the automator application icon and it runs the same shell script for you.
Also, you can test the shell script at the command line, simply by calling:
./myscript.sh example.asm
or
./myscript.sh example.o
Try this...
#!/bin/bash
ext=${1/*./}
test $ext == "asm" && /usr/local/bin/nasm -f macho "$1"
output=${1/*\//}; output=${output/.*/}
test $ext == "o" && ld -macosx_version_min 10.7.0 -lSystem -o $output $1
If you want to try make to do this, the following will do it:
#!/bin/bash
target=$(dirname "$1")/$(basename -s ".${1/*./}" "$1")
export CC=ld
export LDFLAGS="-macosx_version_min 10.7.0 -lSystem"
make -f - "$target" <<EOF
%.o: %.asm
/usr/local/bin/nasm -f macho $<
.PRECIOUS: %.o
EOF
(Note there is a TAB character at the start of the nasm line.)
The beauty of using make is that targets are only rebuilt if they don't exist, or they are older than their prerequisites. Also make has built-in implicit rules for many things, including linking a .o to create an executable, which we make use of.
This script will accept a .asm file or .o file as input, from which it will derive a make target (the name of the expected executable) by stripping off the extension.
explicitly set the linker to be "ld" instead of the default of "cc"
set necessary linker flags in the LDFLAGS variable
call make with the derived target. Normally make will parse a Makefile for its rules, but in this case, we redirect a makefile using a bash here-document.
The redirected makefile has one implicit rule, which says to assemble x.asm into x.o
The redirected makefile also has a special .PRECIOUS rule, which prevents deletion of intermediate files (.o files in this case)

Use $RANDOM in a makefile

I am making a makefile to rename files with a random number in it (I am a newbie in shell script). I don't understand why, but when I run the file $rand is given the value 'ANDOM'. When I run this outside of the makefile it works.
I run this in the Mac os terminal, in case it's helpful.
all: renamefiles
renamefiles:
rand=$RANDOM && mv myfile.css $rand-myfile.css && mv myotherfile.css $rand-myotherfile.css
Wouldn't it be easier/better to use a date/time stamp so that the renamed files are listed in date order?
You need to use two $ signs in the makefile for each $ that you want the shell to see.
Thus:
all: renamefiles
renamefiles:
rand=$$RANDOM && \
mv myfile.css $$rand-myfile.css && \
mv myotherfile.css $$rand-myotherfile.css
Or, with date/time stamps:
all: renamefiles
renamefiles:
time=$$(date +'%Y%m%d-%H%M%S') && \
mv myfile.css $$time-myfile.css && \
mv myotherfile.css $$time-myotherfile.css
To use a random number within one or multiple make variables, the following works fine for me:
FOO="some string with \"$$rand\" in it"
BAR=" you may use it $$rand times."
foobar:
rand=$$$$ && \
echo $(FOO) $(BAR)
You might need to surround a multi-letter macro name with braces (or parentheses), for example
${RANDOM}
$(RANDOM)
ref

How to ignore mv error?

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.

Resources