リリース版・デバッグ版・テスト を作る CMakeLists.txt

CMakeLists.txt の例で、リリース版とデバッグ版を両方作れるものがあまり見当たらなかったので作った。

ついでに googletest によるテストビルドも。

今の所 clang on macOS でしか動かしていない。

# ディレクトリ構造

ビルド前はこんな感じ:

```text
├── CMakeLists.gtest.txt
├── CMakeLists.txt
├── CMakeLists.txt.in
├── coverage.sh
└── src
     ├── main.cpp
     ├── sum_of_digits.cpp
     ├── sum_of_digits.h
     └── test
         └── test_sum_of_digits.cpp
```

そして `make` して `make coverage` するとこうなる:

```text
├── CMakeLists.gtest.txt
├── CMakeLists.txt
├── CMakeLists.txt.in
├── coverage.sh
├── debug
│   └── sample.out
├── gcov
│   └── src#sum_of_digits.cpp##src#sum_of_digits.cpp.gcov
├── lib
│   └── 略
├── release
│   └── sample.out
├── reltest
│   └── sample.out
├── src
│   ├── main.cpp
│   ├── sum_of_digits.cpp
│   ├── sum_of_digits.h
│   └── test
│       └── test_sum_of_digits.cpp
└── test
     └── sample.out
```


# ソース

## CMakeLists.txt.in

ダウンロードする googletest の指定かな。

see https://github.com/google/googletest/blob/master/googletest/README.md

```cmake
cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-src"
  BINARY_DIR        "${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)
```

## CMakeLists.gtest.txt

取得した googletest のビルドかな。

see https://github.com/google/googletest/blob/master/googletest/README.md

```cmake
# google test 対応( see https://github.com/google/googletest/blob/master/googletest/README.md )
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
configure_file(CMakeLists.txt.in  lib/googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  RESULT_VARIABLE result
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-download )
if(result)
  message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
  RESULT_VARIABLE result
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-download )
if(result)
  message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()

# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This defines
# the gtest and gtest_main targets.
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-src
                ${CMAKE_CURRENT_BINARY_DIR}/lib/googletest-build
                EXCLUDE_FROM_ALL)
```

## CMakeLists.txt

```cmake
cmake_minimum_required(VERSION 3.16)

project(cmake_sample1 LANGUAGES CXX )

# source codes
set( SRC_WILDCARDS src/*.cpp )
set( TEST_SRC_WILDCARDS src/test/*.cpp )

# macros
set(MACRO "")
set(NDEBUG_MACRO NDEBUG=1)
set(DEBUG_MACRO DEBUG=1;_DEBUG=1)
set(TEST_MACRO TESTBUILD=1)

# compile options
set(COMPLIE_OPT -Wall)
set(NDEBUG_COMPLIE_OPT -O2)
set(DEBUG_COMPLIE_OPT -g -O0)
set(TEST_COMPLIE_OPT -g --coverage -fno-elide-constructors)

# link options
set(LINK_OPT -Wall -lpthread)
set(NDEBUG_LINK_OPT "")
set(DEBUG_LINK_OPT "")
set(TEST_LINK_OPT --coverage -fno-elide-constructors)

set( CMAKE_CXX_FLAGS -std=c++17 ) # ここで c++11 以降を指定しないと gtest のビルドが通らない

file(GLOB SOURCES ${SRC_WILDCARDS})
file(GLOB REL_SOURCES 
  RELATIVE ${CMAKE_CURRENT_BINARY_DIR}
  ${SRC_WILDCARDS})
file(GLOB TEST_SOURCES ${TEST_SRC_WILDCARDS})

include(CMakeLists.gtest.txt)

function(define_build)
  cmake_parse_arguments(
    DEF           # 変数名につく prefix
    "DEBUG;TEST"  # bool 型の変数のリスト
    "COND;TARGET" # 値を一つとる変数のリスト
    ""            # 値を複数取る変数のリスト
    ${ARGN})

  set(TNAME "${DEF_COND}" )
  add_executable( "${TNAME}" )
  target_sources( "${TNAME}"
    PRIVATE
      ${SOURCES}
      $<$<BOOL:${DEF_TEST}>:${TEST_SOURCES}>
  )
  set_target_properties("${TNAME}" 
    PROPERTIES
      RUNTIME_OUTPUT_DIRECTORY "${DEF_COND}"
      RUNTIME_OUTPUT_NAME "${DEF_TARGET}"
  )
  target_compile_definitions("${TNAME}"
    PRIVATE
      ${MACRO}
      $<$<NOT:$<BOOL:${DEF_DEBUG}>>:${NDEBUG_MACRO}>
      $<$<BOOL:${DEF_DEBUG}>:${DEBUG_MACRO}>
      $<$<BOOL:${DEF_TEST}>:${TEST_MACRO}>
  )
  target_compile_options( "${TNAME}"
    PRIVATE
      ${COMPLIE_OPT}
      $<$<NOT:$<BOOL:${DEF_DEBUG}>>:${NDEBUG_COMPLIE_OPT}>
      $<$<BOOL:${DEF_DEBUG}>:${DEBUG_COMPLIE_OPT}>
      $<$<BOOL:${DEF_TEST}>:${TEST_COMPLIE_OPT}>
  )
  target_include_directories("${TNAME}" PRIVATE
    "src"
  )
  target_link_options( "${TNAME}"
    PUBLIC
      ${LINK_OPT}
      $<$<NOT:$<BOOL:${DEF_DEBUG}>>:${NDEBUG_LINK_OPT}>
      $<$<BOOL:${DEF_DEBUG}>:${DEBUG_LINK_OPT}>
      $<$<BOOL:${DEF_TEST}>:${TEST_LINK_OPT}>
  )
  target_compile_features("${TNAME}" PUBLIC
    cxx_std_17
  )
  if( ${DEF_TEST} STREQUAL "TRUE" )
    # Now simply link against gtest or gtest_main as needed. Eg
    target_link_libraries("${TNAME}" gtest_main)

  endif()

endfunction()

define_build(
  COND "release"
  TARGET "sample.out")

define_build(
  DEBUG
  COND "debug"
  TARGET "sample.out")

define_build(
  TEST
  DEBUG
  COND "test"
  TARGET "sample.out")

define_build(
  TEST
  COND "reltest"
  TARGET "sample.out")

add_custom_target(coverage
  DEPENDS test ./coverage.sh
  COMMAND rm -f CMakeFiles/test.dir/**/*.gcda
  COMMAND mkdir -p gcov
  COMMAND rm -f gcov/*.gcov
  COMMAND test/sample.out
  COMMAND ./coverage.sh ${REL_SOURCES}
  COMMAND mv *.gcov gcov)
```

## coverage.sh

そして、カバレッジ情報から gcov をつくるスクリプト。
macOS じゃない環境ではうまく動かないんじゃないかと思うけど試していない。

```bash
#!/bin/bash

set -eu
HERE=$(cd $(dirname $0); pwd)
cd $HERE

TARGET=test

function makegcov(){
  local SRC=$1
  gcov -p -l -f ./${SRC} \
    -gcno=./CMakeFiles/test.dir/${SRC}.gcno \
    -gcda=./CMakeFiles/test.dir/${SRC}.gcda | c++filt -n
}

for x in "$@"
do
  makegcov "$x"
done
```

# ビルド

`cmake .` → `make` → `make coverage` すると、下記のようになる:

```text
$ cmake .
-- The CXX compiler identification is AppleClang 11.0.0.11000033
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /somewhare/lib/googletest-download
[ 11%] Performing update step for 'googletest'
Current branch master is up to date.
[ 22%] No configure step for 'googletest'
[ 33%] No build step for 'googletest'
[ 44%] No install step for 'googletest'
[ 55%] No test step for 'googletest'
[ 66%] Completed 'googletest'
[100%] Built target googletest
-- The C compiler identification is AppleClang 11.0.0.11000033
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Found PythonInterp: /usr/local/bin/python (found version "2.7.17") 
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE  
-- Configuring done
-- Generating done
-- Build files have been written to: /somewhare
$ make
[  5%] Building CXX object lib/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
[ 11%] Linking CXX static library ../../libgtest.a
[ 11%] Built target gtest
[ 16%] Building CXX object lib/googletest-build/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
[ 22%] Linking CXX static library ../../libgtest_main.a
[ 22%] Built target gtest_main
Scanning dependencies of target reltest
[ 27%] Building CXX object CMakeFiles/reltest.dir/src/main.cpp.o
[ 33%] Building CXX object CMakeFiles/reltest.dir/src/sum_of_digits.cpp.o
[ 38%] Building CXX object CMakeFiles/reltest.dir/src/test/test_sum_of_digits.cpp.o
[ 44%] Linking CXX executable reltest/sample.out
[ 44%] Built target reltest
Scanning dependencies of target release
[ 50%] Building CXX object CMakeFiles/release.dir/src/main.cpp.o
[ 55%] Building CXX object CMakeFiles/release.dir/src/sum_of_digits.cpp.o
[ 61%] Linking CXX executable release/sample.out
[ 61%] Built target release
Scanning dependencies of target test
[ 66%] Building CXX object CMakeFiles/test.dir/src/main.cpp.o
[ 72%] Building CXX object CMakeFiles/test.dir/src/sum_of_digits.cpp.o
[ 77%] Building CXX object CMakeFiles/test.dir/src/test/test_sum_of_digits.cpp.o
[ 83%] Linking CXX executable test/sample.out
[ 83%] Built target test
Scanning dependencies of target debug
[ 88%] Building CXX object CMakeFiles/debug.dir/src/main.cpp.o
[ 94%] Building CXX object CMakeFiles/debug.dir/src/sum_of_digits.cpp.o
[100%] Linking CXX executable debug/sample.out
[100%] Built target debug
$ make coverage
[ 25%] Built target gtest
[ 50%] Built target gtest_main
[100%] Built target test
Scanning dependencies of target coverage
Running main() from /somewhare/lib/googletest-src/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from sum_of_digits
[ RUN      ] sum_of_digits.typical_case
[       OK ] sum_of_digits.typical_case (0 ms)
[ RUN      ] sum_of_digits.huge_base
[       OK ] sum_of_digits.huge_base (0 ms)
[ RUN      ] sum_of_digits.throw_if_invaldparam
[       OK ] sum_of_digits.throw_if_invaldparam (0 ms)
[----------] 3 tests from sum_of_digits (1 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (1 ms total)
[  PASSED  ] 3 tests.
Function 'sum_of_digits(int, int)'
Lines executed:88.89% of 9

Function 'unused_function()'
Lines executed:0.00% of 1

File 'src/sum_of_digits.cpp'
Lines executed:80.00% of 10
src/sum_of_digits.cpp:creating 'src#sum_of_digits.cpp##src#sum_of_digits.cpp.gcov'

[100%] Built target coverage
```

上記は `make` しているので全部ビルドしている。
一部しか必要ない場合は `make test` や `make release` などとすればよい。
`make coverage` すると事前に `make test` する。

# CMakeLists.txt について、かるく説明

関数 `define_build` を呼ぶと release とか debug とかの構成ができる。
呼び方は見て分かる通り、こんな

```cmake
define_build(
  TEST
  COND "reltest"
  TARGET "sample.out")
```

感じ。
`TEST` を指定するとテストビルドになる。
`DEBUG` を指定するとデバッグビルドになる。最適化されず、デバッグ情報が入る。
`DEBUG` を指定 **しない** とリリースビルドになる。最適化され、デバッグ情報が入らない。
`COND` は構成の名前。
`TARGET` は、出来上がるバイナリの名前。

各設定でのコンパイルオプションとかリンクオプションは `define_build` の定義の前の `set(NDEBUG_COMPLIE_OPT -O2)` とかで設定している。

それとは別に `add_custom_target` で `coverage` というビルドターゲットを作った。
macOS じゃない環境ではうまく動かないんじゃないかと思うけど試していない。

# できていないこと

本当は

```shell
$ make clang-release
```

とすると clang / clang++ を使ったコンパイルになり、

```shell
$ make gcc9-release
```

とすると gcc-9 / g++-9 を使ったコンパイルになるようにしたかった。

でも、どうしたらそうできるのかがまだ調べられていない。