How to conditionally append to a variable in a Makefile function? - makefile

I have the following construct in the common make file which is to be included by
the module specific Makefile -
# Conditionally add Logger as a MUT dependency
define COND_ADD_LOGGER
$if($$(findstring -DLOGGER, $$(DEFINES)), SOURCES += logger.c)
endef
define CMN_TESTS_RULE
$(COND_ADD_LOGGER)
$(eval OBJECTS = $(SOURCES:.cpp=.o))
$(eval OBJECTS := $(OBJECTS:.c=.o))
$(TARGET): $(OBJECTS)
$(COMPILE_RULE_CMN)
endef
In my module's Makefile I do this -
SOURCES = a.c b.c test.cpp
TARGET = generic_tests
$(eval $(CMN_TESTS_RULE))
This isn't adding the logger.c files to the SOURCES list as I had expected.
(The DEFINES variable definitely has the sub-string "-DLOGGER" in it.)
This is the output when I use info -
$if($(findstring -DLOGGER, $(DEFINES)), SOURCES += logger.c)
generic_type_abstraction_tests: a.o b.o test.o
g++ -o $# $^ D:/TEST/gtest-1.7.0/make/gtest_main.a
$if($(findstring -DLOGGER, $(DEFINES)), SOURCES += logger.c)
generic_type_abstraction_tests: a.o b.o test.o
g++ -o $# $^ D:/TEST/gtest-1.7.0/make/gtest_main.a
I am using GNU Make version 3.81 from CYGWIN on a Windows 8 machine.

There are several things that do not make sense to me in your Makefile (especially strange use of nested eval). I suggest to fix them with something like:
# Conditionally add Logger as a MUT dependency
define COND_ADD_LOGGER
SOURCES += $(if $(findstring -DLOGGER,$(DEFINES)),logger.c)
endef
define CMN_TESTS_RULE
$(COND_ADD_LOGGER)
OBJECTS = $$(patsubst %.c,%.o,$$(patsubst %.cpp,%.o,$$(SOURCES)))
$(TARGET): $$(OBJECTS)
$(COMPILE_RULE_CMN)
endef
SOURCES = a.c b.c test.cpp
TARGET = generic_tests
$(eval $(CMN_TESTS_RULE))
.PHONY: debug
debug:
$(info SOURCES: $(SOURCES))
$(info OBJECTS: $(OBJECTS))
Demo:
$ make debug
SOURCES: a.c b.c test.cpp
OBJECTS: a.o b.o test.o
gmake: 'debug' is up to date.
$ make DEFINES=-DLOGGER debug
SOURCES: a.c b.c test.cpp logger.c
OBJECTS: a.o b.o test.o logger.o
gmake: 'debug' is up to date.

Related

Makefile with multiple programs with debug and release modes?

I've written simple calculator in C++, and decided to separate lexer+parser and actual "frontends" which can be GUI or command-line. Project structure looks like that:
src/
parser.hpp
parser.cpp
scanner.hpp
scanner.cpp
exceptions.hpp
term-calc.cpp
gui-calc.cpp
Makefile
Obviously parser and scanner should be compiled into object files separately, and term-calc and gui-calc separately. Furthermore I want to have debug builds and release builds,
so I imagine final project structure like that:
src/
...
obj/
debug/
parser.o
scanner.o
...
release/
parser.o
scanner.o
...
out/
debug/
term-calc
gui-calc
release/
term-calc
gui-calc
Makefile
I'm pretty new to Makefiles but this is what I came up with so far (I've ommited automatic dependency generation for now):
CXX := g++
CXXFLAGS := -std=c++17 -pedantic -Wall -Wextra -fno-rtti
SRCDIR := $(CURDIR)/src # sources
OBJDIR := $(CURDIR)/obj # objects
INCDIR := $(CURDIR)/inc # generated dependencies
OUTDIR := $(CURDIR)/out # executables
# target programs
TERM_CALC := term-calc
GUI_CALC := gui-calc
all: debug
# debug flags
debug: CXXFLAGS += -O0 -g -fsanitize=address
# debug objects and executables go into /debug subdirectory
debug: OBJDIR += /debug
debug: OUTDIR += /debug
# release flags
release: CXXFLAGS += -Os -ffunction-sections -fdata-sections -flto -fno-ident
release: LDFLAGS += -Wl,-gc-sections -s -flto
# release objects and executables go into /release subdirectory
release: OBJDIR += /release
release: OUTDIR += /release
# common objects
OBJECTS := $(OBJDIR)/scanner.o $(OBJDIR)/parser.o
# target-specific objects
$(TERM_CALC): OBJECTS += $(OBJDIR)/term-calc.o
$(GUI_CALC): OBJECTS += $(OBJDIR)/gui-calc.o
# TARGET_NAME is name of program to build
# ensure it is valid, if defined
ifdef TARGET_NAME
ifneq ($(TARGET_NAME),$(TERM_CALC))
ifneq ($(TARGET_NAME),$(GUI_CALC))
$(error Invalid target name '$(TARGET_NANE)')
endif
endif
endif
ifdef TARGET_NAME
# how to build target program
$(TARGET_NAME): $(OUTDIR)/$(TARGET_NAME)
$(OUTDIR)/$(TARGET_NAME): $(OBJECTS)
#mkdir -p $(OUTDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $^ -o $#
endif
# how to build objects
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
#mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $#
# if TARGET_NAME is not specified, just compile objects
TARGET_NAME ?= $(OBJECTS)
debug : $(TARGET_NAME)
release : $(TARGET_NAME)
# clean everything
clean:
$(RM) -r $(OBJDIR) $(OUTDIR) $(INCDIR)
.PHONY = all debug release clean
Unfortunately it absolutely does not work, while writing this makefile, I was getting many errors but even after fixing all of them as I thought, I still get:
Makefile:52: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target '/%.cpp', needed by '/root/cpp/calc/obj'. Stop.

How can my makefile include subdirectories?

(updated for clarity) (solution added at bottom)
I found a makefile online which builds all the cpp files in that directory and compiles them.
But I can't work out how I can include files inside a subdirectory.
Here's a breakdown of what happens:
I create the files test.cpp & test.hpp and place them inside the sub-directory '/gui' which is contained within my working directory, they contain the function testFunction().
Without including test.hpp, I type "make" into terminal and I receive the error:
:
g++ -c -o main.o main.cpp
main.cpp: In function 'int main(int, char**)':
main.cpp:6:2: error: 'testFunction' was not declared in this scope
testFunction();
^~~~~~~~~~~~
make: *** [<builtin>: main.o] Error 1
If I include (#include "gui/test.hpp"), I then receive a different error:
:
g++ -c -o main.o main.cpp
g++ main.o -Wall -o testfile
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x14): undefined reference to `testFunction()'
collect2: error: ld returned 1 exit status
make: *** [makefile:34: testfile] Error 1
But if I then add "-I/gui" or (at a guess) "-I./gui" to CFLAGS, I get the exact same error message.
Here's the makefile for reference:
TARGET = testfile
LIBS =
CC = g++
CFLAGS = -g -Wall
.PHONY: default all clean
default: $(TARGET)
all: default
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
HEADERS = $(wildcard *.hpp)
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c $< -o $#
.PRECIOUS: $(TARGET) $(OBJECTS)
$(TARGET): $(OBJECTS)
$(CC) $(OBJECTS) -Wall $(LIBS) -o $#
clean:
-rm -f *.o
-rm -f $(TARGET)
Thanks in advance!
Updated makefile since accepted answer:
(Changes were to include directories, CC replaced with CXX, and %.c replaced with %.cpp)
TARGET = testfile
DIRS =
LDLIBS =
CXX = g++
CXXFLAGS= -g -Wall
# this ensures that if there is a file called default, all or clean, it will still be compiled
.PHONY: default all clean
default: $(TARGET)
all: default
# substitute '.cpp' with '.o' in any *.cpp
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp $(addsuffix /*.cpp, $(DIRS))))
HEADERS = $(wildcard *.h)
# build the executable
%.o: %.cpp $(HEADERS)
$(CXX) $(CXXFLAGS) -c $< -o $#
# if make is interupted, dont delete any object file
.PRECIOUS: $(TARGET) $(OBJECTS)
# build the objects
$(TARGET): $(OBJECTS)
$(CXX) $(OBJECTS) -Wall $(LDLIBS) -o $#
clean:
-rm -f *.o $(addsuffix /*.o, $(DIRS))
-rm -f $(TARGET)
To understand what's happening here you have to look up the definitions of declaration versus definition in C++ (and other languages). You should definitely do that.
A declaration (typically put into a header file) is like the address of your house. If someone wants to send you a letter, they need your address. If your main function wants to call another function like testFunction(), it needs the declaration of the function.
The first error happens because you don't have the header file included, so the compiler doesn't have the declaration of the function you want to call, which means it won't compile your calling function.
But for the letter to actually arrive, you need your actual house. The address is the declaration and your house is the definition... in this case the actual function implementation. That lives in test.cpp file. When you link your code together, the linker (in this scenario I guess the linker is like the postal service :p :) ) will try to link up the call to the definition.
However, you can see that you are not compiling the test.cpp file nor are you linking the object file:
g++ main.o -Wall -o testfile
here we see main.o, but not gui/test.o.
Why not? This line:
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
Matches all *.cpp files and converts them into .o files. But *.cpp matches only files in the current directory, like main.cpp. If you want to put files in a different directory you have to tell make where they are; for example:
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp gui/*.cpp))

What would be the minimal Makefile for a C project?

I find plenty of answers such as this one that doesn't use the implicit rules.
The minimum I can write is this:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
EXEC=a.exe
all: $(EXEC)
$(EXEC): $(OBJ)
$(CC) $^ -o $#
clean:
$(RM) $(OBJ)
$(RM) $(EXEC)
But I am sure I can remove the linking part as well.
Is it possible to reduce this Makefile a bit more?
EDIT
With the help of Maxim Egorushkin I wrote this:
#Makefile
OBJS=$(patsubst %.c,%.o,$(wildcard *.c))
EXEC=a
$(EXEC): $(OBJS)
all : $(EXEC)
clean :
rm -f $(OBJS)
.PHONY: all clean
It does build my files, but it doesn't link anything:
$ make
cc -c -o bar.o bar.c
cc -c -o cow.o cow.c
cc -c -o foo.o foo.c
What should I change?
The dummy source files are created as follow:
echo "int main() {return 0;}" > cow.c
touch foo.c bar.c cow.c
The bare minimum would be:
all : a
a : a.o b.o c.o
clean :
rm -f a a.o
.PHONY: all clean
It expects source files a.c, b.c and c.c to produce executable a:
$ touch a.c b.c c.c
$ make
cc -c -o a.o a.c
cc -c -o b.o b.c
cc -c -o c.o c.c
cc a.o b.o c.o -o a
/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
<builtin>: recipe for target 'a' failed
make: *** [a] Error 1
However, you do not get automatic header dependency generation with the built-in GNU make rules. Extra 5 lines would be required for that.

How to maintain sequence of multiple targets in a makefile with -j option

I have a script which generates C++ files. First I want to generate these files.
Then compile them in a library.
I want to do this in a single Makefile.
TARGETS = GEN_FILE LIBNAME
GEN_FILE: input
BINARYTOGEN input
OBJ =(GENERATED_FILES:.cpp-.o)
LIBNAME: $(OBJ)
cc $(OBJ)
How can I make sure target GEN_FILE always run before LIBNAME in a parallel environment when used –j option.
One way is .NOTPARALLEL.
.generated_marker: a.gen b.gen
touch a.cpp
touch b.cpp
touch $#
SRCS = a.cpp b.cpp
$(SRCS): .generated_marker
OBJ = $(SRCS:.cpp=.o)
OUT = ./libutils.a
.PHONY: all
all: $(OUT)
.SUFFIXES: .cpp
LIBS = -L…
INCLUDES = -I…..
.cpp.o:
gcc $(INCLUDES) -g -c $< -o $#
$(OUT): $(OBJ)
ar rcs $(OUT) $(OBJ)
If your generator processes single source file and generates single output file, it is simple - just add pattern rule, e.g.:
all: a.out
%.c: %.gen
cp $< $#
SRCS:=a.c b.c
a.out: $(SRCS)
gcc $^
(I use cp for simplicity, it should be your actual generator, e.g. flex, bison, lemon, .....)
It is possible your generator produces a lot of files at once (or even all of them), then you probably need to create a flag file which timestamp will indicate latest re-generation, e.g.:
all: a.out
.generated_marker: a.gen b.gen
touch a.c
touch b.c
touch $#
SRCS:=a.c b.c
$(OBJS):=$(SRCS:.c=.o)
$(OBJS): .generated_marker
a.out: $(OBJS)
gcc $^

how Makefiles work exactly

I'm writing a code for my gtk application written in C, and have some questions about it.
# Compiler
CC = gcc
CFLAGS = -Wall -g -o
RM = rm -f
# ADDITIONAL HEADER PATH
GTKINC = `pkg-config --cflags gtk+-3.0`
GTKLIB = `pkg-config --libs gtk+-3.0`
INC = $(GTKINC)
LIBLNK = $(GTKLIB)
# SOURCES, OBJECTS, EXECUTABLE
SRCS = hello.c
OBJS = $(SRCS:.c = .o)
EXEC = hello
.PHONY: clean
all: $(EXEC)
#echo compile complete
$(EXEC): $(OBJS)
$(CC) $(INC) $(CFLAGS) $(EXEC) $(OBJS) $(LIBLNK)
clean:
$(RM) *.o *~ $(EXEC)
previously, when I wrote Makefiles,I added lines for each object files
for example
blah blah
a.o: 1.h A.c B.c
$(CC) blah blah
blah blah
and then, I got a little lazy and tried to do make something more easy-to-modify file
googling up, and finally the product is the above code.
1. Does this actually do the same thing as what I did previously?(like in the example)
I found out the code compiles properly, but I'm not sure if it checks out-of-date
object files.(which is the whole meaning of 'make')
2. do you have to use 'depend' on header files in order to check out-of-date source files??
3. it's a bit out of subject, but what's the difference between
gcc -o hello.o hello.h hello.c and
gcc -c hello.c ?
2. do you have to use 'depend' on header files in order to check out-of-date source files
You should auto-generate dependencies on header files. See https://stackoverflow.com/a/9598716/412080

Resources