I have come to the stackoverflow gurus for a better understanding and possibly a better solution to what is going on. So I'm typically pretty good with Makefiles but there is a critical piece that I am missing with my Makefile. So I typically have a one Makefile fits all for my projects and it works pretty well. Or it did until I found out that I wasn't linking the libraries properly. Since I discovered that my Makefile has been incorrect and I have been having trouble figuring out a way to solve this / have a better solution.
What I want to do
Let's say I have some .cpp files (ignore the awful naming)
src
core
rector
Reactor.cpp
Reactor.h
reactorprj
TestReactor.cpp
I would like to compile each of those .cpp to a corresponding .o file so that my build directory looks like this
build_dir
src
core
rector
Reactor.o
reactorprj
TestReactor.o
After each .cpp file is compiled to a .o file I would like to link all of the .o files together into an executable along with linking the libraries up into this point. So I can kind of do this but not.
Here's what I have
Makefile
include make_rules/config.mk
# Output of the program
OUTPUT := App.exe
# All CPP files to compile
CPP_SRC = $(SRC)/core/reactor/Reactor.cpp
CPP_SRC = $(SRC)/reactorprj/TestReactor.cpp
# Compile OBJ files (.o) to a build directory
CPP_OBJ := $(addprefix $(BLD_DIR), $(subst $(PRJ_DIR), ,$(CPP_SRC:.cpp=.o)))
# Directories on where to include header files
INC_DIR = $(SRC)
INC_DIR = /usr/local/lib/cppzmq
INC_DIR = /usr/local/lib/spdlog/include
# Dependency files
DEP := $(CPP_OBJ:.o=.d)
# The directory to look for the libraries
LIBS_DIR = /usr/lib
LIBS_DIR = /usr/local/lib/spdlog/build
# The library to link
LIBS = zmq
LIBS = pthread
LIBS = spdlog
.PHONY: all
all: $(OUTPUT)
$(OUTPUT): $(CPP_OBJ)
@$(ECHO)
@$(ECHO) Compiling all object files...
@$(ECHO) Linking....
$(VERBOSE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INC) -o $@ $(CPP_OBJ) $(LIBSPATHS) $(LDLIBS)
$(CPP_OBJ): $(CPP_SRC)
$(VERBOSE)$(MKDIR) -p '$(@D)'
@$(ECHO) 'Compiling $<'
$(VERBOSE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INC) -c $< -o $@
.PHONY: clean
clean:
$(RM) -rf $(BLD_DIR)
$(RM) -rf $(OUTPUT)
# Include the dependencies. Since the dependency files won't exist at first the -include supresses the warnings
-include $(DEP)
make_rules/config.mk
#############################
### General Config Rules
#############################
# Commands
ECHO := echo
MAKE := make
CAT := cat
RM := rm
MKDIR := mkdir
LS := ls
# Top Level Variables
PRJ_DIR := /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code
BLD_DIR := $(PRJ_DIR)/build_dir
SRC := $(PRJ_DIR)/src
# This is empty because we can add actual defines in the Makefile
DEFINES =
# Compiler
CXX := g
# Compiler flags
CXXFLAGS = -Wall -Wextra -Wsign-conversion -g -MMD -MP
# Preprocessor Flags
CPPFLAGS =
CPPFLAGS = $(addprefix -D, $(DEFINES))
# Directories on where to include header files
INC_DIR =
# Append -I to each directory
INC = $(INC_DIR:%=-I%)
# LIBS_DIR is used to specify the directories of the libraries
LIBS_DIR =
# Take LIBS_DIR and add -L to each
LIBSPATHS = $(LIBS_DIR:%=-L%)
# The library to link
LIBS =
# Add -l to each library
LDLIBS = $(LIBS:%=-l%)
Here is my result
So here is the result of doing a make all
20:39:41 **** Build of configuration Default for project ExistingMakefile ****
make VERBOSE= all
mkdir -p '/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor'
Compiling /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp
g -Wall -Wextra -Wsign-conversion -g -MMD -MP -I/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src -I/usr/local/lib/cppzmq -I/usr/local/lib/spdlog/include -c /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp -o /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor/Reactor.o
mkdir -p '/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj'
Compiling /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp
g -Wall -Wextra -Wsign-conversion -g -MMD -MP -I/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src -I/usr/local/lib/cppzmq -I/usr/local/lib/spdlog/include -c /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp -o /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj/TestReactor.o
Compiling all object files...
Linking....
g -Wall -Wextra -Wsign-conversion -g -MMD -MP -I/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src -I/usr/local/lib/cppzmq -I/usr/local/lib/spdlog/include -o App.exe /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor/Reactor.o /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj/TestReactor.o -L/usr/lib -L/usr/local/lib/spdlog/build -lzmq -lpthread -lspdlog
/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj/TestReactor.o: in function `Reactor::Reactor(int)':
/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:12: multiple definition of `Reactor::Reactor(int)'; /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor/Reactor.o:/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:12: first defined here
/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj/TestReactor.o: in function `Reactor::Reactor(int)':
/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:12: multiple definition of `Reactor::Reactor(int)'; /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor/Reactor.o:/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:12: first defined here
/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/reactorprj/TestReactor.o:/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:18: multiple definition of `main'; /cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/build_dir/src/core/reactor/Reactor.o:/cygdrive/c/Users/kmgag/Documents/eclipse-projects/existing-cpp-code/src/core/reactor/Reactor.cpp:18: first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:36: App.exe] Error 1
"make VERBOSE= all" terminated with exit code 2. Build might be incomplete.
So I can tell that Reactor.cpp is compiling to both Reactor.o and TestReactor.o which is causing the redefined error. So why is this happening. What should have happened was Reactor.cpp compiles to Reactor.o and TestReactor.cpp compiles to TestReactor.o
Update
Based off of @Sam Varshavchik's answer my new target that was in question now looks like this:
$(CPP_OBJ):
$(VERBOSE)$(MKDIR) -p '$(@D)'
$(eval CPP_FILE := $(addprefix $(PRJ_DIR), $(subst $(BLD_DIR), ,$(subst .o,.cpp,$@))))
@$(ECHO) 'Compiling $(CPP_FILE)'
$(VERBOSE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INC) -c $(CPP_FILE) -o $@
Since I am building to a different directory than my project files I ended up changing the prefix of the target from .o to .cpp then I removed the pattern of my build directory ($(BLD_DIR)) and then added the prefix for where my source code lies. I then put it in the $(CPP_FILE) to be used later for the target. Ends up working perfectly so I accepted Sam's answer! I'm sure there is probably a better way of solving the issue but for now I'm good with it.
CodePudding user response:
Your Makefile defines:
CPP_SRC = $(SRC)/core/reactor/Reactor.cpp
CPP_SRC = $(SRC)/reactorprj/TestReactor.cpp
To avoid confusion, I'll strip off all the directory names and just reference the filenames, from now on. So, this is, basically:
CPP_SRC = Reactor.cpp TestReactor.cpp
The following definition:
CPP_OBJ := $(addprefix $(BLD_DIR), $(subst $(PRJ_DIR), ,$(CPP_SRC:.cpp=.o)))
has the effect of defining:
CPP_OBJ = Reactor.o TestReactor.o
Again, ignoring the paths. So now, we arrive here:
$(CPP_OBJ): $(CPP_SRC)
If you take out a sheet of paper, and write out what all these macros expand to, this defines the following dependency:
Reactor.o TestReactor.o: Reactor.cpp TestReactor.cpp
This means that both Reactor.o
and TestReactor.o
depend on Reactor.cpp
, and TestReactor.cpp
. That's what this Makefile
syntax means. It doesn't mean that the first dependent file depends on the first dependency and the second dependent file depends on the second dependency. It means that both dependent files depend on both dependencies. This is effectively the same as:
Reactor.o: Reactor.cpp TestReactor.cpp
TestReactor.o: Reactor.cpp TestReactor.cpp
And, finally, we get here:
$(VERBOSE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INC) -c $< -o $@
If you check GNU make's documentation you will see that $<
expands to the first dependency.
And that's how both Reactor.o
and TestReactor.o
get compiled from Reactor.cpp
, and explains your Makefile
's incorrect behavior.
You will need to replace this incorrect dependency declaration:
$(CPP_OBJ): $(CPP_SRC)
You'll need to replace $(CPP_SRC)
with a macro that replaces the individual dependent target with the corresponding .cpp
file. Replacing $(CPP_SRC)
with a $(patsubst)
for $@
, replacing the extension, should do the trick.