cmake + clang_tidy - disable checking in directory - c++

I have large project using CMake. I want to add clang_tidy-8 support with following code:
set(BORG_CLANG_TIDY OFF CACHE STRING "If enabled, clang-tidy will be used. If set to 'fix', fixes will be done on source")
set_property(CACHE BORG_CLANG_TIDY PROPERTY STRINGS ON OFF fix)
if(BORG_CLANG_TIDY)
if (BORG_CLANG_TIDY STREQUAL "fix")
set(maybe_fix -fix)
endif()
set(CMAKE_CXX_CLANG_TIDY clang-tidy-8 -extra-arg=-Wno-unknown-warning-option -format-style=file ${maybe_fix} )
endif()
I put proper .clang-tidy in root directory of project (proper = with desired checks). However, there are directories that I don't want clang tidy to check/fix (3rdparty and legacy code that can't be modified because it is brittle). So I tried putting empty .clang-tidy file in those directories (empty = with -checks=-*). This doesn't work because Error: no checks enabled.
I hoped to find some some fake -checks=-*,hello-world-do-nothing-check but nothing presented itself.
Is there other way to disable checks in selected subdirectories (/subtrees)? Those directories are static and may be hardcoded in CMake if needed.

If you want a dummy check that would do nothing there's at least one that's pretty easy to disable by its options: misc-definitions-in-headers
The HeaderFileExtensions option can be used to make the check work with only certain header file suffixes. If you set it to something non-existent line "x" then you have a hello-world-do-nothing-check alternative. Your clang-tidy file would then look something like this:
Checks: '-*,misc-definitions-in-headers'
CheckOptions:
- { key: HeaderFileExtensions, value: "x" }
You can also check https://stackoverflow.com/a/56319752/9874699 and try to adapt the line-filter to filter out files from certain directories.

Is there other way to disable checks in selected subdirectories (/subtrees)?
In CMakeList.txt files contained in those subdirectories, add the following line:
set(CMAKE_CXX_CLANG_TIDY "")
But this is not a good solution: it creates a binding between the build system and a toolchain-specific tool. CMAKE_CXX_CLANG_TIDY should only ever be set via the configuration command (or possibly via a tool-chain file).

Here's what I used in this scenario:
# Disable most checks as this is third-party code
# Have to enable at least one check, so pick a benign one!
#InheritParentConfig: false
Checks: cppcoreguidelines-avoid-goto
I think we can all agree "Go To Statement Considered Harmful".

Related

How to exclude source files by configuration in CLion?

As shown below, Threre are two directories that are exclusively included in the build depending on the build option. (by setting CMake)
ROOT
-- src
-- moduleA
-- codeA.c
-- moduleB
-- codeB.c
commonCode.c
funcSameNameDifferentImpl function exist in both codeA.c and codeB.c. It is used in commonCode.c
In this case, how I exclude codeA.c from CLion source control indexing? When I try to navigate or go to definition, IDE is NOT working as I expected. Even IDE bring me to source file excluded in build with kind warning 'This file does not belong to any project...'
I found out below guide and check overriding file type as plain text is working well. But, I think it is too ugly, not elegant.
https://www.jetbrains.com/help/clion/controlling-source-library-and-exclude-directories.html
Is there a nice way to exclude files and directories from CLion indexing?
I think it's intuitive behavior for indexing to change automatically depending on the build target.

CMake non-configurable option

In CMake, how can I define an option that is not configurable by the user, but automatically calculated?
I would like to do the following:
if (FOO AND NOT BAR)
option(BOO "Foo and not bar" ON)
endif()
And then I can use BOO in different CMake files:
if (BOO)
# do something
endif()
And also in sources:
#ifdef BOO
// do something
#endif
So BOO behaves like a regular option, but is automatically calculated and not configurable by user running cmake.
EDIT: I now realize that options are not implicitly available in sources, but need to be defined explicitly with target_add_definitions or other means. So now it becomes obvious that the solution is to define BOO as a CMake variable, rather than an option.
Your "non-user-configurable" option sounds like a variable, which you can create like:
if(FOO AND NOT BAR)
set(BOO TRUE)
else()
set(BOO FALSE)
endif()
To use this variable in C++ code, you need to tell CMake to create a C++ file which defines this information. Take a look at CMake's configure_file function. Here's the official documentation, and here's part of the official CMake tutorial which walks through a simple usage of this function. I would type out some example code, but it would be a lot of boilerplate, and the linked documents do the job and should be better at answering your questions.
There is an alternative to configure_file: using target_compile_definitions. The tradeoff to make is between simplicity of implementation and build times. If the value of a compile definitions changes upon reconfiguration, then CMake has to assume that every source file of the target needs to be recompiled, since it has no information about how that definition is being used (ie. target_compile_definitions is easy to implement, but can lead to much slower build times in specific scenarios). When you use configure_file, only the files that include the file that defines those definitions need to be recompiled. There's also potentially an argumen to be made about "readability": if your target gets installed, it's easier for someone reading the installed headers to find the definition versus having to read generated CMake files to find it.
This could be done using a normal variable, but in some scenarios this information will not be available everywhere, e.g. if the subdirectory structure created by add_subdirectory does not ensure the information is required only in the CMakeLists.txt file where the info is introduced or in one of it's descendant directories.
In this case you should be using a cache variable with type internal INTERNAL. This has the added benefit of allowing you to persist the info for use during reconfiguration. You could also use FORCE to overwrite the value of any cache variable.
Example
if (... BOO not yet set properly ...)
set(BOO_VALUE ...)
set(BOO ${BOO_VALUE} CACHE INTERNAL "") # internal implies FORCE
endif()
Note that this won't make the preprocessor define available. You still need to use something like
target_compile_definitions(mytarget PRIVATE "BOO=${BOO}")
You can just use variable with set(BOO <what you want>).
You can find the documentation here: https://cmake.org/cmake/help/latest/command/set.html
But there is no option that will restrict the value of your variable for an internal settings. Only you.

Setting CMAKE_CXX_STANDARD to various values

I have a C++ library, that's intended to be usable across several compiler versions and several C++ standards. I have tests for this library - and I need to ensure that these tests pass for this matrix of compilers/versions that I wish to support.
I can provide -DCMAKE_CXX_STANDARD=xx (for 11, 14, 17) on the command line, and this seems to work fine. How do I provide a default value for this field? I would like that, if not provided by the user, that default is 11. It seems that when I do:
$ CXX=/path/to/gcc-7 cmake .. -DCMAKE_CXX_STANDARD=17
# does stuff, CMAKE_CXX_STANDARD is 17
$ CXX=/path/to/gcc-7 cmake ..
# does stuff, CMAKE_CXX_STANDARD is still 17
which is counterintuitive to me. Is there a way to make the latter use the desired default value of 11?
Also, if I just rerun cmake with a different value in the same build directory, would that be enough to trigger a rebuild or would I need a new build directory?
You can use the [option][1] command to let the user choose and give a default value yourself:
option(Barry_CXX_STANDARD "C++ standard" 11)
set(CMAKE_CXX_STANDARD Barry_CXX_STANDARD)
The variable name Barry_CXX_STANDARD indicated that it is specific to your project and should be the same prefix as all project-specific variables are named.
The downside of this approach is, that experienced CMake users would be surprised and set CMAKE_CXX_STANDARD directly.
Another approach is to check whether the variable is set.
if(NOT "${CMAKE_CXX_STANDARD}")
set(CMAKE_CXX_STANDARD 11)
endif()
If CMake provides already a variable, I would use the second approach. If it is only your project-specific variable, the first one is better.
In any case, if you want to change the value you have to delete the CMakeCache.txt in your build directory. Otherwise the caching hides the change.
In CMake world, first invocation of cmake differs from later ones (from the view of setting options in command line):
First invocation:
If option's value is given in command line(-DOPTION=VALUE), this value is used.
If option's value is not given in command line, default value is used.
Later invocations:
If option's value is given in command line(-DOPTION=VALUE), this value is used.
If option's value is not given in command line, previous value is used.
If option is unset (-UOPTION), default value is used.
Thus, having one cmake invocation already done, one may:
modify some of options and leave other unchanged
For doing that, pass modified options' values with -D.
reset some of options and leave other unchanged
For doing that, pass reset options' values with -U.
set some options, and reset others to default values
For doing that, make a clean rebuild by removing CMakeCache.txt from build directory or removing all files from build directory.
For assign default value for an option in the project, use common CACHE variables:
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used")

Difference between cmake variables and cmake arguments

I have been given some source code, along with a CMakeLists.txt file, and told to run cmake by:
cmake ../src -DOPEN_NI_ROOT=/home/karnivaurus/OpenNI
I have also noticed that there is a file called FindOpenNI.cmake, which I believe is used when find_package(OpenNI) is called by cmake.
Therefore, I am guessing that OPEN_NI_ROOT is some kind of variable that is used by cmake for the remainder of setup.
However, I have tried inserting the line set(OPEN_NI_ROOT "/home/karnivaurus/OpenNI") into my CMakeLists.txt file, in an attempt to avoid the need to add it as an argument at the command line. But this does not seem to do the same thing.
Can somebody please explain how these two variable types are different?
The file FindOpenNI.cmake is open source and can be found at:
https://github.com/victorprad/InfiniTAM/blob/master/InfiniTAM/cmake/FindOpenNI.cmake
The issue is this line in FindOpenNI.cmake (link):
set(OPEN_NI_ROOT "/usr/local" CACHE FILEPATH "Root directory of OpenNI2")
This will set OPEN_NI_ROOT unless it's already in the cache. A simple call to:
set(OPEN_NI_ROOT "/home/karnivaurus/OpenNI")
does not set the variable in the cache, so it will be overridden when the line in FindOpenNI.cmake is hit. Using the command line form of setting the variable will set it in the cache, which is why it works just fine.
The easiest way to avoid having to set the command line option is to set the cache explicitly in your own CMakeLists.txt:
set(OPEN_NI_ROOT "/home/karnivaurus/OpenNI" CACHE FILEPATH "Root directory of OpenNI2")
If you're working from a dirty build directory, it's likely this cache variable already exists, so this line would have no effect. In that case, either work from a clean build directory, or set the FORCE option:
set(OPEN_NI_ROOT "/home/karnivaurus/OpenNI" CACHE FILEPATH "Root directory of OpenNI2" FORCE)
This will write over the cached value. Note that this would prevent you from setting the option in the command line in the future, which is why this method isn't preferred. You can find some more information about the mechanics of this here.

How do I set scons system include path

Using scons I can easily set my include paths:
env.Append( CPPPATH=['foo'] )
This passes the flag
-Ifoo
to gcc
However I'm trying to compile with a lot of warnings enabled.
In particular with
env.Append( CPPFLAGS=['-Werror', '-Wall', '-Wextra'] )
which dies horribly on certain boost includes ... I can fix this by adding the boost includes to the system include path rather than the include path as gcc treats system includes differently.
So what I need to get passed to gcc instead of -Ifoo is
-isystem foo
I guess I could do this with the CPPFLAGS variable, but was wondering if there was a better solution built into scons.
There is no built-in way to pass -isystem include paths in SCons, mainly because it is very compiler/platform specific.
Putting it in the CXXFLAGS will work, but note that this will hide the headers from SCons' dependency scanner, which only looks at CPPPATH.
This is probably OK if you don't expect those headers to ever change, but could cause weird issues if you use the build results cache and/or implicit dependency cache.
If you do
print env.Dump()
you'll see _CPPINCFLAGS, and you'll see that variable used in CCCOM (or _CCCOMCOM). _CPPINCFLAGS typically looks like this:
'$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
From this you can probably see how you could add an "isystem" set of includes as well, like _CPPSYSTEMINCFLAGS or some such. Just define your own prefix, path var name (e.g. CPPSYSTEMPATH) and suffix and use the above idiom to concatenate the prefix. Then just append your _CPPSYSTEMINCFLAGS to CCCOM or _CCCOMCOM and off you go.
Of course this is system-specific but you can conditionally include your new variable in the compiler command line as and when you want.
According to the SCons release notes, "-isystem" is supported since version 2.3.4 for the environment's CCFLAGS.
So, you can, for example, do the following:
env.AppendUnique(CCFLAGS=('-isystem', '/your/path/to/boost'))
Still, you need to be sure that your compiler supports that option.
Expanding on the idea proposed by #LangerJan and #BenG... Here's a full cross-platform example (replace env['IS_WINDOWS'] with your windows platform checking)
from SCons.Util import is_List
def enable_extlib_headers(env, include_paths):
"""Enables C++ builders with current 'env' to include external headers
specified in the include_paths (list or string value).
Special treatment to avoid scanning these for changes and/or warnings.
This speeds up the C++-related build configuration.
"""
if not is_List(include_paths):
include_paths = [include_paths]
include_options = []
if env['IS_WINDOWS']:
# Simply go around SCons scanners and add compiler options directly
include_options = ['-I' + p for p in include_paths]
else:
# Tag these includes as system, to avoid scanning them for dependencies,
# and make compiler ignore any warnings
for p in include_paths:
include_options.append('-isystem')
include_options.append(p)
env.Append(CXXFLAGS = include_options)
Now, when configuring the use of external libraries, instead of
env.AppendUnique(CPPPATH=include_paths)
call
enable_extlib_headers(env, include_paths)
In my case this reduced the pruned dependency tree (as produced with --tree=prune) by 1000x on Linux and 3000x on Windows! It sped up the no-action build time (i.e. all targets up to date) by 5-7x
The pruned dependency tree before this change had 4 million includes from Boost. That's insane.