Set Up System Programming Environment¶
The following CMake setup example
is for a single
project:
.
├── cmake
│ ├── coverage.cmake
│ ├── coverage-report.cmake
│ ├── docs.cmake
│ ├── FindDoctest.cmake
│ ├── FindEGL.cmake
│ ├── FindEigen.cmake
│ ├── FindGLEW.cmake
│ ├── FindGLFW.cmake
│ ├── FindNanoFlann.cmake
│ ├── FindOpenGL.cmake
│ ├── FindOpenMesh.cmake
│ ├── format-cpp.cmake
│ ├── glsl.cmake
│ ├── glsl-make-shaders.cmake
│ ├── lint-cpp-style.cmake
│ ├── lint-python-style.cmake
│ ├── macros.cmake
│ └── tidy-cpp.cmake
├── docs
│ ├── proto
│ │ └── system
│ │ └── clock.rst
│ ├── _static
│ ├── _templates
│ ├── conf.py
│ └── index.rst
├── include
│ └── proto
│ ├── dgp
│ │ └── tensor.hpp
│ └── system
│ ├── clock.hpp
│ └── log.hpp
├── resources
│ ├── data
│ │ └── point-correspondences.txt
│ ├── scripts
│ │ ├── clang_tidy_suppression.py
│ │ ├── cpp_include_directive.py
│ │ └── obj_to_profile_image.py
│ └── shaders
│ ├── common
│ │ └── projection.glsl
│ ├── comp
│ ├── frag
│ │ ├── rgb2yuv.frag
│ │ └── per-pixel-ray-cast.glsl
│ ├── geom
│ ├── tesc
│ ├── tese
│ └── vert
│ └── fullscreen-triangle.vert
├── src
│ ├── dgp
│ │ ├── tests
│ │ │ ├── main.cpp
│ │ │ └── tensor.cpp
│ │ ├── CMakeLists.txt
│ │ └── tensor.cpp
│ ├── system
│ │ ├── tests
│ │ │ └── game-loop.cpp
│ │ ├── clock.cpp
│ │ ├── CMakeLists.txt
│ │ └── log.cpp
│ ├── CMakeLists.txt
│ └── main.cpp
├── third-party
│ ├── include
│ │ ├── doctest
│ │ │ └── doctest.h
│ │ ├── EGL
│ │ │ ├── eglext.h
│ │ │ ├── egl.h
│ │ │ ├── eglplatform.h
│ │ │ └── khrplatform.h
│ │ └── GL
│ │ ├── glcorearb.h
│ │ ├── glext.h
│ │ ├── glxext.h
│ │ └── wglext.h
│ ├── cpplint.py
│ └── run-clang-tidy.py
└── CMakeLists.txt
Top Level Directory¶
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
# must be declared before project()
find_program(ClangCompiler "clang")
if(ClangCompiler)
set(CMAKE_C_COMPILER ${ClangCompiler})
endif()
unset(ClangCompiler CACHE)
find_program(ClangPlusPlusCompiler "clang++")
if(ClangPlusPlusCompiler)
set(CMAKE_CXX_COMPILER ${ClangPlusPlusCompiler})
endif()
unset(ClangPlusPlusCompiler CACHE)
project(proto LANGUAGES C CXX)
if(MSVC)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# execute only once at ${CMAKE_SOURCE_DIR}
option(BUILD_TESTING "Enable/Disable unit tests." ON)
if(BUILD_TESTING)
enable_testing()
endif()
set(BUILD_SHARED_LIBS TRUE CACHE
BOOL "If this value is on, makefiles will generate a shared library.")
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
include(macros)
enable_code_hardening()
check_coverage(CoverageFlags)
check_sanitizer(SanitizerFlags)
check_compiler_settings(CoverageFlags SanitizerFlags)
find_package(Doctest REQUIRED)
find_package(OpenGL REQUIRED)
find_package(EGL REQUIRED)
#find_package(Eigen REQUIRED)
#find_package(OpenMesh REQUIRED)
#find_package(NanoFlann REQUIRED)
#find_package(GLEW REQUIRED)
#find_package(GLFW REQUIRED)
include_directories(SYSTEM ${DOCTEST_INCLUDE_DIR}
# ${EIGEN_INCLUDE_DIR}
# ${NANOFLANN_INCLUDE_DIR}
# ${GLEW_INCLUDE_DIR} ${GLFW_INCLUDE_DIR}
${OPENGL_INCLUDE_DIR} ${EGL_INCLUDE_DIR})
include(docs)
include(coverage)
include(format-cpp)
include(glsl)
include(lint-cpp-style)
include(lint-python-style)
include(tidy-cpp)
include_directories(include)
add_subdirectory(src)
# each shared library will append its target to ${PROJECT_LIBRARIES}
define_coverage(PROJECT_LIBRARIES)
set(PROJECT_LIBRARIES ${PROJECT_LIBRARIES}
# ${OPENMESH_LIBRARIES}
# ${GLEW_LIBRARIES} ${GLFW_LIBRARIES}
${OPENGL_LIBRARIES} ${EGL_LIBRARIES})
set(Mains src/headless_gl.cpp)
make_standalones(PROJECT_LIBRARIES ${Mains})
Macros¶
function(list2string TARGET DELIMITER)
foreach(arg ${ARGN})
if(tmp)
set(tmp "${tmp}${DELIMITER}${arg}")
else()
set(tmp "${arg}")
endif()
endforeach()
set(${TARGET} "${tmp}" PARENT_SCOPE)
endfunction(list2string)
function(make_standalones LIBS)
foreach(file_path ${ARGN})
get_filename_component(file_name ${file_path} NAME)
string(REPLACE ".cpp" "" bin ${file_name})
add_executable(${bin} ${file_path})
foreach(lib ${PROJECT_LIBRARIES})
target_link_libraries(${bin} ${lib})
endforeach()
endforeach()
endfunction(make_standalones)
macro(check_coverage FLAGS)
option(CODE_COVERAGE "Enable/Disable code coverage instrumentation." OFF)
if(CODE_COVERAGE)
set(${FLAGS} -g -O0 -fprofile-instr-generate -fcoverage-mapping)
else()
set(${FLAGS} "")
endif()
endmacro(check_coverage)
macro(check_sanitizer FLAGS)
set(CLANG_SANITIZER "None" CACHE STRING
"Choose the type of sanitizer, options are: None ASan MSan TSan")
set(${FLAGS} -g -O1 -fno-optimize-sibling-calls -fno-omit-frame-pointer)
if(CLANG_SANITIZER STREQUAL "ASan")
set(${FLAGS} ${${FLAGS}} -fsanitize=undefined,integer,address)
elseif(CLANG_SANITIZER STREQUAL "MSan")
set(${FLAGS} ${${FLAGS}} -fsanitize=undefined,integer,memory)
elseif(CLANG_SANITIZER STREQUAL "TSan")
set(${FLAGS} ${${FLAGS}} -fsanitize=undefined,integer,thread)
else()
set(${FLAGS} "")
endif()
endmacro(check_sanitizer)
function(enable_code_hardening)
option(CODE_HARDENING "Enable/Disable secure code instrumentation." OFF)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CODE_HARDENING)
# add_definitions(-D_FORTIFY_SOURCE=2)
set(CompilerFlags -fstack-protector
-flto -fvisibility=default -fsanitize=cfi
-fsanitize=safe-stack)
# Use -fPIC and -fPIE for all targets by default, including static libs
set(CMAKE_POSITION_INDEPENDENT_CODE ON PARENT_SCOPE)
# CMake doesn't add "-pie" by default for executables
set(LinkerFlags -pie -Wl,--strip-all
-Wl,-z,relro
-Wl,-z,now
-Wl,-z,nodlopen -Wl,-z,nodump -Wl,-z,noexecstack)
endif()
list2string(CompilerFlags " " ${CompilerFlags})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CompilerFlags}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CompilerFlags}" PARENT_SCOPE)
list2string(LinkerFlags " " ${LinkerFlags})
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} ${LinkerFlags}" PARENT_SCOPE)
endfunction(enable_code_hardening)
function(check_compiler_settings SANITIZER COVERAGE)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(Flags -std=c++1z -stdlib=libc++ -Werror -Weverything
-Wno-c++98-compat -Wno-c++98-compat-pedantic
# -Wno-format-nonliteral -Wno-unreachable-code
# -Wno-double-promotion -fno-exceptions
# -Wno-documentation-unknown-command
${${COVERAGE}} ${${SANITIZER}})
endif()
list2string(Flags " " ${Flags})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${Flags}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Flags}" PARENT_SCOPE)
if(MSVC)
# Useless warnings
add_compile_options(/wd4138)
# Disable warnings related to Eigen
add_compile_options(/wd4996)
# OpenMesh requires this definition
add_definitions(-D_USE_MATH_DEFINES)
endif()
endfunction(check_compiler_settings)
check_coverage¶
The proposed code coverage tool consists of llvm-cov and llvm-profdata, which can be installed via
sudo apt-get install llvm
llvm-cov also supports an output format put forth by gcov, which
in turn can be visualized by lcov. If one prefers to use these tools with
clang, then replace the existing
-fprofile-instr-generate -fcoverage-mapping
with
-fprofile-arcs -ftest-coverage
, which is equivalent to specifying
--coverage
.
check_sanitizer¶
ASan, MSan, and TSan are mutually exclusive and cannot be used together. The proposed configuration enables UBSan by default. Note that LSan only works on Linux and is enabled by default when using ASan. Furthermore, MSan requires an instrumented C++ standard library.
enable_code_hardening¶
_FORTIFY_SOURCE is not supported by Clang at the moment. SafeStack is the only instrumentation that works out of the box. The Control Flow Integrity (CFI) sanitizer assumes the LLVMgold plugin is already installed e.g. via
sudo apt-get install llvm-5.0-dev
The correct usage of CFI requires -fvisibility=hidden
, but that will
make the source code incompatible with MSVC.
Enabling ASLR requires hacking CMake a bit. To check whether the security hardening features have been enabled, use hardening-check to examine the ELF binary. Unfortunately, these bare minimum features are the only ones supported by Clang.
find_package¶
FindOpenGL.cmake
and FindEGL.cmake
are mandatory while
FindGLEW.cmake
and FindGLFW.cmake
have become
optional conveniences for OpenGL.
Documentation¶
The proposed docs
setup is no different from a
blog.
Two extra things to take care of are:
conf.py
should containprimary_domain='cpp'
index.rst
should containIndices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`
The reason behind this is to keep documentation separate from code. Code comments do not count as documentation. This separation enables full utilization of a documentation tool to design the artifact before writing any code. Note that the mantra of self-documentating code is also applicable during this design phase.
find_program(SPHINX_BUILD "sphinx-build")
if(SPHINX_BUILD)
set(DocsSrcDir ${PROJECT_SOURCE_DIR}/docs)
set(DocsDstDir ${PROJECT_BINARY_DIR}/docs)
add_custom_target(docs
COMMAND ${SPHINX_BUILD} -b html
${DocsSrcDir} ${DocsDstDir})
set_property(DIRECTORY APPEND PROPERTY
ADDITIONAL_MAKE_CLEAN_FILES ${DocsDstDir})
unset(DocsSrcDir)
unset(DocsDstDir)
else()
message(STATUS "docs require sphinx-build.")
endif()
Code Coverage and Tests¶
find_program(COV NAMES llvm-cov-5.0 llvm-cov)
find_program(PROFDATA NAMES llvm-profdata-5.0 llvm-profdata)
if(CODE_COVERAGE AND COV AND PROFDATA)
add_custom_target(coverage)
elseif(CODE_COVERAGE AND BUILD_TESTING)
message(STATUS "Code coverage requires llvm-cov and llvm-profdata.")
endif()
function(define_coverage MODULES)
set(CoverageResultsDir ${PROJECT_BINARY_DIR}/coverage-results)
set(MergedCoverageData merged-coverage.profdata)
if(CODE_COVERAGE AND COV AND PROFDATA)
foreach(module ${${MODULES}})
set(TargetLibraries ${TargetLibraries} $<TARGET_FILE:${module}>)
endforeach()
list2string(TargetLibraries "," ${TargetLibraries})
add_custom_command(TARGET coverage POST_BUILD
COMMAND ${CMAKE_COMMAND}
-D COV=${COV}
-D PROFDATA=${PROFDATA}
-D ProfDataDir=${PROJECT_BINARY_DIR}
-D TargetLibraries=${TargetLibraries}
-D CoverageResultsDir=${CoverageResultsDir}
-D MergedCoverageData=${MergedCoverageData} -P
${PROJECT_SOURCE_DIR}/cmake/coverage-report.cmake)
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES
${CoverageResultsDir} ${MergedCoverageData})
endif()
endfunction(define_coverage)
function(compile_test MODULE_NAME SOURCE LIBS EXTERNAL_LIBS)
if(BUILD_TESTING)
set(CoverageResultsDir ${PROJECT_BINARY_DIR}/coverage-results)
set(test_binary test-${MODULE_NAME})
add_executable(${test_binary} ${SOURCE})
foreach(lib ${LIBS})
add_dependencies(${test_binary} ${lib})
target_link_libraries(${test_binary} ${lib})
endforeach()
foreach(lib ${EXTERNAL_LIBS})
target_link_libraries(${test_binary} ${lib})
endforeach()
add_test(NAME "${MODULE_NAME}"
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/resources
COMMAND ${test_binary})
endif()
if(BUILD_TESTING AND CODE_COVERAGE AND COV AND PROFDATA)
set(raw_profile ${CMAKE_CURRENT_BINARY_DIR}/${test_binary}.profraw)
set(prof_data ${CMAKE_CURRENT_BINARY_DIR}/${test_binary}.profdata)
target_compile_options(${test_binary} PRIVATE
-fprofile-instr-generate=${raw_profile})
foreach(lib ${LIBS})
set(BINS ${BINS} $<TARGET_FILE:${lib}>)
endforeach()
add_custom_command(TARGET ${test_binary} POST_BUILD
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/resources
COMMAND ${test_binary}
COMMAND ${PROFDATA} merge -sparse -o ${prof_data} ${raw_profile}
COMMAND ${COV} show -format=html
-instr-profile=${prof_data} -object ${BINS}
-output-dir=${CoverageResultsDir}/${MODULE_NAME})
add_dependencies(coverage ${test_binary})
set_property(DIRECTORY APPEND PROPERTY
ADDITIONAL_MAKE_CLEAN_FILES ${raw_profile} ${prof_data})
endif()
endfunction(compile_test)
Since performance analysis tools are orthogonal to code coverage and tests, it will not be included in the build system.
define_coverage¶
This will compile the total code coverage over the specified library modules
via coverage-report.cmake
. The script invocation is needed in order to
glob all the individual coverage reports. Note that default.profraw
is
the default file name unless LLVM_PROFILE_FILE=”<name>.profraw”
or
-fprofile-instr-generate=<name>.profraw
is specified.
file(REMOVE ${MergedCoverageData})
file(GLOB_RECURSE IndexedProfiles ${ProfDataDir}/*.profdata)
execute_process(COMMAND ${PROFDATA} merge -sparse ${IndexedProfiles}
-o ${MergedCoverageData})
execute_process(COMMAND ${COV} show
-instr-profile ${MergedCoverageData} -object ${TargetLibraries}
-format=html -output-dir=${CoverageResultsDir})
ClangFormat¶
clang-format is a tool that will automatically format C/C++/Java/JavaScript
code. One should avoid globbing files because new files will not be added to
the existing glob. The solution to recompute the glob is
touch_nocreate
.
file(GLOB_RECURSE CxxIncludeFiles ${PROJECT_SOURCE_DIR}/include/*.[ch]pp)
file(GLOB_RECURSE CxxSourceFiles ${PROJECT_SOURCE_DIR}/src/*.[ch]pp)
set(AllCxxSourceFiles ${CxxIncludeFiles} ${CxxSourceFiles})
find_program(CLANG_FORMAT NAMES clang-format-5.0 clang-format)
if(CLANG_FORMAT)
add_custom_target(format-cpp
COMMAND ${CMAKE_COMMAND} -E touch_nocreate
${CMAKE_CURRENT_LIST_FILE}
COMMAND ${CLANG_FORMAT} -i -style=google
${AllCxxSourceFiles})
else()
message(STATUS "format-cpp requires clang-format.")
endif()
unset(AllCxxSourceFiles)
unset(CxxIncludeFiles)
unset(CxxSourceFiles)
GLSL¶
Since GLSL is C-like, one can use clang-format to enforce a coding style.
set(SrcDir ${PROJECT_SOURCE_DIR}/resources)
set(DstDir ${PROJECT_BINARY_DIR}/resources)
set(ShaderSrcDir ${SrcDir}/shaders)
set(ShaderDstDir ${DstDir}/shaders)
set(PreprocessorScript ${SrcDir}/scripts/cpp_include_directive.py)
find_program(CLANG_FORMAT "clang-format")
find_program(PYTHON3 "python3")
if(CLANG_FORMAT AND PYTHON3)
add_custom_target(glsl
COMMAND ${CMAKE_COMMAND}
-D CLANG_FORMAT=${CLANG_FORMAT}
-D ShaderSrcDir=${ShaderSrcDir}
-D ShaderDstDir=${ShaderDstDir}
-D PreprocessorScript=${PreprocessorScript}
-D PYTHON3=${PYTHON3} -P
${CMAKE_CURRENT_LIST_DIR}/glsl-make-shaders.cmake)
set_property(DIRECTORY APPEND PROPERTY
ADDITIONAL_MAKE_CLEAN_FILES ${ShaderDstDir})
else()
message(STATUS "glsl requires clang-format and python3.")
endif()
unset(SrcDir)
unset(DstDir)
unset(ShaderSrcDir)
unset(ShaderDstDir)
unset(PreprocessorScript)
Once again, invoking the script glsl-make-shaders.cmake
is needed to
glob all the individual shaders. The current setup does not search for files
with a .glsl
extension.
file(REMOVE_RECURSE ${ShaderDstDir})
file(GLOB_RECURSE ShaderFiles ${ShaderSrcDir}/*)
if(${ShaderFiles})
execute_process(COMMAND ${CLANG_FORMAT} -i -style=google ${ShaderFiles})
endif()
set(ComputeShaderExt comp)
set(VertexShaderExt vert)
set(TessellationControlShaderExt tesc)
set(TessellationEvaluationShaderExt tese)
set(GeometryShaderExt geom)
set(FragmentShaderExt frag)
set(AllShaderExtensions
${ComputeShaderExt}
${VertexShaderExt}
${TessellationControlShaderExt} ${TessellationEvaluationShaderExt}
${GeometryShaderExt}
${FragmentShaderExt})
foreach(ext ${AllShaderExtensions})
file(GLOB glob ${ShaderSrcDir}/${ext}/*.${ext})
file(MAKE_DIRECTORY ${ShaderDstDir}/${ext})
foreach(shader ${glob})
get_filename_component(file_name ${shader} NAME)
execute_process(COMMAND ${PYTHON3} ${PreprocessorScript}
${shader} ${ShaderDstDir}/${ext}/${file_name})
endforeach()
endforeach()
GLSL does not support the include preprocessor directive, but it’s quite easy to implement one in Python.
"""Implements the include directive of cpp."""
import argparse
import logging
import sys
from pathlib import Path
def find_first_match(idirs, ifile):
"""Return the first file that matches or None."""
for idir in idirs:
_ = Path(idir).joinpath(ifile)
if _.exists():
return str(_.resolve())
return None
def process_include(args, infile):
"""Recursively process include directive."""
for line in infile:
_ = line.strip()
if _.startswith('#include'):
include_file = _.split()[1]
if include_file.startswith('<') and include_file.endswith('>'):
# do not search in local file directory
include_dirs = args.include_dirs
else:
# search local file directory first
_ = [str(Path(infile.name).parent.resolve()),
str(Path(infile.name).parent.parent.resolve())]
include_dirs = args.include_dirs + _
# remove <> and ""
include_file = include_file[1:-1]
match = find_first_match(include_dirs, include_file)
if match is None:
_ = 'Cannot find {} while searching in {}.'
logging.warning(_.format(include_file, include_dirs))
continue
logging.info('Including {}'.format(match))
with open(match, 'r') as match_infile:
process_include(args, match_infile)
args.outfile.write('\n')
else:
args.outfile.write(line)
def main():
"""Imitate cpp include directive."""
_ = main.__doc__
parser = argparse.ArgumentParser(description=_)
parser.add_argument('infile', nargs='?', default=sys.stdin,
type=argparse.FileType('r'),
help='input to run preprocessor on')
parser.add_argument('outfile', nargs='?', default=sys.stdout,
type=argparse.FileType('w'),
help='destination to store generated outputs')
_ = 'directories to be searched for include directive'
parser.add_argument('-I', nargs='+', dest='include_dirs', metavar='',
default=[], help=_)
args = parser.parse_args()
process_include(args, args.infile)
if __name__ == "__main__":
main()
C++ Lint¶
cpplint is an additional set of style checks that one can apply after clang-format.
set(StyleLinter ${PROJECT_SOURCE_DIR}/third-party/cpplint.py)
set(Filters -legal/copyright
-build/c++11)
list2string(Filters "," ${Filters})
set(CxxIncludeDir ${PROJECT_SOURCE_DIR}/include)
set(CxxSourceDir ${PROJECT_SOURCE_DIR}/src)
find_program(PYTHON3 "python3")
if(PYTHON3)
add_custom_target(lint-cpp-style
COMMAND ${PYTHON3} ${StyleLinter}
--filter=${Filters}
--root=${CxxIncludeDir}
--recursive ${CxxIncludeDir}
COMMAND ${PYTHON3} ${StyleLinter}
--filter=${Filters}
--root=${CxxSourceDir}
--recursive ${CxxSourceDir})
else()
message(STATUS "lint-cpp-style style checker requires python3.")
endif()
unset(StyleLinter)
unset(Filters)
unset(CxxIncludeDir)
unset(CxxSourceDir)
Python Lint¶
pycodestyle and pydocstyle are mandatory when coding in Python.
set(ScriptsDir ${PROJECT_SOURCE_DIR}/resources/scripts)
find_program(PY_CODE_STYLE "pycodestyle" DOC "PEP 8 Style Checker")
find_program(PY_DOC_STYLE "pydocstyle" DOC "PEP 257 Style Checker")
if(PY_CODE_STYLE AND PY_DOC_STYLE)
add_custom_target(lint-python-style
COMMAND ${PY_CODE_STYLE} ${ScriptsDir}
COMMAND ${PY_DOC_STYLE} ${ScriptsDir})
else()
message(STATUS "lint-python-style requires pycodestyle and pydocstyle.")
endif()
unset(ScriptsDir)
ClangTidy¶
Clang Static Analyzer tries to find bugs without executing the program. It
can be invoked via clang --analyze
or the scan-build script.
The former runs the static analyzer and generates text reports. The latter has
the option to generate a nice looking html report.
clang-tidy is a front end to scan-build. Note that a static analyzer check is different from a clang-tidy check; it just so happens that clang-tidy implements the default suite of static analyzer checks. A custom clang-tidy check should be implemented using clang-check.
Prefer run-clang-tidy-5.0.py
to clang-tidy-5.0
because the
former will use multithreading while the latter is single-threaded. The
following uses a modified version that accepts line filters, which can be
generated using clang_tidy_suppression.py
.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(TidyChecks -google-readability-namespace-comments
-cppcoreguidelines-pro-type-reinterpret-cast
-cppcoreguidelines-pro-bounds-pointer-arithmetic
-cppcoreguidelines-pro-bounds-array-to-pointer-decay
-cppcoreguidelines-pro-bounds-constant-array-index)
else()
set(TidyChecks -google-readability-namespace-comments
-cppcoreguidelines-pro-bounds-pointer-arithmetic
-cppcoreguidelines-pro-bounds-array-to-pointer-decay
-cppcoreguidelines-pro-bounds-constant-array-index)
endif()
list2string(ClangTidyChecks "," ${TidyChecks})
find_program(CLANG_TIDY NAMES run-clang-tidy.py
PATHS ${PROJECT_SOURCE_DIR}/third-party
/usr/bin)
if(CLANG_TIDY)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(READ ${PROJECT_BINARY_DIR}/line_filter.json LineFilter)
add_custom_target(tidy-cpp
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMAND ${CLANG_TIDY}
-p ${PROJECT_BINARY_DIR}
-header-filter="^${PROJECT_SOURCE_DIR}/include"
-checks=*,-*llvm*,${ClangTidyChecks}
-line-filter=${LineFilter})
else()
message(STATUS "tidy-cpp requires clang-tidy.")
endif()
Source Code Organization¶
The current layout is arbitrary and is merely one way to decompose a system into subsystems.
src/CMakeLists.txt
determines the overall system decomposition.
set(ModuleName system)
set(Libs "")
set(ExternalLibs "")
add_subdirectory(${ModuleName})
list(APPEND Modules ${ModuleName})
set(ModuleName dgp)
set(Libs system)
set(ExternalLibs "")
add_subdirectory(${ModuleName})
list(APPEND Modules ${ModuleName})
set(PROJECT_LIBRARIES ${Modules} PARENT_SCOPE)
src/system/CMakeLists.txt
generates the subsystem and the corresponding
tests, both of which could depend on other subsystems.
set(LocalSource clock.cpp
log.cpp)
add_library(${ModuleName} ${LocalSource})
set_target_properties(${ModuleName} PROPERTIES DEBUG_POSTFIX "d")
foreach(lib ${Libs})
add_dependencies(${ModuleName} ${lib})
target_link_libraries(${ModuleName} ${lib})
endforeach()
foreach(lib ${ExternalLibs})
target_link_libraries(${ModuleName} ${lib})
endforeach()
set(LocalTestSource tests/main.cpp
tests/clock.cpp)
set(Libs ${Libs} ${ModuleName})
compile_test("${ModuleName}" "${LocalTestSource}" "${Libs}" "${ExternalLibs}")