How to integrate clang-tidy to CMake and GCC? - c++

I want to integrate clang-tidy to our C and C++, CMake based project which is compiled using a custom GCC toolchain.
I've tried following this tutorial, setting CMAKE_CXX_CLANG_TIDY. I've also tried generating a compilation database by setting CMAKE_EXPORT_COMPILE_COMMANDS to ON and pointing run-clang-tidy.py to its directory.
In both cases, I've encountered (the same) few errors that are probably related to differences between Clang and GCC:
Some warning flags that are enabled in the CMake files are not supported in Clang but are supported in GCC (like -Wlogical-op). As the compiler is GCC, the file builds correctly, and the flag is written to the compilation database, but clang-tidy complains about it.
clang-tidy complains some defines and functions are unavailable, even though the code compiles just fine. As an example, the android-cloexec-open check suggested using O_CLOEXEC to improve security and force the closing of files, but trying to use this define leads to an undefined identifier error (even though our GCC compiles the code).
As an example to a function that is not found, there is clock_gettime.
Our code compiles with the C11 standard and C++14 standard, without GNU extensions:
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_EXTENSIONS OFF)
The custom toolchain is a cross-compilation toolchain which runs on Linux and compiles to FreeBSD.
Is there a way to disable the passing of some flags by CMake to clang-tidy? Am I using clang-tidy wrong?
I suspect this issue is related to disabling GNU extensions, using a cross-compilation toolchain, and some feature-test-macro which is not defined by default in Clang but is defined with GCC (e.g. _GNU_SOURCE/_POSIX_SOURCE). If this is the case, how can I check it? If not, should I use clang-tidy differently?
EDIT
As #pablo285 asked, here are 2 warnings I get for a single file, and then as I added --warnings-as-errors=*, the build stops:
error: unknown warning option '-Wlogical-op' ; did you mean '-Wlong-long'? [clang-diagnostic-error]
<file path>: error: use of undeclared identifier 'O_CLOEXEC' [clang-diagnostic-error]
O_WRONLY | O_CLOEXEC
^
I decided to write a python script that will replace clang-tidy, receive the commandline from CMake and edit it to fix various errors. Here are the modification to the commandline I tried:
Remove none clang compile flags
This helps with things like the first warning, as now I don't pass flags that clang doesn't know. It seems like I can't configure CMake to pass different set of flags to GCC and to clang-tidy, so if anyone is familiar with some solution to this problem, I'll be happy to hear!
I changed the include directories that are passed to clang-tidy
As mentioned in the post, I use a custom toolchain (which cross-compiles). I used this post and Python to extract the list of standard include directories, and added them to the flag list as a list of -isystem <dir>. I also added -nostdinc so that clang-tidy won't try to look on his own headers instead of mine
This helped with the issue above, as now various defines such as O_CLOEXEC is defined in the toolchain's headers, but as my toolchain is based on GCC, clang couldn't parse the <type_traits> header which includes calls to many compiler intrinsics
I'm not sure what's the best approach in this case
#shycha: Thanks for the tip, I'll try disabling this specific check and I'll edit this post again

Ok, I think that I have a solution. After a couple of evenings I was able to make it work.
In general I compile like this
rm -rf build
mkdir build
cd build
cmake -C ../cmake-scripts/clang-tidy-all.cmake .. && make
Where cmake-scripts directory contains:
clang-tidy-all.cmake
toolchain_arm_clang.cmake
The two important files are listed below.
But what is more important, is how you need to compile this.
First, toolchain_arm_clang.cmake is referenced directly from clang-tidy-all.cmake via set(CMAKE_TOOLCHAIN_FILE ...). It must be, however, referenced from the point of view of the building directory, so if you use multiple levels of build-dirs, e.g.: build/x86, build/arm, build/darwin, etc., then you must modify that path accordingly.
Second, the purpose of set(CONFIG_SCRIPT_PRELOADED ...) is to be sure that the config script was pre-loaded, i.e., cmake -C ../cmake-scripts/clang-tidy-all.cmake ..
Typically, you would want to have something like this somewhere in your CMakeLists.txt file:
message(STATUS "CONFIG_SCRIPT_PRELOADED: ${CONFIG_SCRIPT_PRELOADED}")
if(NOT CONFIG_SCRIPT_PRELOADED)
message(FATAL_ERROR "Run cmake -C /path/to/cmake.script to preload a config script!")
endif()
Third, there is /lib/ld-musl-armhf.so.1 hard-coded in set(CMAKE_LINKER_ARM_COMPAT_STATIC ...); on the development box that I use, it points to /lib/libc.so, so it might by OK to use /lib/libc.sh instead. I've never tried.
Fourth, using set(CMAKE_C_LINK_EXECUTABLE ...) and set(CMAKE_LINKER_ARM_COMPAT_STATIC ...) was because CMake was complaining about some linking problems during checking the compiler, i.e., before even running make.
Fifth, I was only compiling C++ code, so if you need to compile some C, then it might be required to also properly configure set(CMAKE_C_CREATE_SHARED_LIBRARY ...), but I have no idea whether there is such a config option.
General Advice
Do not integrate it immediately. First test some simple CMake project with one library (preferably a C++ one) and make it work, then add the second library, but in C, tweak it again. And only after that incorporate it into the code base.
Toolchain
I used a custom toolchain with GCC 8.3.0 and musl C library, so locations of some files might be different for other toolchains.
Custom CMake
Some variables, like (already mentioned) CONFIG_SCRIPT_PRELOADED, EXPORT_PACKAGE_TO_GLOBAL_REGISTRY, DO_NOT_BUILD_TESTS, or DO_NOT_BUILD_BENCHMARKS are not generic CMake options, i.e., I use them only in my CMakeLists.txt, so you can safely ignore them.
Variables that are unset at the end of each *.cmake file, e.g., build_test, extra_clang_tidy_unchecks_for_tests_only, don't need to be present in the project's main CMakeLists.txt.
Clang
$ clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git 4650b2f36949407ef25686440e3d65ac47709deb)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /opt/local/bin
Files
clang-tidy-all.cmake:
set(ALL_CXX_WARNING_FLAGS --all-warnings -Weverything -Wno-c++98-compat -Wno-c++98-c++11-compat -Wno-c++98-c++11-c++14-compat -Wno-padded -Wno-c++98-compat-pedantic)
set(CXX_COMPILE_OPTIONS "-std=c++17;-O3;${ALL_CXX_WARNING_FLAGS}" CACHE INTERNAL "description")
set(CMAKE_CROSSCOMPILING True)
set(CMAKE_TOOLCHAIN_FILE "../cmake-scripts/toolchain_arm_clang.cmake" CACHE FILEPATH "CMake toolchain file")
set(CONFIG_SCRIPT_PRELOADED true CACHE BOOL "Ensures that config script was preloaded")
set(build_test False)
if(build_test)
message(STATUS "Using test mode clang-tidy checks!")
set(extra_clang_tidy_unchecks_for_tests_only ",-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-special-member-functions")
endif()
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--enable-check-profile;--checks=-*,abseil-string-find-startswith,bugprone-*,cert-*,clang-analyzer-*,cppcoreguidelines-*,google-*,hicpp-*,llvm-*,misc-*,modernize-*,-modernize-use-trailing-return-type,performance-*,readability-*,-readability-static-definition-in-anonymous-namespace,-readability-simplify-boolean-expr,portability-*${extra_clang_tidy_unchecks_for_tests_only}" CACHE INTERNAL "clang-tidy")
message(STATUS "build_test: ${build_test}")
message(STATUS "extra_clang_tidy_unchecks_for_tests_only: ${extra_clang_tidy_unchecks_for_tests_only}")
message(STATUS "CMAKE_CXX_CLANG_TIDY: ${CMAKE_CXX_CLANG_TIDY}")
# We want to skip building tests when clang-tidy is run (it takes too much time and serves nothing)
if(DEFINED CMAKE_CXX_CLANG_TIDY AND NOT build_test)
set(DO_NOT_BUILD_TESTS true CACHE BOOL "Turns OFF building tests")
set(DO_NOT_BUILD_BENCHMARKS true CACHE BOOL "Turns OFF building benchmarks")
endif()
unset(build_test)
unset(extra_clang_tidy_unchecks_for_tests_only)
set(EXPORT_PACKAGE_TO_GLOBAL_REGISTRY "OFF" CACHE INTERNAL "We don't export clang-tidy-all version to global register")
toolchain_arm_clang.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 4.14.0)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(gcc_version 8.3.0)
set(x_tools "/opt/zynq/xtl")
set(CMAKE_C_COMPILER "clang" CACHE INTERNAL STRING)
set(CMAKE_CXX_COMPILER "clang++" CACHE INTERNAL STRING)
set(CMAKE_RANLIB "llvm-ranlib" CACHE INTERNAL STRING)
set(CMAKE_AR "llvm-ar" CACHE INTERNAL STRING)
set(CMAKE_AS "llvm-as" CACHE INTERNAL STRING)
set(CMAKE_LINKER "ld.lld" CACHE INTERNAL STRING)
execute_process(
COMMAND bash -c "dirname `whereis ${CMAKE_LINKER} | tr -s ' ' '\n' | grep ${CMAKE_LINKER}`"
OUTPUT_VARIABLE cmake_linker_dir
)
string(REGEX REPLACE "\n$" "" cmake_linker_dir "${cmake_linker_dir}")
set(cmake_linker_with_dir "${cmake_linker_dir}/${CMAKE_LINKER}" CACHE INTERNAL STRING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -iwithsysroot /include/c++/${gcc_version} -iwithsysroot /include/c++/${gcc_version}/arm-linux-musleabihf" CACHE INTERNAL STRING)
set(CMAKE_SYSROOT ${x_tools}/arm-linux-musleabihf)
set(CMAKE_FIND_ROOT_PATH ${x_tools}/arm-linux-musleabihf)
set(CMAKE_INSTALL_PREFIX ${x_tools}/arm-linux-musleabihf)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
set(triple arm-linux-musleabihf)
set(CMAKE_LIBRARY_ARCHITECTURE ${triple})
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER_TARGET ${triple})
set(lib_path_arm ${x_tools}/arm-linux-musleabihf/lib)
## Bootstrap library stuff:
set(Scrt1_o ${lib_path_arm}/Scrt1.o)
set(crti_o ${lib_path_arm}/crti.o)
set(crtn_o ${lib_path_arm}/crtn.o)
set(lib_path_gcc ${x_tools}/lib/gcc/${triple}/${gcc_version})
set(crtbeginS_o ${lib_path_gcc}/crtbeginS.o)
set(crtendS_o ${lib_path_gcc}/crtendS.o)
# Clang as linker
# --no-pie disable position independent executable, which is required when building
# statically linked executables.
set(CMAKE_CXX_LINK_EXECUTABLE "clang++ --target=${triple} -Wl,--no-pie --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
set(CMAKE_CXX_CREATE_SHARED_LIBRARY "clang++ -Wl, --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} -shared <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
#
# Do not use CMAKE_CXX_CREATE_STATIC_LIBRARY -- it is created automatically
# by cmake using ar and ranlib
#
#set(CMAKE_CXX_CREATE_STATIC_LIBRARY "clang++ -Wl,--no-pie,--no-export-dynamic,-v -v --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=ld.lld <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET> ")
## Linker as linker
set(CMAKE_LINKER_ARM_COMPAT_STATIC "-pie -EL -z relro -X --hash-style=gnu --eh-frame-hdr -m armelf_linux_eabi -dynamic-linker /lib/ld-musl-armhf.so.1 ${Scrt1_o} ${crti_o} ${crtbeginS_o} -lstdc++ -lm -lgcc_s -lgcc -lc ${crtendS_o} ${crtn_o}")
set(CMAKE_C_LINK_EXECUTABLE "${CMAKE_LINKER} ${CMAKE_LINKER_ARM_COMPAT_STATIC} <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o <TARGET>")
# Debian bug 708744(?)
#include_directories("${CMAKE_SYSROOT}/usr/include/")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}/${triple}")
## Clang workarounds:
set(toolchain_lib_dir_0 "${CMAKE_SYSROOT}/lib")
set(toolchain_lib_dir_1 "${CMAKE_SYSROOT}/../lib")
set(toolchain_lib_dir_2 "${CMAKE_SYSROOT}/../lib/gcc/${triple}/${gcc_version}")
set(CMAKE_TOOLCHAIN_LINK_FLAGS "-L${toolchain_lib_dir_0} -L${toolchain_lib_dir_1} -L${toolchain_lib_dir_2}")
## CMake workarounds
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "exe link flags")
set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "module link flags")
set(CMAKE_SHARED_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "shared link flags")
unset(cmake_linker_with_dir)
unset(cmake_linker_dir)

Maybe not exactly what you're looking for but I'm using this in CMakeLists.txt:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_target(lint
COMMAND sh -c "run-clang-tidy -header-filter=.* -checks=`tr '\\n' , <${CMAKE_SOURCE_DIR}/checks.txt` >lint.out 2>lint.err"
COMMAND sh -c "grep warning: lint.out || true"
COMMAND ls -lh ${CMAKE_BINARY_DIR}/lint.out
VERBATIM
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
This creates a separate build target (make lint) for the clang-tidy check. clang-tidy takes a long time for my project so I don't want to run it during every build; make lint can be run manually if required, and it's also executed in a CI job after every push to the repo (in a way that makes the CI pipeline fail, blocking the merge, if there are any findings).
The output of make lint is the list of clang-tidy findings with as little context as possible. The full output, including context for findings, is in lint.out, and error messages are in lint.err, both of which I'm saving as CI artefacts.
checks.txt is a text file in the project root that defines which clang-tidy checks to activate, like so:
*
-altera-id-dependent-backward-branch
-altera-struct-pack-align
-altera-unroll-loops
-android-*
The first line enables all available checks, the other lines disable checks that I don't want.
Will only work in a Unix-like system of course.

Related

CMake: Clang-based cross-compile for Linux uses wrong target platform

I'm attempting to build a library for Linux using CMake and Clang on Windows 10. Because this library needs to be compatible with a specific version of the Unreal Engine, I'm using the recommended cross-compilation toolchain for Unreal Engine 4.26, which is labelled v17_clang-10.0.1-centos7.
I've not made a toolchain file yet, but have been experimenting with CMake options on the command line to find a setup that works. My current configuration is:
CC environment variable points to v17_clang-10.0.1-centos7\x86_64-unknown-linux-gnu\bin\clang.exe
CXX environment variable points to v17_clang-10.0.1-centos7\x86_64-unknown-linux-gnu\bin\clang++.exe
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY: According to this answer, this must be set to STATIC_LIBRARY or the compiler checks will fail (and did when I first ran them) when they try to link.
-DCMAKE_C_COMPILER_TARGET=x86_64-unknown-linux-gnu and -DCMAKE_CXX_COMPILER_TARGET=x86_64-unknown-linux-gnu are set to match the Clang distribution I'm using.
For good measure, -DCMAKE_SYSTEM_NAME=linux and -DCMAKE_SYSTEM_PROCESSOR=x86_64 are also set.
This configuration is generated correctly (I'm using Ninja as the generator), but when building it appears that the target is still Windows and not Linux. I get lots of errors like the following:
[14/106] Building CXX object core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/CMakeFiles/cppfs.dir/source/Diff.cpp.obj
FAILED: core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/CMakeFiles/cppfs.dir/source/Diff.cpp.obj
C:\UnrealToolchains\v17_clang-10.0.1-centos7\x86_64-unknown-linux-gnu\bin\clang++.exe -DCLIENT_VERSION_MAJOR=-1
-DCLIENT_VERSION_MINOR=-1 -DCLIENT_VERSION_PATCH=-1 -DCPPFS_STATIC_DEFINE -DFACEIT_CORELIB_BUILD_PRODUCER
-DHAVE_OPENSSL -DSERVER_VERSION_MAJOR=-1 -DSERVER_VERSION_MINOR=-1 -DSERVER_VERSION_PATCH=-1 -DSYSTEM_WINDOWS
-Icore/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/include
-I../core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/3rdparty
-I../core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/include
-Icore/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/include -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT
-Xclang --dependent-lib=msvcrtd -Wall -Wextra -pedantic -Werror -Wl,--fatal-warnings -fPIC -Wno-unused-parameter
-std=gnu++14 -MD -MT core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/CMakeFiles/cppfs.dir/source/Diff.cpp.obj
-MF core\thirdParty\cppfs-1.2.0\cppfs-1.2.0\source\cppfs\CMakeFiles\cppfs.dir\source\Diff.cpp.obj.d
-o core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/CMakeFiles/cppfs.dir/source/Diff.cpp.obj
-c ../core/thirdParty/cppfs-1.2.0/cppfs-1.2.0/source/cppfs/source/Diff.cpp
clang++: error: -Wl,--fatal-warnings: 'linker' input unused [-Werror,-Wunused-command-line-argument]
clang++: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'
It seems that the target is x86_64-pc-windows-msvc, and PLATFORM_WINDOWS is defined, so my input settings have been ignored. What am I missing here? Do I need an actual toolchain file after all?
EDIT: After recommendations in the comments, I have created a toolchain file which looks like this:
if("$ENV{LINUX_MULTIARCH_ROOT}" STREQUAL "")
message(FATAL_ERROR "Provide the path to the Clang toolchain in the LINUX_MULTIARCH_ROOT environment variable.")
endif()
# Clang target triple
set(TARGET_TRIPLE x86_64-unknown-linux-gnu)
# Clean path separators
file(TO_CMAKE_PATH $ENV{LINUX_MULTIARCH_ROOT}/${TARGET_TRIPLE} TOOLCHAIN_ROOT)
list(APPEND CMAKE_PROGRAM_PATH ${TOOLCHAIN_ROOT}/bin)
set(CMAKE_CROSSCOMPILING TRUE)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# specify the cross compiler
set(CMAKE_C_COMPILER_TARGET ${TARGET_TRIPLE})
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER_TARGET ${TARGET_TRIPLE})
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_ASM_COMPILER_TARGET ${TARGET_TRIPLE})
set(CMAKE_ASM_COMPILER clang)
# C/C++ toolchain
set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN ${TOOLCHAIN_ROOT})
set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN ${TOOLCHAIN_ROOT})
# This must be set or compiler checks fail when linking
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_ROOT})
set(CMAKE_SYSROOT ${TOOLCHAIN_ROOT})
After starting from a clean environment it seems the target is now detected correctly, but instead I get a configuring error saying Could NOT find Threads (missing: Threads_FOUND). I'm assuming this is because the Threads package is not being found in the toolchain, although I can verify that the libraries are present in Windows explorer.
Another edit: I'm beginning to wonder whether the package cannot be found because CMake is attempting to look for a package description file rather than the libraries themselves? Is there a way I can fix that for this particular case of cross-compilation?

How to use c++20 modules with CMake?

Clang and MSVC already supports Modules TS from unfinished C++20 standard.
Can I build my modules based project with CMake or other build system and how?
I tried build2, it supports modules and it works very well, but i have a question about it's dependency management (UPD: question is closed).
CMake currently does not support C++20 modules.
See also the relevant issue in the CMake issue tracker. Note that supporting modules requires far more support from the build system than inserting a new compiler option. It fundamentally changes how dependencies between source files have to be handled during the build: In a pre-modules world all cpp source files can be built independently in any order. With modules that is no longer true, which has implications not only for CMake itself, but also for the downstream build system.
Take a look at the CMake Fortran modules paper for the gory details. From a build system's point of view, Fortran's modules behave very similar to the C++20 modules.
Update: CMake 3.20 introduces experimental support for Modules with the Ninja Generator (and only for Ninja). Details can be found in the respective pull request. At this stage, this feature is still highly experimental and not intended for production use. If you intend to play around with this anyway, you really should be reading both the Fortran modules paper and the dependency format paper to understand what you're getting into.
This works on Linux Manjaro (same as Arch), but should work on any Unix OS. Of course, you need to build with new clang (tested with clang-10).
helloworld.cpp:
export module helloworld;
import <cstdio>;
export void hello() { puts("Hello world!"); }
main.cpp:
import helloworld; // import declaration
int main() {
hello();
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(main)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/modules)
function(add_module name)
file(MAKE_DIRECTORY ${PREBUILT_MODULE_PATH})
add_custom_target(${name}.pcm
COMMAND
${CMAKE_CXX_COMPILER}
-std=c++20
-stdlib=libc++
-fmodules
-c
${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
-Xclang -emit-module-interface
-o ${PREBUILT_MODULE_PATH}/${name}.pcm
)
endfunction()
add_compile_options(-fmodules)
add_compile_options(-stdlib=libc++)
add_compile_options(-fbuiltin-module-map)
add_compile_options(-fimplicit-module-maps)
add_compile_options(-fprebuilt-module-path=${PREBUILT_MODULE_PATH})
add_module(helloworld helloworld.cpp)
add_executable(main
main.cpp
helloworld.cpp
)
add_dependencies(main helloworld.pcm)
Assuming that you're using gcc 11 with a Makefile generator, the following code should work even without CMake support for C++20:
cmake_minimum_required(VERSION 3.19) # Lower versions should also be supported
project(cpp20-modules)
# Add target to build iostream module
add_custom_target(std_modules ALL
COMMAND ${CMAKE_COMMAND} -E echo "Building standard library modules"
COMMAND g++ -fmodules-ts -std=c++20 -c -x c++-system-header iostream
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Function to set up modules in GCC
function (prepare_for_module TGT)
target_compile_options(${TGT} PUBLIC -fmodules-ts)
set_property(TARGET ${TGT} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TGT} PROPERTY CXX_EXTENSIONS OFF)
add_dependencies(${TGT} std_modules)
endfunction()
# Program name and sources
set (TARGET prog)
set (SOURCES main.cpp)
set (MODULES mymod.cpp)
# Setup program modules object library
set (MODULE_TARGET prog-modules)
add_library(${MODULE_TARGET} OBJECT ${MODULES})
prepare_for_module(${MODULE_TARGET})
# Setup executable
add_executable(${TARGET} ${SOURCES})
prepare_for_module(${TARGET})
# Add modules to application using object library
target_link_libraries(${TARGET} PRIVATE ${MODULE_TARGET})
Some explanation:
A custom target is added to build the standard library modules, in case you want to include standard library header units (search for "Standard Library Header Units" here). For simplicity, I just added iostream here.
Next, a function is added to conveniently enable C++20 and Modules TS for targets
We first create an object library to build the user modules
Finally, we create our executable and link it to the object library created in the previous step.
Not consider the following main.cpp:
import mymod;
int main() {
helloModule();
}
and mymod.cpp:
module;
export module mymod;
import <iostream>;
export void helloModule() {
std::cout << "Hello module!\n";
}
Using the above CMakeLists.txt, your example should compile fine (successfully tested in Ubuntu WSL with gcc 1.11.0).
Update:
Sometimes when changing the CMakeLists.txt and recompiling, you may encounter an error
error: import "/usr/include/c++/11/iostream" has CRC mismatch
Probably the reason is that every new module will attempt to build the standard library modules, but I'm not sure. Unfortunately I didn't find a proper solution to this (avoiding rebuild if the gcm.cache directory already exists is bad if you want to add new standard modules, and doing it per-module is a maintenance nightmare). My Q&D solution is to delete ${CMAKE_BINARY_DIR}/gcm.cache and rebuild the modules. I'm happy for better suggestions though.
CMake ships with experimental support for C++20 modules:
https://gitlab.kitware.com/cmake/cmake/-/blob/master/Help/dev/experimental.rst
This is tracked in this issue:
https://gitlab.kitware.com/cmake/cmake/-/issues/18355
There is also a CMakeCXXModules repository that adds support for modules to CMake.
https://github.com/NTSFka/CMakeCxxModules
While waiting for proper C++20 modules support in CMake, I've found that if using MSVC Windows, for right now you can make-believe it's there by hacking around the build instead of around CMakeLists.txt: continously generate with latest VS generator, and open/build the .sln with VS2020. The IFC dependency chain gets taken care of automatically (import <iostream>; just works). Haven't tried Windows clang or cross-compiling. It's not ideal but for now at least another decently workable alternative today, so far.
Important afterthought: use .cppm and .ixx extensions.
CMake does not currently support C++20 modules like the others have stated. However, module support for Fortran is very similar, and perhaps this could be easily changed to support modules in C++20.
http://fortranwiki.org/fortran/show/Build+tools
Now, perhaps there i an easy way to modify this to support C++20 directly. Not sure. It is worth exploring and doing a pull request should you resolve it.
Add MSVC version (revised from #warchantua 's answer):
cmake_minimum_required(VERSION 3.16)
project(Cpp20)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_DIR ${CMAKE_BINARY_DIR}/modules)
set(STD_MODULES_DIR "D:/MSVC/VC/Tools/MSVC/14.29.30133/ifc/x64") # macro "$(VC_IFCPath)" in MSVC
function(add_module name)
file(MAKE_DIRECTORY ${PREBUILT_MODULE_DIR})
add_custom_target(${name}.ifc
COMMAND
${CMAKE_CXX_COMPILER}
/std:c++latest
/stdIfcDir ${STD_MODULES_DIR}
/experimental:module
/c
/EHsc
/MD
${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
/module:export
/ifcOutput
${PREBUILT_MODULE_DIR}/${name}.ifc
/Fo${PREBUILT_MODULE_DIR}/${name}.obj
)
endfunction()
set(CUSTOM_MODULES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/modules)
add_module(my_module ${CUSTOM_MODULES_DIR}/my_module.ixx)
add_executable(test
test.cpp
)
target_compile_options(test
BEFORE
PRIVATE
/std:c++latest
/experimental:module
/stdIfcDir ${STD_MODULES_DIR}
/ifcSearchDir ${PREBUILT_MODULE_DIR}
/reference my_module=${PREBUILT_MODULE_DIR}/my_module.ifc
/EHsc
/MD
)
target_link_libraries(test ${PREBUILT_MODULE_DIR}/my_module.obj)
add_dependencies(test my_module.ifc)
With C++20 Modules the file compilation order matters, which is totally new. That's why the implementation is complicated and still experimental in 2023. Please read the authors blogpost
I was not able to find Cmake support for modules. Here is an example how to use modules using clang. I am using Mac and this example works ok on my system. It took me quite a while to figure this out so unsure how general this is across linux or Windows.
Source code in file driver.cxx
import hello;
int main() { say_hello("Modules"); }
Source code in file hello.cxx
#include <iostream>
module hello;
void say_hello(const char *n) {
std::cout << "Hello, " << n << "!" << std::endl;
}
Source code in file hello.mxx
export module hello;
export void say_hello (const char* name);
And to compile the code with above source files, here are command lines on terminal
clang++ \
-std=c++2a \
-fmodules-ts \
--precompile \
-x c++-module \
-Xclang -fmodules-embed-all-files \
-Xclang -fmodules-codegen \
-Xclang -fmodules-debuginfo \
-o hello.pcm hello.mxx
clang++ -std=c++2a -fmodules-ts -o hello.pcm.o -c hello.pcm
clang++ -std=c++2a -fmodules-ts -x c++ -o hello.o \
-fmodule-file=hello.pcm -c hello.cxx
clang++ -std=c++2a -fmodules-ts -x c++ -o driver.o \
-fmodule-file=hello=hello.pcm -c driver.cxx
clang++ -o hello hello.pcm.o driver.o hello.o
and to get clean start on next compile
rm -f *.o
rm -f hello
rm -f hello.pcm
expected output
./hello
Hello, Modules!
Hope this helps, all the best.

Emscripten library make from CMake cannot link

I am using Grassroot Dicom viewer version 2.8.9 to compile into Emscripten library bc on Windows 10 64-bit platform.
I followed the instruction from this website
emconfigure cmake .
emmake make VERBOSE=1
After that I got library file ending with .a instead of .bc similar to
libgdcmcharls.a libgdcmCommon.a libgdcmDICT.a ....
when I looked into the assembly code for each static library file it starts with
! arch./
instead of
BC
as mention in the tutorial website Note
Some build systems may not properly emit bitcode using the above
procedure, and you may see is not valid bitcode warnings. You can run
file to check if a file contains bitcode (also you can manually check
if the contents start with BC). It is also worth running emmake make
VERBOSE=1 which will print out the commands it runs - you should see
emcc being used, and not the native system compiler. If emcc is not
used, you may need to modify the configure or cmake scripts.
I think this is the problem that I cannot generate libraries with contents start with BC but instead I got ! arch ./ ?
and of course those files cannot be linked from emcc ( cannot find reference error )
error: undefined symbol: _ZN4gdcm11ImageReader8GetImageEv
error: undefined symbol: _ZN4gdcm11ImageReaderC1Ev
error: undefined symbol: _ZN4gdcm11ImageReaderD1Ev
error: undefined symbol: _ZN4gdcm5Trace14GetErrorStreamE
So I modified GDCM CMakefile.txt according to this
if (EMSCRIPTEN)
set(CMAKE_AR "emcc" CACHE)
set(CMAKE_STATIC_LIBRARY_SUFFIX ".bc")
set(CMAKE_C_CREATE_STATIC_LIBRARY "<CMAKE_AR> -o <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_CREATE_STATIC_LIBRARY "<CMAKE_AR> -o <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
However, it does not help anything. The "make" still generates .a library files
The command that I compiled my code as follows; I also copy all .a files to the same folder as main.cpp
emcc -std=c++17 -O3 -s WASM=1 -s USE_WEBGL2=1 -s FULL_ES3=1 -s ALLOW_MEMORY_GROWTH=1 -o hello.html -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" --no-heap-copy libgdcmcharls.a libgdcmCommon.a libgdcmDICT.a libgdcmDSED.a libgdcmexpat.a libgdcmIOD.a libgdcmjpeg12.a libgdcmjpeg16.a libgdcmjpeg8.a libgdcmMEXD.a libgdcmMSFF.a libgdcmopenjp2.a libgdcmzlib.a libgdcmuuid.a libsocketxx.a main.cpp ...........
The link is my GDCM 2.8.9 library files. Compiled in Windows10 using
emcmake cmake and finally emmake make ( based on mingw32-make ).
Here is my CMakeCXXCompiler.cmake after calling emconfigure cmake .
set(CMAKE_CXX_COMPILER "C:/workspace/emsdk/emscripten/1.38.14/em++.bat")
set(CMAKE_CXX_COMPILER_ARG1 "")
set(CMAKE_CXX_COMPILER_ID "Clang")
set(CMAKE_CXX_COMPILER_VERSION "6.0.1")
set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "")
set(CMAKE_CXX_COMPILER_WRAPPER "")
set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "98")
set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17")
set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters")
set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates")
set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates")
set(CMAKE_CXX17_COMPILE_FEATURES "")
set(CMAKE_CXX20_COMPILE_FEATURES "")
set(CMAKE_CXX_PLATFORM_ID "emscripten")
set(CMAKE_CXX_SIMULATE_ID "")
set(CMAKE_CXX_SIMULATE_VERSION "")
set(CMAKE_AR "C:/workspace/emsdk/emscripten/1.38.14/emar.bat")
set(CMAKE_CXX_COMPILER_AR "C:/Program Files/LLVM/bin/llvm-ar.exe")
set(CMAKE_RANLIB "C:/workspace/emsdk/emscripten/1.38.14/emranlib.bat")
set(CMAKE_CXX_COMPILER_RANLIB "C:/Program Files/LLVM/bin/llvm-ranlib.exe")
set(CMAKE_LINKER "C:/Program Files/LLVM/bin/wasm-ld.exe")
set(CMAKE_COMPILER_IS_GNUCXX )
set(CMAKE_CXX_COMPILER_LOADED 1)
set(CMAKE_CXX_COMPILER_WORKS TRUE)
set(CMAKE_CXX_ABI_COMPILED )
set(CMAKE_COMPILER_IS_MINGW )
set(CMAKE_COMPILER_IS_CYGWIN )
if(CMAKE_COMPILER_IS_CYGWIN)
set(CYGWIN 1)
set(UNIX 1)
endif()
set(CMAKE_CXX_COMPILER_ENV_VAR "CXX")
if(CMAKE_COMPILER_IS_MINGW)
set(MINGW 1)
endif()
set(CMAKE_CXX_COMPILER_ID_RUN 1)
set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP)
set(CMAKE_CXX_LINKER_PREFERENCE 30)
set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1)
# Save compiler ABI information.
set(CMAKE_CXX_SIZEOF_DATA_PTR "4")
set(CMAKE_CXX_COMPILER_ABI "")
set(CMAKE_CXX_LIBRARY_ARCHITECTURE "")
if(CMAKE_CXX_SIZEOF_DATA_PTR)
set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}")
endif()
if(CMAKE_CXX_COMPILER_ABI)
set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}")
endif()
if(CMAKE_CXX_LIBRARY_ARCHITECTURE)
set(CMAKE_LIBRARY_ARCHITECTURE "")
endif()
set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "")
if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX)
set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}")
endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "")
set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "")
Finally I found the problem. The library that generated from command;
emconfigure cmake .
is not the emcc compatible even when I see the compiler it used em++.bat as the compiler.
How to spot the problem.
I use a program called "Hex Editor Neo" to open the library files ( use only small library files to test, big library files the program crashes).
If it is not compatible to use with emcc. The first few character will be ! arch This will not work. see the picture below for the library file that is wrong generated from cmake;
Make sure that you have BC like in the picture below;
but how to do this
you cannot just use
emconfigure cmake . <---------- wrong
you need to put an option to generate bitcode (.bc) by following command
emconfigure cmake . -DEMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES=1
Then it will generate all library files ending with .bc and these library files can be linked with emcc

LTO with LLVM and CMake

I am trying to apply Link Time Optimization with LLVM on a CMake Project, that creates a shared library. My question is pretty much the same as this one:
Switching between GCC and Clang/LLVM using CMake.
However, the answers do not seem to be applicable anymore, since llvm-ld is not present in the new versions. On the command line, I run the following commands to get LTO (Assuming there are only 2 .cpp files):
Compile to byte code:
clang++ -c FirstClass.cpp -O3 -flto -o FirstClass.bc
clang++ -c SecondClass.cpp -O3 -flto -o SecondClass.bc
Link byte code:
llvm-link FirstClass.bc SecondClass.bc -o unoptimized.bc
Optimize byte code:
opt -O3 unoptimized.bc -o optimized.bc
Convert byte code to shared object:
clang++ -shared optimized.bc -o libTest.so
Could somebody please tell me how to have CMake run the additional steps?
The correct way to use Clang and enable LTO is using the -flto flag to the clang command line both at compile and link time.
In addition, you will need to be working on a platform with a linker that either directly supports LTO (Apple's platforms generally) or that have an LLVM linker plugin (Linux using the Gold linker, but I think some have gotten the BFD linker to support the linker plugin as well). If you're using the linker plugin, you'll need to make sure your install of LLVM built and installed the plugin. If it did, Clang will automatically add the necessary linker command line options to use the plugin when linking with -flto, even for shared objects.
Also, The LLVM project is working on a new linker (LLD) which will support LTO out of the box on all the platforms it supports, but it is still pretty early days. Currently I know of folks testing out its LTO support on Windows and Linux, and it seems to be working well but still misses many features.
check_ipo_supported() resulted for me in "Policy CMP0069 is not set" error on CMake 3.9.1.
Per its help, CMake up to 3.8 only supported Intel compiler's LTO. It didn't work on XCode 9's clang for me either.
What worked, in the end:
cmake_policy(SET CMP0069 NEW)
include(CheckIPOSupported)
check_ipo_supported()
add_executable(Foobar SOURCES)
set_target_properties(Foobar PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
Looks like add_executable() needs to be after cmake_policy(SET CMP0069 NEW).
LTO cache
target_link_libraries(Foobar "-Wl,-cache_path_lto,${PROJECT_BINARY_DIR}/lto.cache") did no harm.
Pick your command-line option depending on your linker.
More brutal option
According to #ChandlerCarruth's answer:
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto")
target_link_libraries(Foobar -flto)
endif ()
Enabling (thin) lto on Cmake 3.9 and newer should be straightforward:
include(CheckIPOSupported)
check_ipo_supported()
set_target_properties(myProject PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
Instead of set_target_properties per project, a single global setting of set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) can be done.
In order to speed up recompiles, a cache for LTO can be set:
function(append value)
foreach(variable ${ARGN})
set(${variable} "${${variable}} ${value}" PARENT_SCOPE)
endforeach(variable)
endfunction()
append("-fuse-ld=gold -Wl,--no-threads,--plugin-opt,cache-dir=${PROJECT_BINARY_DIR}/lto.cache" CMAKE_EXE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS)
This forces gold as linker, in order to use the right command line options. It might require a symlink of /usr/lib/LLVMgold.so to /usr/lib/llvm-4.0/lib/LLVMgold.so.

DistCC and CMake - select between local and distributed build when running make

My project is build using CMake and is compiled with DistCC + GCC.
I configure the compiler as follows:
SET(CMAKE_C_COMPILER "distcc variation-of-gcc")
To build the project, I simply run 'cmake' and then 'make -jXX'.
Although distcc really speeds up things, I sometimes want to build without distribution - I want it to build locally on the machine.
I know I can modify DISTCC_HOSTS to include only localhost - but this still has the overhead of distcc networking, although it is faster than the overhead for other machines...
I can also do that by rerunning cmake again and modifying the CMAKE_C_COMPILER using customization flags.
But I am looking for a way to do that by just adding a flag directly to 'make'.
I.e.
# This will use distcc:
make -jXX ...
# This will run locally:
make LOCAL_BUILD=1 -jX ...
Is there a CMake trick I can use?
We use the following to allow make time (rather than cmake time) switching on and off of the -Werror flag.
if(CMAKE_GENERATOR STREQUAL "Unix Makefiles")
# TODO: this approach for the WERROR only works with makefiles not Ninja
set(CMAKE_CXX_COMPILE_OBJECT "<CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES> <FLAGS> $(WERROR) -o <OBJECT> -c <SOURCE>")
endif()
Then we run
make WERROR=-Werror
to turn on warnings as error.
I expect you could do something similar to have whether to use distcc come from a make variable. Like this:
set(CMAKE_CXX_COMPILE_OBJECT "$(USE_DISTCC) <CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>")
And then run either
make USE_DISTCC=distcc
or just
make
The simplest thing to do (IMO) is write a little script in your project that invokes the compiler, and change your CMake files to run that script instead of containing the name of the compiler directly:
SET(CMAKE_C_COMPILER "my-gcc-script")
Now you can have that script normally run distcc, but (based on an environment variable or something) also run without distcc. There isn't any need to change anything in your CMake files.