Compiled binary path based on compiler options - c++

I have a large program and library written in C++ and built with make. About a dozen options that are set in the makefile turn into preprocessor directives that change the implementation (with ifdefs, etc.). Right now I'm brute-forcing my build process by make cleaning when I change these compiler options before I run the code. I'd like to set up the system so that the binary's name changes based on the options. But, I'm worried that I'm going to miss one or add one in the future and forget to change the name, etc. Is there a clean way to handle this problem?
Some options I've considered:
manually create a binary name, such as APP.#{OPT_1}.#{OPT_2}.#{OPT_3}.#{OPT_4} when I build, and then run that one
create a hash (e.g., SHA1) from all of the compiler flags (CXXFLAGS) and put that hash into the name of my binary, such as APP.#{SHA1(CXXFLAGS)}. This has the value of extensibility in the future.
Any better approaches / recommendations?

Any better approaches / recommendations?
If I understand correctly, your GNU Make build system can build several variants of
your executable, differentiated by preprocessor macros that are defined (or not)
in the compilation commands depending on conditions that are tested in your Makefile
and/or on the arguments that you pass to make. And you want to be able to build
any of these variants independently, without needing a make clean to remove the
artifacts of the previous build, which might well have been a build of a different
variant.
This is one of the basic needs of build systems. The conventional solution is not the
one you're thinking about - to somehow encode differentiations into the name of
the executable. That won't work anyway, unless you do the same thing with the names of
the object files that are linked into the executable. If you don't, then when
you switch from variant X to variant Y, a variant-X object file foo.o
that is not older than foo.cpp, will not need to be recompiled,
even if should be for variant-Y, and that variant-X foo.o will be linked into the
variant Y executable, no matter what it is called.
The conventional solution is to differentiate, per variant, the place where the compiler
will output the object files and correspondingly the place where the linker
outputs the executable. No doubt all of the C/C++ IDEs you have ever used allow you
to build either a debug variant or a release variant of your project, and
they differentiate the debug object files and executable from the release object
files and executables by generating them in different subdirectories of the
project directory, e.g.
<projdir>/Debug/{obj|bin}
<projdir>/Release/{obj|bin}
or maybe:
<projdir>/obj/{debug|release}
<projdir>/bin/{debug|release}
This approach automatically encodes the variant of an object file or executable
into its absolute pathname,e.g.
<projdir>/Debug/obj/foo.o
<projdir>/bin/release/prog
without any further ado, and the variants can be built independently.
It's straightforward to implement this scheme in a makefile. Most of the IDEs
that use it do implement it in the makefiles that they generate behind the scenes.
And it's also straightforward to extend the scheme to more variants than just debug
and release (although whatever variants you want, you'll certainly want debug
and release variants of those variants).
Here's an illustration for a toy program that we want to build in any of the
variants that we get for combinations of two build-properties that we'll call
TRAIT_A and TRAIT_B:
| TRAIT_A | TRAIT_B |
|---------|---------|
| Y | Y |
|---------|---------|
| Y | N |
|---------|---------|
| N | Y |
|---------|---------|
| N | N |
And we want to be able to build any of those variants in debug mode or release
mode. TRAIT_{A|B} might map directly to a preprocessor macro, or to an
arbitrary combination of preprocessor flags, compiler options and/or linkage options.
Our program, prog, is built from just one source file:
main.cpp
#include <string>
#include <cstdlib>
int main(int atgc, char * argv[])
{
std::string cmd{"readelf -p .GCC.command.line "};
cmd += argv[0];
return system(cmd.c_str());
}
And all it does is invoke readelf to dump the linkage section .GCC.command.line
within its own executable. That linkage section only exists when we compile or
link with the GCC option -frecord-gcc-switches.
So purely for the purpose of the demo we'll always compile and link with that option.
Here's a makefile that adopts one way of differentiating all the variants:
object files are compiled in ./obj[/trait...]; executables are linked in
./bin[/trait...]:
Makefile
CXX = g++
CXXFLAGS := -frecord-gcc-switches
BINDIR := ./bin
OBJDIR := ./obj
ifdef RELEASE
ifdef DEBUG
$(error RELEASE and DEBUG are mutually exclusive)
endif
CPPFLAGS := -DNDEBUG
CXXFLAGS += -O3
BINDIR := $(BINDIR)/release
OBJDIR := $(OBJDIR)/release
endif
ifdef DEBUG
ifdef RELEASE
$(error RELEASE and DEBUG are mutually exclusive)
endif
CXXFLAGS += -O0 -g
BINDIR := $(BINDIR)/debug
OBJDIR := $(OBJDIR)/debug
endif
ifdef TRAIT_A
CPPFLAGS += -DTRAIT_A # or whatever
BINDIR := $(BINDIR)/TRAIT_A
OBJDIR := $(OBJDIR)/TRAIT_A
endif
ifdef TRAIT_B
CPPFLAGS += -DTRAIT_B # or whatever
BINDIR := $(BINDIR)/TRAIT_B
OBJDIR := $(OBJDIR)/TRAIT_B
endif
SRCS := main.cpp
OBJS := $(OBJDIR)/$(SRCS:.cpp=.o)
EXE := $(BINDIR)/prog
.PHONY: all clean
all: $(EXE)
$(EXE): $(OBJS) | $(BINDIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $# $(LDFLAGS) $^ $(LIBS)
$(OBJDIR)/%.o: %.cpp | $(OBJDIR)
$(CXX) -c -o $# $(CPPFLAGS) $(CXXFLAGS) $<
$(BINDIR) $(OBJDIR):
mkdir -p $#
clean:
$(RM) $(EXE) $(OBJS)
Now let's build, say, two variants in debug mode and two other variants in
release mode, one after the other
$ make DEBUG=1 TRAIT_A=1
mkdir -p obj/debug/TRAIT_A
g++ -c -o obj/debug/TRAIT_A/main.o -DTRAIT_A -frecord-gcc-switches -O0 -g main.cpp
mkdir -p bin/debug/TRAIT_A
g++ -DTRAIT_A -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_A/prog obj/debug/TRAIT_A/main.o
$ make DEBUG=1 TRAIT_B=1
mkdir -p obj/debug/TRAIT_B
g++ -c -o obj/debug/TRAIT_B/main.o -DTRAIT_B -frecord-gcc-switches -O0 -g main.cpp
mkdir -p bin/debug/TRAIT_B
g++ -DTRAIT_B -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_B/prog obj/debug/TRAIT_B/main.o
$ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
mkdir -p obj/release/TRAIT_A/TRAIT_B
g++ -c -o obj/release/TRAIT_A/TRAIT_B/main.o -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 main.cpp
mkdir -p bin/release/TRAIT_A/TRAIT_B
g++ -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 -o bin/release/TRAIT_A/TRAIT_B/prog obj/release/TRAIT_A/TRAIT_B/main.o
$ make RELEASE=1
g++ -c -o obj/release/main.o -DNDEBUG -frecord-gcc-switches -O3 main.cpp
g++ -DNDEBUG -frecord-gcc-switches -O3 -o bin/release/prog obj/release/main.o
That last one is the release variant with neither TRAIT_A nor TRAIT_B.
We've now built four versions of program prog in different ./bin[/...] subdirectories
of the project, from different object files that are in different ./obj[/...] subdirectories,
and those versions will all tell us how they were differently built. Running in the order
we built them:-
$ bin/debug/TRAIT_A/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D TRAIT_A
[ 36] main.cpp
[ 3f] -mtune=generic
[ 4e] -march=x86-64
[ 5c] -auxbase-strip obj/debug/TRAIT_A/main.o
[ 84] -g
[ 87] -O0
[ 8b] -frecord-gcc-switches
[ a1] -fstack-protector-strong
[ ba] -Wformat
[ c3] -Wformat-security
$ bin/debug/TRAIT_B/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D TRAIT_B
[ 36] main.cpp
[ 3f] -mtune=generic
[ 4e] -march=x86-64
[ 5c] -auxbase-strip obj/debug/TRAIT_B/main.o
[ 84] -g
[ 87] -O0
[ 8b] -frecord-gcc-switches
[ a1] -fstack-protector-strong
[ ba] -Wformat
[ c3] -Wformat-security
$ bin/release/TRAIT_A/TRAIT_B/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D NDEBUG
[ 35] -D TRAIT_A
[ 40] -D TRAIT_B
[ 4b] main.cpp
[ 54] -mtune=generic
[ 63] -march=x86-64
[ 71] -auxbase-strip obj/release/TRAIT_A/TRAIT_B/main.o
[ a3] -O3
[ a7] -frecord-gcc-switches
[ bd] -fstack-protector-strong
[ d6] -Wformat
[ df] -Wformat-security
$ bin/release/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D NDEBUG
[ 35] main.cpp
[ 3e] -mtune=generic
[ 4d] -march=x86-64
[ 5b] -auxbase-strip obj/release/main.o
[ 7d] -O3
[ 81] -frecord-gcc-switches
[ 97] -fstack-protector-strong
[ b0] -Wformat
[ b9] -Wformat-security
We can clean the first one:
$ make DEBUG=1 TRAIT_A=1 clean
rm -f ./bin/debug/TRAIT_A/prog ./obj/debug/TRAIT_A/main.o
And the last one:
$ make RELEASE=1 clean
rm -f ./bin/release/prog ./obj/release/main.o
The second and third are still there and up to date:
$ make DEBUG=1 TRAIT_B=1
make: Nothing to be done for 'all'.
$ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
make: Nothing to be done for 'all'.
For the exercise, you might consider refining the makefile to let you build, or clean,
all the variants at the same time. Or to default to DEBUG if RELEASE not is defined, or vice versa. Or to fail if no valid combination of traits is selected, for some definition of valid.
BTW, note that preprocessor options are conventionally assigned in the make variable
CPPFLAGS, for either C or C++ compilation; C compiler options are assigned
in CFLAGS and C++ compiler options in CXXFLAGS. GNU Make's built-in
rules assume that you follow these conventions.

I'm not sure it is a good idea to just use the binary name to separate different build configurations. Won't a change in the compiler options still cause the object files to be stomped on since I presume they will be named the same as the source files, and it will still effectively be a full rebuild anyway?
This looks to me as a prime candidate for out-of-source builds. Configure your build scripts to create all intermediate and output files in a separate directory outside the source directory. Each different set of build options will use a different build directory, perhaps choosing a directory name dynamically based on the hash of the compiler flags as you suggested.
This will give you a clean source tree, and by changing the compiler options in the makefile/build scripts you will change to which directory the intermediate and output files will be placed.

Related

Make: How to write makefile with both hpp and cpp file mixture project

I am trying to make a makefile for my project which makes out of a Main.cpp file which includes some *.hpp/h files (calling it L.hpp,D.h,UC.hpp). The header files(mainly the hpp) contain both implementation and declaration. In addition, Main.cpp and L.hpp uses a class object C. Below is a diagram of its relationship.
After which I proceed to write a makefile for it (as below). The common variables eg CXX are left out (as it is written in an environment script and I just source the file before running the makefile). The additional variables ($(MISC), $(ADD_INC), $(LIB_LOC) $(LDLIBS)) are various includes, libraries, compilation settings that main.cpp and L.hpp requires
###
### Library file
###
LDLIBS = -lxml2 -ldl -lgstpbutils-1.0 -lgstsdp-1.0 -lgstapp-1.0 -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 -lpthread -lstdc++ -lnsl -lm
LIB_PKG = -L${SDKTARGETSYSROOT}/usr/lib/pkgconfig
LIB_GST = -L${SDKTARGETSYSROOT}/usr/lib/gstreamer
LIB_PRJ = -L${SDKTARGETSYSROOT}/usr/lib
LIB_LOC = $(LIB_PKG) $(LIB_GST) $(LIB_PRJ)
###
### Header file dependency list
###
# include for gstreamer
INC_GST = -I$(SDKTARGETSYSROOT)/usr/include/gstreamer-1.0/
# include for glib
INC_GLIB = -I$(SDKTARGETSYSROOT)/usr/include/glib-2.0
INC_GLIB_CONFIG = -I$(SDKTARGETSYSROOT)/usr/lib/glib-2.0/include
ADD_INC = $(INC_GST) $(INC_GLIB) $(INC_GLIB_CONFIG)
MISC = -std=c++11 -c -fpermissive -fmessage-length=0 $(shell pkg-config --cflags)
# partial makefile
#
# Define the Target name and main object list
#
FINAL_TARGET = VS2
OBJ = ../Main.cpp C.o
#
# Build Rules
#
all: $(FINAL_TARGET)
C.o: ../C.cpp ../C.h
$(CXX) $(CXXFLAGS) $(MISC) -c $< -o $# $(LDFLAGS) $(LIB_LOC) $(LDLIBS)
$(FINAL_TARGET): $(OBJ)
$(CXX) $(CXXFLAGS) $(ADD_INC) $(MISC) -o $(FINAL_TARGET) $(OBJ) $(LDFLAGS) $(LIB_LOC) $(LDLIBS)
clean:
rm -rf $(FINAL_TARGET) *.o
The compilation, apart from getting C.o: linker input file unused because linking not done as a warning completed without any other problem. However, the end product of the VS2 seem not to be running properly when transplanted to the environment and run. I have used file VS2 and found that VS2 is actually a relocatable files (seem like linking not done)
So I have the following question:
Am I writing the makefile correctly? by only compiling for main.cpp and C.cpp
How should i include hpp file? Have read the Write makefile for .cpp and .hpp c++. Can't say I fully understand the author but it seem to call for putting in lines as
L.o: ../L.hpp
OBJ = ../Main.cpp C.o L.o
Need your advice
==================================================================
EDIT For more information
Basically I just source the file below before I make using the makefile above
# Check for LD_LIBRARY_PATH being set, which can break SDK and generally is a bad practice
# http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html#AEN80
# http://xahlee.info/UnixResource_dir/_/ldpath.html
# Only disable this check if you are absolutely know what you are doing!
if [ ! -z "$LD_LIBRARY_PATH" ]; then
echo "Your environment is misconfigured, you probably need to 'unset LD_LIBRARY_PATH'"
echo "but please check why this was set in the first place and that it's safe to unset."
echo "The SDK will not operate correctly in most cases when LD_LIBRARY_PATH is set."
echo "For more references see:"
echo " http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html#AEN80"
echo " http://xahlee.info/UnixResource_dir/_/ldpath.html"
return 1
fi
export SDKTARGETSYSROOT=/opt/wayland/sysroots/cortexa53-crypto-poky-linux
export PATH=/opt/wayland/sysroots/x86_64-pokysdk-linux/usr/bin:/opt/wayland/sysroots/x86_64-pokysdk-linux/usr/sbin:/opt/wayland/sysroots/x86_64-pokysdk-linux/bin:/opt/wayland/sysroots/x86_64-pokysdk-linux/sbin:/opt/wayland/sysroots/x86_64-pokysdk-linux/usr/bin/../x86_64-pokysdk-linux/bin:/opt/wayland/sysroots/x86_64-pokysdk-linux/usr/bin/aarch64-poky-linux:/opt/wayland/sysroots/x86_64-pokysdk-linux/usr/bin/aarch64-poky-linux-musl:$PATH
export PKG_CONFIG_SYSROOT_DIR=$SDKTARGETSYSROOT
export PKG_CONFIG_PATH=$SDKTARGETSYSROOT/usr/lib/pkgconfig:$SDKTARGETSYSROOT/usr/share/pkgconfig
export CONFIG_SITE=/opt/wayland/site-config-cortexa53-crypto-poky-linux
export OECORE_NATIVE_SYSROOT="/opt/wayland/sysroots/x86_64-pokysdk-linux"
export OECORE_TARGET_SYSROOT="$SDKTARGETSYSROOT"
export OECORE_ACLOCAL_OPTS="-I /opt/wayland/sysroots/x86_64-pokysdk-linux/usr/share/aclocal"
export OECORE_BASELIB="lib"
export OECORE_TARGET_ARCH="aarch64"
export OECORE_TARGET_OS="linux"
unset command_not_found_handle
export CC="aarch64-poky-linux-gcc -mcpu=cortex-a53 -march=armv8-a+crc+crypto -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=$SDKTARGETSYSROOT"
export CXX="aarch64-poky-linux-g++ -mcpu=cortex-a53 -march=armv8-a+crc+crypto -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=$SDKTARGETSYSROOT"
export CPP="aarch64-poky-linux-gcc -E -mcpu=cortex-a53 -march=armv8-a+crc+crypto -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=$SDKTARGETSYSROOT"
export AS="aarch64-poky-linux-as "
export LD="aarch64-poky-linux-ld --sysroot=$SDKTARGETSYSROOT"
export GDB=aarch64-poky-linux-gdb
export STRIP=aarch64-poky-linux-strip
export RANLIB=aarch64-poky-linux-ranlib
export OBJCOPY=aarch64-poky-linux-objcopy
export OBJDUMP=aarch64-poky-linux-objdump
export READELF=aarch64-poky-linux-readelf
export AR=aarch64-poky-linux-ar
export NM=aarch64-poky-linux-nm
export M4=m4
export TARGET_PREFIX=aarch64-poky-linux-
export CONFIGURE_FLAGS="--target=aarch64-poky-linux --host=aarch64-poky-linux --build=x86_64-linux --with-libtool-sysroot=$SDKTARGETSYSROOT"
export CFLAGS=" -O2 -pipe -g -feliminate-unused-debug-types "
export CXXFLAGS=" -O2 -pipe -g -feliminate-unused-debug-types "
export LDFLAGS="-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-z,relro,-z,now"
export CPPFLAGS=""
export KCFLAGS="--sysroot=$SDKTARGETSYSROOT"
export OECORE_DISTRO_VERSION="3.3.0"
export OECORE_SDK_VERSION="3.3.0"
export ARCH=arm64
export CROSS_COMPILE=aarch64-poky-linux-
# Append environment subscripts
if [ -d "$OECORE_TARGET_SYSROOT/environment-setup.d" ]; then
for envfile in $OECORE_TARGET_SYSROOT/environment-setup.d/*.sh; do
. $envfile
done
fi
if [ -d "$OECORE_NATIVE_SYSROOT/environment-setup.d" ]; then
for envfile in $OECORE_NATIVE_SYSROOT/environment-setup.d/*.sh; do
. $envfile
done
fi
=================================================================
Edit for #n. 1.8e9-where's-my-share m. [CORRECTED]
After make, I have noticed this message as part of output on terminal
C.o: linker input file unused because linking not done
And copying VS2 to the environment, I execute the file VS2
root#z-mx8mm:/usr/local# ./VS2
-sh: ./VS2: cannot execute binary file: Exec format error
root#z-mx8mmt:/usr/local#
At first I have thought of maybe that the file permission is not set properly
root#z-mx8mmt:/usr/local# chmod +x VS2
But the above error still remains. Then I use file command
root#z-mx8mmt:/usr/local# file VS2
VS2: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
That's how i derive the conclusion
Exhibit A:
$(FINAL_TARGET): $(OBJ)
$(CXX) $(CXXFLAGS) $(ADD_INC) $(MISC) ....
Exhibit B:
MISC = -std=c++11 -c ...
-c produces an object file, not an executable. You don't need any of these MISC flags when linking.

How to correctly setup a make file for a project?

Vulkan SDK hosts a C++ header-only C-wrapped library. Problem is, it is really a heavy header, so it wastes a lot of compilation time.
So I looked into using a make file for my Vulkan project, which I never did before. I decided to programatically generate all the dependencies. Probably I messed up this part as well, making it too difficult to understand.
It does kind of work, probably there is an easier way to do the same thing(s).
Here comes the part I can't really work out with:
I generated all the .o (object) files for my project along with the .gch (precompiled headers)
Then I did the same for the vulkan.hpp and the glfw3.h. (Remember, that's where most of my compilation time is going into).
... And here is where I have several problems about this procedure:
Can I, somehow, force the g++ compiler to use .gch files? The combination of -I, -include doesn't seem to work at all and I cannot even debug what is going on with -H/-M/-MM because i have no output whatsoever from this commands.
I am using MSYS2 MINGW_64 release, if it makes a difference.
I'd be glad if someone could give me a tip or two about make files and, in particular, if there is something I completely misunderstood about the compilation process.
# Methods
define uniq =
$(eval seen :=)
$(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
${seen}
endef
# Compilation flags
_COMPILER := g++ -std=c++17
_FLAGS_WARNING := -Wall -Wextra -Wshadow
_FLAGS_COMPILE := -g -O0
_FLAGS_VULKAN := -lglfw3 -lvulkan
# Custom Flags
_FLAGS_DEBUG := -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0
# Output
_OUTPUT_NAME := test
# Directories
_TMP_DIR := _tmp
_O_DIR := ${_TMP_DIR}\.o
_GCH_DIR := ${_TMP_DIR}\.gch
_SOURCE_DIR := src
_BUILD_DIR := build
_DEBUG_DIR := ${_BUILD_DIR}\debug
_RELEASE_DIR := ${_BUILD_DIR}\release
# Files
_VULKAN_HPP := C:/VulkanSDK/1.2.148.1/Include/vulkan/vulkan.hpp
_GLFW_HPP := C:/msys64/mingw64/include/GLFW/glfw3.h
# Grouping Files
# .cpp & .o
_CPP_LIST := ${wildcard ${_SOURCE_DIR}/*.cpp ${_SOURCE_DIR}/*/*.cpp}
_CPP_LIST_NAME := ${notdir ${_CPP_LIST}}
_O_LIST := ${addprefix ${_O_DIR}/, ${patsubst %.cpp, %.o, ${_CPP_LIST_NAME}}}
# .hpp & .gch
_HPP_LIST := ${wildcard ${_SOURCE_DIR}/*.hpp ${_SOURCE_DIR}/*/*.hpp} ${_VULKAN_HPP}
_HPP_LIST_NAME := ${notdir ${_HPP_LIST}}
_GCH_LIST := ${addprefix ${_GCH_DIR}/, ${patsubst %.hpp, %.gch, ${_HPP_LIST_NAME}}}
# .h & .gch
_H_LIST := ${_GLFW_HPP}
_H_LIST_NAME := ${notdir ${_H_LIST}}
_C_GCH_LIST := ${addprefix ${_GCH_DIR}/, ${patsubst %.h, %.gch, ${_H_LIST_NAME}}}
_COMPILATION_BUNDLE_CPP = ${_FLAGS_WARNING} ${_FLAGS_COMPILE} ${_FLAGS_VULKAN} ${_FLAGS_DEBUG} -I ${_GCH_DIR}
_COMPILATION_BUNDLE_HPP = ${_FLAGS_COMPILE} -x c++-header ${_FLAGS_DEBUG} -I ${_GCH_DIR}
${_DEBUG_DIR}/${_OUTPUT_NAME}: ${_GCH_LIST} ${_C_GCH_LIST} ${_O_LIST}
${_COMPILER} ${_COMPILATION_BUNDLE_CPP} ${_O_LIST} ${addprefix -include , ${_GCH_LIST}} ${addprefix -include , ${_C_GCH_LIST}} -o $#
vpath %.cpp $(call uniq, ${dir ${_CPP_LIST}})
${_O_DIR}/%.o: %.cpp
${_COMPILER} ${_COMPILATION_BUNDLE_CPP} -c $< -o $#
vpath %.hpp $(call uniq, ${dir ${_HPP_LIST}})
${_GCH_DIR}/%.gch: %.hpp
${_COMPILER} ${_COMPILATION_BUNDLE_HPP} $< -o $#
vpath %.h $(call uniq, ${dir ${_H_LIST}})
${_GCH_DIR}/%.gch: %.h
${_COMPILER} ${_COMPILATION_BUNDLE_HPP} $< -o $#
The execution on a clean project (thus easier to follow along):
g++ -std=c++17 -g -O0 -x c++-header -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch src/helper/helper.hpp -o _tmp\.gch/helper.gch
g++ -std=c++17 -g -O0 -x c++-header -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch src/renderer/renderer.hpp -o _tmp\.gch/renderer.gch
g++ -std=c++17 -g -O0 -x c++-header -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch C:/VulkanSDK/1.2.148.1/Include/vulkan/vulkan.hpp -o _tmp\.gch/vulkan.gch
g++ -std=c++17 -g -O0 -x c++-header -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch C:/msys64/mingw64/include/GLFW/glfw3.h -o _tmp\.gch/glfw3.gch
g++ -std=c++17 -Wall -Wextra -Wshadow -g -O0 -lglfw3 -lvulkan -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch -c src/main.cpp -o _tmp\.o/main.o
g++ -std=c++17 -Wall -Wextra -Wshadow -g -O0 -lglfw3 -lvulkan -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch -c src/helper/helper.cpp -o _tmp\.o/helper.o
g++ -std=c++17 -Wall -Wextra -Wshadow -g -O0 -lglfw3 -lvulkan -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch -c src/renderer/renderer.cpp -o _tmp\.o/renderer.o
g++ -std=c++17 -Wall -Wextra -Wshadow -g -O0 -lglfw3 -lvulkan -D _DEBUG -D _DEBUG_SEVERITY=0 -D _DEBUG_TYPE=0 -I _tmp\.gch _tmp\.o/main.o _tmp\.o/helper.o _tmp\.o/renderer.o -include _tmp\.gch/helper.gch -include _tmp\.gch/renderer.gch -include _tmp\.gch/vulkan.gch -include _tmp\.gch/glfw3.gch -o build\debug/test
Using pre-compiled headers is actually fairly straight forward. It might be easiest to break it down into manual steps. You can even retro fit to an existing project pretty easily. The steps could be:
Create a PCH file including a bunch of std libs or other headers you want to pre-compile. Note: Compile your PCH with the same flags as your other c++ code is compiled with, otherwise it may not work.
Add the include flags for the PCH in your makefile... and that is basically it.
So lets try a practical example:
Start with a source file, src1.cpp and compile it: g++ -I. <other flags> src1.cpp -o src1.o
Create your pch file, call it pch.hpp (or whatever)
Compile your PCH file: g++ -I. <other flags> pch.hpp (same flags as the previous compile). This generates the pch.hpp.gch file.
Now you can use your PCH file in your original compile line: g++ -I. <other flags> src1.cpp -o src1.o -Winvalid-pch -include pch.hpp
Couple of things to note:
If you use -Winvalid-pch warning flag this usually gives you enough information to figure out why the pch is not used (like its missing, bad path, bad options etc...).
Use -include pch.hpp not -include pch.hpp.gch since g++ will figure that out. You can see that retro fitting it is easy - since you just append the include flags to your compiler lines.
If your .gch file is not generated then the code should still compile using the pch.hpp header directly...

PRE_TARGETDEPS fails when using parallel builds

I'm using a custom ressource management (in replacement to qrc) and I'm trying to integrate it to QtCreaor.
I have a Python script that generates a source file to be compiled. I use QMAKE_EXTRA_TARGETS/PRE_TARGETDEPS to tell QMake that this script must be executed before files are compiled. So I do, in my pro file:
CONFIG += ordered
generated_file.target = my_custom_target
generated_file.commands = echo "Generating..." && d:/dev/vobs_ext_2015/tools_ext/python/Python34_light/python.exe $$PWD/pyc_res_generator.py -o $$PWD/generated/generated.cpp && echo "Generated!"
generated_file.depends = FORCE
QMAKE_EXTRA_TARGETS += generated_file
PRE_TARGETDEPS += my_custom_target
SOURCES += generated/generated.cpp
#DEPENDPATH = ./generated
With pyc_res_generator.py simply being:
#! /usr/bin/env python
# -*- coding: utf8 *-*
import argparse
parser = argparse.ArgumentParser(description="", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-o", type=str, help="Output file name")
args = parser.parse_args()
with open(args.o, 'w') as output_file:
output_file.write( "// This is valid C++!" )
To test this, I write some invalid C++ in generated.cpp (like fjkfkfk). When I compile (target is Android), I see in the log:
echo Generating... && python.exe pyc_res_generator.py -o generated/generated.cpp && echo Generated!
Generating...
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o main.obj ..\TestQt\main.cpp
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o dialog.obj ..\TestQt\dialog.cpp
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o generated.obj ..\TestQt\generated\generated.cpp
B:\QtCreator5_6_1\5.6\android_armv7\bin\moc.exe -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -IB:/QtCreator5_6_1/5.6/android_armv7/mkspecs/android-g++ -IC:/Users/jp225611/Documents/TestQt -IB:/QtCreator5_6_1/5.6/android_armv7/include -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtWidgets -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtGui -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtCore -I. -IB:\Android\android-ndk-r11b/sources/cxx-stl/gnu-libstdc++/4.9/include -IB:\Android\android-ndk-r11b/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include -IB:\Android\android-ndk-r11b/platforms/android-9/arch-arm//usr/include ..\TestQt\dialog.h -o moc_dialog.cpp
..\TestQt\generated\generated.cpp:1:1: error: 'fjkfkfk' does not name a type
// This is valid C++!
^
makefile:670: recipe for target 'generated.obj' failed
mingw32-make: *** [generated.obj] Error 1
mingw32-make: *** Waiting for unfinished jobs....
Generated!
13:59:08: Le processus "B:\QtCreator5_6_1\Tools\mingw492_32\bin\mingw32-make.exe" s'est terminé avec le code 2.
Erreur lors de la compilation/déploiement du projet TestQt (kit : android_armeabi-v7a)
When executing step "Make"
13:59:08: Temps écoulé : 00:03.
And I see that generated\generated.cpp was correctly generated by pyc_res_generator.py (it now contains // This is valid C++!)...but apparently too lately as the compiler saw fjkfkfk...
As you can see, the output reports:
Generating...
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ .... ..\TestQt\generated\generated.cpp
Generated...
While one would expect:
Generating...
Generated...
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ .... ..\TestQt\generated\generated.cpp
This only occurs when parallel builds are enabled (I set MAKEFLAGS environment variable to '-j8`)
It looks like my custom command generated_file is started before generated\generated.cpp is compiled, but the system does not wait for it to end before actually compiling generated\generated.cpp.
Am I doing something wrong?
PRE_TARGETDEPS (custom targets) is not the good approach here as generated_file.target must match exactly the Makefile target name which may be vary on different options:
shadow build enabled or not
host platform (using slashs or backslash as a folder separator)
probably targetted compiler/platform
However, custom compiler apparently works much better. That's how moccing files is done. A custom compiler can generate a new file to be compiled (and then the system waits for the file to be generated before compiling it, even when performing parallel builds).
# Set fuiles to be generated in a variable
TO_GENERATE = $$PWD/generated/generated.cpp
# Specify custom command output file:
custom_generator.output = $$PWD/generated/generated.cpp
# Specify custom command:
custom_generator.commands = 'echo "Generating..." && python $$PWD/pyc_res_generator.py -o $$PWD/generated/generated.cpp && echo "Generated..."'
# dependency:
custom_generator.depends = FORCE
# link to input file variable
custom_generator.input = TO_GENERATE
# link to variable to store generated file to
custom_generator.variable_out = SOURCES
# add to qmake:
QMAKE_EXTRA_COMPILERS += custom_generator
Then, you don't even need to specify SOURCES += $$PWD/generated/generated.cpp. And also, $$PWD/generated/generated.cpp gets deleted upon clean!
This works perfectly as expected!
You need to set the name of the target to the name of the file that it generates. Here's a full project file that works for me (qmake 3.1, Qt 5.10.1):
# Filename relative to the *build* directory.
generated_file.target = generated/generated.cpp
generated_file.commands = 'echo "Generating..." && mkdir -p generated && echo "int main() {}" > generated/generated.cpp'
generated_file.depends = FORCE
QMAKE_EXTRA_TARGETS += generated_file
# Filename relative to the *source* directory.
SOURCES += $$OUT_PWD/generated/generated.cpp
The reason is that generated_file.target becomes the name of the target in the Makefile, so when generated.cpp is needed, your custom rule will be executed to create the file.
You can also remove it from PRE_TARGETDEPS, because you're already adding the generated file to SOURCES instead, which creates the proper dependency.
Note that this approach generates the file in the build directory, not the source directory. This is why you need to specify $$OUT_PWD when adding it to SOURCES: by default, qmake seems to assume that SOURCES are relative to the source directory.
If you want to generate it in the source directory instead, you might think that you just prefix $$PWD everywhere, but then qmake sometimes uses absolute paths and sometimes relative, and everything fails. You can probably work around that with $$absolute_path($$PWD) but I haven't tried.
Relevant sections from the generated Makefile (comments are mine):
# How to create generated/generated.cpp.
# Depending on FORCE to make it always stale (fails if you create a file named FORCE :)).
generated/generated.cpp: FORCE
echo "Generating..." && mkdir -p generated && echo "int main() {}" > generated/generated.cpp
# generated.o depends on generated/generated.cpp.
# Note that it's not in the same directory; we don't care.
generated.o: generated/generated.cpp
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o generated.o generated/generated.cpp
# The final executable depends on the compiled generated.o.
OBJECTS = generated.o
$(TARGET): $(OBJECTS)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)
The qmake documentation is pretty vague about this, only stating that target should be "The name of the custom build target.". However, the example shows that they're setting it to the name of the output file. (As a bonus, it also shows how to not repeat that name in the commands.)
An alternative relies on the TEMPLATE=subdirs, to honour libname.depends in a parallel build environment. You would need two .pro files in each application folder. And one in the shared library folder. Any other folders in your development area that use TEMPLATE=subdirs need to be made compatible.
eg TestApp using LibLarry.
Folder TestApp has
bld.pro and TestApp.pro.
TestApp.pro:
#This is a TEMPLATE=subdirs, which gmake will interpret;
# making sure the build dependencies are respected
TEMPLATE = subdirs
SUBDIRS += bld mylib
bld.file = bld.pro
bld.depends = mylib # force library to be built first.
mylib.file = $${PATH_TO_LIB_LARRY}/liblarry.pro
bld.pro:
TARGET=TestApp
SOURCES+=TestApp.cpp
PRE_TARGETDEPS+=$${PATH_TO_LIB_LARRY}/liblarry.a
#boilerplate...
QT -= gui
CONFIG = ...
include ($${PATH_TO_LIB_LARRY}/liblarry.pri) # contains include file info
CONFIG += link_prl # allow lib larry to specify its position in the linker input command.
Folder LibLarry has
LibLarry.pro
containing
LibLarry.pro:
TARGET=larry
TEMPLATE=lib
CONFIG+=staticlib
CONFIG+=create_prl # export information, so apps can link correctly
SOURCES+=my_shared_function.cpp
include (liblarry.pri)
liblarry.pri:
HEADERS += $$PWD/my_shared_function.h
INCLUDEPATH += $$PWD

"'omp.h' file not found" when compiling using Clang

I'm trying to set up an OpenMP project using Clang (3.7.0) on my laptop running Linux Mint.
Now I've read that OpenMP is not supported right away so I followed the tutorial https://clang-omp.github.io/ to integrate OpenMP into Clang.
I've cloned the source code, set the environment variables and set the -fopenmp flag to my project, but I still get the error:
fatal error: 'omp.h' file not found
when building.
My guess is that I have set the environment variables wrong. Is there a way to check if I have put them in the right place? I have just copied them in the .bashrc file.
When I run locate omp.h I get:
/usr/include/re_comp.h
/usr/include/linux/ppp-comp.h
/usr/include/linux/seccomp.h
/usr/include/net/ppp-comp.h
/usr/include/openssl/comp.h
/usr/lib/gcc/x86_64-linux-gnu/4.8/include/omp.h
/usr/lib/perl/5.18.2/CORE/regcomp.h
/usr/src/linux-headers-3.13.0-24/arch/arm/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/microblaze/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/mips/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/powerpc/include/uapi/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/s390/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/sh/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/sparc/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/arch/x86/include/asm/seccomp.h
/usr/src/linux-headers-3.13.0-24/include/linux/ppp-comp.h
/usr/src/linux-headers-3.13.0-24/include/linux/seccomp.h
/usr/src/linux-headers-3.13.0-24/include/net/ipcomp.h
/usr/src/linux-headers-3.13.0-24/include/uapi/linux/ppp-comp.h
/usr/src/linux-headers-3.13.0-24/include/uapi/linux/seccomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/seccomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/crypto/pcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/inet/ipcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/inet6/ipcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/isdn/ppp/bsdcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/ppp/bsdcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/config/xfrm/ipcomp.h
/usr/src/linux-headers-3.13.0-24-generic/include/linux/ppp-comp.h
/usr/src/linux-headers-3.13.0-24-generic/include/linux/seccomp.h
Here is my makefile:
# Requires the following project directory structure:
# /bin
# /obj
# /src
# Use 'make remove' to clean up the whole project
# Name of target file
TARGET = main
CXX = clang++
CFLAGS = -std=c++11 \
-Weverything -Wall -Wextra -Wold-style-cast -Wpointer-arith -Wcast-qual \
-Wno-missing-braces -Wempty-body -Wno-error=uninitialized \
-Wno-error=deprecated-declarations -Wno-c++98-compat \
-pedantic-errors -pedantic \
-Os -fopenmp
LINKER = clang++ -o
LFLAGS = -Wall -Weverything -pedantic
SRCDIR = src
OBJDIR = obj
BINDIR = bin
SOURCES := $(wildcard $(SRCDIR)/*.cpp)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS := $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)
RM = rm -f
$(BINDIR)/$(TARGET): $(OBJECTS)
#$(LINKER) $# $(LFLAGS) $(OBJECTS)
#echo "Linking complete!"
$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
#$(CXX) $(CFLAGS) -c $< -o $#
#echo "Compiled "$<" successfully!"
.PHONEY: prepare
prepare:
mkdir -p bin
mkdir -p obj
.PHONEY: clean
clean:
#$(RM) $(OBJECTS)
#echo "Cleanup complete!"
#$(RM) tmp_file-*
#echo "Temporary files removed!"
.PHONEY: remove
remove: clean
#$(RM) $(BINDIR)/$(TARGET)
#echo "Executable removed!"
.PHONEY: run
run:
./bin/$(TARGET)
OpenMP is well supported in Clang 3.7, but you might need to enable it see here.
OpenMP 3.1 is fully supported, but disabled by default. To enable it,
please use the -fopenmp=libomp command line option.
Also see Status of supported OpenMP constructs for more precision.
So you don't have to clone the clang-omp project any more.
What build system do you use for your project and what errors do you get when you compile?
If you use Makefile: do not forget to add the -fopenmp flag.
If you use CMake: you should also look for right OpenMP flags with the FindOpenMP module and add them accordingly.
If you still get the include error, then your omp.h header file may not be in the Clang default search path. So you should try to include the one that comes with GCC and add -I/usr/lib/gcc/x86_64-linux-gnu/4.8/include/.
So in your case you should add this line:
CFLAGS = -std=c+11 [etc...]
CFLAGS += -I/usr/lib/gcc/x86_64-linux-gnu/4.8/include/
LINKER = [etc...]
'omp.h' is a C header that comes with the "Mint" libgcc-[version]-dev (RPM-based OSes have this header in a different package, e.g. libgomp-*).
Example libgcc-4.8-dev: /usr/lib/gcc/x86_64-linux-gnu/4.8/include/omp.h
Solution: Install the version for your default GCC: gcc --version
libgcc-dev
In case the context is a build of Clang from source - one solution is to:
Ensure the openmp subproject is built by adding it to LLVM_ENABLE_PROJECTS at CMake invocation
Build that subproject with cmake --build . --target omp
Copy the generated omp.h from build/projects/openmp/runtime/src/omp.h to build/lib/clang/10.0.0/include, which is in the newly-built Clang's default search path.
I previously used the "add GCC's path to omp.h to every build command" approach, but I found this easier.

Extending a simple makefile to support different implicit compiler flags

I have been using a makefile that was fairly straightforward. I defined OBJS with a list of .cc files. I set up dependencies and include flags and appended all of those to $CXXFLAGS. It looks something like this:
SRCS = file1.cc file2.cc file3.cc
OBJS = $(SRCS:.cc=.o)
CXXFLAGS=some flags
CXXFLAGS+=some include dirs
$(mylib.so): $OBJS
$CXX -shared -o $# $^
mylib.so uses the CXXFLAGS (implicitly) and everything builds just fine.
I have recently had the need to have mylib_1.so and mylib_2.so, in addition to mylib.so. Each .so depend on all the same .cc files, but the compiler flags are all different (including include directories).
How do I get it so I can set the compiler flags based on the target .so? The problem that I have is that if I set CXXFLAGS more than once it gets overwritten. It's almost like I need an if/else situation.
I tried doing something like setting three different flags, $CXXFLAGS1, $CXXFLAGS2, $CXXFLAGS3, and using those in the line
$(mylib1.so): $OBJS
$CXX $(CXXFLAGS1) -shared -o $# $^
but that does not work.
How do I accomplish what I am trying to do? Is it better to have 3 separate makefiles? I did find a way to get it to work. I can stop using $OBJS and spell out the flags explicitly for each source file but this seems like a horrible idea in terms of scaling to size.
Your CXXFLAGS1 in your example is only used at the stage of creating the .so file, not for compilation of the actual C++ sources (which is what you are trying to do, I assume).
To achieve the above, consider making the Makefile invoke itself 3 times for 3 different targets and pass CXXFLAGS (with different values) as part of MAKEFLAGS or in the command line.
Update: here's an example
all: build-lib1 build-lib2 build-lib3
build-lib1:
$(MAKE) $(MAKEFLAGS) CXXFLAGS="$(CXXFLAGS1)" lib1.so
build-lib2:
$(MAKE) $(MAKEFLAGS) CXXFLAGS="$(CXXFLAGS2)" lib2.so
build-lib3:
$(MAKE) $(MAKEFLAGS) CXXFLAGS="$(CXXFLAGS3)" lib3.so
$(lib1.so): $OBJS
$(CXX) -shared -o $# $^
etc...
Makefiles can have target-specific variable values. Something like:
$(mylib1.so): CXXFLAGS += -lib1flags
$(mylib2.so): CXXFLAGS += -lib2flags
$(mylib3.so): CXXFLAGS += -lib3flags
According to the documentation, the flags will propagate to prerequisite targets.
There is one more special feature of target-specific variables: when
you define a target-specific variable that variable value is also in
effect for all prerequisites of this target, and all their
prerequisites, etc. (unless those prerequisites override that variable
with their own target-specific variable value). So, for example, a
statement like this:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
will set CFLAGS to ‘-g’ in the recipe for prog, but it will also set
CFLAGS to ‘-g’ in the recipes that create prog.o, foo.o, and bar.o,
and any recipes which create their prerequisites.
I would do this with a recursive call to make. I would use two makefiles:
In Makefile:
all: mylib1.so mylib2.so
SRCS := file1.cc file2.cc file3.cc
mylib1.so: $(SRCS)
test -d mylib1 || mkdir mylib1
$(MAKE) -f ../lib.mak -C mylib1 TARGET=mylib1.so CXXFLAGS=-DMYLIB=1
cp mylib1/mylib1.so mylib1.so
mylib2.so: $(SRCS)
test -d mylib2 || mkdir mylib2
$(MAKE) -f ../lib.mak -C mylib2 TARGET=mylib2.so CXXFLAGS=-DMYLIB=2
cp mylib2/mylib2.so mylib2.so
In lib.mak, in the same directory:
VPATH = ..
SRCS := file1.cc file2.cc file3.cc
OBJS := $(SRCS:.cc=.o)
$(TARGET): $(OBJS)
$(CXX) -shared -o $# $^
The second makefile actually builds the library, but only uses one set of CXXFLAGS. The primary makefile calls the first makefile for each version with separate CXXFLAGS and in a separate directory. The VPATH makes it easier to compile source files that aren't in the same directory.
I tested this setup with a dry run,
test -d mylib1 || mkdir mylib1
make -f ../lib.mak -C mylib1 TARGET=mylib1.so CXXFLAGS=-DMYLIB=1
make[1]: Entering directory `/home/depp/Maketest2/mylib1'
g++ -DMYLIB=1 -c -o file1.o ../file1.cc
g++ -DMYLIB=1 -c -o file2.o ../file2.cc
g++ -DMYLIB=1 -c -o file3.o ../file3.cc
g++ -shared -o mylib1.so file1.o file2.o file3.o
make[1]: Leaving directory `/home/depp/Maketest2/mylib1'
cp mylib1/mylib1.so mylib1.so
test -d mylib2 || mkdir mylib2
make -f ../lib.mak -C mylib2 TARGET=mylib2.so CXXFLAGS=-DMYLIB=2
make[1]: Entering directory `/home/depp/Maketest2/mylib2'
g++ -DMYLIB=2 -c -o file1.o ../file1.cc
g++ -DMYLIB=2 -c -o file2.o ../file2.cc
g++ -DMYLIB=2 -c -o file3.o ../file3.cc
g++ -shared -o mylib2.so file1.o file2.o file3.o
make[1]: Leaving directory `/home/depp/Maketest2/mylib2'
cp mylib2/mylib2.so mylib2.so