Make (GNU make utility to maintain groups of programs)

The make utility will determine automatically which pieces of a large program need to be recompiled, and issue the commands to recompile them. The manual describes the GNU implementation of make, which was written by Richard Stallman and Roland McGrath, and is currently maintained by Paul Smith. Our examples show C programs, since they are very common, but you can use make with any programming language whose compiler can be run with a shell command. In fact, make is not limited to programs. You can use it to describe any task where some files must be updated automatically from others whenever the others change.

To prepare to use make, you must write a file called the makefile that describes the relationships among files in your program, and the states the commands for updating each file. In a program, typically the executable file is updated from object files, which are in turn made by compiling source files.

Once a suitable makefile exists, each time you change some source files, this simple shell command: make suffices to perform all necessary recompilations. The make program uses the makefile description and the last-modification times of the files to decide which of the files need to be updated. For each of those files, it issues the commands recorded in the makefile.

make executes commands in the makefile to update one or more target names, where name is typically a program. If no -f option is present, make will look for the makefiles GNUmakefile, makefile, and Makefile, in that order.

Normally you should call your makefile either makefile or Makefile. (We recommend Makefile because it appears prominently near the beginning of a directory listing, right near other important files such as README.) The first name checked, GNUmakefile, is not recommended for most makefiles. You should use this name if you have a makefile that is specific to GNU make, and will not be understood by other versions of make. If makefile is ‘-‘, the standard input is read.

make updates a target if it depends on prerequisite files that have been modified since the target was last modified, or if the target does not exist.

refer:

Ninja (a small build system with a focus on speed)

Ninja is a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

对比使用ninjamake编译llvm-project-11.0.0.tar.xz

结论:

  • ninja在高并发时表现出色,因此对于编译性能要求很高的场景,建议使用ninja构建系统。
  • 在上文的llvm-project对比测试中,同样由cmake生成编译脚本(-G Ninja, -G "Unix Makefiles")的情况下,ninja表现明显优于Makefile

测试脚本:

#!/bin/bash

rm -rf build
mkdir -p build && cd build

export LLVM_INSTALL_DIR=$HOME/compile/test/install

COMPILE_MODE=gcc
#COMPILE_MODE=clang

## use `ccmake .` to use cmake gui
if [ $COMPILE_MODE == "gcc"  ]; then
        export CC=/opt/rh/devtoolset-7/root/usr/bin/cc
        export CXX=/opt/rh/devtoolset-7/root/usr/bin/c++

        ## ninja
        cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi;libunwind;lldb;compiler-rt;lld" -DCMAKE_INSTALL_PREFIX=$LLVM_INSTALL_DIR ../llvm-project-11.0.0/llvm

        ## makefile
        #cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi;libunwind;lldb;compiler-rt;lld" -DCMAKE_INSTALL_PREFIX=$LLVM_INSTALL_DIR ../llvm-project-11.0.0/llvm


elif [ $COMPILE_MODE == "clang" ]; then

        export CC=/root/compile/llvm_install/bin/clang
        export CXX=/root/compile/llvm_install/bin/clang++

        cmake -G "Ninja" -fuse-ld=lld  -DCMAKE_USER_MAKE_RULES_OVERRIDE=./ClangOverrides.txt  -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi;libunwind;lldb;compiler-rt;lld" -DCMAKE_INSTALL_PREFIX=$LLVM_INSTALL_DIR ../llvm-project-11.0.0/llvm
        #cmake -G "Ninja" -fuse-ld=lld  -DCMAKE_TOOLCHAIN_FILE=./LinuxToolchains.cmake  -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi;libunwind;lldb;compiler-rt;lld" -DCMAKE_INSTALL_PREFIX=$LLVM_INSTALL_DIR ../llvm-project-11.0.0/llvm

else
        echo "error: $COMPILE_MODE invalid"
        exit 1
fi

## 8 cpu, 16G mem
/usr/bin/time -f "real %e user %U sys %S" ninja -j8 -v
#/usr/bin/time -f "real %e user %U sys %S" ninja -j256

## LLD leaves its name and version number to a .comment section in an output
## readelf --string-dump .comment <output-file>

echo "have done"

ninja + gcc

top - 21:08:49 up 181 days, 16 min,  4 users,  load average: 8.18, 8.43, 16.17
Tasks: 193 total,  10 running, 183 sleeping,   0 stopped,   0 zombie
%Cpu0  : 92.7 us,  7.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 96.7 us,  3.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 97.3 us,  2.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 96.7 us,  3.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  : 97.3 us,  2.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  : 90.3 us,  9.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  : 96.3 us,  3.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu7  : 92.7 us,  7.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 16165976 total,  7329180 free,  4121088 used,  4715708 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 11429800 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
19664 root      20   0  700252 652588   8600 R  99.7  4.0   0:07.02 cc1plus
19684 root      20   0  446644 397780   6464 R  99.7  2.5   0:03.94 cc1plus
19687 root      20   0  450332 402040   6400 R  99.7  2.5   0:03.84 cc1plus
19677 root      20   0  665556 615736   6476 R  99.3  3.8   0:05.49 cc1plus
19681 root      20   0  505488 457364   6452 R  98.7  2.8   0:04.45 cc1plus
19691 root      20   0  444044 394996   6404 R  97.0  2.4   0:03.17 cc1plus
19695 root      20   0  304324 254396   6028 R  47.2  1.6   0:01.42 cc1plus
19699 root      20   0  149372 102168   5920 R  20.3  0.6   0:00.61 cc1plus

results:

$time ninja -j8

real    40m26.115s
user    301m22.380s
sys     14m17.540s

增加ninja并发可以增加速度,但是需要有较大的内存。以下为256并发时内存空间已不足,编译时会报内部错误。

[71/6710] Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/Debug.cpp.o
FAILED: lib/Support/CMakeFiles/LLVMSupport.dir/Debug.cpp.o
/opt/rh/devtoolset-7/root/usr/bin/c++  -DGTEST_HAS_RTTI=0 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -Ilib/Support -I/root/compile/test/llvm-project-11.0.0/llvm/lib/Support -Iinclude -I/root/compile/test/llvm-project-11.0.0/llvm/include -fPIC -fvisibility-inlines-hidden -Werror=date-time -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wimplicit-fallthrough -Wno-maybe-uninitialized -Wno-noexcept-type -Wdelete-non-virtual-dtor -Wno-comment -fdiagnostics-color -ffunction-sections -fdata-sections -O3 -DNDEBUG   -std=c++14  -fno-exceptions -fno-rtti -MD -MT lib/Support/CMakeFiles/LLVMSupport.dir/Debug.cpp.o -MF lib/Support/CMakeFiles/LLVMSupport.dir/Debug.cpp.o.d -o lib/Support/CMakeFiles/LLVMSupport.dir/Debug.cpp.o -c /root/compile/test/llvm-project-11.0.0/llvm/lib/Support/Debug.cpp
c++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://bugzilla.redhat.com/bugzilla> for instructions.
top - 23:38:58 up 181 days,  2:46,  4 users,  load average: 101.87, 24.81, 8.97
Tasks: 693 total, 105 running, 587 sleeping,   0 stopped,   1 zombie
%Cpu(s): 18.0 us, 17.8 sy,  0.0 ni,  0.2 id, 63.9 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 16165976 total,   157708 free, 15388396 used,   619872 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   183040 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
   64 root      20   0       0      0      0 R  27.8  0.0   3:15.14 kswapd0
    7 root      rt   0       0      0      0 S  15.2  0.0   1:25.26 migration/0
   22 root      rt   0       0      0      0 S  14.8  0.0   1:20.00 migration/3
12958 root      20   0       0      0      0 Z   5.1  0.0   4:09.43 base_agent_net
15647 root      20   0   84008  35320    896 D   3.3  0.2   0:00.31 cc1plus
26692 root      20   0  286276   4072      0 D   3.0  0.0  72:34.73 sap1012
15430 root      20   0  109000  60828   2428 D   2.9  0.4   0:00.48 cc1plus
15158 root      20   0  166464 116372   1908 R   2.7  0.7   0:00.86 cc1plus
15470 root      20   0   99312  49680    356 R   2.6  0.3   0:00.41 cc1plus
15532 root      20   0   99356  49384    360 R   2.6  0.3   0:00.37 cc1plus

makefile + gcc

TODO

refer:

链接器

ld (The GNU linker)

ld [options] objfile …

ld combines a number of object and archive files, relocates their data and ties up symbol references. Usually the last step in compiling a program is to run ld.

refer: ld(1) - Linux man page

ld.gold (an official GNU package)

The motivation for writing gold was to make a linker that is faster than the GNU linker, especially for large applications coded in C++.

# use ld.gold instead of ld for performance
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")

# get link stage stats
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--stats")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stats")

lld (The LLVM Linker)

Replace ld to lld:

# orig: /opt/rh/devtoolset-7/root/etc/alternatives/ld -> /opt/rh/devtoolset-7/root/usr/bin/ld.bfd
# ln -s /opt/rh/devtoolset-7/root/usr/bin/ld.bfd /opt/rh/devtoolset-7/root/etc/alternatives/ld
#
rm /opt/rh/devtoolset-7/root/etc/alternatives/ld
ln -s /root/compile/llvm_install/bin/ld.lld /opt/rh/devtoolset-7/root/etc/alternatives/ld

ls -l /opt/rh/devtoolset-7/root/etc/alternatives/ld
lrwxrwxrwx 1 root root 37 Dec 13 16:47 /opt/rh/devtoolset-7/root/etc/alternatives/ld -> /root/compile/llvm_install/bin/ld.lld

LLD leaves its name and version number to a .comment section in an output. If you are in doubt whether you are successfully using LLD or not, run readelf --string-dump .comment <output-file> and examine the output. If the string “Linker: LLD” is included in the output, you are using LLD.

$readelf --string-dump .comment demo

String dump of section '.comment':
  [     0]  Linker: LLD 12.0.0 (/root/compile/llvm-project/lld f76b7f22f085fbf9f2585923f7a3a0558d75964b)
  [    5d]  clang version 12.0.0 (/root/compile/llvm-project/clang f76b7f22f085fbf9f2585923f7a3a0558d75964b)
  [    be]  GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-4)
  [    ea]  GCC: (GNU) 7.3.1 20180303 (Red Hat 7.3.1-5)

refer:

CCache (a fast C/C++ compiler cache)

ccache is a compiler cache. It speeds up recompilation by caching the result of previous compilations and detecting when the same compilation is being done again. Supported languages are C, C++, Objective-C and Objective-C++.

ccache has been carefully written to always produce exactly the same compiler output that you would get without the cache. The only way you should be able to tell that you are using ccache is the speed. Currently known exceptions to this goal are listed under CAVEATS. If you ever discover an undocumented case where ccache changes the output of your compiler, please let us know.

There are two ways to use ccache. You can either prefix your compilation commands with ccache or you can let ccache masquerade as the compiler by creating a symbolic link (named as the compiler) to ccache. The first method is most convenient if you just want to try out ccache or wish to use it for some specific projects. The second method is most useful for when you wish to use ccache for all your compilations.

To use the first method, just make sure that ccache is in your PATH.

To use the symlinks method, do something like this:

cp ccache /usr/local/bin/
ln -s ccache /usr/local/bin/gcc
ln -s ccache /usr/local/bin/g++
ln -s ccache /usr/local/bin/cc
ln -s ccache /usr/local/bin/c++

And so forth. This will work as long as the directory with symlinks comes before the path to the compiler (which is usually in /usr/bin). After installing you may wish to run “which gcc” to make sure that the correct link is being used.

  • Features
    • Keeps statistics on hits/misses.
    • Automatic cache size management.
    • Can cache compilations that generate warnings.
    • Easy installation.
    • Low overhead.
    • Optionally compresses files in the cache to reduce disk space.
  • Limitations
    • Only knows how to cache the compilation of a single C/C++/Objective-C/Objective-C++ file. Other types of compilations (multi-file compilation, linking, etc) will silently fall back to running the real compiler.
    • Only works with GCC and compilers that behave similar enough.
    • Some compiler flags are not supported. If such a flag is detected, ccache will silently fall back to running the real compiler.

More: man ccache

$which gcc
/usr/lib64/ccache/gcc
$ll -lh `which gcc`
lrwxrwxrwx 1 root root 16 3月   5 2021 /usr/lib64/ccache/gcc -> ../../bin/ccache
$ll -lh /usr/bin/ccache
-rwxr-xr-x 1 root root 135K 2月  19 2020 /usr/bin/ccache

禁用 ccache

要禁用本地的 ccache,可以采用以下几种方法之一:

  • 临时禁用 ccache:

在构建命令之前,将 CCACHE_DISABLE 环境变量设置为 1:

export CCACHE_DISABLE=1

然后运行构建命令(例如:make)。这将在当前会话中禁用 ccache。

  • 永久禁用 ccache:

编辑 ~/.bashrc 或 ~/.bash_profile 文件,将以下行添加到文件末尾:

export CCACHE_DISABLE=1

保存文件并重新启动终端。这将在所有新的终端会话中禁用 ccache。

  • 使用原始的编译器而不是 ccache 包装器

在构建命令中,直接指定原始编译器的路径,而不是使用 ccache 包装器。例如,如果您的原始 Clang 编译器位于 /usr/bin/clang,则可以在构建命令中使用此路径。

make CC=/usr/bin/clang

这将确保在构建过程中不使用 ccache。

编译效率对比

在8核CPU,16G内存机器,对比gcc, clang, make, ninja, ld, lld不同组合情况下的编译效率。

测试代码

  • 使用CMake生成Unix Makefiles,分别指定不同的gcc或者clang版本编译构建:(make.sh)
  • 使用CMake生成Ninja,分别指定不同的gcc或者clang版本编译构建:(ninja.sh)
  • 对比不同链接器ldlld的性能差异

make.sh

#!/bin/bash

## https://stackoverflow.com/questions/7031126/switching-between-gcc-and-clang-llvm-using-cmake

rm -rf build
mkdir -p build && cd build

COMPILE_MODE=gcc
#COMPILE_MODE=clang

## use `ccmake .` to use cmake gui
if [ $COMPILE_MODE == "gcc"  ]; then
        export CC=/opt/rh/devtoolset-7/root/usr/bin/cc
        export CXX=/opt/rh/devtoolset-7/root/usr/bin/c++

        cmake -G "Unix Makefiles" -DCMAKE_USER_MAKE_RULES_OVERRIDE=./GccOverrides.txt  ..

elif [ $COMPILE_MODE == "clang" ]; then

        #export CC=/root/compile/llvm_install/bin/clang
        #export CXX=/root/compile/llvm_install/bin/clang++

        export CC=/usr/local/bin/clang
        export CXX=/usr/local/bin/clang++

        cmake -G "Unix Makefiles" -fuse-ld=lld  -DCMAKE_USER_MAKE_RULES_OVERRIDE=./ClangOverrides.txt  ..
        #cmake -G "Unix Makefiles" -fuse-ld=lld  -DCMAKE_TOOLCHAIN_FILE=./ClangToolchains.cmake  ..

else
        echo "error: $COMPILE_MODE invalid"
        exit 1
fi

/usr/bin/time -f "real %e user %U sys %S" make -j8 VERBOSE=1

## LLD leaves its name and version number to a .comment section in an output
## readelf --string-dump .comment <output-file>

echo "have done"

ninja.sh

#!/bin/bash

## https://stackoverflow.com/questions/7031126/switching-between-gcc-and-clang-llvm-using-cmake

rm -rf build
mkdir -p build && cd build

COMPILE_MODE=gcc
#COMPILE_MODE=clang

## use `ccmake .` to use cmake gui
if [ $COMPILE_MODE == "gcc"  ]; then
        export CC=/opt/rh/devtoolset-7/root/usr/bin/cc
        export CXX=/opt/rh/devtoolset-7/root/usr/bin/c++

        cmake -G "Ninja" -DCMAKE_USER_MAKE_RULES_OVERRIDE=./GccOverrides.txt  ..

elif [ $COMPILE_MODE == "clang" ]; then

        #export CC=/root/compile/llvm_install/bin/clang
        #export CXX=/root/compile/llvm_install/bin/clang++

        export CC=/usr/local/bin/clang
        export CXX=/usr/local/bin/clang++

        cmake -G "Ninja" -fuse-ld=lld  -DCMAKE_USER_MAKE_RULES_OVERRIDE=./ClangOverrides.txt  ..
        #cmake -G "Ninja" -fuse-ld=lld  -DCMAKE_TOOLCHAIN_FILE=./ClangToolchains.cmake  ..

else
        echo "error: $COMPILE_MODE invalid"
        exit 1
fi

/usr/bin/time -f "real %e user %U sys %S" ninja -j8 -v

## LLD leaves its name and version number to a .comment section in an output
## readelf --string-dump .comment <output-file>

echo "have done"

测试结果:

clang12 优于 gcc4.8/7/9,ninja 优于 make,lld 优于 ld。

Case Time
gcc7 + make + ld 25.7s
clang12 + make + ld 5.2s
gcc7 + ninja + ld 22s
clang12 + ninja + ld 4.7s
gcc7 + make + lld 17.8s
clang12 + make + lld 4.82s
gcc7 + ninja + lld 18.34s
clang12 + ninja + lld 4.15s
gcc9 + make + lld 10.03s
gcc9 + ninja + lld 7.90s
gcc4.8 + make + lld 8.93s
gcc4.8 + ninja + lld 8.30s

Mixing Clang with GCC

Can Clang compile code with GCC compiled .a libs?

I have my project currently compiling under gcc. It uses Boost, ZeroMQ as static .a libraries and some .so libraries like SDL. I want to go clang all the way but not right now. I wonder if it is possible to compile code that uses .a and .so libraries that were compiled under gcc with clang?

Answers:

Yes, you usually can use clang with GCC compiled libraries (and vice versa, use gcc with CLANG compiled libraries), because in fact it is not compilation(编译) but linking(链接) which is relevant. You might be unlucky and get unpleasant suprises.

You could in principle have some dependencies on the version of libstdc++ used to link the relevant libraries (if they are coded in C++). Actually, that usually does not matter much.

In C++, name mangling might in theory be an issue (there might be some corner cases, even incompatibilities between two different versions of g++). Again, in practice it is usually not an issue.

So usually you can mix CLANG (even different but close versions of it) with GCC but you may have unpleasant surprises. What should be expected from any C++ compiler (be it CLANG or GCC) is just to be able to compile and link an entire software (and all libraries) together using the same compiler and version (and that includes the same C++ standard library implementation). This is why upgrading a compiler in a distribution is a lot of work: the distribution makers have to ensure that all the packages compile well (and they do get surprises!).

Beware that the version of libstdc++ does matter. Both Clang & GCC communities work hard to make its ABI compatible for compiler upgrades, but there are subtle corner cases. Read the documentation of your particular and specific C++ standard library implementation. These corner cases could explain mysterious crashes when using a good C++ library binary (compiled with GCC 5) in your code compiled with GCC 8. The bug is not in the library, but the ABI evolved incompatibly.

Another answers:

At least for Crypto++ library this does not work (verified :-( ). So for c++ code it is less likely to work, while pure c code would probably link OK.

The solution appears to be: if you need to compile C++ code with clang, and link it to a gcc-compiled library, use clang++ -stdlib=libstdc++. The linking is successful, and the resulting binary runs correctly.

CAVEAT(注意): It does not seem to work the other way: even though you can build a library compiled with “clang++ -stdlib=libstdc++” and link gcc-compiled code with it, this code will crash with SEGV. So far I found the only way to link with a clang-compiled library is compiling your code with clang, not gcc.

gcc vs clang common library issue

I have two applications, one compiled with gcc(c++) and another compiled with clang++. I am to use common shared boost library for both the applications. My question is whether to compile boost shared library using clang compiler or gcc compiler. Can I use boost library compiled with gcc in my application that is being compiled using clang?

Answers:

g++ and clang++ are compatible as compilers (because they both follow the Itanium ABI), but they may come with incompatible standard library implementations.

g++ comes with a standard library implementation called libstdc++. You can direct g++ to use a different implementation but this is not exactly trivial.

clang++ sometimes comes without a standard library implementation of its own (and is configured to use implementation provided by g++), and sometimes comes with an implementation called libc++. One can easily switch clang++ to use either libc++ or libstdc++ with a single command line option.

So your question boils down to what standard library implementation(s) your applications use. If they use the same implementation, you need to build Boost with that implementation (and either compiler). If they use different implementations, you need two separate builds of Boost.

Mixing components built against different standard library implementations in the same application can sometimes be done, but is not straightforward, entails a lot of restrictions, and with things like boost is either not feasible or downright impossible.

Refer