What does "$(#:%.o=%.d)" mean in a makefile? - makefile

I see below command options for GCC in a makefile:
... -MMD -MP -MF "$(#:%.o=%.d)" -MT "$(#:%.o=%.d)" -o "$#" "$<"
How to interpret it?
I searched through the makefile document, but no luck so far.
(This is not just about Automatic Variables)

$(:=) performs a string replacement.
$# is the name of the file being generated (the target).
So $(#:%.o=%.d) is the name of the file, with the .o extension changed to .d.
This command line generates one .d dependency file for each .o file.

(Thanks to #Quentin's clue. I just found it!)
It is called Substitution Reference
Some quote:
A substitution reference substitutes the value of a variable with
alterations that you specify. It has the form ‘$(var:a=b)’ (or
‘${var:a=b}’) and its meaning is to take the value of the variable
var, replace every a at the end of a word with b in that value, and
substitute the resulting string.
When we say “at the end of a word”, we mean that a must appear either
followed by whitespace or at the end of the value in order to be
replaced; other occurrences of a in the value are unaltered. For
example:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
sets ‘bar’ to ‘a.c b.c c.c’. See Setting Variables.
A substitution reference is actually an abbreviation for use of the
patsubst expansion function (see Functions for String Substitution and
Analysis). We provide substitution references as well as patsubst for
compatibility with other implementations of make.
So, the full interpretation of the following command:
... -MMD -MP -MF "$(#:%.o=%.d)" -MT "$(#:%.o=%.d)" -o "$#" "$<"
is:
Use gcc to compile the 1st prerequisite file ($<) and generate the
output file named after the current rule target's name (%#). And by
the way, generate a makefile named as *.d containing a rule to describe the dependency of current rule target. And change the name of rule target in the
generated *.d makefile from *.o to *.d.

Related

Understanding the "subst" keyword and specific line in a makefile

I take a course in CPP language and I'm trying to understand the use of "subst" in makefiles (in general) and the specific use in this makefile.
I' have tried to google the use of "subst", but didn't find.
CXX=clang++-5.0
RM=rm -f
CPPFLAGS=-std=c++17
ifndef MAIN
MAIN=./main.cpp
endif
MAINEXECUTABLE=$(subst .cpp,,$(MAIN)).exe
SOURCES=$(MAIN)
all: $(MAINEXECUTABLE)
$(MAINEXECUTABLE)
$(MAINEXECUTABLE): $(SOURCES) $(HEADERS)
$(CXX) $(CPPFLAGS) $(SOURCES) -o $(MAINEXECUTABLE)
clean:
$(RM) *.exe a.out *.class *.ppm
This makefile is a generic makefile and can be used to compile any single cpp file.
From the GNU make reference manual:
$(subst from,to,text)
Performs a textual replacement on the text text: each occurrence of
from is replaced by to. The result is substituted for the function
call. For example,
$(subst ee,EE,feet on the street)
substitutes the string ‘fEEt on the strEEt’.
Applying it to your case, the function invocation looks at the main file name and strips the .cpp extension (by substituting an empty string for it). It then adds the .exe extension to the now extension-less file name.

filter function in Makefile

Is there a way to pick up the target name using automatic variable.
SOURCES = $(wildcard *.c)
dummytgt: $(OBJ)/tier.o
$(GCC) $(CFLAGS) -c $(filter $#,$(SOURCES)).c -o $#
I do not want to mention the filename as input but would want to use the filter function to get the .c file which is same as target name. make throws an error no input files
It's helpful to have a look at how make parses this:
SOURCES = $(wildcard *.c)
dummytgt: $(OBJ)/tier.o
$(GCC) $(CFLAGS) -c $(filter $#,$(SOURCES)).c -o $#
First off,
it will read the makefile, defining and expanding macros as it goes.
SOURCES = $(wildcard *.c) means that ${SOURCES} is a lazy variable with definition $(wildcard *.c).
Lazy? Yes, these recursive variables (as the make manual has it) only expand their right-hand side when they are themselves expanded.
Make needs the dependencies as it reads the file, so $(OBJ) is expanded.
Let's assume that the expansion of ${OBJ} is objs (say).
The shell command block remains as a single lazy variable.
It's important to note that these are not expanded until make decides that it wants to build dmmytgt.
You could have written this to exactly the same effect:
dummytgt: objs/tier.o
$(GCC) $(CFLAGS) -c $(filter $#,$(wildcard *.c)).c -o $#
For this fragment to work,
the file objs/tier.o must already exist.
Let's assume it does.
Make now has all it needs to build dummytgt (according to your makefile),
so now it expands the command block.
$(wildcard *.c) expands to 1.c 2.c (say).
$# is dummytgt
$(filter dummytgt,1.c 2.c) is of course empty (and always will be!)
${GCC} is gcc (say)
${CFLAGS} is empty (say)
Thus the shell gets
gcc -c .c -o dummytgt
Presumably gcc complains that there is no file called .c.
The resulting error stops make's execution.
A few thing not to like here:
$(wildcard ) is only for hacky one-liner makefiles IMHO.
dummytgt requires objs/tier.o, but its build instructions never reference it.
Your $(filter ) always expands to nothing.
$(filter $#.c,$(SOURCES))
But I don't see why you don't use
$#.c
Or better still, make it a prerequisite.

% not matching zero or more characters in rule?

According to the manual on Defining and Redefining Pattern Rules (and if I am reading it correctly):
‘%’ character matching any sequence of zero or more characters...
But the following is not matching both bench.cpp and bench2.cpp:
bench%.o : bench%.cpp
$(CXX) $(CXXFLAGS) -DCRYPTOPP_DATA_DIR='"$(PREFIX)/share/cryptopp"' -c $<
%.o : %.cpp
$(CXX) $(CXXFLAGS) -c $<
Here's what I see when running make:
$ rm bench*.o
$ make static dynamic cryptest.exe PREFIX=/usr/local
make: Nothing to be done for `static'.
make: Nothing to be done for `dynamic'.
g++ -DNDEBUG -g -O2 -fPIC -march=native -pipe -c bench.cpp
g++ -DNDEBUG -g -O2 -fPIC -march=native -pipe -DCRYPTOPP_DATA_DIR='"/usr/local/share/cryptopp"' -c bench2.cpp
Above, both bench.cpp and bench2.cpp should have -DCRYPTOPP_DATA_DIR='"/usr/local/share/cryptopp"'. I also tried using the asterisk (*) with no joy.
How do I craft a rule that matches both bench.cpp and bench2.cpp?
According to the link you provided
A pattern rule contains the character ‘%’ (exactly one of them) in the
target; otherwise, it looks exactly like an ordinary rule. The target
is a pattern for matching file names; the ‘%’ matches any nonempty
substring, while other characters match only themselves.
So % doesn't match empty strings.
‘%’ character matching any sequence of zero or more characters...
refers to the definition of vpath which is totally different.
I'm afraid you'll have to use bench1 instead of bench. Alternatively you can use macro to defines 2 rules but write it only once.
Well, I happened to stumble across the proper section of the documentation on this.
According to 4.4 Using Wildcard Characters in File Names, I should probably use an asterisk in this case.
And according to Stallman and the GNUMake manual, % is not a wildcard for specifying file names:
A single file name can specify many files using wildcard characters.
The wildcard characters in make are ‘*’, ‘?’ and ‘[…]’ ...

Object file directory per compiler option combinations

I was reading gnu make section 10.5.4 "How patterns match" and it does not sound like I can do what I want.
I want to setup a directory structure where my source code is in one directory, and there are sub-directories to hold object files.
One sub-directory for each build configuration.
So I might have these files
a.c
debug/a.o # compiled with -g
release/a.o # compiled with -O
So I would like to make rules like this
debug/%.o : %.c
gcc -c -g %.c -o $#
release/%.o : %.c
gcc -c -O %.c -o $#
But section 10.5.4 tells me a match on "debug/a.o" will make the stem be "debug/a" so gnu make
will look for the source file at "debug/a.c" which is not what I want.
Is there a way to get GNU make to help me ?
Your makefile will work as written.
From that section of the manual:
When the target pattern does not contain a slash (and it usually does
not), directory names in the file names are removed from the file name
before it is compared with the target prefix and suffix. After the
comparison of the file name to the target pattern, the directory
names, along with the slash that ends them, are added on to the
prerequisite file names generated from the pattern rule's prerequisite
patterns... [bold added]
Your target patterns do contain slashes.
Try it if you don't believe me.
EDIT:
Correction: in the commands you should use $< rather than %.c.
CC=gcc
DEBUGFLAGS=-g
RELEASEFLAGS=-O
debug/%.o : %.c
$(CC) $(DEBUGFLAGS) -c $< -o $#
release/%.o : %.c
$(CC) $(RELEASEFLAGS) -c $< -o $#

WINAVR not finding file in include path with whitespace

When I supply an path for EXTRAINCDIRS (in the Makefile, following the sample provided by WINAVR) without whitespace, the compiler is able to find my header file, but when I use a path containing whitespace (enclosed in quotation marks, as the comments in the Makefile direct), it raises: error: No such file or directory.
"d:/dev/avr/atmega/shared/" # will search files in this dir
"d:/dev/avr/atmega/sha ed/" # will not search this dir for files
I mean, the comments say:
# List any extra directories to look for include files here.
# Each directory must be seperated by a space.
# Use forward slashes for directory separators.
# For a directory that has spaces, enclose it in quotes.
Any idea how to get WINAVR to handle this correctly?
I'm using Programmer's Notepad (WINAVR) on Windows XP. Here's the command line command:
avr-g++ -c -mmcu=atmega328p -I. -gdwarf-2 -DF_CPU=UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main.lst -I"d:/dev/avr/atmega/shared/" -I"d:/dev/avr/atmega/sha -Ied/" -std=gnu99 -MMD -MP -MF .dep/main.o.d main.c -o main.o
What is happening is that I'm guessing somewhere else in the makefile there is a line that does something like the following:
INCLUDES = $(addprefix -I, $(INCDIRS))
When that happens addprefix treats any spaces in the $(INCDIRS) variable as a seperator to the next variable and will add the -I there. What you could do is use a special character for spaces say '\\' and then before the command is generated call a substitute function to resubstitute spaces. Something like the below example:
SPACE = \\
INCDIRS = /home/posey/test$(SPACE)dir
INCLUDES = $(addprefix -I, $(INCDIRS))
REAL_INCLUDES = $(subst $(SPACE), ,$(INCLUDES))
.PHONY : all
all:
$(info $(REAL_INCLUDES))
If this doesn't make sense you can post the entire makefile and we can show you exactly what's going on. Once the space has been substituted back into the variable you cannot run it through any further make functions that work with space delimiters without the same behavior occurring.

Resources