I used to use -frecord-gcc-switches to embed a version string in a binary.
With gcc 10.4, when compiling some c code with -Wall -Werror -O3 -frecord-gcc-switches -DVERSION_THING=123hello, I'd get the following output from readelf -p .GCC.command.line
String dump of section '.GCC.command.line':
[ 0] -fdiagnostics-color=always
[ 1b] -imultiarch x86_64-linux-gnu
[ 38] -iprefix /opt/compiler-explorer/gcc-10.4.0/bin/../lib/gcc/x86_64-linux-gnu/10.4.0/
[ 8b] -D VERSION_THING=123hello
[ a5] /app/example.c
[ b4] -mtune=generic
[ c3] -march=x86-64
[ d1] -g
[ d4] -O3
[ d8] -Wall
[ de] -Werror
[ e6] -frecord-gcc-switches
However, with gcc 11.1 and later, I get this
String dump of section '.GCC.command.line':
[ 0] GNU C17 11.1.0 -mtune=generic -march=x86-64 -g -O3
Does anybody know where I should be looking to see what's changed between versions and why with regard to -frecord-gcc-switches and if it's possible to get the old behaviour back?
I typed info source while using gdb and here is what I got :
(gdb) info source
Current source file is code.cpp
Compilation directory is ~/dev/Code
Located in ~/dev/Code/code.cpp
Contains 8 lines.
Source language is c++.
Producer is GNU C++14 9.3.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
but I was surprised to see this line which seems to indicate the flags I apparently used when I compiled the sources :
Producer is GNU C++14 9.3.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection
I'm sure I didn't use any of these flags except -g tho. Do you think these flags are used by default ? Is it possible to unactivate them ?
I was building some Cython extensions, and have to link it against a static library (it has CUDA code in them, so have to be static):
running build_ext
building 'k3lib' extension
gcc -pthread -B /home/kelvin/anaconda3/envs/torch/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/kelvin/repos/tools/include -I/home/kelvin/anaconda3/envs/torch/include/python3.8 -c main.cpp -o build/temp.linux-x86_64-3.8/main.o -O3 -march=native
cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for C/ObjC but not for C++
g++ -pthread -shared -B /home/kelvin/anaconda3/envs/torch/compiler_compat -L/home/kelvin/anaconda3/envs/torch/lib -Wl,-rpath=/home/kelvin/anaconda3/envs/torch/lib -Wl,--no-as-needed -Wl,--sysroot=/ build/temp.linux-x86_64-3.8/main.o /home/kelvin/repos/tools/include/libk2.a -L/home/kelvin/repos/tools/include -lk2 -o build/lib.linux-x86_64-3.8/k3lib.cpython-38-x86_64-linux-gnu.so -static -Wl,-Bstatic -flinker-output=exec
However, Cython's g++ compile command includes the options -shared -fPIC by default. I tried a number of options at the end of the command via this setup file (the static library is at $(LOCAL_INCLUDE)/libk2.a):
includes = [os.getenv("LOCAL_INCLUDE")]
ext_modules = [
Extension("k3lib", sources=["main.pyx"],
libraries=["k2"], include_dirs=includes, library_dirs=includes, language="c++",
extra_compile_args=["-O3", "-march=native"], extra_objects=[f"{includes[0]}/libk2.a"],
extra_link_args=['-static', '-Wl,-Bstatic', '-flinker-output=exec'])
]
#extra_objects=[f"{includes[0]}/libk2.a"]
#extra_link_args=['-static']
setup(name="k3lib", ext_modules=cythonize(ext_modules, language_level="3"))
Still, g++ thinks that I want to build a shared library, and thus the error message. Is there a way to override the -shared option? I'm planning to go into Cython's files and edit them myself, but was wondering is there a simpler way?
Context: I was following this question on SO but can't replicate their success.
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.
I am cross-compiling using the CodeSourcery toolchain for arm (arm-none-linux-gnueabi). I use -isysroot to point at the /usr/include folder on a rootfs folder, but run into trouble when compiling. I have verified that the include folder is accessible.
Below is the gcc call and the output. Some of the output was removed because I don't think it is relevant.
The preprocessor comes up with an include path that is correct except for the "=" character that it starts with, and indicates that the path does not exist. It then fails to find the header files.
How can I make this work?
willem#jacta:~/Projects/button/Debug$ arm-none-linux-gnueabi-gcc -isysroot=/home/willem/Projects/rootfs -nostdinc -I=/usr/include -O0 -g3 -Wall -c -fmessage-length=0 -v -o src/smd/button/button.o ../src/smd/button/button.c -H
Using built-in specs.
Target: arm-none-linux-gnueabi
Configured with: [ ... stuff omitted ... ]
Thread model: posix
gcc version 4.4.1 (Sourcery G++ Lite 2010q1-202)
COLLECT_GCC_OPTIONS='-isysroot=/home/willem/Projects/rootfs' '-nostdinc' '-I' '=/usr/include' '-O0' '-g3' '-Wall' '-c' '-fmessage-length=0' '-v' '-o' 'src/smd/button/button.o' '-H' '-march=armv5te' '-funwind-tables'
/home/willem/Tools/CodeSourcery/Sourcery_G++_Lite/bin/../libexec/gcc/arm-none-linux-gnueabi/4.4.1/cc1 -quiet -nostdinc -v -I =/usr/include -iprefix /home/willem/Tools/CodeSourcery/Sourcery_G++_Lite/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.1/ -isysroot /home/willem/Tools/CodeSourcery/Sourcery_G++_Lite/bin/../arm-none-linux-gnueabi/libc -dD -H -isysroot=/home/willem/Projects/rootfs ../src/smd/button/button.c -quiet -dumpbase button.c -march=armv5te -auxbase-strip src/smd/button/button.o -g3 -O0 -Wall -version -fmessage-length=0 -funwind-tables -o /tmp/ccWnd3Xk.s
ignoring nonexistent directory "=/home/willem/Projects/rootfs/usr/include"
#include "..." search starts here:
#include <...> search starts here:
End of search list.
GNU C (Sourcery G++ Lite 2010q1-202) version 4.4.1 (arm-none-linux-gnueabi)
compiled by GNU C version 4.3.2, GMP version 4.3.1, MPFR version 2.4.2.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 250bf78701f747da89e730786c466148
. ../src/smd/button/button.h
../src/smd/button/button.c:2: error: no include path in which to search for pthread.h
[ ... etc. ... ]
Thanks!
W
The answer is that the -isysroot option does not need/want an "=" in the path specification, so the correct command is:
arm-none-linux-gnueabi-gcc -isysroot /home/willem/Projects/rootfs -nostdinc -I=/usr/include -O0 -g3 -Wall -c -fmessage-length=0 -v -o src/smd/button/button.o ../src/smd/button/button.c -H
Cheers,
W