CMake add target for invoking clang analyzer

11,259

Solution 1

I found a way:

function(add_clang_static_analysis target)
    get_target_property(SRCs ${target} SOURCES)
    add_library(${target}_analyze OBJECT EXCLUDE_FROM_ALL ${SRCs})
    set_target_properties(${target}_analyze PROPERTIES
                          COMPILE_OPTIONS "--analyze"
                          EXCLUDE_FROM_DEFAULT_BUILD true)
endfunction()

Combining clang's plist files (which get extension .o this way) into a report is still open ($<TARGET_OBJECTS:objlibtarget>?).

Solution 2

You can use scan-build when running cmake.

scan-build cmake /path/to/source
scan-build make

scan-build sets the CC and CXX environment variables which are picked up by cmake.

Solution 3

The following solution has the drawback of compiling and analyzing the entire project, not the specific targets.

set(on_side_build_path ${CMAKE_BINARY_DIR}/clang_static_analysis)
set(scan_build_path scan-build)
set(reports_path ${CMAKE_BINARY_DIR}/clang_static_analysis_reports)

# Creates clean directory where the analysis will be built.
add_custom_target(clang_static_analysis_prepare
    COMMAND ${CMAKE_COMMAND} -E rm -rf ${on_side_build_path}
    COMMAND ${CMAKE_COMMAND} -E make_directory ${on_side_build_path}
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Runs the analysis from the path created specifically for that task. Use 'my own' project source directory as the source directory.
add_custom_target(clang_static_analysis
    # scan-build wants Debug build, for better analysis.
    COMMAND ${scan_build_path} ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} -DCMAKE_BUILD_TYPE=Debug
    COMMAND ${scan_build_path}
                -v -v -o ${reports_path} 
                ${CMAKE_COMMAND} --build .
    WORKING_DIRECTORY ${on_side_build_path}
)

# Run the *_prepare target always before the analysis
add_dependencies(clang_static_analysis clang_static_analysis_prepare)

Invoke it with:

cmake --build . --target clang_static_analysis

Bonus #1: Allowing custom toolchains to work

scan-build injects the CC and CXX environment variables to specify the replaced compilers, which are ccc-analyzer and c++-analyzer. When defining CMAKE_C_COMPILER and CMAKE_CXX_COMPILER the CC and CXX variables will be ignored. What you need to do to support scan-build is to point CMAKE_C*_COMPILER variables to use the one from environment, if defined. So having that in your toolchain file:

set(CMAKE_C_COMPILER some/path/to/c_compiler)
set(CMAKE_Cxx_COMPILER some/path/to/cxx_compiler)

Replace it with:

if(DEFINED ENV{CC})
    set(CMAKE_C_COMPILER $ENV{CC})
else()
    set(CMAKE_C_COMPILER some/path/to/c_compiler)
endif()

if(DEFINED ENV{CXX})
    set(CMAKE_CXX_COMPILER $ENV{CXX})
else()
    set(CMAKE_CXX_COMPILER some/path/to/cxx_compiler)
endif()

Bonus #2: Using scan-build from the LLVM toolchain

If your custom toolchain is LLVM, then most probably you want to use the scan-build command from the toolchain. To enable it, simply define the path to toolchain path using cmake's cache variables:

set(LLVM_TOOLCHAIN_PATH "some/path/here" CACHE PATH "Path to the LLVM toolchain") 

or from the command line:

cmake -DLLVM_TOOLCHAIN_PATH=some/path/here ...

and then reuse the path from the custom target:

set(scan_build_path ${LLVM_TOOLCHAIN_PATH}/bin/scan-build)

Bonus #3: Adding test command

Note: enable_testing shall be called before add_test. enable_testing() must be called from the project's top level CMakeLists.txt for ctest to resolve paths to tests.

add_test(
    NAME clang_static_analysis
    COMMAND ${CMAKE_COMMAND} --build . --target clang_static_analysis
)

Run it like that:

# Fire configure and generate stages
cmake ../source/dir
ctest -R clang_static_analysis --verbose
Share:
11,259

Related videos on Youtube

Trass3r
Author by

Trass3r

Updated on June 26, 2022

Comments

  • Trass3r
    Trass3r almost 2 years

    I'd basically like to achieve the same as http://blog.alexrp.com/2013/09/26/clangs-static-analyzer-and-automake, but with CMake.

    analyze_srcs = foo.c
    analyze_plists = $(analyze_srcs:%.c=%.plist)
    CLEANFILES = $(analyze_plists)
    
    $(analyze_plists): %.plist: %.c
      @echo "  CCSA  " $@
      @$(COMPILE) --analyze $< -o $@
    
    analyze: $(analyze_plists)
    .PHONY: analyze
    

    So you can run

    make analyze
    make clean
    

    I guess I need to use add_custom_command/add_custom_target and somehow change the "object file" extension just for that target.

    Afterwards get a list of the generated files to perhaps pass them to a script for combining them into 1 output file.

    Can anyone point me in the right direction?

  • Escualo
    Escualo about 6 years
    And remember to erase the CMake cache beforehand (otherwise the command will have no effect)
  • David Ledger
    David Ledger over 3 years
    If you use a custom toolchain file, I'm not sure this works.
  • K. Koovalsky
    K. Koovalsky over 2 years
    The build fails because of missing compilation flags (like INCLUDE_DIRECTORIES, DEFINITIONS), that are not copied from the source target.
  • Trass3r
    Trass3r over 2 years
    True, you'd need to copy the whole target basically.