recipes in Makefiles prefixed with a dash [duplicate] - bash

This question already has answers here:
What do #, - and + do as prefixes to recipe lines in Make?
(2 answers)
what is the use of parentheses in linux command [duplicate]
(2 answers)
Closed 3 years ago.
I see that in some recipes in Makefiles, the commands are prefixed with a “-“. For example:
Recipe A (the "-" in "-if")
-if test "X$(topdir)" != "X$(BUILD_DIR)" ; then \
$(RM) parser-built y.tab.c y.tab.h ; \
fi
Recipe B
( cd $(DOCDIR) && $(MAKE) $(MFLAGS) $# )
( cd builtins && $(MAKE) $(MFLAGS) $# )
-( cd $(SDIR) && $(MAKE) $(MFLAGS) $# )
-for libdir in ${LIB_SUBDIRS}; do \
(cd $$libdir && test -f Makefile && $(MAKE) $(MFLAGS) $#) ;\
done
-( cd $(PO_DIR) ; $(MAKE) $(MFLAGS) DESTDIR=$(DESTDIR) $# )
$(RM) $(CREATED_SUPPORT)
Recipe C
-size $(Program)
Been trying to understand what they are, but can’t find anything in both bash shell and GNU make manuals. Does anyone know what they mean? Is it a feature of Bash or Make?
P.S.: Also, what do the brackets mean? eg. -( … )

The parentheses are a shell feature and execute the command in a subshell. This allows you to e.g. modify environment variables temporarily or run multiple commands in background.
Here, it is used to change the working directory temporarily (via cd) without affecting subsequent commands. In this case, this could also be achieved by passing -C to the make command. Calling make from make is called "recursive make" and is somewhat problematic (Google "recursive make considered harmful").

Related

error in a recursively-called make doesn't stop the calling make

I have a makefile that loops over all child directories that have files called Makefile in them, and runs a new make in that directory with my current target. It works fine when it works.
However, if the child make process finds an error it stops... but my calling make doesn't. The calling make continues to run, giving a false impression it is working. How can I make the calling make halt in this case?
# Make the same target in all subdirectories with a Makefile.
SubMakefiles = $(shell find -L . -maxdepth 2 -type f -name Makefile)
SubMakeDirs = $(filter-out ./,$(dir $(SubMakefiles)))
clean depend all::
#for dir in $(SubMakeDirs); do \
make -C $$dir $#
done
Without changing your targeting, you can capture the return value of one or more commands in sh with parenthesis, and exit with their error code should there be an error code, as follows:
# Make the same target in all subdirectories with a Makefile.
SubMakefiles = $(shell find -L . -maxdepth 2 -type f -name Makefile)
SubMakeDirs = $(filter-out ./,$(dir $(SubMakefiles)))
clean depend all::
#for dir in $(SubMakeDirs); do \
( make -C $$dir $# ) || exit $$?; \
done
Ultimately I settled on the following approach. Since the recursive make is run as a command directly by make, any error code produced will cause the master make to abort as well.
Walkthrough: we recurse to any sub-directory with a Makefile in it.
MAKECMDGOALS is a gmake-managed variable holding the command goal. A little counterintuitively, this top-level makefile says that whatever command you're trying to run depends on all the subdirectories with makefiles, and the rule to "create" those subdirectories (of course they already exist, but gmake isn't specifically aware of that) it recursively runs the same command in the subdirectory (specified with -C).
SUBDIRS = ${sort ${dir ${wildcard */Makefile}}}
all $(MAKECMDGOALS):: $(SUBDIRS)
.PHONY: $(SUBDIRS)
$(SUBDIRS)::
$(MAKE) --no-print-directory -C $# $(MAKECMDGOALS)

How to interpret the $# symbol in a makefile loop

In a makefile I have this piece of code
all clean dep depend print:
for dir in $(DIRS); do \
if $(MAKE) $(MAKE_FLAGS) -C $$dir $#; then \
true; \
else \
exit 1; \
fi; \
done
What is the meaning of $# in the line
if $(MAKE) $(MAKE_FLAGS) -C $$dir $#; then \
I know this is an Automatic Variable that matches the file name of the target of the rule. Here the target appears to be a command like cancel:
cancell:
rm -rf *.o
It is an Automatic variable that expands to the name of the target which caused the recipe to run. In your case, if you type make all, it expands to all. If you type make all clean, it will run the recipes twice -- the first time, $# will expand to all, and the second time it will expand to clean.
See documentation here

Make says file is up to date, but it does not exist

I want
I am trying to compile some latex that has snippets of python code and the output of those snippets. I need the document to be always updated with the last changes made in the snippets and in their outputs, so the idea is maintain a makefile that could monitor this changes and generate the updated outputs.
So if I modify the file a/11.py, I want make to execute it to generate a new output a/11.out.
I have
This is my makefile
DOC=myPdf
STY=st
PY_DIR=a/
TEX=pdflatex -shell-escape -interaction=batchmode -file-line-error
$(DOC).pdf: $(PY_DIR)11.out $(PY_DIR)12.out $(DOC).tex $(STY).sty
$(TEX) $(DOC).tex
$(PY_DIR)11.out:
$(cd PY_DIR && python3 11.py > 11.out)
$(PY_DIR)12.out:
$(cd PY_DIR && python3 12.py > 12.out)
.PHONY: clean
clean:
rm *.aux *.log > /dev/null 2>&1
I wonder
Even when the file a/11.out doesn't exist, and I instruct make a/11.out make says: make: 'a/11.out' is up to date. (I am still learning make, so I probably have more mistakes).
I saw
Make in subfolder, but because I am not using $(MAKE), I cannot use it.
Similar question, but I don't think it is the same.
Thank you for your time :)
Update
This is my new version, based in the answer of Renaud (thanks for your help), some python scripts are intended to output text (xxxt.py), and others to plot images (xxxi.py), so there is no redirection for them:
DOC :=myPdf
STY :=st
PY_DIR :=a/
TEX :=pdflatex -shell-escape -interaction=batchmode -file-line-error
PYS := $(wildcard $(PY_DIR)*.py)
OUTS := $(patsubst %.py,%.out,$(PYS))
.PHONY: all clean
all: $(DOC).pdf
%.pdf: %.tex $(STY).sty $(OUTS)
$(TEX) $<
$(PY_DIR)%.out: $(PY_DIR)%t.py
cd $(PY_DIR) && python3 $*t.py > $*.out
$(PY_DIR)%.png: $(PY_DIR)%i.py
cd $(PY_DIR) && python3 $*i.py
clean:
rm *.aux *.log > /dev/null 2>&1
The directory looks like this:
./st.sty
./myPdf.tex
./myPdf.pdf
./a/11t.py
./a/11.out
./a/12i.py
./a/12.png
./a/21t.py
./a/...
However, now right after modifying myPdf.tex, make says make: Nothing to be done for 'all'.
What am I doing wrong?
Your recipes are wrong. Make expands the recipes before passing them to the shell. As there is no make variable named cd PY_DIR && python3 11.py > 11.out, $(cd PY_DIR && python3 11.py > 11.out) expands as the empty string and make considers that there is nothing to do for $(PY_DIR)11.out. Just write your recipes as plain shell (and fix the other bug with the unexpanded PY_DIR):
$(PY_DIR)11.out:
cd $(PY_DIR) && python3 11.py > 11.out
$(PY_DIR)12.out:
cd $(PY_DIR) && python3 12.py > 12.out
Note: if you want make to re-run the recipes when your python scripts change you should let him know that the output files depend on the python scripts. The best is probably to use a pattern rule instead of one specific rule per file:
$(PY_DIR)%.out: $(PY_DIR)%.py
cd $(PY_DIR) && python3 $*.py > $*.out
($* is a make automatic variable, it expands as the stem of the pattern).
A few more improvements:
You could ask make to find alone the python scripts, compute the names of the output files and store all this in make variables that you can used in your other rules.
You can use a pattern rule for the xx.tex -> xx.pdf process. And use another make automatic variable for it: $< that expands as the first prerequisite.
DOC := myPdf
STY := st
PY_DIR := a/
TEX := pdflatex -shell-escape -interaction=batchmode -file-line-error
PYS := $(wildcard $(PY_DIR)*.py)
OUTS := $(patsubst %.py,%.out,$(PYS))
.PRECIOUS: $(OUTS)
.PHONY: all clean
all: $(DOC).pdf
%.pdf: %.tex $(OUTS) $(STY).sty
$(TEX) $<
$(PY_DIR)%.out: $(PY_DIR)%.py
cd $(PY_DIR) && python3 $*.py > $*.out
.PHONY: clean
clean:
rm *.aux *.log > /dev/null 2>&1
Note: I declared $(OUTS) as precious such that make does not delete them when it is done with the building of $(DOC).pdf.
Update with the new specifications and separated python scripts for xx.out and xx.png production:
DOC := myPdf
STY := st
PY_DIR := a
TEX := pdflatex -shell-escape -interaction=batchmode -file-line-error
PYTS := $(wildcard $(PY_DIR)/*t.py)
PYIS := $(wildcard $(PY_DIR)/*i.py)
OUTS := $(patsubst $(PY_DIR)/%t.py,$(PY_DIR)/%.out,$(PYTS))
PNGS := $(patsubst $(PY_DIR)/%i.py,$(PY_DIR)/%.png,$(PYIS))
.PRECIOUS: $(OUTS) $(PNGS)
.PHONY: all clean
all: $(DOC).pdf
%.pdf: %.tex $(STY).sty $(OUTS) $(PNGS)
$(TEX) $<
$(PY_DIR)/%.out: $(PY_DIR)/%t.py
cd $(PY_DIR) && python3 $*t.py > $*.out
$(PY_DIR)/%.png: $(PY_DIR)/%i.py
cd $(PY_DIR) && python3 $*i.py
clean:
rm -f *.aux *.log > /dev/null 2>&1
Notes:
I slightly modified the definition of PY_DIR such that, when used in other parts of the Makefile, it is clear that it is a directory path. Just a matter of taste, I guess.
I added the -f option to your clean recipe such that it doesn't fail if the files to delete do not exist.
Update:
As noted by MadScientist in a comment, using $* is less generic than referring to the target ($#) and the prerequisite ($<). But as we are operating not directly on them but on their directory ($(PY_DIR)) and base file names (xx[it].py, xx.out, xx.png), switching from $* to other, more generic, automatic variables is not that simple.
But make has some more tricks that can help here: $#, $<... have variants ($(#F), $(#D)...) that expand to just the directory part or the file part. Note that, according the GNU make manual:
These variants are semi-obsolete in GNU make since the functions dir
and notdir can be used to get a similar effect.
Anyway, if we wanted to avoid $* here is what we could use instead:
$(PY_DIR)/%.out: $(PY_DIR)/%t.py
cd $(#D) && python3 $(<F) > $(#F)
$(PY_DIR)/%.png: $(PY_DIR)/%i.py
cd $(#D) && python3 $(<F)
Or (modern version):
$(PY_DIR)/%.out: $(PY_DIR)/%t.py
cd $(dir $#) && python3 $(notdir $<) > $(notdir $#)
$(PY_DIR)/%.png: $(PY_DIR)/%i.py
cd $(dir $#) && python3 $(notdir $<)

GNU make ignores slash on cmd.exe

I had a problem with GNU make on cmd.exe. Somehow it ignores '/' output by dir and says there is no rule.
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)))
%/:
mkdir -p $#
So I made this dirty hack.
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)D))
%/D:
mkdir -p $#
Any better solution? Please do not tell me to throw the broken shell away. I don't use the shell, but others use it.
As far as I know target pattern matching doesn't have anything to do with the shell make will eventually use to run recipe targets. It is done entirely internally to make.
That said that exact makefile snippet (using OBJS := a/a.o b/b.o c/c.o) works for me with make 3.81, 3.82, 4.0 and 4.1 on CentOS 5. (I don't have make on Windows to test with.) So maybe there is something OS-specific involved here.
You could try removing the / and just using % as the target?
That all said a better solution might be to assign the directory components to a variable and use that variable as the target.
DIRS :=
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)) $(eval DIRS += $(dir $f)))
$(DIRS):
mkdir -p $#

Setting target variable for the name of the makefile from a subdirectory

How can I have the variable for $(MAKEFILE) be defined during target execution?
Basically I have a few make files in subdirectories that are named for a specific platform "Makefile.aix" and just Makefile in all other directories. I would like to set a variable for $(MAKEFILE) that gets defined in each subdirectory. Code would look something like this.
MAKEFILE = Makefile
SUBDIR = ./sub ./sub2
ifneq ($(wildcard Makefile),)
MAKEFILE = Makefile
else
MAKEFILE = Makefile.$(PLATFORM)
endif
all:;
#for i in $(SUBDIR);\
do (\
echo Making $$i ...;\
cd $$i;\
make -f $(MAKEFILE)\
); done
Is there just one Makefile.$(PLATFORM) in each subdirectory, or are there several, for different platforms?
In the first case, you could do something like this:
SUBDIR = ./sub ./sub2
define script
cd $(1); \
$(MAKE) -f Makefile*
endef
all:
$(foreach dir, $(SUBDIR), $(call script,$(dir)))
(The empty line inside the define is significant. It can be omitted, if you add a semicolon at the end of the line $(MAKE) ..., leading to one long command line, containing the commands for all directories, which will then be executed in one chunk.)
An alternative script would be (just a matter of personal preference which you like better):
define script
$(MAKE) -C $(1) -f $(notdir $(wildcard $(1)/Makefile*))
endef
If there are several Makefile.$(PLATFORM) files in a directory it becomes more difficult. I'll have to think about that one some more.
UPDATE: In response to your comment, something like this should work:
define script
$(MAKE) -C $(1) -f $(notdir $(wildcard $(1)/Makefile $(1)/Makefile.$(PLATFORM)))
endef
Following your logic, I'd propose update do () section with:
do (\
echo Making $$i ...;\
cd $$i;\
if [ -f Makefile.$(PLATFORM) ] \
then\
make -f Makefile.$(PLATFORM) \
else\
make -f Makefile\
fi\
); done
This is actually not a make style, but I can't suggest anything better without specific of your project
You can do most of this, including the loop over directories, using GNU make's built-in functions. Put the following in a central place, say $(TOP_DIR)/mk/subdir.mk:
makefile-for-dir = \
$(if $(wildcard $(1)/Makefile),Makefile,Makefile.$(PLATFORM))
make-recursive = \
$(foreach _d,$(1),$(MAKE) -C $(_d) -f $(call makefile-for-dir,$(_d)) && ) :
In each makefile that start recursive makes, use
include $(TOP_DIR)/mk/subdir.mk
SUBDIRS = dir1 dir2 dir3
.PHONY: all
all:
+#$(call make-recursive,$(SUBDIRS))

Resources