├── .clang-format ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── afl-fuzzing.cmake ├── c++-standards.cmake ├── c-standards.cmake ├── code-coverage.cmake ├── compiler-options.cmake ├── dependency-graph.cmake ├── doxygen.cmake ├── example ├── all │ └── CMakeLists.txt ├── code-coverage-all │ └── CMakeLists.txt ├── code-coverage-public │ ├── CMakeLists.txt │ ├── code.c │ └── header.h ├── code-coverage-target │ └── CMakeLists.txt └── src │ ├── asan │ ├── double_free.c │ ├── out_of_bounds_global.c │ ├── out_of_bounds_heap.c │ ├── out_of_bounds_stack.c │ ├── use_after_free.c │ ├── use_after_return.c │ └── use_after_scope.c │ ├── coverage.cpp │ ├── coverage.hpp │ ├── coverage.main.cpp │ ├── lsan │ ├── direct_leak.c │ └── indirect_leak.c │ ├── msan │ ├── uninitialized_pointer_used.c │ └── uninitialized_value_used.c │ ├── tsan │ └── data_race.cpp │ └── ubsan │ ├── dereferencing_misaligned_pointer.c │ └── signed_integer_overflow.c ├── formatting.cmake ├── glsl-shaders.cmake ├── img ├── code-cov.png └── dp-graph.png ├── link-time-optimization.cmake ├── prepare-catch.cmake ├── sanitizers.cmake └── tools.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Language: Cpp 3 | Standard: Cpp11 4 | IndentWidth: 4 5 | ColumnLimit: 100 6 | BinPackParameters: false 7 | AlwaysBreakTemplateDeclarations: true 8 | PenaltyReturnTypeOnItsOwnLine: 10000 9 | BreakConstructorInitializers: AfterColon 10 | ConstructorInitializerAllOnOneLineOrOnePerLine: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *build*/ -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - Analysis 3 | - Targeted Code Coverage 4 | - All Code Coverage 5 | - Sanitizers 6 | 7 | variables: 8 | ASAN_OPTIONS: detect_leaks=1 9 | 10 | # Analysis 11 | 12 | cmake-format Check: 13 | image: stabletec/build-core:fedora 14 | stage: Analysis 15 | parallel: 16 | matrix: 17 | - ARCH: [amd64, arm64, ppc64le] 18 | tags: 19 | - container 20 | - linux 21 | - ${ARCH} 22 | allow_failure: true 23 | script: 24 | - dnf install -y python3-pip 25 | - pip install cmake-format 26 | - cmake-format --version 27 | - cmake-format -i $(find . -name "*.cmake") 28 | - cmake-format -i $(find . -name "CMakeLists.txt") 29 | - git diff --exit-code 30 | 31 | clang-format Check: 32 | image: stabletec/build-core:fedora 33 | stage: Analysis 34 | parallel: 35 | matrix: 36 | - ARCH: [amd64, arm64, ppc64le] 37 | tags: 38 | - container 39 | - linux 40 | - ${ARCH} 41 | allow_failure: true 42 | script: 43 | - clang-format --version 44 | - clang-format -i $(find . -name "*.c") 45 | - clang-format -i $(find . -name "*.cpp") 46 | - clang-format -i $(find . -name "*.h") 47 | - clang-format -i $(find . -name "*.hpp") 48 | - git diff --exit-code 49 | 50 | # Targeted Code Coverage 51 | 52 | Linux/Targeted CC GCC Static: 53 | image: stabletec/build-core:fedora 54 | stage: Targeted Code Coverage 55 | parallel: 56 | matrix: 57 | - ARCH: [amd64, arm64, ppc64le] 58 | tags: 59 | - container 60 | - linux 61 | - ${ARCH} 62 | variables: 63 | CC: gcc 64 | CXX: g++ 65 | script: 66 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 67 | - ninja -C build 68 | - ninja -C build ccov 69 | 70 | Linux/Targeted CC GCC Shared: 71 | image: stabletec/build-core:fedora 72 | stage: Targeted Code Coverage 73 | parallel: 74 | matrix: 75 | - ARCH: [amd64, arm64, ppc64le] 76 | tags: 77 | - container 78 | - linux 79 | - ${ARCH} 80 | variables: 81 | CC: gcc 82 | CXX: g++ 83 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 84 | script: 85 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 86 | - ninja -C build 87 | - ninja -C build ccov 88 | 89 | Linux/Targeted CC Clang Static: 90 | image: stabletec/build-core:fedora 91 | stage: Targeted Code Coverage 92 | parallel: 93 | matrix: 94 | - ARCH: [amd64, arm64, ppc64le] 95 | tags: 96 | - container 97 | - linux 98 | - ${ARCH} 99 | variables: 100 | CC: clang 101 | CXX: clang++ 102 | script: 103 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 104 | - ninja -C build 105 | - ninja -C build ccov 106 | - ninja -C build ccov-report 107 | 108 | Linux/Targeted CC Clang Shared: 109 | image: stabletec/build-core:fedora 110 | stage: Targeted Code Coverage 111 | parallel: 112 | matrix: 113 | - ARCH: [amd64, arm64, ppc64le] 114 | tags: 115 | - container 116 | - linux 117 | - ${ARCH} 118 | variables: 119 | CC: clang 120 | CXX: clang++ 121 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 122 | script: 123 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 124 | - ninja -C build 125 | - ninja -C build ccov 126 | - ninja -C build ccov-report 127 | 128 | macOS/Targeted CC AppleClang Static: 129 | stage: Targeted Code Coverage 130 | parallel: 131 | matrix: 132 | - ARCH: [arm64] 133 | tags: 134 | - macos 135 | - ${ARCH} 136 | script: 137 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 138 | - ninja -C build 139 | - ninja -C build ccov 140 | - ninja -C build ccov-report 141 | 142 | macOS/Targeted CC AppleClang Shared: 143 | stage: Targeted Code Coverage 144 | parallel: 145 | matrix: 146 | - ARCH: [arm64] 147 | tags: 148 | - macos 149 | - ${ARCH} 150 | variables: 151 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 152 | script: 153 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 154 | - ninja -C build 155 | - ninja -C build ccov 156 | - ninja -C build ccov-report 157 | 158 | macOS/Targeted CC Clang Static: 159 | stage: Targeted Code Coverage 160 | parallel: 161 | matrix: 162 | - ARCH: [arm64] 163 | tags: 164 | - macos 165 | - ${ARCH} 166 | variables: 167 | CC: clang 168 | CXX: clang++ 169 | script: 170 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 171 | - ninja -C build 172 | - ninja -C build ccov 173 | - ninja -C build ccov-report 174 | 175 | macOS/Targeted CC Clang Shared: 176 | stage: Targeted Code Coverage 177 | parallel: 178 | matrix: 179 | - ARCH: [arm64] 180 | tags: 181 | - macos 182 | - ${ARCH} 183 | variables: 184 | CC: clang 185 | CXX: clang++ 186 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 187 | script: 188 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 189 | - ninja -C build 190 | - ninja -C build ccov 191 | - ninja -C build ccov-report 192 | 193 | Windows/Targeted CC Clang Static: 194 | image: stabletec/build-core:windows-ltsc2022 195 | stage: Targeted Code Coverage 196 | parallel: 197 | matrix: 198 | - ARCH: [amd64] 199 | tags: 200 | - container 201 | - windows 202 | - ltsc2022 203 | - ${ARCH} 204 | variables: 205 | CC: clang 206 | CXX: clang++ 207 | script: 208 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 209 | - ninja -C build 210 | - ninja -C build ccov 211 | - ninja -C build ccov-report 212 | 213 | Windows/Targeted CC Clang Shared: 214 | image: stabletec/build-core:windows-ltsc2022 215 | stage: Targeted Code Coverage 216 | parallel: 217 | matrix: 218 | - ARCH: [amd64] 219 | tags: 220 | - container 221 | - windows 222 | - ltsc2022 223 | - ${ARCH} 224 | variables: 225 | CC: clang 226 | CXX: clang++ 227 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 228 | script: 229 | - cmake -S example/code-coverage-target/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 230 | - ninja -C build 231 | - ninja -C build ccov 232 | - ninja -C build ccov-report 233 | 234 | # All Code Coverage 235 | 236 | Linux/All CC GCC Static: 237 | image: stabletec/build-core:fedora 238 | stage: All Code Coverage 239 | parallel: 240 | matrix: 241 | - ARCH: [amd64, arm64, ppc64le] 242 | tags: 243 | - container 244 | - linux 245 | - ${ARCH} 246 | variables: 247 | CC: gcc 248 | CXX: g++ 249 | script: 250 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 251 | - ninja -C build 252 | - ninja -C build ccov 253 | - ninja -C build ccov-all 254 | 255 | Linux/All CC GCC Shared: 256 | image: stabletec/build-core:fedora 257 | stage: All Code Coverage 258 | parallel: 259 | matrix: 260 | - ARCH: [amd64, arm64, ppc64le] 261 | tags: 262 | - container 263 | - linux 264 | - ${ARCH} 265 | variables: 266 | CC: gcc 267 | CXX: g++ 268 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 269 | script: 270 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 271 | - ninja -C build 272 | - ninja -C build ccov 273 | - ninja -C build ccov-all 274 | 275 | Linux/All CC Clang Static: 276 | image: stabletec/build-core:fedora 277 | stage: All Code Coverage 278 | parallel: 279 | matrix: 280 | - ARCH: [amd64, arm64, ppc64le] 281 | tags: 282 | - container 283 | - linux 284 | - ${ARCH} 285 | variables: 286 | CC: clang 287 | CXX: clang++ 288 | script: 289 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 290 | - ninja -C build 291 | - ninja -C build ccov-all 292 | - ninja -C build ccov-report 293 | - ninja -C build ccov-all-report 294 | 295 | Linux/All CC Clang Shared: 296 | image: stabletec/build-core:fedora 297 | stage: All Code Coverage 298 | parallel: 299 | matrix: 300 | - ARCH: [amd64, arm64, ppc64le] 301 | tags: 302 | - container 303 | - linux 304 | - ${ARCH} 305 | variables: 306 | CC: clang 307 | CXX: clang++ 308 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 309 | script: 310 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 311 | - ninja -C build 312 | - ninja -C build ccov 313 | - ninja -C build ccov-all 314 | - ninja -C build ccov-report 315 | - ninja -C build ccov-all-report 316 | 317 | macOS/All CC AppleClang Static: 318 | stage: All Code Coverage 319 | parallel: 320 | matrix: 321 | - ARCH: [arm64] 322 | tags: 323 | - macos 324 | - ${ARCH} 325 | script: 326 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 327 | - ninja -C build 328 | - ninja -C build ccov-all 329 | - ninja -C build ccov-report 330 | - ninja -C build ccov-all-report 331 | 332 | macOS/All CC AppleClang Shared: 333 | stage: All Code Coverage 334 | parallel: 335 | matrix: 336 | - ARCH: [arm64] 337 | tags: 338 | - macos 339 | - ${ARCH} 340 | variables: 341 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 342 | script: 343 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 344 | - ninja -C build 345 | - ninja -C build ccov 346 | - ninja -C build ccov-all 347 | - ninja -C build ccov-report 348 | - ninja -C build ccov-all-report 349 | 350 | macOS/All CC Clang Static: 351 | stage: All Code Coverage 352 | parallel: 353 | matrix: 354 | - ARCH: [arm64] 355 | tags: 356 | - macos 357 | - ${ARCH} 358 | variables: 359 | CC: clang 360 | CXX: clang++ 361 | script: 362 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 363 | - ninja -C build 364 | - ninja -C build ccov-all 365 | - ninja -C build ccov-report 366 | - ninja -C build ccov-all-report 367 | 368 | macOS/All CC Clang Shared: 369 | stage: All Code Coverage 370 | parallel: 371 | matrix: 372 | - ARCH: [arm64] 373 | tags: 374 | - macos 375 | - ${ARCH} 376 | variables: 377 | CC: clang 378 | CXX: clang++ 379 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 380 | script: 381 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 382 | - ninja -C build 383 | - ninja -C build ccov 384 | - ninja -C build ccov-all 385 | - ninja -C build ccov-report 386 | - ninja -C build ccov-all-report 387 | 388 | Windows/All CC Clang Static: 389 | image: stabletec/build-core:windows-ltsc2022 390 | stage: All Code Coverage 391 | parallel: 392 | matrix: 393 | - ARCH: [amd64] 394 | tags: 395 | - container 396 | - windows 397 | - ltsc2022 398 | - ${ARCH} 399 | variables: 400 | CC: clang 401 | CXX: clang++ 402 | script: 403 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 404 | - ninja -C build 405 | - ninja -C build ccov 406 | - ninja -C build ccov-all 407 | - ninja -C build ccov-report 408 | - ninja -C build ccov-all-report 409 | 410 | Windows/All CC Clang Shared: 411 | image: stabletec/build-core:windows-ltsc2022 412 | stage: All Code Coverage 413 | parallel: 414 | matrix: 415 | - ARCH: [amd64] 416 | tags: 417 | - container 418 | - windows 419 | - ltsc2022 420 | - ${ARCH} 421 | variables: 422 | CC: clang 423 | CXX: clang++ 424 | CMAKE_OPTIONS: -D BUILD_SHARED_LIBS=ON 425 | script: 426 | - cmake -S example/code-coverage-all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Release -D CODE_COVERAGE=ON ${CMAKE_OPTIONS} 427 | - ninja -C build 428 | - ninja -C build ccov 429 | - ninja -C build ccov-all 430 | - ninja -C build ccov-report 431 | - ninja -C build ccov-all-report 432 | 433 | # Sanitizers 434 | 435 | .linux_success_template: &linux_success_template 436 | stage: Sanitizers 437 | image: stabletec/build-core:fedora 438 | parallel: 439 | matrix: 440 | - ARCH: [amd64, arm64] 441 | tags: 442 | - container 443 | - linux 444 | - ${ARCH} 445 | script: 446 | - cmake -S example/all -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug ${CMAKE_OPTIONS} 447 | - ninja -C build 448 | - ctest --test-dir build --output-on-failure ${CTEST_OPTIONS} 449 | 450 | .linux_failure_template: &linux_failure_template 451 | stage: Sanitizers 452 | image: stabletec/build-core:fedora 453 | parallel: 454 | matrix: 455 | - ARCH: [amd64, arm64] 456 | tags: 457 | - container 458 | - linux 459 | - ${ARCH} 460 | script: 461 | - cmake -S example/all -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug ${CMAKE_OPTIONS} 462 | - ninja -C build 463 | - "! ctest --test-dir build --output-on-failure ${CTEST_OPTIONS}" 464 | 465 | Linux/Static Analysis: 466 | variables: 467 | CC: clang 468 | CXX: clang++ 469 | CMAKE_OPTIONS: -D CLANG_TIDY=ON -D CPPCHECK=ON 470 | <<: *linux_success_template 471 | 472 | Linux/Clang/Baseline: 473 | variables: 474 | CC: clang 475 | CXX: clang++ 476 | <<: *linux_success_template 477 | 478 | Linux/GCC/ThreadSanitizer: 479 | variables: 480 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=thread 481 | <<: *linux_failure_template 482 | 483 | Linux/Clang/ThreadSanitizer: 484 | variables: 485 | CC: clang 486 | CXX: clang++ 487 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=thread 488 | <<: *linux_failure_template 489 | 490 | Linux/GCC/AddressSanitizer: 491 | variables: 492 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=address 493 | <<: *linux_failure_template 494 | 495 | Linux/Clang/AddressSanitizer: 496 | variables: 497 | CC: clang 498 | CXX: clang++ 499 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=address 500 | <<: *linux_failure_template 501 | 502 | Linux/GCC/LeakSanitizer: 503 | variables: 504 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=leak 505 | <<: *linux_failure_template 506 | 507 | Linux/Clang/LeakSanitizer: 508 | variables: 509 | CC: clang 510 | CXX: clang++ 511 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=leak 512 | <<: *linux_failure_template 513 | 514 | Linux/Clang/MemorySanitizer: 515 | variables: 516 | CC: clang 517 | CXX: clang++ 518 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=memory 519 | <<: *linux_failure_template 520 | 521 | Linux/GCC/UndefinedSanitizer: 522 | variables: 523 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=undefined 524 | <<: *linux_failure_template 525 | 526 | Linux/Clang/UndefinedSanitizer: 527 | variables: 528 | CC: clang 529 | CXX: clang++ 530 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=undefined 531 | <<: *linux_failure_template 532 | 533 | .macos_success_template: &macos_success_template 534 | stage: Sanitizers 535 | parallel: 536 | matrix: 537 | - ARCH: [arm64] 538 | tags: 539 | - macos 540 | - ${ARCH} 541 | script: 542 | - cmake -S example/all -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug ${CMAKE_OPTIONS} 543 | - ninja -C build 544 | - ctest --test-dir build --output-on-failure ${CTEST_OPTIONS} 545 | 546 | .macos_failure_template: &macos_failure_template 547 | stage: Sanitizers 548 | parallel: 549 | matrix: 550 | - ARCH: [arm64] 551 | tags: 552 | - macos 553 | - ${ARCH} 554 | script: 555 | - cmake -S example/all -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug ${CMAKE_OPTIONS} 556 | - ninja -C build 557 | - "! ctest --test-dir build --output-on-failure ${CTEST_OPTIONS}" 558 | 559 | macOS/AppleClang/Baseline: 560 | <<: *macos_success_template 561 | 562 | macOS/Clang/Baseline: 563 | variables: 564 | CC: clang 565 | CXX: clang++ 566 | <<: *macos_success_template 567 | 568 | macOS/AppleClang/ThreadSanitizer: 569 | variables: 570 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=thread 571 | <<: *macos_failure_template 572 | 573 | macOS/Clang/ThreadSanitizer: 574 | variables: 575 | CC: clang 576 | CXX: clang++ 577 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=thread 578 | <<: *macos_failure_template 579 | 580 | macOS/AppleClang/AddressSanitizer: 581 | variables: 582 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=address 583 | <<: *macos_failure_template 584 | 585 | macOS/Clang/AddressSanitizer: 586 | variables: 587 | CC: clang 588 | CXX: clang++ 589 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=address 590 | <<: *macos_failure_template 591 | 592 | macOS/Clang/LeakSanitizer: 593 | variables: 594 | CC: clang 595 | CXX: clang++ 596 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=leak 597 | <<: *macos_failure_template 598 | 599 | macOS/AppleClang/UndefinedSanitizer: 600 | variables: 601 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=undefined 602 | <<: *macos_failure_template 603 | 604 | macOS/Clang/UndefinedSanitizer: 605 | variables: 606 | CC: clang 607 | CXX: clang++ 608 | CMAKE_OPTIONS: -D EXAMPLE_USE_SANITIZER=undefined 609 | <<: *macos_failure_template 610 | 611 | Windows/MSVC/Address Sanitizer: 612 | image: stabletec/build-core:windows-ltsc2022 613 | stage: Sanitizers 614 | parallel: 615 | matrix: 616 | - ARCH: [amd64] 617 | tags: 618 | - container 619 | - windows 620 | - ltsc2022 621 | - ${ARCH} 622 | script: 623 | - cmake -S example/all/ -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug -D EXAMPLE_USE_SANITIZER=address $env:CMAKE_OPTIONS 624 | - ninja -C build 625 | - ctest --test-dir build --output-on-failure $env:CTEST_OPTIONS ; if ($? -ne 0) { exit 1 } else { exit 0 } 626 | 627 | # Legacy Sanitizer 628 | 629 | Linux/GCC/ThreadSanitizer (LEGACY): 630 | variables: 631 | CMAKE_OPTIONS: -D USE_SANITIZER=thread 632 | <<: *linux_failure_template 633 | 634 | Linux/Clang/ThreadSanitizer (LEGACY): 635 | variables: 636 | CC: clang 637 | CXX: clang++ 638 | CMAKE_OPTIONS: -D USE_SANITIZER=thread 639 | <<: *linux_failure_template 640 | 641 | Linux/GCC/AddressSanitizer (LEGACY): 642 | variables: 643 | CMAKE_OPTIONS: -D USE_SANITIZER=address 644 | <<: *linux_failure_template 645 | 646 | Linux/Clang/AddressSanitizer (LEGACY): 647 | variables: 648 | CC: clang 649 | CXX: clang++ 650 | CMAKE_OPTIONS: -D USE_SANITIZER=address 651 | <<: *linux_failure_template 652 | 653 | Linux/GCC/LeakSanitizer (LEGACY): 654 | variables: 655 | CMAKE_OPTIONS: -D USE_SANITIZER=leak 656 | <<: *linux_failure_template 657 | 658 | Linux/Clang/LeakSanitizer (LEGACY): 659 | variables: 660 | CC: clang 661 | CXX: clang++ 662 | CMAKE_OPTIONS: -D USE_SANITIZER=leak 663 | <<: *linux_failure_template 664 | 665 | Linux/Clang/MemorySanitizer (LEGACY): 666 | variables: 667 | CC: clang 668 | CXX: clang++ 669 | CMAKE_OPTIONS: -D USE_SANITIZER=memory 670 | <<: *linux_failure_template 671 | 672 | Linux/GCC/UndefinedSanitizer (LEGACY): 673 | variables: 674 | CMAKE_OPTIONS: -D USE_SANITIZER=undefined 675 | CTEST_OPTIONS: --verbose 676 | <<: *linux_success_template 677 | 678 | Windows/MSVC/Address Sanitizer (LEGACY): 679 | image: stabletec/build-core:windows-ltsc2022 680 | stage: Sanitizers 681 | parallel: 682 | matrix: 683 | - ARCH: [amd64] 684 | tags: 685 | - container 686 | - windows 687 | - ltsc2022 688 | - ${ARCH} 689 | script: 690 | - cmake -S example/all/ -B build -G Ninja -D USE_SANITIZER=address 691 | - ninja -C build 692 | - ctest --test-dir build --output-on-failure $env:CTEST_OPTIONS ; if ($? -ne 0) { exit 1 } else { exit 0 } 693 | 694 | macOS/AppleClang/ThreadSanitizer (LEGACY): 695 | variables: 696 | CMAKE_OPTIONS: -D USE_SANITIZER=thread 697 | <<: *macos_failure_template 698 | 699 | macOS/Clang/ThreadSanitizer (LEGACY): 700 | variables: 701 | CC: clang 702 | CXX: clang++ 703 | CMAKE_OPTIONS: -D USE_SANITIZER=thread 704 | <<: *macos_failure_template 705 | 706 | macOS/AppleClang/AddressSanitizer (LEGACY): 707 | variables: 708 | CMAKE_OPTIONS: -D USE_SANITIZER=address 709 | <<: *macos_failure_template 710 | 711 | macOS/Clang/AddressSanitizer (LEGACY): 712 | variables: 713 | CC: clang 714 | CXX: clang++ 715 | CMAKE_OPTIONS: -D USE_SANITIZER=address 716 | <<: *macos_failure_template 717 | 718 | macOS/Clang/LeakSanitizer (LEGACY): 719 | variables: 720 | CC: clang 721 | CXX: clang++ 722 | CMAKE_OPTIONS: -D USE_SANITIZER=leak 723 | <<: *macos_failure_template 724 | 725 | macOS/AppleClang/UndefinedSanitizer (LEGACY): 726 | variables: 727 | CMAKE_OPTIONS: -D USE_SANITIZER=undefined 728 | CTEST_OPTIONS: --verbose 729 | <<: *macos_success_template 730 | 731 | macOS/Clang/UndefinedSanitizer (LEGACY): 732 | variables: 733 | CC: clang 734 | CXX: clang++ 735 | CMAKE_OPTIONS: -D USE_SANITIZER=undefined 736 | CTEST_OPTIONS: --verbose 737 | <<: *macos_success_template 738 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMake Scripts 2 | 3 | [![pipeline status](https://git.stabletec.com/other/cmake-scripts/badges/main/pipeline.svg)](https://git.stabletec.com/other/cmake-scripts/commits/main) 4 | [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://git.stabletec.com/other/cmake-scripts/blob/main/LICENSE) 5 | 6 | This is a collection of quite useful scripts that expand the possibilities for building software with CMake, by making some things easier and otherwise adding new build types 7 | 8 | - [C++ Standards `c++-standards.cmake`](#c-standards-c-standardscmake) 9 | - [Sanitizer Builds `sanitizers.cmake`](#sanitizer-builds-sanitizerscmake) 10 | - [Usage](#usage) 11 | - [Code Coverage `code-coverage.cmake`](#code-coverage-code-coveragecmake) 12 | - [Added Targets](#added-targets) 13 | - [Usage](#usage-1) 14 | - [Example 1 - All targets instrumented](#example-1---all-targets-instrumented) 15 | - [1a - Via global command](#1a---via-global-command) 16 | - [1b - Via target commands](#1b---via-target-commands) 17 | - [Example 2: Target instrumented, but with regex pattern of files to be excluded from report](#example-2-target-instrumented-but-with-regex-pattern-of-files-to-be-excluded-from-report) 18 | - [Example 3: Target added to the 'ccov' and 'ccov-all' targets](#example-3-target-added-to-the-ccov-and-ccov-all-targets) 19 | - [AFL Fuzzing Instrumentation `afl-fuzzing.cmake`](#afl-fuzzing-instrumentation-afl-fuzzingcmake) 20 | - [Usage](#usage-2) 21 | - [Compiler Options `compiler-options.cmake`](#compiler-options-compiler-optionscmake) 22 | - [Dependency Graph `dependency-graph.cmake`](#dependency-graph-dependency-graphcmake) 23 | - [Required Arguments](#required-arguments) 24 | - [OUTPUT\_TYPE *STR*](#output_type-str) 25 | - [Optional Arguments](#optional-arguments) 26 | - [ADD\_TO\_DEP\_GRAPH](#add_to_dep_graph) 27 | - [TARGET\_NAME *STR*](#target_name-str) 28 | - [OUTPUT\_DIR *STR*](#output_dir-str) 29 | - [GLSL Shader File Targeted Compilation`glsl-shaders.cmake`](#glsl-shader-file-targeted-compilationglsl-shaderscmake) 30 | - [Example](#example) 31 | - [Required Arguments](#required-arguments-1) 32 | - [TARGET\_NAME](#target_name) 33 | - [Optional Arguments](#optional-arguments-1) 34 | - [INTERFACE *FILES*](#interface-files) 35 | - [PUBLIC *FILES*](#public-files) 36 | - [PRIVATE *FILES*](#private-files) 37 | - [COMPILE\_OPTIONS *OPTIONS*](#compile_options-options) 38 | - [Doxygen `doxygen.cmake`](#doxygen-doxygencmake) 39 | - [Optional Arguments](#optional-arguments-2) 40 | - [ADD\_TO\_DOC](#add_to_doc) 41 | - [INSTALLABLE](#installable) 42 | - [PROCESS\_DOXYFILE](#process_doxyfile) 43 | - [TARGET\_NAME *STR*](#target_name-str-1) 44 | - [OUTPUT\_DIR *STR*](#output_dir-str-1) 45 | - [INSTALL\_PATH *STR*](#install_path-str) 46 | - [DOXYFILE\_PATH *STR*](#doxyfile_path-str) 47 | - [Prepare the Catch Test Framework `prepare-catch.cmake`](#prepare-the-catch-test-framework-prepare-catchcmake) 48 | - [Optional Arguments](#optional-arguments-3) 49 | - [COMPILED\_CATCH](#compiled_catch) 50 | - [CATCH1](#catch1) 51 | - [CLONE](#clone) 52 | - [Tools `tools.cmake`](#tools-toolscmake) 53 | - [clang-tidy](#clang-tidy) 54 | - [include-what-you-use](#include-what-you-use) 55 | - [cppcheck](#cppcheck) 56 | - [Formatting `formatting.cmake`](#formatting-formattingcmake) 57 | - [clang-format](#clang-format) 58 | - [cmake-format](#cmake-format) 59 | - [Link Time Optimization / Interprocedural Optimization `link-time-optimization.cmake`](#link-time-optimization--interprocedural-optimization-link-time-optimizationcmake) 60 | - [Optional Arguments](#optional-arguments-4) 61 | - [REQUIRED](#required) 62 | 63 | ## C++ Standards [`c++-standards.cmake`](c++-standards.cmake) 64 | 65 | Using the functions `cxx_11()`, `cxx_14()`, `cxx_17()` or `cxx_20()` this adds the appropriate flags for both unix and MSVC compilers, even for those before 3.11 with improper support. 66 | 67 | These obviously force the standard to be required, and also disables compiler-specific extensions, ie `--std=gnu++11`. This helps to prevent fragmenting the code base with items not available elsewhere, adhering to the agreed C++ standards only. 68 | 69 | ## Sanitizer Builds [`sanitizers.cmake`](sanitizers.cmake) 70 | 71 | Sanitizers are tools that perform checks during a program’s runtime and returns issues, and as such, along with unit testing, code coverage and static analysis, is another tool to add to the programmers toolbox. And of course, like the previous tools, are tragically simple to add into any project using CMake, allowing any project and developer to quickly and easily use. 72 | 73 | A quick rundown of the tools available, and what they do: 74 | - [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) detects memory leaks, or issues where memory is allocated and never deallocated, causing programs to slowly consume more and more memory, eventually leading to a crash. 75 | - [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) is a fast memory error detector. It is useful for detecting most issues dealing with memory, such as: 76 | - Out of bounds accesses to heap, stack, global 77 | - Use after free 78 | - Use after return 79 | - Use after scope 80 | - Double-free, invalid free 81 | - Memory leaks (using LeakSanitizer) 82 | - [ThreadSanitizer](https://clang.llvm.org/docs/ThreadSanitizer.html) detects data races for multi-threaded code. 83 | - [UndefinedBehaviourSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) detects the use of various features of C/C++ that are explicitly listed as resulting in undefined behaviour. Most notably: 84 | - Using misaligned or null pointer. 85 | - Signed integer overflow 86 | - Conversion to, from, or between floating-point types which would overflow the destination 87 | - Division by zero 88 | - Unreachable code 89 | - [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) detects uninitialized reads. 90 | - [Control Flow Integrity](https://clang.llvm.org/docs/ControlFlowIntegrity.html) is designed to detect certain forms of undefined behaviour that can potentially allow attackers to subvert the program's control flow. 91 | 92 | ### Usage 93 | 94 | The most basic way to enable sanitizers is to simply call `add_sanitizer_support` with the desired sanitizer names, whereupon it will check for the availability and compatability of the combined flags and apply to following compile targets: 95 | ```cmake 96 | # apply address and leak sanitizers 97 | add_sanitizer_support(address leak) 98 | # future targets will be compiled with '-fsanitize=address -fsanitize=leak' 99 | ``` 100 | 101 | Compile options on a per-sanitizer basis can be accomplished by calling `set_sanitizer_options` before with the name of the sanitizer and desired compile options: 102 | ```cmake 103 | # set custom options that will be applies with that specific sanitizer 104 | set_sanitizer_options(address -fno-omit-frame-pointer) 105 | 106 | add_sanitizer_support(address leak) 107 | # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer -fsanitize=leak' 108 | ``` 109 | 110 | Per-sanitizer compile options can also be set by setting the named `SANITIZER_${SANITIZER_NAME}_OPTIONS` variable before, either in script or via the command line. 111 | ```cmake 112 | # CMake called from command line as `cmake -S . -B build -D SANITIZER_ADDRESS_OPTION='-fno-omit-frame-pointer'` 113 | 114 | add_sanitizer_support(address) 115 | # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer' 116 | # despite no call to `set_sanitizer_options` 117 | ``` 118 | 119 | To prevent custom sanitizer options from external source being overwritten, the `DEFAULT` option can be used, so that the flags are only used if none have been set previously: 120 | ```cmake 121 | # command line has options set via command-line: `cmake -S . -B build -D SANITIZER_ADDRESS_OPTION='-fno-omit-frame-pointer'` 122 | 123 | # attempt to set custom options that will not apply since the variable already exists 124 | # and `DEFAULT` option is passed in. 125 | set_sanitizer_options(address DEFAULT -some-other-flag) 126 | 127 | add_sanitizer_support(address) 128 | # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer' 129 | ``` 130 | 131 | Different sets of options used with the sanitizer can be accomplished by defining the sanitizer serparately with the call to `set_sanitizer_option`: 132 | ```cmake 133 | # Despite both using the 'memory' sanitizer, which specific set of flags can be chosen 134 | # when calling `add_sanitizer_support` with either 'memory' or 'memorywithorigins' 135 | set_sanitizer_options(memory DEFAULT -fno-omit-frame-pointer) 136 | set_sanitizer_options(memorywithorigins DEFAULT 137 | SANITIZER memory 138 | -fno-omit-frame-pointer 139 | -fsanitize-memory-track-origins) 140 | ``` 141 | 142 | ## Code Coverage [`code-coverage.cmake`](code-coverage.cmake) 143 | 144 | ![Code Coverage Examples](img/code-cov.png) 145 | 146 | > In computer science, test coverage is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. A program with high test coverage, measured as a percentage, has had more of its source code executed during testing, which suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage. Many different metrics can be used to calculate test coverage; some of the most basic are the percentage of program subroutines and the percentage of program statements called during execution of the test suite. 147 | > 148 | > [Wikipedia, Code Coverage](https://en.wikipedia.org/wiki/Code_coverage) 149 | 150 | Code coverage is the detailing of, during the execution of a binary, which regions, functions, or lines of code are *actually* executed. This can be used in a number of ways, from figuring out areas that automated testing is lacking or not touching, to giving a user an instrumented binary to determine which areas of code are used most/least to determine which areas to focus on. Although this does come with the caveat that coverage is no guarantee of good testing, just of what code has been. 151 | 152 | Coverage here is supported on both GCC and Clang. GCC requires the `lcov` program, and Clang requires `llvm-cov` and `llvm-profdata`, often provided with the llvm toolchain. 153 | 154 | To enable, turn on the `CODE_COVERAGE` variable. 155 | 156 | ### Added Targets 157 | 158 | - GCOV/LCOV: 159 | - ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 160 | - ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. 161 | - ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 162 | - ccov-all-capture : Generates an all-merged.info file, for use with coverage dashboards (e.g. codecov.io, coveralls). 163 | - LLVM-COV: 164 | - ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 165 | - ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. 166 | - ccov-${TARGET_NAME} : Generates HTML code coverage report. 167 | - ccov-rpt-${TARGET_NAME} : Prints to command line summary per-file coverage information. 168 | - ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. 169 | - ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 170 | - ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. 171 | 172 | ### Usage 173 | 174 | To enable any code coverage instrumentation/targets, the single CMake option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or on the command line ie `-DCODE_COVERAGE=ON`. 175 | 176 | From this point, there are two primary methods for adding instrumentation to targets: 177 | 1. A blanket instrumentation by calling `add_code_coverage()`, where all targets in that directory and all subdirectories are automatically instrumented. 178 | 2. Per-target instrumentation by calling `target_code_coverage()`, where the target is given and thus only that target is instrumented. This applies to both libraries and executables. 179 | 180 | To add coverage targets, such as calling `make ccov` to generate the actual coverage information for perusal or consumption, call `target_code_coverage()` on an *executable* target. 181 | 182 | **NOTE:** For more options, please check the actual [`code-coverage.cmake`](code-coverage.cmake) file. 183 | 184 | #### Example 1 - All targets instrumented 185 | 186 | In this case, the coverage information reported will will be that of the `theLib` library target and `theExe` executable. 187 | 188 | ##### 1a - Via global command 189 | 190 | ``` 191 | add_code_coverage() # Adds instrumentation to all targets 192 | 193 | add_library(theLib lib.cpp) 194 | 195 | add_executable(theExe main.cpp) 196 | target_link_libraries(theExe PRIVATE theLib) 197 | target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports. 198 | ``` 199 | 200 | ##### 1b - Via target commands 201 | 202 | ``` 203 | add_library(theLib lib.cpp) 204 | target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. 205 | 206 | add_executable(theExe main.cpp) 207 | target_link_libraries(theExe PRIVATE theLib) 208 | target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. 209 | ``` 210 | 211 | #### Example 2: Target instrumented, but with regex pattern of files to be excluded from report 212 | 213 | ``` 214 | add_executable(theExe main.cpp non_covered.cpp) 215 | target_code_coverage(theExe EXCLUDE non_covered.cpp) # As an executable target, the reports will exclude the non_covered.cpp file. 216 | ``` 217 | 218 | #### Example 3: Target added to the 'ccov' and 'ccov-all' targets 219 | 220 | ``` 221 | add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. 222 | 223 | add_executable(theExe main.cpp non_covered.cpp) 224 | target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. 225 | ``` 226 | 227 | ## AFL Fuzzing Instrumentation [`afl-fuzzing.cmake`](afl-fuzzing.cmake) 228 | 229 | > American fuzzy lop is a security-oriented fuzzer that employs a novel type of compile-time instrumentation and genetic algorithms to automatically discover clean, interesting test cases that trigger new internal states in the targeted binary. This substantially improves the functional coverage for the fuzzed code. The compact synthesized corpora produced by the tool are also useful for seeding other, more labor- or resource-intensive testing regimes down the road. 230 | > 231 | > [american fuzzy lop](https://lcamtuf.coredump.cx/afl/) 232 | 233 | NOTE: This actually works based off the still-developed daughter project [AFL++](https://aflplus.plus/). 234 | 235 | ### Usage 236 | 237 | To enable the use of AFL instrumentation, this file needs to be included into the CMake scripts at any point *before* any of the compilers are setup by CMake, typically at/before the first call to project(), or any part before compiler detection/validation occurs. This is since CMake does not support changing the compiler after it has been set: 238 | 239 | ``` 240 | cmake_minimum_required(VERSION 3.4) 241 | include(cmake/afl-fuzzing.cmake) 242 | project(Example C CXX) 243 | ``` 244 | 245 | Using `-DAFL=ON` will search for and switch to the AFL++ compiler wrappers that will instrument builds, or error if it cannot. 246 | 247 | Using `-DAFL_MODE=` will attempt to use the specified instrumentation type, see [here](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/fuzzing_in_depth.md). Options are: 248 | - LTO 249 | - LLVM 250 | - GCC-PLUGIN 251 | - CLANG 252 | - GCC 253 | 254 | Using `-DAFL_ENV_OPTIONS=<...;...>` allows adding any number of AFL++'s instrumentation enabled via environment variables, and these will be prefixed to the build calls (see `afl-cc -hh`). 255 | 256 | As an example, a CMake configuration such as this: 257 | ```cmake .. -DAFL_MODE=LTO -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1``` 258 | would result in build commands such as this: 259 | ```AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto --afl-lto <...>``` 260 | 261 | ## Compiler Options [`compiler-options.cmake`](compiler-options.cmake) 262 | 263 | Allows for easy use of some pre-made compiler options for the major compilers. 264 | 265 | Using `-DENABLE_ALL_WARNINGS=ON` will enable almost all of the warnings available for a compiler: 266 | 267 | | Compiler | Options | 268 | | :------- | :------------ | 269 | | MSVC | /W4 | 270 | | GCC | -Wall -Wextra | 271 | | Clang | -Wall -Wextra | 272 | 273 | Using `-DENABLE_EFFECTIVE_CXX=ON` adds the `-Weffc++` for both GCC and clang. 274 | 275 | Using `-DGENERATE_DEPENDENCY_DATA=ON` generates `.d` files along with regular object files on a per-source file basis on GCC/Clang compilers. These files contains the list of all header files used during compilation of that compilation unit. 276 | 277 | ## Dependency Graph [`dependency-graph.cmake`](dependency-graph.cmake) 278 | 279 | CMake, with the dot application available, will build a visual representation of the library/executable dependencies, like so: 280 | ![Dependency Graph](img/dp-graph.png) 281 | 282 | ### Required Arguments 283 | 284 | #### OUTPUT_TYPE *STR* 285 | The type of output of `dot` to produce. Can be whatever `dot` itself supports (eg. png, ps, pdf). 286 | 287 | ### Optional Arguments 288 | 289 | #### ADD_TO_DEP_GRAPH 290 | If specified, add this generated target to be a dependency of the more general `dep-graph` target. 291 | 292 | #### TARGET_NAME *STR* 293 | The name to give the doc target. (Default: doc-${PROJECT_NAME}) 294 | 295 | #### OUTPUT_DIR *STR* 296 | The directory to place the generated output 297 | 298 | ## GLSL Shader File Targeted Compilation[`glsl-shaders.cmake`](glsl-shaders.cmake) 299 | 300 | This function acts much like the 'target_sources' function, as in raw GLSL shader files can be passed in and will be compiled using 'glslangValidator', provided it is available, where the compiled files will be located where the sources files are but with the '.spv' suffix appended. 301 | 302 | The first argument is the target that the files are associated with, and will be compiled as if it were a source file for it. All provided shaders are also only recompiled if the source shader file has been modified since the last compilation. 303 | 304 | ### Example 305 | When calling `make vk_lib` the shaders will also be compiled with the library's `.c` files. 306 | 307 | ``` 308 | add_library(vk_lib lib.c, shader_manager.c) 309 | target_glsl_shaders(vk_lib 310 | PRIVATE test.vert test.frag 311 | COMPILE_OPTIONS --target-env vulkan1.1) 312 | ``` 313 | 314 | ### Required Arguments 315 | 316 | #### TARGET_NAME 317 | Name of the target the shader files are associated with and to be compiled for. 318 | 319 | ### Optional Arguments 320 | 321 | #### INTERFACE *FILES* 322 | When the following shader files are added to a target, they are done so as 'INTERFACE' type files 323 | 324 | #### PUBLIC *FILES* 325 | When the following shader files are added to a target, they are done so as 'PUBLIC' type files 326 | 327 | #### PRIVATE *FILES* 328 | When the following shader files are added to a target, they are done so as 'PRIVATE' type files 329 | 330 | #### COMPILE_OPTIONS *OPTIONS* 331 | These are other options passed straight to the 'glslangValidator' call with the source shader file 332 | 333 | ## Doxygen [`doxygen.cmake`](doxygen.cmake) 334 | 335 | Builds doxygen documentation with a default 'Doxyfile.in' or with a specified one, and can make the results installable (under the `doc` install target) 336 | 337 | This can only be used once per project, as each target generated is as `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. 338 | 339 | ### Optional Arguments 340 | 341 | #### ADD_TO_DOC 342 | If specified, adds this generated target to be a dependency of the more general `doc` target. 343 | 344 | #### INSTALLABLE 345 | Adds the generated documentation to the generic `install` target, under the `documentation` installation group. 346 | 347 | #### PROCESS_DOXYFILE 348 | If set, then will process the found Doxyfile through the CMAKE `configure_file` function for macro replacements before using it. (@ONLY) 349 | 350 | #### TARGET_NAME *STR* 351 | The name to give the doc target. (Default: doc-${PROJECT_NAME}) 352 | 353 | #### OUTPUT_DIR *STR* 354 | The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc) 355 | 356 | #### INSTALL_PATH *STR* 357 | The path to install the documenttation under. (if not specified, defaults to 'share/${PROJECT_NAME}) 358 | 359 | #### DOXYFILE_PATH *STR* 360 | The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile') 361 | 362 | ## Prepare the Catch Test Framework [`prepare-catch.cmake`](prepare-catch.cmake) 363 | 364 | **DEPRECATED**: Catch now has good CMake integration available natively, so this is no longer required and will be dropped in a future release. 365 | 366 | The included `prepare_catch` function contained within attempts to add the infrastructure necessary for automatically adding C/C++ tests using the Catch2 library, including either an interface or pre-compiled 'catch' target library. 367 | 368 | It first attempts to find the header on the local machine, and failing that, clones the single header variant for use. It does make the determination between pre-C++11 and will use Catch1.X rather than Catch2 (when cloned), automatically or forced.. Adds a subdirectory of tests/ if it exists from the macro's calling location. 369 | 370 | ### Optional Arguments 371 | 372 | #### COMPILED_CATCH 373 | If this option is specified, then generates the 'catch' target as a library with catch already pre-compiled as part of the library. Otherwise acts just an interface library for the header location. 374 | 375 | #### CATCH1 376 | Force the use of Catch1.X, rather than auto-detecting the C++ version in use. 377 | 378 | #### CLONE 379 | Force cloning of Catch, rather than attempting to use a locally-found variant. 380 | 381 | ## Tools [`tools.cmake`](tools.cmake) 382 | 383 | The three tools in this are used via two provided functions each, for example for clang-tidy: 384 | ``` 385 | add_executable(big_test) 386 | 387 | clang_tidy() 388 | 389 | # Sources provided here are run with clang-tidy with no options 390 | add_executable(test2 main2.cpp) 391 | target_sources(big_test test2.c test2.cpp) 392 | 393 | clang_tidy(-header-filter='${CMAKE_SOURCE_DIR}/*') 394 | 395 | # Sources provided here are run with clang-tidy with the header-filter options provided to it from above 396 | add_execuable(test1 main1.cpp) 397 | target_sources(big_test test1.c test1.cpp) 398 | 399 | reset_clang_tidy() 400 | 401 | # Sources provided here are not run with clang-tidy at all 402 | add_executable(test3 main3.cpp) 403 | target_sources(big_test test3.c test3.cpp) 404 | 405 | clang_tidy() 406 | 407 | # Sources provided here are run with clang-tidy with no options 408 | add_executable(test4 main4.cpp) 409 | target_sources(big_test test4.c test4.cpp) 410 | ``` 411 | 412 | ### clang-tidy 413 | 414 | > clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. clang-tidy is modular and provides a convenient interface for writing new checks. 415 | > 416 | > [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) 417 | 418 | To use, add the `clang_tidy()` macro, with the arguments being the options passed to the clang-tidy call in the form of `clang-tidy ${ARGS}`. The settings used with clang-tidy can be changed by calling `clang_tidy()` macro again. It can be turned off by calling the `reset_clang_tidy()` macro. 419 | 420 | ### include-what-you-use 421 | 422 | > "Include what you use" means this: for every symbol (type, function variable, or macro) that you use in foo.cc, either foo.cc or foo.h should #include a .h file that exports the declaration of that symbol. The include-what-you-use tool is a program that can be built with the clang libraries in order to analyze #includes of source files to find include-what-you-use violations, and suggest fixes for them. 423 | > 424 | > The main goal of include-what-you-use is to remove superfluous #includes. It does this both by figuring out what #includes are not actually needed for this file (for both .cc and .h files), and replacing #includes with forward-declares when possible. 425 | > 426 | > [include-what-you-use](https://include-what-you-use.org/) 427 | 428 | To use, add the `include_what_you_use()` macro, with the arguments being the options passed to the include_what_you_use call in the form of `include-what-you-use ${ARGS}`. The settings used with include-what-you-use can be changed by calling `include_what_you_use()` macro again. It can be turned off by calling the `reset_include_what_you_use()` macro. 429 | 430 | ### cppcheck 431 | 432 | > Cppcheck is a static analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting undefined behaviour and dangerous coding constructs. The goal is to have very few false positives. Cppcheck is designed to be able to analyze your C/C++ code even if it has non-standard syntax (common in embedded projects). 433 | > 434 | > [cppcheck](http://cppcheck.net/) 435 | 436 | To use, add the `cppcheck()` macro, with the arguments being the options passed to the cppcheck call in the form of `cppcheck ${ARGS}`. The settings used with iwyu can be changed by calling `cppcheck()` macro again. It can be turned off by calling the `reset_cppcheck()` macro. 437 | 438 | ## Formatting [`formatting.cmake`](formatting.cmake) 439 | 440 | ### clang-format 441 | 442 | Allows to automatically perform code formatting using the clang-format program, by calling an easy-to-use target ala `make format`. It requires a target name, and the list of files to format. As well, if the target name is the name of another target, then all files associated with that target will be added, and the target name changed to be `format_`. As well, any targets otherwise listed with the files will also have their files imported for formatting. 443 | 444 | ``` 445 | file(GLOB_RECURSE ALL_CODE_FILES 446 | ${PROJECT_SOURCE_DIR}/src/*.[ch]pp 447 | ${PROJECT_SOURCE_DIR}/src/*.[ch] 448 | ${PROJECT_SOURCE_DIR}/include/*.[h]pp 449 | ${PROJECT_SOURCE_DIR}/include/*.[h] 450 | ${PROJECT_SOURCE_DIR}/example/*.[ch]pp 451 | ${PROJECT_SOURCE_DIR}/example/*.[ch] 452 | ) 453 | 454 | clang_format(TARGET_NAME ${ALL_CODE_FILES}) 455 | ``` 456 | 457 | ### cmake-format 458 | 459 | Similar to the clang-format above, creates a target `cmake-format` when the `cmake_format()` function is defined in CMake scripts, and any passed in will be formatted by the cmake-format program, if it is found. 460 | 461 | ``` 462 | file(GLOB_RECURSE CMAKE_FILES 463 | CMakeLists.txt 464 | ) 465 | 466 | cmake_format(TARGET_NAME ${CMAKE_FILES}) 467 | ``` 468 | 469 | ## Link Time Optimization / Interprocedural Optimization [`link-time-optimization.cmake`](link-time-optimization.cmake) 470 | 471 | There are two callable objects here, `link_time_optimization` which applies LTO/IPO for all following targets, and `target_link_time_optimization` which applies it to a specified target. 472 | 473 | Doesn't work with GCC. 474 | 475 | ### Optional Arguments 476 | 477 | #### REQUIRED 478 | If this is passed in, CMake configuration will fail with an error if LTO/IPO is not supported 479 | -------------------------------------------------------------------------------- /afl-fuzzing.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2022 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # USAGE: To enable the use of AFL instrumentation, this file needs to be 17 | # included into the CMake scripts at any point *before* any of the compilers are 18 | # setup by CMake, typically at/before the first call to project(), or any part 19 | # before compiler detection/validation occurs. 20 | # 21 | # This is since CMake does not support changing the compiler after it has been 22 | # set. 23 | # 24 | # For example for CMakeLists.txt: 25 | # ~~~ 26 | # cmake_minimum_required(VERSION 3.15) 27 | # include(cmake/afl-fuzzing.cmake) 28 | # project(FoE-Engine C CXX) 29 | # ~~~ 30 | # And then configuring CMake with: `cmake .. -DAFL_MODE=LTO 31 | # -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1` 32 | # 33 | # Would setup the AFL compiler to use the LTO mode (afl-clang-lto), and prefix 34 | # any build calls to have the two given environment settings, ie: 35 | # `AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto <...>` 36 | # 37 | # NOTE: If using multiple ENV_OPTIONS, delimit via semi-colons and it will be 38 | # separated correctly. 39 | 40 | # Options 41 | option(AFL "Switch to using an AFL compiler" OFF) 42 | set(AFL_MODE 43 | "" 44 | CACHE 45 | STRING 46 | "Use a specific AFL instrumentation mode: LTO, LLVM, GCC-PLUGIN, CLANG, GCC" 47 | ) 48 | set(AFL_ENV_OPTIONS 49 | "" 50 | CACHE STRING 51 | "Add environmental settings to build calls (check `afl-cc -hh`)") 52 | 53 | # Sets up for AFL fuzzing by detecting finding and using AFL compilers and 54 | # setting a few flags and environmental build flags as requested. 55 | if(AFL) 56 | find_program(AFL_C_COMPILER afl-cc) 57 | find_program(AFL_CXX_COMPILER afl-c++) 58 | 59 | if(AFL_C_COMPILER AND AFL_CXX_COMPILER) 60 | if((CMAKE_C_COMPILER AND NOT CMAKE_C_COMPILER STREQUAL AFL_C_COMPILER) 61 | OR (CMAKE_CXX_COMPILER AND NOT CMAKE_CXX_COMPILER STREQUAL 62 | AFL_CXX_COMPILER)) 63 | # CMake doesn't support changing compilers after they've been set 64 | message( 65 | FATAL_ERROR 66 | "Cannot change to AFL compilers after they have been previously set. Clear the cache, reconfigure and ensure setup_afl is called before the first C or CXX compiler is set, typically before the first project() call." 67 | ) 68 | else() 69 | # Set the AFL compiler 70 | message(STATUS "Changed to AFL compiler") 71 | set(CMAKE_C_COMPILER ${AFL_C_COMPILER}) 72 | set(CMAKE_CXX_COMPILER ${AFL_CXX_COMPILER}) 73 | 74 | # Set a specific AFL mode for both compile and link stages 75 | if(AFL_MODE MATCHES "[Ll][Tt][Oo]") 76 | message(STATUS "Set AFL to Clang-LTO mode") 77 | add_compile_options(--afl-lto) 78 | add_link_options(--afl-lto) 79 | elseif(AFL_MODE MATCHES "[Ll][Ll][Vv][Mm]") 80 | message(STATUS "Set AFL to Clang-LLVM mode") 81 | add_compile_options(--afl-llvm) 82 | add_link_options(--afl-llvm) 83 | elseif(AFL_MODE MATCHES "[Gg][Cc][Cc][-_][Pp][Ll][Uu][Gg][Ii][Nn]") 84 | message(STATUS "Set AFL to GCC-Plugin mode") 85 | add_compile_options(--afl-gcc-plugin) 86 | add_link_options(--afl-gcc-plugin) 87 | elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]") 88 | message(STATUS "Set AFL to Clang mode") 89 | add_compile_options(--afl-clang) 90 | add_link_options(--afl-clang) 91 | elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]") 92 | message(STATUS "Set AFL to GCC mode") 93 | add_compile_options(--afl-gcc) 94 | add_link_options(--afl-gcc) 95 | endif() 96 | 97 | # Add specified environment options 98 | if(AFL_ENV_OPTIONS) 99 | set(CMAKE_C_COMPILER_LAUNCHER ${CMAKE_C_COMPILER_LAUNCHER} 100 | ${AFL_ENV_OPTIONS}) 101 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CMAKE_CXX_COMPILER_LAUNCHER} 102 | ${AFL_ENV_OPTIONS}) 103 | endif() 104 | endif() 105 | else() 106 | message(FATAL_ERROR "Usable AFL compiler was not found!") 107 | endif() 108 | endif() 109 | -------------------------------------------------------------------------------- /c++-standards.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018-2024 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # Set the compiler standard to C++11 17 | macro(cxx_11) 18 | set(CMAKE_CXX_STANDARD 11) 19 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 20 | set(CMAKE_CXX_EXTENSIONS OFF) 21 | 22 | if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) 23 | include(CheckCXXCompilerFlag) 24 | check_cxx_compiler_flag("/std:c++11" _cpp_11_flag_supported) 25 | if(_cpp_11_flag_supported) 26 | add_compile_options("/std:c++11") 27 | endif() 28 | endif() 29 | endmacro() 30 | 31 | # Set the compiler standard to C++14 32 | macro(cxx_14) 33 | set(CMAKE_CXX_STANDARD 14) 34 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 35 | set(CMAKE_CXX_EXTENSIONS OFF) 36 | 37 | if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) 38 | include(CheckCXXCompilerFlag) 39 | check_cxx_compiler_flag("/std:c++14" _cpp_14_flag_supported) 40 | if(_cpp_14_flag_supported) 41 | add_compile_options("/std:c++14") 42 | endif() 43 | endif() 44 | endmacro() 45 | 46 | # Set the compiler standard to C++17 47 | macro(cxx_17) 48 | set(CMAKE_CXX_STANDARD 17) 49 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 50 | set(CMAKE_CXX_EXTENSIONS OFF) 51 | 52 | if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) 53 | include(CheckCXXCompilerFlag) 54 | check_cxx_compiler_flag("/std:c++17" _cpp_17_flag_supported) 55 | if(_cpp_17_flag_supported) 56 | add_compile_options("/std:c++17") 57 | endif() 58 | endif() 59 | endmacro() 60 | 61 | # Set the compiler standard to C++20 62 | macro(cxx_20) 63 | set(CMAKE_CXX_STANDARD 20) 64 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 65 | set(CMAKE_CXX_EXTENSIONS OFF) 66 | endmacro() 67 | 68 | # Set the compiler standard to C++23 69 | macro(cxx_23) 70 | set(CMAKE_CXX_STANDARD 23) 71 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 72 | set(CMAKE_CXX_EXTENSIONS OFF) 73 | endmacro() 74 | -------------------------------------------------------------------------------- /c-standards.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2021 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # Set the compiler standard to C89/90 17 | macro(c_90) 18 | set(CMAKE_C_STANDARD 90) 19 | set(CMAKE_C_STANDARD_REQUIRED ON) 20 | set(CMAKE_C_EXTENSIONS OFF) 21 | endmacro() 22 | 23 | # Set the compiler standard to C99 24 | macro(c_99) 25 | set(CMAKE_C_STANDARD 99) 26 | set(CMAKE_C_STANDARD_REQUIRED ON) 27 | set(CMAKE_C_EXTENSIONS OFF) 28 | endmacro() 29 | 30 | # Set the compiler standard to C11 31 | macro(c_11) 32 | set(CMAKE_C_STANDARD 11) 33 | set(CMAKE_C_STANDARD_REQUIRED ON) 34 | set(CMAKE_C_EXTENSIONS OFF) 35 | endmacro() 36 | 37 | # Set the compiler standard to C17 38 | macro(c_17) 39 | set(CMAKE_C_STANDARD 17) 40 | set(CMAKE_C_STANDARD_REQUIRED ON) 41 | set(CMAKE_C_EXTENSIONS OFF) 42 | endmacro() 43 | 44 | # Set the compiler standard to C23 45 | macro(c_23) 46 | set(CMAKE_C_STANDARD 23) 47 | set(CMAKE_C_STANDARD_REQUIRED ON) 48 | set(CMAKE_C_EXTENSIONS OFF) 49 | endmacro() 50 | -------------------------------------------------------------------------------- /code-coverage.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018-2024 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # USAGE: To enable any code coverage instrumentation/targets, the single CMake 17 | # option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or 18 | # on the command line. 19 | # 20 | # From this point, there are two primary methods for adding instrumentation to 21 | # targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where 22 | # all targets in that directory and all subdirectories are automatically 23 | # instrumented. 2 - Per-target instrumentation by calling 24 | # `target_code_coverage()`, where the target is given and thus only 25 | # that target is instrumented. This applies to both libraries and executables. 26 | # 27 | # To add coverage targets, such as calling `make ccov` to generate the actual 28 | # coverage information for perusal or consumption, call 29 | # `target_code_coverage()` on an *executable* target. 30 | # 31 | # Example 1: All targets instrumented 32 | # 33 | # In this case, the coverage information reported will will be that of the 34 | # `theLib` library target and `theExe` executable. 35 | # 36 | # 1a: Via global command 37 | # 38 | # ~~~ 39 | # add_code_coverage() # Adds instrumentation to all targets 40 | # 41 | # add_library(theLib lib.cpp) 42 | # 43 | # add_executable(theExe main.cpp) 44 | # target_link_libraries(theExe PRIVATE theLib) 45 | # target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports. 46 | # ~~~ 47 | # 48 | # 1b: Via target commands 49 | # 50 | # ~~~ 51 | # add_library(theLib lib.cpp) 52 | # target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. 53 | # 54 | # add_executable(theExe main.cpp) 55 | # target_link_libraries(theExe PRIVATE theLib) 56 | # target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. 57 | # ~~~ 58 | # 59 | # Example 2: Target instrumented, but with regex pattern of files to be excluded 60 | # from report 61 | # 62 | # ~~~ 63 | # add_executable(theExe main.cpp non_covered.cpp) 64 | # target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. 65 | # ~~~ 66 | # 67 | # Example 3: Target added to the 'ccov' and 'ccov-all' targets 68 | # 69 | # ~~~ 70 | # add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. 71 | # 72 | # add_executable(theExe main.cpp non_covered.cpp) 73 | # target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. 74 | # ~~~ 75 | 76 | # Options 77 | option( 78 | CODE_COVERAGE 79 | "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" 80 | OFF) 81 | 82 | # Programs 83 | find_program(LLVM_COV_PATH llvm-cov) 84 | find_program(LLVM_PROFDATA_PATH llvm-profdata) 85 | find_program(LCOV_PATH lcov) 86 | find_program(GENHTML_PATH genhtml) 87 | # Hide behind the 'advanced' mode flag for GUI/ccmake 88 | mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) 89 | 90 | # Variables 91 | set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) 92 | set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) 93 | 94 | # Common initialization/checks 95 | if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) 96 | set(CODE_COVERAGE_ADDED ON) 97 | 98 | # Common Targets 99 | file(MAKE_DIRECTORY ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}) 100 | 101 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 102 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 103 | 104 | if(CMAKE_C_COMPILER_ID MATCHES "AppleClang" OR CMAKE_CXX_COMPILER_ID 105 | MATCHES "AppleClang") 106 | # When on macOS and using the Apple-provided toolchain, use the 107 | # XCode-provided llvm toolchain via `xcrun` 108 | message( 109 | STATUS 110 | "Building with XCode-provided llvm code coverage tools (via `xcrun`)") 111 | set(LLVM_COV_PATH xcrun llvm-cov) 112 | set(LLVM_PROFDATA_PATH xcrun llvm-profdata) 113 | else() 114 | # Use the regular llvm toolchain 115 | message(STATUS "Building with llvm code coverage tools") 116 | endif() 117 | 118 | if(NOT LLVM_COV_PATH) 119 | message(FATAL_ERROR "llvm-cov not found! Aborting.") 120 | else() 121 | # Version number checking for 'EXCLUDE' compatibility 122 | execute_process(COMMAND ${LLVM_COV_PATH} --version 123 | OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) 124 | string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION 125 | ${LLVM_COV_VERSION_CALL_OUTPUT}) 126 | 127 | if(LLVM_COV_VERSION VERSION_LESS "7.0.0") 128 | message( 129 | WARNING 130 | "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" 131 | ) 132 | endif() 133 | endif() 134 | 135 | # Targets 136 | if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 137 | add_custom_target( 138 | ccov-clean 139 | COMMAND ${CMAKE_COMMAND} -E remove -f 140 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 141 | COMMAND ${CMAKE_COMMAND} -E remove -f 142 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) 143 | else() 144 | add_custom_target( 145 | ccov-clean 146 | COMMAND ${CMAKE_COMMAND} -E rm -f 147 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 148 | COMMAND ${CMAKE_COMMAND} -E rm -f 149 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) 150 | endif() 151 | 152 | # Used to get the shared object file list before doing the main all- 153 | # processing 154 | add_custom_target( 155 | ccov-libs 156 | COMMAND ; 157 | COMMENT "libs ready for coverage report.") 158 | 159 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 160 | "GNU") 161 | # Messages 162 | message(STATUS "Building with lcov Code Coverage Tools") 163 | 164 | if(CMAKE_BUILD_TYPE) 165 | string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) 166 | if(NOT ${upper_build_type} STREQUAL "DEBUG") 167 | message( 168 | WARNING 169 | "Code coverage results with an optimized (non-Debug) build may be misleading" 170 | ) 171 | endif() 172 | else() 173 | message( 174 | WARNING 175 | "Code coverage results with an optimized (non-Debug) build may be misleading" 176 | ) 177 | endif() 178 | if(NOT LCOV_PATH) 179 | message(FATAL_ERROR "lcov not found! Aborting...") 180 | endif() 181 | if(NOT GENHTML_PATH) 182 | message(FATAL_ERROR "genhtml not found! Aborting...") 183 | endif() 184 | 185 | # Targets 186 | add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory 187 | ${CMAKE_BINARY_DIR} --zerocounters) 188 | 189 | else() 190 | message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") 191 | endif() 192 | endif() 193 | 194 | # Adds code coverage instrumentation to a library, or instrumentation/targets 195 | # for an executable target. 196 | # ~~~ 197 | # EXECUTABLE ADDED TARGETS: 198 | # GCOV/LCOV: 199 | # ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 200 | # ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. 201 | # ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 202 | # 203 | # LLVM-COV: 204 | # ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 205 | # ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. 206 | # ccov-${TARGET_NAME} : Generates HTML code coverage report. 207 | # ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. 208 | # ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. 209 | # ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. 210 | # ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 211 | # ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. 212 | # ccov-all-export : Exports the coverage report to a JSON file. 213 | # 214 | # Required: 215 | # TARGET_NAME - Name of the target to generate code coverage for. 216 | # Optional: 217 | # PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. 218 | # INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. 219 | # PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) 220 | # AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. 221 | # ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. 222 | # EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory 223 | # COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. 224 | # EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** 225 | # OBJECTS - For executables ONLY, if the provided targets are static or shared libraries, adds coverage information to the output 226 | # PRE_ARGS - For executables ONLY, prefixes given arguments to the associated ccov-* executable call ($ ccov-*) 227 | # ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call (ccov-* $) 228 | # ~~~ 229 | function(target_code_coverage TARGET_NAME) 230 | # Argument parsing 231 | set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) 232 | set(single_value_keywords COVERAGE_TARGET_NAME) 233 | set(multi_value_keywords EXCLUDE OBJECTS PRE_ARGS ARGS) 234 | cmake_parse_arguments( 235 | target_code_coverage "${options}" "${single_value_keywords}" 236 | "${multi_value_keywords}" ${ARGN}) 237 | 238 | # Set the visibility of target functions to PUBLIC, INTERFACE or default to 239 | # PRIVATE. 240 | if(target_code_coverage_PUBLIC) 241 | set(TARGET_VISIBILITY PUBLIC) 242 | set(TARGET_LINK_VISIBILITY PUBLIC) 243 | elseif(target_code_coverage_INTERFACE) 244 | set(TARGET_VISIBILITY INTERFACE) 245 | set(TARGET_LINK_VISIBILITY INTERFACE) 246 | elseif(target_code_coverage_PLAIN) 247 | set(TARGET_VISIBILITY PUBLIC) 248 | set(TARGET_LINK_VISIBILITY) 249 | else() 250 | set(TARGET_VISIBILITY PRIVATE) 251 | set(TARGET_LINK_VISIBILITY PRIVATE) 252 | endif() 253 | 254 | if(NOT target_code_coverage_COVERAGE_TARGET_NAME) 255 | # If a specific name was given, use that instead. 256 | set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) 257 | endif() 258 | 259 | if(CODE_COVERAGE) 260 | 261 | # Add code coverage instrumentation to the target's linker command 262 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 263 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 264 | target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} 265 | -fprofile-instr-generate -fcoverage-mapping) 266 | target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} 267 | -fprofile-instr-generate -fcoverage-mapping) 268 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 269 | "GNU") 270 | target_compile_options( 271 | ${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs -ftest-coverage 272 | $<$:-fno-elide-constructors> -fno-default-inline) 273 | target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) 274 | endif() 275 | 276 | # Targets 277 | get_target_property(target_type ${TARGET_NAME} TYPE) 278 | 279 | # Add shared library to processing for 'all' targets 280 | if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) 281 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 282 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 283 | add_custom_target( 284 | ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 285 | COMMAND 286 | ${CMAKE_COMMAND} -E echo "-object=$" >> 287 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 288 | DEPENDS ${TARGET_NAME}) 289 | 290 | if(NOT TARGET ccov-libs) 291 | message( 292 | FATAL_ERROR 293 | "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." 294 | ) 295 | endif() 296 | 297 | add_dependencies(ccov-libs 298 | ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 299 | endif() 300 | endif() 301 | 302 | # For executables add targets to run and produce output 303 | if(target_type STREQUAL "EXECUTABLE") 304 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 305 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 306 | 307 | # If there are static or shared objects to also work with, generate the 308 | # string to add them here 309 | foreach(LINK_OBJECT ${target_code_coverage_OBJECTS}) 310 | # Check to see if the target is a shared object 311 | if(TARGET ${LINK_OBJECT}) 312 | get_target_property(LINK_OBJECT_TYPE ${LINK_OBJECT} TYPE) 313 | if(${LINK_OBJECT_TYPE} STREQUAL "STATIC_LIBRARY" 314 | OR ${LINK_OBJECT_TYPE} STREQUAL "SHARED_LIBRARY") 315 | set(LINKED_OBJECTS ${LINKED_OBJECTS} 316 | -object=$) 317 | endif() 318 | endif() 319 | endforeach() 320 | 321 | # Run the executable, generating raw profile data Make the run data 322 | # available for further processing. Separated to allow Windows to run 323 | # this target serially. 324 | add_custom_target( 325 | ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 326 | COMMAND 327 | ${CMAKE_COMMAND} -E env ${CMAKE_CROSSCOMPILING_EMULATOR} 328 | ${target_code_coverage_PRE_ARGS} 329 | LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw 330 | $ ${target_code_coverage_ARGS} 331 | COMMAND 332 | ${CMAKE_COMMAND} -E echo "-object=$" 333 | ${LINKED_OBJECTS} >> 334 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 335 | COMMAND 336 | ${CMAKE_COMMAND} -E echo 337 | "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" 338 | >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list 339 | JOB_POOL ccov_serial_pool 340 | DEPENDS ccov-libs ${TARGET_NAME}) 341 | 342 | # Merge the generated profile data so llvm-cov can process it 343 | add_custom_target( 344 | ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} 345 | COMMAND 346 | ${LLVM_PROFDATA_PATH} merge -sparse 347 | ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o 348 | ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 349 | DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 350 | 351 | # Ignore regex only works on LLVM >= 7 352 | if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") 353 | foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) 354 | set(EXCLUDE_REGEX ${EXCLUDE_REGEX} 355 | -ignore-filename-regex='${EXCLUDE_ITEM}') 356 | endforeach() 357 | endif() 358 | 359 | # Print out details of the coverage information to the command line 360 | add_custom_target( 361 | ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} 362 | COMMAND 363 | ${LLVM_COV_PATH} show $ 364 | -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 365 | -show-line-counts-or-regions ${LINKED_OBJECTS} ${EXCLUDE_REGEX} 366 | DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 367 | 368 | # Print out a summary of the coverage information to the command line 369 | add_custom_target( 370 | ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} 371 | COMMAND 372 | ${LLVM_COV_PATH} report $ 373 | -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 374 | ${LINKED_OBJECTS} ${EXCLUDE_REGEX} 375 | DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 376 | 377 | # Export coverage information so continuous integration tools (e.g. 378 | # Jenkins) can consume it 379 | add_custom_target( 380 | ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} 381 | COMMAND 382 | ${LLVM_COV_PATH} export $ 383 | -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 384 | -format="text" ${LINKED_OBJECTS} ${EXCLUDE_REGEX} > 385 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json 386 | DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 387 | 388 | # Generates HTML output of the coverage information for perusal 389 | add_custom_target( 390 | ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 391 | COMMAND 392 | ${LLVM_COV_PATH} show $ 393 | -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 394 | -show-line-counts-or-regions 395 | -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} 396 | -format="html" ${LINKED_OBJECTS} ${EXCLUDE_REGEX} 397 | DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 398 | 399 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 400 | "GNU") 401 | set(COVERAGE_INFO 402 | "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" 403 | ) 404 | 405 | # Run the executable, generating coverage information 406 | add_custom_target( 407 | ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 408 | COMMAND 409 | ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} 410 | $ ${target_code_coverage_ARGS} 411 | DEPENDS ${TARGET_NAME}) 412 | 413 | # Generate exclusion string for use 414 | foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) 415 | set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} 416 | '${EXCLUDE_ITEM}') 417 | endforeach() 418 | 419 | if(EXCLUDE_REGEX) 420 | set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file 421 | ${COVERAGE_INFO}) 422 | else() 423 | set(EXCLUDE_COMMAND ;) 424 | endif() 425 | 426 | if(NOT ${target_code_coverage_EXTERNAL}) 427 | set(EXTERNAL_OPTION --no-external) 428 | endif() 429 | 430 | # Capture coverage data 431 | if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 432 | add_custom_target( 433 | ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} 434 | COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} 435 | COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters 436 | COMMAND 437 | ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} 438 | $ ${target_code_coverage_ARGS} 439 | COMMAND 440 | ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory 441 | ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file 442 | ${COVERAGE_INFO} 443 | COMMAND ${EXCLUDE_COMMAND} 444 | DEPENDS ${TARGET_NAME}) 445 | else() 446 | add_custom_target( 447 | ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} 448 | COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} 449 | COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters 450 | COMMAND 451 | ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} 452 | $ ${target_code_coverage_ARGS} 453 | COMMAND 454 | ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory 455 | ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file 456 | ${COVERAGE_INFO} 457 | COMMAND ${EXCLUDE_COMMAND} 458 | DEPENDS ${TARGET_NAME}) 459 | endif() 460 | 461 | # Generates HTML output of the coverage information for perusal 462 | add_custom_target( 463 | ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 464 | COMMAND 465 | ${GENHTML_PATH} -o 466 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} 467 | ${COVERAGE_INFO} 468 | DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) 469 | endif() 470 | 471 | add_custom_command( 472 | TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 473 | POST_BUILD 474 | COMMAND ; 475 | COMMENT 476 | "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." 477 | ) 478 | 479 | # AUTO 480 | if(target_code_coverage_AUTO) 481 | if(NOT TARGET ccov) 482 | add_custom_target(ccov) 483 | endif() 484 | add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) 485 | 486 | if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID 487 | MATCHES "GNU") 488 | if(NOT TARGET ccov-report) 489 | add_custom_target(ccov-report) 490 | endif() 491 | add_dependencies( 492 | ccov-report 493 | ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) 494 | endif() 495 | endif() 496 | 497 | # ALL 498 | if(target_code_coverage_ALL) 499 | if(NOT TARGET ccov-all-processing) 500 | message( 501 | FATAL_ERROR 502 | "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." 503 | ) 504 | endif() 505 | 506 | add_dependencies(ccov-all-processing 507 | ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 508 | endif() 509 | endif() 510 | endif() 511 | endfunction() 512 | 513 | # Adds code coverage instrumentation to all targets in the current directory and 514 | # any subdirectories. To add coverage instrumentation to only specific targets, 515 | # use `target_code_coverage`. 516 | function(add_code_coverage) 517 | if(CODE_COVERAGE) 518 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 519 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 520 | add_compile_options(-fprofile-instr-generate -fcoverage-mapping) 521 | add_link_options(-fprofile-instr-generate -fcoverage-mapping) 522 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 523 | "GNU") 524 | add_compile_options( 525 | -fprofile-arcs -ftest-coverage 526 | $<$:-fno-elide-constructors> -fno-default-inline) 527 | link_libraries(gcov) 528 | endif() 529 | endif() 530 | endfunction() 531 | 532 | # Adds the 'ccov-all' type targets that calls all targets added via 533 | # `target_code_coverage` with the `ALL` parameter, but merges all the coverage 534 | # data from them into a single large report instead of the numerous smaller 535 | # reports. Also adds the ccov-all-capture Generates an all-merged.info file, for 536 | # use with coverage dashboards (e.g. codecov.io, coveralls). 537 | # ~~~ 538 | # Optional: 539 | # EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! 540 | # ~~~ 541 | function(add_code_coverage_all_targets) 542 | # Argument parsing 543 | set(multi_value_keywords EXCLUDE) 544 | cmake_parse_arguments(add_code_coverage_all_targets "" "" 545 | "${multi_value_keywords}" ${ARGN}) 546 | 547 | if(CODE_COVERAGE) 548 | if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 549 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 550 | 551 | # Merge the profile data for all of the run executables 552 | if(WIN32) 553 | add_custom_target( 554 | ccov-all-processing 555 | COMMAND 556 | powershell -Command $$FILELIST = Get-Content 557 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe 558 | merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 559 | -sparse $$FILELIST) 560 | else() 561 | add_custom_target( 562 | ccov-all-processing 563 | COMMAND 564 | ${LLVM_PROFDATA_PATH} merge -o 565 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat 566 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) 567 | endif() 568 | 569 | # Regex exclude only available for LLVM >= 7 570 | if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") 571 | foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) 572 | set(EXCLUDE_REGEX ${EXCLUDE_REGEX} 573 | -ignore-filename-regex='${EXCLUDE_ITEM}') 574 | endforeach() 575 | endif() 576 | 577 | # Print summary of the code coverage information to the command line 578 | if(WIN32) 579 | add_custom_target( 580 | ccov-all-report 581 | COMMAND 582 | powershell -Command $$FILELIST = Get-Content 583 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe 584 | report $$FILELIST 585 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 586 | ${EXCLUDE_REGEX} 587 | DEPENDS ccov-all-processing) 588 | else() 589 | add_custom_target( 590 | ccov-all-report 591 | COMMAND 592 | ${LLVM_COV_PATH} report `cat 593 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 594 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 595 | ${EXCLUDE_REGEX} 596 | DEPENDS ccov-all-processing) 597 | endif() 598 | 599 | # Export coverage information so continuous integration tools (e.g. 600 | # Jenkins) can consume it 601 | if(WIN32) 602 | add_custom_target( 603 | ccov-all-export 604 | COMMAND 605 | powershell -Command $$FILELIST = Get-Content 606 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe 607 | export $$FILELIST 608 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 609 | -format="text" ${EXCLUDE_REGEX} > 610 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json 611 | DEPENDS ccov-all-processing) 612 | else() 613 | add_custom_target( 614 | ccov-all-export 615 | COMMAND 616 | ${LLVM_COV_PATH} export `cat 617 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 618 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 619 | -format="text" ${EXCLUDE_REGEX} > 620 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json 621 | DEPENDS ccov-all-processing) 622 | endif() 623 | 624 | # Generate HTML output of all added targets for perusal 625 | if(WIN32) 626 | add_custom_target( 627 | ccov-all 628 | COMMAND 629 | powershell -Command $$FILELIST = Get-Content 630 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show 631 | $$FILELIST 632 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 633 | -show-line-counts-or-regions 634 | -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 635 | -format="html" ${EXCLUDE_REGEX} 636 | DEPENDS ccov-all-processing) 637 | else() 638 | add_custom_target( 639 | ccov-all 640 | COMMAND 641 | ${LLVM_COV_PATH} show `cat 642 | ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 643 | -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 644 | -show-line-counts-or-regions 645 | -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 646 | -format="html" ${EXCLUDE_REGEX} 647 | DEPENDS ccov-all-processing) 648 | endif() 649 | 650 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 651 | "GNU") 652 | set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") 653 | 654 | # Nothing required for gcov 655 | add_custom_target(ccov-all-processing COMMAND ;) 656 | 657 | # Exclusion regex string creation 658 | set(EXCLUDE_REGEX) 659 | foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) 660 | set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} 661 | '${EXCLUDE_ITEM}') 662 | endforeach() 663 | 664 | if(EXCLUDE_REGEX) 665 | set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file 666 | ${COVERAGE_INFO}) 667 | else() 668 | set(EXCLUDE_COMMAND ;) 669 | endif() 670 | 671 | # Capture coverage data 672 | if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 673 | add_custom_target( 674 | ccov-all-capture 675 | COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} 676 | COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture 677 | --output-file ${COVERAGE_INFO} 678 | COMMAND ${EXCLUDE_COMMAND} 679 | DEPENDS ccov-all-processing) 680 | else() 681 | add_custom_target( 682 | ccov-all-capture 683 | COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} 684 | COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture 685 | --output-file ${COVERAGE_INFO} 686 | COMMAND ${EXCLUDE_COMMAND} 687 | DEPENDS ccov-all-processing) 688 | endif() 689 | 690 | # Generates HTML output of all targets for perusal 691 | add_custom_target( 692 | ccov-all 693 | COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 694 | ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} 695 | DEPENDS ccov-all-capture) 696 | 697 | endif() 698 | 699 | add_custom_command( 700 | TARGET ccov-all 701 | POST_BUILD 702 | COMMAND ; 703 | COMMENT 704 | "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." 705 | ) 706 | endif() 707 | endfunction() 708 | -------------------------------------------------------------------------------- /compiler-options.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | option(ENABLE_ALL_WARNINGS "Compile with all warnings for the major compilers" 17 | OFF) 18 | option(ENABLE_EFFECTIVE_CXX "Enable Effective C++ warnings" OFF) 19 | option(GENERATE_DEPENDENCY_DATA "Generates .d files with header dependencies" 20 | OFF) 21 | 22 | if(ENABLE_ALL_WARNINGS) 23 | if(CMAKE_C_COMPILER_ID MATCHES "GNU" 24 | OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" 25 | OR CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 26 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 27 | # GCC/Clang 28 | add_compile_options(-Wall -Wextra) 29 | elseif(MSVC) 30 | # MSVC 31 | add_compile_options(/W4) 32 | endif() 33 | endif() 34 | 35 | if(ENABLE_EFFECTIVE_CXX) 36 | if(CMAKE_C_COMPILER_ID MATCHES "GNU" 37 | OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" 38 | OR CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 39 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") 41 | endif() 42 | endif() 43 | 44 | if(GENERATE_DEPENDENCY_DATA) 45 | if(CMAKE_C_COMPILER_ID MATCHES "GNU" 46 | OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" 47 | OR CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 48 | OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 49 | add_compile_options(-MD) 50 | else() 51 | message( 52 | WARNING "Cannot generate header dependency on non GCC/Clang compilers.") 53 | endif() 54 | endif() 55 | -------------------------------------------------------------------------------- /dependency-graph.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | find_program(DOT_EXE "dot") 17 | mark_as_advanced(FORCE DOT_EXE) 18 | if(DOT_EXE) 19 | message(STATUS "dot found: ${DOT_EXE}") 20 | else() 21 | message(STATUS "dot not found!") 22 | endif() 23 | 24 | if(NOT DOT_EXE) 25 | option( 26 | BUILD_DEP_GRAPH 27 | "Builds a visual representation of the dependencies of that included targets" 28 | OFF) 29 | else() 30 | option( 31 | BUILD_DEP_GRAPH 32 | "Builds a visual representation of the dependencies of that included targets" 33 | ON) 34 | endif() 35 | 36 | # Builds a dependency graph of the active code targets using the `dot` 37 | # application 38 | # 39 | # This can only be used once per project, as each target generated is as 40 | # `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. 41 | # ~~~ 42 | # Required Arguments: 43 | # OUTPUT_TYPE 44 | # This is the output type, which doubles as the output file type, such as pdf, png. 45 | # This can be whatever the `dot` application allows. 46 | # 47 | # Options Arguments: 48 | # ADD_TO_DEP_GRAPH 49 | # If specified, add this generated target to be a dependency of the more general 50 | # `dep-graph` target. 51 | # 52 | # TARGET_NAME 53 | # The name to give the doc target. (Default: dep-graph-${PROJECT_NAME}) 54 | # 55 | # OUTPUT_DIR 56 | # The directory to place the generated output 57 | # ~~~ 58 | function(gen_dep_graph OUTPUT_TYPE) 59 | set(OPTIONS ADD_TO_DEP_GRAPH) 60 | set(SINGLE_VALUE_KEYWORDS TARGET_NAME OUTPUT_DIR) 61 | set(MULTI_VALUE_KEYWORDS) 62 | cmake_parse_arguments(gen_dep_graph "${OPTIONS}" "${SINGLE_VALUE_KEYWORDS}" 63 | "${MULTI_VALUE_KEYWORDS}" ${ARGN}) 64 | 65 | if(BUILD_DEP_GRAPH) 66 | if(NOT DOT_EXE) 67 | message(FATAL_ERROR "`dot` is needed to build the dependency graph.") 68 | endif() 69 | 70 | if(gen_dep_graph_TARGET_NAME) 71 | set(TARGET_NAME ${gen_dep_graph_TARGET_NAME}) 72 | else() 73 | set(TARGET_NAME dep-graph-${PROJECT_NAME}) 74 | endif() 75 | 76 | if(gen_dep_graph_OUTPUT_DIR) 77 | set(OUT_DIR ${gen_dep_graph_OUTPUT_DIR}) 78 | else() 79 | set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) 80 | endif() 81 | 82 | add_custom_target( 83 | ${TARGET_NAME} 84 | COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} 85 | --graphviz=${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot 86 | COMMAND 87 | ${DOT_EXE} -T${OUTPUT_TYPE} 88 | ${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot -o 89 | ${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE}) 90 | 91 | add_custom_command( 92 | TARGET ${TARGET_NAME} 93 | POST_BUILD 94 | COMMAND ; 95 | COMMENT 96 | "Dependency graph for ${TARGET_NAME} generated and located at ${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE}" 97 | ) 98 | 99 | if(gen_dep_graph_ADD_TO_DEP_GRAPH) 100 | if(NOT TARGET dep-graph) 101 | add_custom_target(dep-graph) 102 | endif() 103 | 104 | add_dependencies(dep-graph ${TARGET_NAME}) 105 | endif() 106 | endif() 107 | endfunction() 108 | -------------------------------------------------------------------------------- /doxygen.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | find_package(Doxygen) 17 | 18 | option(BUILD_DOCUMENTATION "Build API documentation using Doxygen. (make doc)" 19 | ${DOXYGEN_FOUND}) 20 | 21 | # Builds doxygen documentation with a default 'Doxyfile.in' or with a specified 22 | # one, and can make the results installable (under the `doc` install target) 23 | # 24 | # This can only be used once per project, as each target generated is as 25 | # `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. 26 | # ~~~ 27 | # Optional Arguments: 28 | # 29 | # ADD_TO_DOC 30 | # If specified, adds this generated target to be a dependency of the more general 31 | # `doc` target. 32 | # 33 | # INSTALLABLE 34 | # Adds the generated documentation to the generic `install` target, under the 35 | # `documentation` installation group. 36 | # 37 | # PROCESS_DOXYFILE 38 | # If set, then will process the found Doxyfile through the CMAKE `configure_file` 39 | # function for macro replacements before using it. (@ONLY) 40 | # 41 | # TARGET_NAME 42 | # The name to give the doc target. (Default: doc-${PROJECT_NAME}) 43 | # 44 | # OUTPUT_DIR 45 | # The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc) 46 | # 47 | # INSTALL_PATH 48 | # The path to install the documentation under. (if not specified, defaults to 49 | # 'share/${PROJECT_NAME}) 50 | # 51 | # DOXYFILE_PATH 52 | # The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile') 53 | # ~~~ 54 | function(build_docs) 55 | set(OPTIONS ADD_TO_DOC INSTALLABLE PROCESS_DOXYFILE) 56 | set(SINGLE_VALUE_KEYWORDS TARGET_NAME INSTALL_PATH DOXYFILE_PATH OUTPUT_DIR) 57 | set(MULTI_VALUE_KEYWORDS) 58 | cmake_parse_arguments(build_docs "${OPTIONS}" "${SINGLE_VALUE_KEYWORDS}" 59 | "${MULTI_VALUE_KEYWORDS}" ${ARGN}) 60 | 61 | if(BUILD_DOCUMENTATION) 62 | if(NOT DOXYGEN_FOUND) 63 | message(FATAL_ERROR "Doxygen is needed to build the documentation.") 64 | endif() 65 | 66 | if(NOT build_docs_DOXYFILE_PATH) 67 | set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile) 68 | elseif(EXISTS ${build_docs_DOXYFILE_PATH}) 69 | set(DOXYFILE_PATH ${build_docs_DOXYFILE_PATH}) 70 | else() 71 | set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${build_docs_DOXYFILE_PATH}) 72 | endif() 73 | 74 | if(NOT EXISTS ${DOXYFILE_PATH}) 75 | message( 76 | SEND_ERROR 77 | "Could not find Doxyfile to use for processing documentation at: ${DOXYFILE_PATH}" 78 | ) 79 | return() 80 | endif() 81 | 82 | if(build_docs_PROCESS_DOXYFILE) 83 | set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 84 | configure_file(${DOXYFILE_PATH} ${DOXYFILE} @ONLY) 85 | else() 86 | set(DOXYFILE ${DOXYFILE_PATH}) 87 | endif() 88 | 89 | if(build_docs_OUTPUT_DIR) 90 | set(OUT_DIR ${build_docs_OUTPUT_DIR}) 91 | else() 92 | set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doc) 93 | endif() 94 | 95 | file(MAKE_DIRECTORY ${OUT_DIR}) 96 | 97 | if(build_docs_TARGET_NAME) 98 | set(TARGET_NAME ${build_docs_TARGET_NAME}) 99 | else() 100 | set(TARGET_NAME doc-${PROJECT_NAME}) 101 | endif() 102 | 103 | add_custom_target( 104 | ${TARGET_NAME} 105 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} 106 | WORKING_DIRECTORY ${OUT_DIR} 107 | VERBATIM) 108 | 109 | if(build_docs_ADD_TO_DOC) 110 | if(NOT TARGET doc) 111 | add_custom_target(doc) 112 | endif() 113 | 114 | add_dependencies(doc ${TARGET_NAME}) 115 | endif() 116 | 117 | if(build_docs_INSTALLABLE) 118 | if(NOT build_docs_INSTALL_PATH) 119 | set(build_docs_INSTALL_PATH share/${PROJECT_NAME}) 120 | endif() 121 | install( 122 | DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/ 123 | COMPONENT documentation 124 | DESTINATION ${build_docs_INSTALL_PATH}) 125 | endif() 126 | endif() 127 | endfunction() 128 | -------------------------------------------------------------------------------- /example/all/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(sanitizer-tests C CXX) 3 | 4 | # Set the searching location for cmake 'include' locations 5 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../..;") 6 | 7 | include(c++-standards) 8 | include(code-coverage) 9 | include(formatting) 10 | include(tools) 11 | include(dependency-graph) 12 | 13 | # Require C++11 14 | cxx_11() 15 | 16 | # Tools 17 | if(UNIX AND NOT APPLE) 18 | file(GLOB_RECURSE FFILES *.[hc] *.[hc]pp) 19 | clang_format(format ${FFILES}) 20 | 21 | cmake_format(cmake-format ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt 22 | CMakeLists.txt) 23 | 24 | clang_tidy(-format-style=file -checks=* 25 | -header-filter='${CMAKE_SOURCE_DIR}/*') 26 | include_what_you_use(-Xiwyu) 27 | cppcheck( 28 | --enable=warning,performance,portability,missingInclude 29 | --template="[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)" 30 | --suppress=missingIncludeSystem --quiet --verbose --force) 31 | endif() 32 | 33 | enable_testing() 34 | 35 | # Sanitizers 36 | include(sanitizers) 37 | 38 | set_sanitizer_options(address DEFAULT -fsanitize-address-use-after-scope 39 | -fsanitize-address-use-after-return=runtime) 40 | set_sanitizer_options(leak DEFAULT) 41 | set_sanitizer_options(memory DEFAULT) 42 | set_sanitizer_options(memorywithorigins DEFAULT SANITIZER memory 43 | -fsanitize-memory-track-origins) 44 | set_sanitizer_options(undefined DEFAULT -fno-sanitize-recover=undefined) 45 | set_sanitizer_options(thread DEFAULT) 46 | 47 | set(EXAMPLE_USE_SANITIZER 48 | "" 49 | CACHE STRING "Sanitizer to use with examples") 50 | 51 | if(EXAMPLE_USE_SANITIZER) 52 | add_sanitizer_support(${EXAMPLE_USE_SANITIZER}) 53 | endif() 54 | 55 | # Fails with ThreadSanitizer 56 | add_executable(tsan_data_race ../src/tsan/data_race.cpp) 57 | target_code_coverage(tsan_data_race AUTO ALL) 58 | if(UNIX) 59 | target_link_libraries(tsan_data_race PUBLIC pthread) 60 | endif() 61 | add_test(tsan_data_race tsan_data_race) 62 | 63 | # Fails with LeakSanitizer 64 | add_executable(lsan_direct_leak ../src/lsan/direct_leak.c) 65 | target_code_coverage(lsan_direct_leak AUTO ALL) 66 | add_test(lsan_direct_leak lsan_direct_leak) 67 | 68 | add_executable(lsan_indirect_leak ../src/lsan/indirect_leak.c) 69 | target_code_coverage(lsan_indirect_leak AUTO ALL) 70 | add_test(lsan_indirect_leak lsan_indirect_leak) 71 | 72 | # Fails with AddressSanitizer 73 | if(EXAMPLE_USE_SANITIZER STREQUAL "address") 74 | # double-free now has solid detection without sanitizers too 75 | add_executable(asan_double_free ../src/asan/double_free.c) 76 | target_code_coverage(asan_double_free AUTO ALL) 77 | add_test(asan_double_free asan_double_free) 78 | endif() 79 | 80 | add_executable(asan_out_of_bounds_global ../src/asan/out_of_bounds_global.c) 81 | target_code_coverage(asan_out_of_bounds_global AUTO ALL) 82 | add_test(asan_out_of_bounds_global asan_out_of_bounds_global) 83 | 84 | add_executable(asan_out_of_bounds_heap ../src/asan/out_of_bounds_heap.c) 85 | target_code_coverage(asan_out_of_bounds_heap AUTO ALL) 86 | add_test(asan_out_of_bounds_heap asan_out_of_bounds_heap) 87 | 88 | add_executable(asan_out_of_bounds_stack ../src/asan/out_of_bounds_stack.c) 89 | target_code_coverage(asan_out_of_bounds_stack AUTO ALL) 90 | add_test(asan_out_of_bounds_stack asan_out_of_bounds_stack) 91 | 92 | add_executable(asan_use_after_free ../src/asan/use_after_free.c) 93 | target_code_coverage(asan_use_after_free AUTO ALL) 94 | add_test(asan_use_after_free asan_use_after_free) 95 | 96 | add_executable(asan_use_after_return ../src/asan/use_after_return.c) 97 | target_code_coverage(asan_use_after_return AUTO ALL) 98 | add_test(asan_use_after_return asan_use_after_return) 99 | 100 | add_executable(asan_use_after_scope ../src/asan/use_after_scope.c) 101 | target_code_coverage(asan_use_after_scope AUTO ALL) 102 | add_test(asan_use_after_scope asan_use_after_scope) 103 | 104 | # Fails with MemorySanitizer 105 | add_executable(msan_uninitialized_value_used 106 | ../src/msan/uninitialized_value_used.c) 107 | target_code_coverage(msan_uninitialized_value_used AUTO ALL) 108 | add_test(msan_uninitialized_value_used msan_uninitialized_value_used) 109 | 110 | add_executable(msan_uninitialized_pointer_used 111 | ../src/msan/uninitialized_pointer_used.c) 112 | target_code_coverage(msan_uninitialized_pointer_used AUTO ALL) 113 | add_test(msan_uninitialized_pointer_used msan_uninitialized_pointer_used) 114 | 115 | # Fails with UndefinedBehaviourSanitizer 116 | add_executable(ubsan_dereferencing_misaligned_pointer 117 | ../src/ubsan/dereferencing_misaligned_pointer.c) 118 | target_code_coverage(ubsan_dereferencing_misaligned_pointer AUTO ALL) 119 | add_test(ubsan_dereferencing_misaligned_pointer 120 | ubsan_dereferencing_misaligned_pointer) 121 | 122 | add_executable(ubsan_signed_integer_overflow 123 | ../src/ubsan/signed_integer_overflow.c) 124 | target_code_coverage(ubsan_signed_integer_overflow AUTO ALL) 125 | add_test(ubsan_signed_integer_overflow ubsan_signed_integer_overflow) 126 | -------------------------------------------------------------------------------- /example/code-coverage-all/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(code-coverage-all C CXX) 3 | 4 | # Set the searching location for cmake 'include' locations 5 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../..;") 6 | # Include the code coverage module 7 | include(code-coverage) 8 | 9 | # Require C++11 10 | include(c++-standards) 11 | cxx_11() 12 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 13 | 14 | # This introduces the 'ccov-all' targets Also excludes the main file via a regex 15 | add_code_coverage_all_targets(EXCLUDE coverage.main.cpp) 16 | 17 | # The library 18 | add_library(lib ../src/coverage.cpp) 19 | 20 | # Instruments the library 21 | target_code_coverage(lib AUTO ALL) 22 | 23 | # The executable 24 | add_executable(main ../src/coverage.main.cpp) 25 | target_link_libraries(main PUBLIC lib) 26 | 27 | # Adds the executable to the 'ccov' and 'ccov-all' targets and excludes the file 28 | # itself via regex 29 | target_code_coverage(main AUTO ALL EXTERNAL) 30 | 31 | # The second executable 32 | add_executable(main2 ../src/coverage.main.cpp) 33 | target_link_libraries(main2 PUBLIC lib) 34 | 35 | # Adds the executable to the 'ccov' and 'ccov-all' targets and excludes the file 36 | # itself via regex 37 | target_code_coverage(main2 AUTO ALL EXTERNAL) 38 | -------------------------------------------------------------------------------- /example/code-coverage-public/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(test C) 3 | 4 | include(../../code-coverage.cmake) 5 | 6 | add_library(head INTERFACE) 7 | target_code_coverage(head INTERFACE) 8 | 9 | add_executable(test code.c) 10 | target_link_libraries(test PRIVATE head) 11 | # target_code_coverage(test) 12 | -------------------------------------------------------------------------------- /example/code-coverage-public/code.c: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | 3 | int main() { return func(); } -------------------------------------------------------------------------------- /example/code-coverage-public/header.h: -------------------------------------------------------------------------------- 1 | int func() { return 0; } -------------------------------------------------------------------------------- /example/code-coverage-target/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(code-covare-all C CXX) 3 | 4 | # Set the searching location for cmake 'include' locations 5 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../..;") 6 | # Include the sanitizer module 7 | include(code-coverage) 8 | 9 | # Require C++11 10 | include(c++-standards) 11 | cxx_11() 12 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 13 | 14 | # The library 15 | add_library(lib ../src/coverage.cpp) 16 | 17 | # Instruments the library 18 | target_code_coverage(lib AUTO) 19 | 20 | # The executable 21 | add_executable(main ../src/coverage.main.cpp) 22 | target_link_libraries(main PUBLIC lib) 23 | 24 | # Adds the executable to the 'ccov' target 25 | target_code_coverage(main AUTO EXTERNAL) 26 | -------------------------------------------------------------------------------- /example/src/asan/double_free.c: -------------------------------------------------------------------------------- 1 | // this is an example of a double-free error 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | int *array = (int *)malloc(sizeof(int)); 6 | free(array); 7 | free(array); // failure point 8 | return 0; 9 | } -------------------------------------------------------------------------------- /example/src/asan/out_of_bounds_global.c: -------------------------------------------------------------------------------- 1 | // this is an example of an out-of-bounds error with global data 2 | #include 3 | 4 | int array[1]; 5 | 6 | int main(int argc, char **argv) { 7 | printf("val: %i\n", array[1]); // failure point 8 | return 0; 9 | } -------------------------------------------------------------------------------- /example/src/asan/out_of_bounds_heap.c: -------------------------------------------------------------------------------- 1 | // this is an example of an out-of-bounds error with heap data 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv) { 6 | int *array = malloc(sizeof(int)); 7 | printf("val: %i\n", array[1]); // failure point 8 | free(array); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /example/src/asan/out_of_bounds_stack.c: -------------------------------------------------------------------------------- 1 | // this is an example of an out-of-bounds error with stack data 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | int array[1]; 6 | printf("val: %i\n", array[1]); // failure point 7 | return 0; 8 | } -------------------------------------------------------------------------------- /example/src/asan/use_after_free.c: -------------------------------------------------------------------------------- 1 | // this is an example of a use-after-free error 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv) { 6 | int *array = malloc(sizeof(int)); 7 | free(array); 8 | printf("val: %i\n", *array); // failure point 9 | return 0; 10 | } -------------------------------------------------------------------------------- /example/src/asan/use_after_return.c: -------------------------------------------------------------------------------- 1 | // this is an example of a use-after-return error 2 | #include 3 | 4 | int *array; 5 | 6 | void setPointerWithEscapedData() { 7 | int internalArray[1]; 8 | array = internalArray; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | setPointerWithEscapedData(); 13 | printf("val: %i\n", array[0]); // failure point 14 | return 0; 15 | } -------------------------------------------------------------------------------- /example/src/asan/use_after_scope.c: -------------------------------------------------------------------------------- 1 | // this is an example of a use-after-scope error 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | int *array; 6 | { 7 | int internalArray[1]; 8 | array = internalArray; 9 | } 10 | printf("val: %i\n", array[0]); // failure point 11 | return 0; 12 | } -------------------------------------------------------------------------------- /example/src/coverage.cpp: -------------------------------------------------------------------------------- 1 | #include "coverage.hpp" 2 | 3 | #include 4 | 5 | int tested_func(double param1) { return std::sqrt(param1); } 6 | 7 | int untested_func(double param1) { return std::sqrt(param1); } -------------------------------------------------------------------------------- /example/src/coverage.hpp: -------------------------------------------------------------------------------- 1 | int tested_func(double param1); 2 | 3 | int untested_func(double param1); -------------------------------------------------------------------------------- /example/src/coverage.main.cpp: -------------------------------------------------------------------------------- 1 | #include "coverage.hpp" 2 | 3 | int main() { 4 | tested_func(1.0); 5 | 6 | return 0; 7 | } -------------------------------------------------------------------------------- /example/src/lsan/direct_leak.c: -------------------------------------------------------------------------------- 1 | // example of a direct memory leak, where no pointer to the memory exists 2 | #include 3 | 4 | int main() { 5 | void *p = malloc(7); 6 | p = 0; // failure point 7 | return 0; 8 | } -------------------------------------------------------------------------------- /example/src/lsan/indirect_leak.c: -------------------------------------------------------------------------------- 1 | // example of an indirect memory leak, where a pointer to the memory still exists 2 | #include 3 | 4 | int main() { 5 | void *p = malloc(7); 6 | return 0; 7 | } // failure point -------------------------------------------------------------------------------- /example/src/msan/uninitialized_pointer_used.c: -------------------------------------------------------------------------------- 1 | // example of using an uninitialized pointer 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | int *val; 6 | printf("val: %i", *val); 7 | return 0; 8 | } -------------------------------------------------------------------------------- /example/src/msan/uninitialized_value_used.c: -------------------------------------------------------------------------------- 1 | // example of using an uninitialized value 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | int val; 6 | printf("val: %i", val); 7 | return 0; 8 | } -------------------------------------------------------------------------------- /example/src/tsan/data_race.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void threadfunc(int *p) { *p = 1; } 5 | 6 | int main() { 7 | int val = 0; 8 | std::thread t(threadfunc, &val); 9 | printf("foo=%i\n", val); 10 | t.join(); 11 | } -------------------------------------------------------------------------------- /example/src/ubsan/dereferencing_misaligned_pointer.c: -------------------------------------------------------------------------------- 1 | // example of a program that dereferences a mis-aligned pointer 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char **argv) { 7 | int *array = malloc(10 * sizeof(int)); 8 | memset(array, 0, 10 * sizeof(int)); 9 | // aligned 10 | printf("aligned: %i", *array); 11 | // mis-aligned 12 | int *misArray = (int *)((char *)(array) + 1); 13 | printf("aligned: %i", *misArray); // failure point 14 | 15 | free(array); 16 | return 0; 17 | } -------------------------------------------------------------------------------- /example/src/ubsan/signed_integer_overflow.c: -------------------------------------------------------------------------------- 1 | // example program that performs a signed integer overflow 2 | int main(int argc, char **argv) { 3 | int k = 0x7fffffff; 4 | k += argc; // failure point 5 | return 0; 6 | } -------------------------------------------------------------------------------- /formatting.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # 17 | # clang-format 18 | # 19 | find_program(CLANG_FORMAT_EXE "clang-format") 20 | mark_as_advanced(FORCE CLANG_FORMAT_EXE) 21 | if(CLANG_FORMAT_EXE) 22 | message(STATUS "clang-format found: ${CLANG_FORMAT_EXE}") 23 | else() 24 | message(STATUS "clang-format not found!") 25 | endif() 26 | 27 | # Generates a 'format' target using a custom name, files, and include 28 | # directories all being parameters. 29 | # 30 | # Do note that in order for sources to be inherited properly, the source paths 31 | # must be reachable from where the macro is called, or otherwise require a full 32 | # path for proper inheritance. 33 | # 34 | # ~~~ 35 | # Required: 36 | # TARGET_NAME - The name of the target to create. 37 | # 38 | # Optional: ARGN - The list of targets OR files to format. Relative and absolute 39 | # paths are accepted. 40 | # ~~~ 41 | function(clang_format TARGET_NAME) 42 | if(CLANG_FORMAT_EXE) 43 | set(FORMAT_FILES) 44 | # Check through the ARGN's, determine existent files 45 | foreach(item IN LISTS ARGN) 46 | if(TARGET ${item}) 47 | # If the item is a target, then we'll attempt to grab the associated 48 | # source files from it. 49 | get_target_property(_TARGET_TYPE ${item} TYPE) 50 | if(NOT _TARGET_TYPE STREQUAL "INTERFACE_LIBRARY") 51 | get_property( 52 | _TEMP 53 | TARGET ${item} 54 | PROPERTY SOURCES) 55 | foreach(iter IN LISTS _TEMP) 56 | if(EXISTS ${iter}) 57 | set(FORMAT_FILES ${FORMAT_FILES} ${iter}) 58 | endif() 59 | endforeach() 60 | endif() 61 | elseif(EXISTS ${item}) 62 | # Check if it's a full file path 63 | set(FORMAT_FILES ${FORMAT_FILES} ${item}) 64 | elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${item}) 65 | # Check if it's based on the current source dir 66 | set(FORMAT_FILES ${FORMAT_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/${item}) 67 | endif() 68 | endforeach() 69 | 70 | # Make the target 71 | if(FORMAT_FILES) 72 | add_custom_target(${TARGET_NAME} COMMAND ${CLANG_FORMAT_EXE} -i 73 | -style=file ${FORMAT_FILES}) 74 | 75 | if(NOT TARGET format) 76 | add_custom_target(format) 77 | endif() 78 | 79 | add_dependencies(format ${TARGET_NAME}) 80 | endif() 81 | 82 | endif() 83 | endfunction() 84 | 85 | # 86 | # cmake-format 87 | # 88 | find_program(CMAKE_FORMAT_EXE "cmake-format") 89 | mark_as_advanced(FORCE CMAKE_FORMAT_EXE) 90 | if(CMAKE_FORMAT_EXE) 91 | message(STATUS "cmake-format found: ${CMAKE_FORMAT_EXE}") 92 | else() 93 | message(STATUS "cmake-format not found!") 94 | endif() 95 | 96 | # When called, this function will call 'cmake-format' program on all listed 97 | # files (if both the program and the files exist and are found) 98 | # ~~~ 99 | # Required: 100 | # TARGET_NAME - The name of the target to create. 101 | # 102 | # Optional: 103 | # ARGN - Any arguments passed in will be considered as 'files' to perform the 104 | # formatting on. Any items that are not files will be ignored. Both relative and 105 | # absolute paths are accepted. 106 | # ~~~ 107 | function(cmake_format TARGET_NAME) 108 | if(CMAKE_FORMAT_EXE) 109 | set(FORMAT_FILES) 110 | # Determine files that exist 111 | foreach(iter IN LISTS ARGN) 112 | if(EXISTS ${iter}) 113 | set(FORMAT_FILES ${FORMAT_FILES} ${iter}) 114 | elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${iter}) 115 | set(FORMAT_FILES ${FORMAT_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/${iter}) 116 | endif() 117 | endforeach() 118 | 119 | # Generate target 120 | if(FORMAT_FILES) 121 | if(TARGET ${TARGET_NAME}) 122 | message( 123 | ERROR 124 | "Cannot create cmake-format target '${TARGET_NAME}', already exists.") 125 | else() 126 | add_custom_target(${TARGET_NAME} COMMAND ${CMAKE_FORMAT_EXE} -i 127 | ${FORMAT_FILES}) 128 | 129 | if(NOT TARGET cmake-format) 130 | add_custom_target(cmake-format) 131 | endif() 132 | add_dependencies(cmake-format ${TARGET_NAME}) 133 | endif() 134 | endif() 135 | endif() 136 | endfunction() 137 | -------------------------------------------------------------------------------- /glsl-shaders.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # 17 | # glslangValidator 18 | # 19 | find_program(GLSLANGVALIDATOR_EXE "glslangValidator") 20 | mark_as_advanced(FORCE GLSLANGVALIDATOR_EXE) 21 | if(GLSLANGVALIDATOR_EXE) 22 | message(STATUS "glslangValidator found: ${GLSLANGVALIDATOR_EXE}") 23 | else() 24 | message(STATUS "glslangValidator not found!") 25 | endif() 26 | 27 | # This function acts much like the 'target_sources' function, as in raw GLSL 28 | # shader files can be passed in and will be compiled using 'glslangValidator', 29 | # provided it is available, where the compiled files will be located where the 30 | # sources files are but with the '.spv' suffix appended. 31 | # 32 | # The first argument is the target that the files are associated with, and will 33 | # be compiled as if it were a source file for it. All provided shaders are also 34 | # only recompiled if the source shader file has been modified since the last 35 | # compilation. 36 | # 37 | # ~~~ 38 | # Required: 39 | # TARGET_NAME - Name of the target the shader files are associated with and to be compiled for. 40 | # 41 | # Optional: 42 | # INTERFACE - When the following shader files are added to a target, they are done so as 'INTERFACE' type files 43 | # PUBLIC - When the following shader files are added to a target, they are done so as 'PUBLIC' type files 44 | # PRIVATE - When the following shader files are added to a target, they are done so as 'PRIVATE' type files 45 | # COMPILE_OPTIONS - These are other options passed straight to the 'glslangValidator' call with the source shader file 46 | # 47 | # Example: 48 | # When calling `make vk_lib` the shaders will also be compiled with the library's `.c` files. 49 | # 50 | # add_library(vk_lib lib.c, shader_manager.c) 51 | # target_glsl_shaders(vk_lib 52 | # PRIVATE test.vert test.frag 53 | # COMPILE_OPTIONS --target-env vulkan1.1) 54 | # ~~~ 55 | function(target_glsl_shaders TARGET_NAME) 56 | if(NOT GLSLANGVALIDATOR_EXE) 57 | message( 58 | FATAL_ERROR "Cannot compile GLSL to SPIR-V is glslangValidator not found!" 59 | ) 60 | endif() 61 | 62 | set(OPTIONS) 63 | set(SINGLE_VALUE_KEYWORDS) 64 | set(MULTI_VALUE_KEYWORDS INTERFACE PUBLIC PRIVATE COMPILE_OPTIONS) 65 | cmake_parse_arguments( 66 | target_glsl_shaders "${OPTIONS}" "${SINGLE_VALUE_KEYWORDS}" 67 | "${MULTI_VALUE_KEYWORDS}" ${ARGN}) 68 | 69 | foreach(GLSL_FILE IN LISTS target_glsl_shaders_INTERFACE) 70 | add_custom_command( 71 | OUTPUT ${GLSL_FILE}.spv 72 | COMMAND ${GLSLANGVALIDATOR_EXE} ${target_glsl_shaders_COMPILE_OPTIONS} -V 73 | "${GLSL_FILE}" -o "${GLSL_FILE}.spv" 74 | MAIN_DEPENDENCY ${GLSL_FILE}) 75 | 76 | target_sources(${TARGET_NAME} INTERFACE ${GLSL_FILE}.spv) 77 | endforeach() 78 | 79 | foreach(GLSL_FILE IN LISTS target_glsl_shaders_PUBLIC) 80 | add_custom_command( 81 | OUTPUT ${GLSL_FILE}.spv 82 | COMMAND ${GLSLANGVALIDATOR_EXE} ${target_glsl_shaders_COMPILE_OPTIONS} -V 83 | "${GLSL_FILE}" -o "${GLSL_FILE}.spv" 84 | MAIN_DEPENDENCY ${GLSL_FILE}) 85 | 86 | target_sources(${TARGET_NAME} PUBLIC ${GLSL_FILE}.spv) 87 | endforeach() 88 | 89 | foreach(GLSL_FILE IN LISTS target_glsl_shaders_PRIVATE) 90 | add_custom_command( 91 | OUTPUT ${GLSL_FILE}.spv 92 | COMMAND ${GLSLANGVALIDATOR_EXE} ${target_glsl_shaders_COMPILE_OPTIONS} -V 93 | "${GLSL_FILE}" -o "${GLSL_FILE}.spv" 94 | MAIN_DEPENDENCY ${GLSL_FILE}) 95 | 96 | target_sources(${TARGET_NAME} PRIVATE ${GLSL_FILE}.spv) 97 | endforeach() 98 | endfunction() 99 | -------------------------------------------------------------------------------- /img/code-cov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StableCoder/cmake-scripts/ea9e8b7579687b4f305907f4e0df460e49c0f097/img/code-cov.png -------------------------------------------------------------------------------- /img/dp-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StableCoder/cmake-scripts/ea9e8b7579687b4f305907f4e0df460e49c0f097/img/dp-graph.png -------------------------------------------------------------------------------- /link-time-optimization.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2021 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | include(CheckIPOSupported) 17 | 18 | # Checks for, and enables IPO/LTO for all following targets 19 | # 20 | # Running with GCC seems to have no effect 21 | # ~~~ 22 | # Optional: 23 | # REQUIRED - If this is passed in, CMake configuration will fail with an error if LTO/IPO is not supported 24 | # ~~~ 25 | macro(link_time_optimization) 26 | # Argument parsing 27 | set(options REQUIRED) 28 | set(single_value_keywords) 29 | set(multi_value_keywords) 30 | cmake_parse_arguments( 31 | link_time_optimization "${options}" "${single_value_keywords}" 32 | "${multi_value_keywords}" ${ARGN}) 33 | 34 | check_ipo_supported(RESULT result OUTPUT output) 35 | if(result) 36 | # It's available, set it for all following items 37 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 38 | else() 39 | if(link_time_optimization_REQUIRED) 40 | message( 41 | FATAL_ERROR 42 | "Link Time Optimization not supported, but listed as REQUIRED: ${output}" 43 | ) 44 | else() 45 | message(WARNING "Link Time Optimization not supported: ${output}") 46 | endif() 47 | endif() 48 | endmacro() 49 | 50 | # Checks for, and enables IPO/LTO for the specified target 51 | # 52 | # Running with GCC seems to have no effect 53 | # ~~~ 54 | # Required: 55 | # TARGET_NAME - Name of the target to generate code coverage for 56 | # Optional: 57 | # REQUIRED - If this is passed in, CMake configuration will fail with an error if LTO/IPO is not supported 58 | # ~~~ 59 | function(target_link_time_optimization TARGET_NAME) 60 | # Argument parsing 61 | set(options REQUIRED) 62 | set(single_value_keywords) 63 | set(multi_value_keywords) 64 | cmake_parse_arguments( 65 | target_link_time_optimization "${options}" "${single_value_keywords}" 66 | "${multi_value_keywords}" ${ARGN}) 67 | 68 | check_ipo_supported(RESULT result OUTPUT output) 69 | if(result) 70 | # It's available, set it for all following items 71 | set_property(TARGET ${TARGET_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION 72 | TRUE) 73 | else() 74 | if(target_link_time_optimization_REQUIRED) 75 | message( 76 | FATAL_ERROR 77 | "Link Time Optimization not supported, but listed as REQUIRED for the ${TARGET_NAME} target: ${output}" 78 | ) 79 | else() 80 | message(WARNING "Link Time Optimization not supported: ${output}") 81 | endif() 82 | endif() 83 | endfunction() 84 | -------------------------------------------------------------------------------- /prepare-catch.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # Options 17 | option(FORCE_CATCH_CLONE 18 | "Forces cloning of the Catch test headers rather than using local" OFF) 19 | 20 | # Attempts to find a local header of the Catch test frameword 21 | find_file(HAVE_CATCH_HPP catch.hpp PATH_SUFFIXES catch2 catch) 22 | mark_as_advanced(FORCE HAVE_CATCH_HPP) 23 | 24 | # **DEPRECATED** Catch now has proper CMake integration, and this will be 25 | # dropped in a future release. 26 | # 27 | # Attempts to add the infrastructure necessary for automatically adding C/C++ 28 | # tests using the Catch2 library, including either an interface or pre-compiled 29 | # 'catch' target library. 30 | # 31 | # It first attempts to find the header on the local machine, and failing that, 32 | # clones the single header variant for use. It does make the determination 33 | # between pre-C++11 and will use Catch1.X rather than Catch2 (when cloned), 34 | # automatically or forced.. Adds a subdirectory of tests/ if it exists from the 35 | # macro's calling location. 36 | # 37 | # ~~~ 38 | # COMPILED_CATCH - If this option is specified, then generates the 'catch' target as a library with 39 | # catch already pre-compiled as part of the library. Otherwise acts just an interface library for 40 | # the header location. 41 | # CATCH1 - Force the use of Catch1.X, rather than auto-detecting the C++ version in use. 42 | # CLONE - Force cloning of Catch, rather than attempting to use a locally-found variant. 43 | # 44 | # !WARNING! - When switching between COMPILED_CATCH and non-COMPILED_CATCH, the binary folders will need 45 | # to be cleared for it to take proper effect. 46 | # !WARNING! - The parameters of the first processed instance of the macro will determine the catch target 47 | # configuration. There's no mixing of configs here. 48 | # ~~~ 49 | function(prepare_catch) 50 | set(options COMPILED_CATCH CATCH1 CLONE) 51 | cmake_parse_arguments(build_tests "${options}" "" "" ${ARGN}) 52 | 53 | if(BUILD_TESTS AND NOT TARGET catch) 54 | if(NOT HAVE_CATCH_HPP 55 | OR FORCE_CATCH_CLONE 56 | OR build_tests_CLONE) 57 | # Cloning 58 | message(STATUS "No local Catch header detected, cloning via Git.") 59 | include(ExternalProject) 60 | find_package(Git REQUIRED) 61 | 62 | if(CMAKE_CXX_STANDARD 63 | AND CMAKE_CXX_STANDARD GREATER 10 64 | AND NOT build_tests_CATCH1) 65 | message(STATUS "Cloning Catch2") 66 | ExternalProject_Add( 67 | git_catch 68 | PREFIX ${CMAKE_BINARY_DIR}/catch2 69 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 70 | GIT_TAG v2.x 71 | GIT_SHALLOW 1 72 | TIMEOUT 10 73 | UPDATE_COMMAND ${GIT_EXECUTABLE} pull 74 | CONFIGURE_COMMAND "" 75 | BUILD_COMMAND "" 76 | INSTALL_COMMAND "" 77 | LOG_DOWNLOAD ON) 78 | else() 79 | message(STATUS "Cloning Catch1") 80 | ExternalProject_Add( 81 | git_catch 82 | PREFIX ${CMAKE_BINARY_DIR}/catch1 83 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 84 | GIT_TAG Catch1.x 85 | GIT_SHALLOW 1 86 | TIMEOUT 10 87 | UPDATE_COMMAND ${GIT_EXECUTABLE} pull 88 | CONFIGURE_COMMAND "" 89 | BUILD_COMMAND "" 90 | INSTALL_COMMAND "" 91 | LOG_DOWNLOAD ON) 92 | endif() 93 | 94 | ExternalProject_Get_Property(git_catch source_dir) 95 | set(CATCH_PATH ${source_dir}/single_include 96 | ${source_dir}/single_include/catch2) 97 | else() 98 | # Using Local 99 | message(STATUS "Local Catch header detected at: " ${HAVE_CATCH_HPP}) 100 | get_filename_component(CATCH_PATH ${HAVE_CATCH_HPP} DIRECTORY) 101 | endif() 102 | 103 | if(build_tests_COMPILED_CATCH) 104 | # A pre-compiled catch library has been requested 105 | message(STATUS "Generating a pre-compiled Catch library") 106 | 107 | if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/pre_compiled_catch.cpp) 108 | file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/pre_compiled_catch.cpp 109 | "#define CATCH_CONFIG_MAIN\n#include \n") 110 | endif() 111 | if(WIN32) 112 | # Catch on WIN32 doesn't work when dynamically linked 113 | add_library(catch STATIC 114 | ${CMAKE_CURRENT_BINARY_DIR}/pre_compiled_catch.cpp) 115 | else() 116 | # Make sure it's visible if it's a shared object. 117 | set(CMAKE_CXX_VISIBILITY_PRESET default) 118 | set(CMAKE_VISIBILITY_INLINES_HIDDEN 0) 119 | add_library(catch SHARED 120 | ${CMAKE_CURRENT_BINARY_DIR}/pre_compiled_catch.cpp) 121 | endif() 122 | target_include_directories(catch PUBLIC ${CATCH_PATH}) 123 | else() 124 | add_library(catch INTERFACE) 125 | target_include_directories(catch INTERFACE ${CATCH_PATH}) 126 | endif() 127 | 128 | if(TARGET git_catch) 129 | # If cloning, make sure it's cloned BEFORE it's needed. 130 | add_dependencies(catch git_catch) 131 | endif() 132 | endif() 133 | endfunction() 134 | -------------------------------------------------------------------------------- /sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018-2024 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # USAGE: 17 | 18 | # The most basic way to enable sanitizers is to simply call 19 | # `add_sanitizer_support` with the desired sanitizer names, whereupon it will 20 | # check for the availability and compatability of the combined flags and apply 21 | # to following compile targets: 22 | # ~~~ 23 | # # apply address and leak sanitizers 24 | # add_sanitizer_support(address leak) 25 | # # future targets will be compiled with '-fsanitize=address -fsanitize=leak' 26 | # ~~~ 27 | # 28 | # Compile options on a per-sanitizer basis can be accomplished by calling 29 | # `set_sanitizer_options` before with the name of the sanitizer and desired 30 | # compile options: 31 | # ~~~ 32 | # # set custom options that will be applies with that specific sanitizer 33 | # set_sanitizer_options(address -fno-omit-frame-pointer) 34 | # 35 | # add_sanitizer_support(address leak) 36 | # # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer -fsanitize=leak' 37 | # ~~~ 38 | # 39 | # Per-sanitizer compile options can also be set by setting the named 40 | # `SANITIZER_${SANITIZER_NAME}_OPTIONS` variable before, either in script or via 41 | # the command line. 42 | # ~~~ 43 | # # CMake called from command line as `cmake -S . -B build -D SANITIZER_ADDRESS_OPTION='-fno-omit-frame-pointer'` 44 | # 45 | # add_sanitizer_support(address) 46 | # # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer' 47 | # # despite no call to `set_sanitizer_options` 48 | # ~~~ 49 | # 50 | # To prevent custom sanitizer options from an external source being overwritten, 51 | # the `DEFAULT` option can be used, so that the flags are only used if none have 52 | # been set previously: 53 | # ~~~ 54 | # # command line has options set via command-line: `cmake -S . -B build -D SANITIZER_ADDRESS_OPTION='-fno-omit-frame-pointer'` 55 | # 56 | # # attempt to set custom options that will not apply since the variable already exists 57 | # # and `DEFAULT` option is passed in. 58 | # set_sanitizer_options(address DEFAULT -some-other-flag) 59 | # 60 | # add_sanitizer_support(address) 61 | # # future targets will be compiled with '-fsanitize=address -fno-omit-frame-pointer' 62 | # ~~~ 63 | # 64 | # Different sets of options used with the sanitizer can be accomplished by 65 | # defining the sanitizer serparately with the call to `set_sanitizer_option`: 66 | # ~~~ 67 | # set_sanitizer_options(memory DEFAULT -fno-omit-frame-pointer) 68 | # 69 | # set_sanitizer_options(memorywithorigins DEFAULT 70 | # SANITIZER memory 71 | # -fno-omit-frame-pointer 72 | # -fsanitize-memory-track-origins) 73 | # 74 | # # Despite both using the 'memory' sanitizer, which specific set of flags can be chosen 75 | # # when calling `add_sanitizer_support` with either 'memory' or 'memorywithorigins' 76 | # ~~~ 77 | 78 | # LEGACY USAGE: 79 | 80 | # Previous versions had a strict set of options that could be used via having a 81 | # set CMake variable either in the script or from the command line: 82 | # ~~~ 83 | # # this can also be set via command line as `-D USE_SANITIZER='address leak'` 84 | # set(USE_SANITIZER address leak) 85 | # ~~~ 86 | # This is now deprecated to be removed in a future version, but should still be 87 | # functional until then, either by defining `USE_SANITIZER` or 88 | # `SANITIZER_ENABLE_LEGACY_SUPPORT` before including the script file. 89 | 90 | include(CheckCXXCompilerFlag) 91 | include(CheckCXXSourceCompiles) 92 | 93 | function(append_quoteless value) 94 | foreach(variable ${ARGN}) 95 | set(${variable} 96 | ${${variable}} ${value} 97 | PARENT_SCOPE) 98 | endforeach(variable) 99 | endfunction() 100 | 101 | function(test_san_flags RETURN_VAR LINK_OPTIONS) 102 | set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET}) 103 | set(CMAKE_REQUIRED_QUIET TRUE) 104 | unset(${RETURN_VAR} CACHE) 105 | 106 | # backup test flags 107 | set(OPTION_FLAGS_BACKUP ${CMAKE_REQUIRED_FLAGS}) 108 | set(LINK_FLAGS_BACKUP ${CMAKE_REQUIRED_LINK_OPTIONS}) 109 | 110 | # set link options 111 | unset(CMAKE_REQUIRED_LINK_OPTIONS) 112 | foreach(ARG ${${LINK_OPTIONS}}) 113 | set(CMAKE_REQUIRED_LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS};${ARG}) 114 | endforeach() 115 | 116 | # set compile options 117 | unset(CMAKE_REQUIRED_FLAGS) 118 | unset(test_san_flags_OPTION_TEST CACHE) 119 | foreach(ARG ${ARGN}) 120 | unset(test_san_flags_OPTION_TEST CACHE) 121 | check_cxx_compiler_flag(${ARG} test_san_flags_OPTION_TEST) 122 | if(NOT test_san_flags_OPTION_TEST) 123 | break() 124 | endif() 125 | set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${ARG}") 126 | endforeach() 127 | 128 | # actually test if compilation can occur with given compiler/link options 129 | if(NOT DEFINED test_san_flags_OPTION_TEST OR test_san_flags_OPTION_TEST) 130 | check_cxx_source_compiles("int main() { return 0; }" ${RETURN_VAR}) 131 | endif() 132 | 133 | # reset backed-up flags 134 | set(CMAKE_REQUIRED_LINK_OPTIONS "${LINK_FLAGS_BACKUP}") 135 | set(CMAKE_REQUIRED_FLAGS "${OPTION_FLAGS_BACKUP}") 136 | 137 | set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}") 138 | endfunction() 139 | 140 | # Adds/sets compile flags for a given sanitizer, and checks for 141 | # compatability/availability with the current compiler. 142 | # 143 | # Each time the compile options for a sanitizer is modified, the availability 144 | # will be re-checked and cached. 145 | # 146 | # After the check, the compile options will be stored in the CMake cache as the 147 | # `SANITIZER_${SANITIZER_NAME}_OPTIONS` name which will be available as a 148 | # modifiable CMake string. 149 | # 150 | # ~~~ 151 | # Required: 152 | # SANITIZER_NAME - Name of the sanitizer. When selected, this name also doubles 153 | # as the name given to the compiler in the form of 154 | # `-fsanitize=` in lower-case lettering. 155 | # 156 | # Optional: 157 | # DEFAULT - Passed compile flags will only be applied if there is no currently defined 158 | # `SANITIZER_${SANITIZER_NAME}_OPTIONS` variable 159 | # SANITIZER - If defined, this replaces the SANITIZER_NAME for the compile/link 160 | # in the form of `-fsanitize=` 161 | # 162 | # Additional parameters are added as compile flags 163 | function(set_sanitizer_options SANITIZER_NAME) 164 | # Argument parsing 165 | set(options DEFAULT) 166 | set(single_value_keywords SANITIZER) 167 | set(multi_value_keywords) 168 | cmake_parse_arguments( 169 | set_sanitizer_options "${options}" "${single_value_keywords}" 170 | "${multi_value_keywords}" ${ARGN}) 171 | 172 | string(TOUPPER ${SANITIZER_NAME} UPPER_SANITIZER_NAME) 173 | string(TOLOWER ${SANITIZER_NAME} LOWER_SANITIZER_NAME) 174 | 175 | unset(USED_SANITIZER_OPTION) 176 | if(NOT set_sanitizer_options_SANITIZER) 177 | set(set_sanitizer_options_SANITIZER ${LOWER_SANITIZER_NAME}) 178 | endif() 179 | 180 | # if `DEFAULT` is specified, only apply new arguments if there is no previous 181 | # cache 182 | if(set_sanitizer_options_DEFAULT 183 | AND DEFINED SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS) 184 | return() 185 | endif() 186 | 187 | # check if the cache does not match what we have here, update the cache and 188 | # check for availability 189 | if(NOT DEFINED SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS 190 | OR NOT (ARGN STREQUAL SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS)) 191 | # don't overwrite options set via non-legacy path 192 | if(DEFINED SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS 193 | AND SANITIZER_LEGACY_SUPPORT) 194 | # @todo remove when legacy support is 195 | return() 196 | endif() 197 | 198 | # set as the new cache 199 | set(SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER 200 | ${set_sanitizer_options_SANITIZER} 201 | CACHE INTERNAL "" FORCE) 202 | set(SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS 203 | "${set_sanitizer_options_UNPARSED_ARGUMENTS}" 204 | CACHE STRING "${LOWER_SANITIZER_NAME} sanitizer compile options" FORCE) 205 | 206 | # check if sanitizer is available 207 | message(CHECK_START 208 | "Checking if '${LOWER_SANITIZER_NAME}' sanitizer is available") 209 | 210 | # check if the compile option combination can compile 211 | unset(SANITIZER_${UPPER_SANITIZER_NAME}_AVAILABLE CACHE) 212 | set(set_sanitizer_options_LINK_OPTIONS 213 | -fsanitize=${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER}) 214 | test_san_flags( 215 | SANITIZER_${UPPER_SANITIZER_NAME}_AVAILABLE 216 | set_sanitizer_options_LINK_OPTIONS 217 | -fsanitize=${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER};${set_sanitizer_options_UNPARSED_ARGUMENTS} 218 | ) 219 | 220 | if(SANITIZER_${UPPER_SANITIZER_NAME}_AVAILABLE) 221 | message(CHECK_PASS "available") 222 | 223 | # add sanitizer to available options 224 | if(NOT DEFINED SANITIZERS_AVAILABLE_LIST) 225 | set(SANITIZERS_AVAILABLE_LIST 226 | "${LOWER_SANITIZER_NAME}" 227 | CACHE INTERNAL "" FORCE) 228 | elseif(NOT SANITIZERS_AVAILABLE_LIST MATCHES "(${LOWER_SANITIZER_NAME})") 229 | set(SANITIZERS_AVAILABLE_LIST 230 | "${SANITIZERS_AVAILABLE_LIST};${LOWER_SANITIZER_NAME}" 231 | CACHE INTERNAL "" FORCE) 232 | endif() 233 | else() 234 | message(CHECK_FAIL "not available") 235 | 236 | # remove from available list if it is not available 237 | if(SANITIZERS_AVAILABLE_LIST MATCHES "(${LOWER_SANITIZER_NAME})") 238 | string(REPLACE "${LOWER_SANITIZER_NAME};" "" REPLACED_STRING 239 | SANITIZERS_AVAILABLE_LIST) 240 | string(REPLACE ";${LOWER_SANITIZER_NAME}" "" REPLACED_STRING2 241 | REPLACED_STRING) 242 | string(REPLACE "${LOWER_SANITIZER_NAME}" "" REPLACED_STRING3 243 | REPLACED_STRING2) 244 | set(SANITIZERS_AVAILABLE_LIST 245 | "${REPLACED_STRING3}" 246 | CACHE INTERNAL "" FORCE) 247 | endif() 248 | endif() 249 | endif() 250 | endfunction() 251 | 252 | # Adds the given sanitizer compile options together, checks the combined 253 | # compatability and adds the compile_options and link_options 254 | # ~~~ 255 | # Required: 256 | # All given parameters are either sanitizer/options set previously via 257 | # `set_sanitizer_options` or are created/checked dynamically with no 258 | # options, in the form of `-fsanitize=${SANITIZER_NAME}` in lower case. 259 | function(add_sanitizer_support) 260 | unset(SANITIZER_COMPILE_OPTIONS_SELECTED) 261 | unset(SANITIZER_SELECTED_LINK_OPTIONS) 262 | 263 | # iterate selected sanitizers, check availability of each 264 | foreach(SELECTED_SANITIZER ${ARGN}) 265 | string(TOUPPER ${SELECTED_SANITIZER} UPPER_SANITIZER_NAME) 266 | string(TOLOWER ${SELECTED_SANITIZER} LOWER_SANITIZER_NAME) 267 | 268 | # if the sanitizer is not yet known/checked, check it now 269 | if(NOT DEFINED SANITIZER_${UPPER_SANITIZER_NAME}_AVAILABLE) 270 | set_sanitizer_options(${LOWER_SANITIZER_NAME}) 271 | endif() 272 | 273 | if(SANITIZER_${UPPER_SANITIZER_NAME}_AVAILABLE) 274 | # sanitizer is available, add the flags to the selection 275 | set(SANITIZER_COMPILE_OPTIONS_SELECTED 276 | ${SANITIZER_COMPILE_OPTIONS_SELECTED} 277 | -fsanitize=${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} 278 | ${SANITIZER_${UPPER_SANITIZER_NAME}_OPTIONS}) 279 | 280 | set(SANITIZER_SELECTED_LINK_OPTIONS 281 | ${SANITIZER_SELECTED_LINK_OPTIONS} 282 | -fsanitize=${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER}) 283 | 284 | # special for AFL 285 | if(AFL) 286 | if(${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} MATCHES "address") 287 | append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER 288 | CMAKE_CXX_COMPILER_LAUNCHER) 289 | elseif(${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} MATCHES "leak") 290 | append_quoteless(AFL_USE_LSAN=1 CMAKE_C_COMPILER_LAUNCHER 291 | CMAKE_CXX_COMPILER_LAUNCHER) 292 | elseif(${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} MATCHES "memory") 293 | append_quoteless(AFL_USE_MSAN=1 CMAKE_C_COMPILER_LAUNCHER 294 | CMAKE_CXX_COMPILER_LAUNCHER) 295 | elseif(${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} MATCHES 296 | "undefined") 297 | append_quoteless(AFL_USE_UBSAN=1 CMAKE_C_COMPILER_LAUNCHER 298 | CMAKE_CXX_COMPILER_LAUNCHER) 299 | elseif(${SANITIZER_${UPPER_SANITIZER_NAME}_SANITIZER} MATCHES "cfi") 300 | append_quoteless(AFL_USE_CFISAN=1 CMAKE_C_COMPILER_LAUNCHER 301 | CMAKE_CXX_COMPILER_LAUNCHER) 302 | endif() 303 | endif() 304 | else() 305 | message( 306 | SEND_ERROR "'${LOWER_SANITIZER_NAME}' sanitizer set not available") 307 | endif() 308 | endforeach() 309 | 310 | # check if all selected sanitizer options/flags are compatible together 311 | if(NOT DEFINED SANITIZER_COMPILE_OPTIONS_GLOBAL_CACHE 312 | OR NOT SANITIZER_COMPILE_OPTIONS_SELECTED STREQUAL 313 | SANITIZER_COMPILE_OPTIONS_GLOBAL_CACHE) 314 | # set of flags needs to be tested for compatability 315 | unset(SANITIZER_SELECTED_OPTIONS_AVAILABLE CACHE) 316 | test_san_flags( 317 | SANITIZER_SELECTED_OPTIONS_AVAILABLE SANITIZER_SELECTED_LINK_OPTIONS 318 | ${SANITIZER_COMPILE_OPTIONS_SELECTED}) 319 | 320 | # whatever the result, cache it to reduce repeating test 321 | set(SANITIZER_COMPILE_OPTIONS_GLOBAL_CACHE 322 | ${SANITIZER_COMPILE_OPTIONS_SELECTED} 323 | CACHE INTERNAL "") 324 | endif() 325 | 326 | if(SANITIZER_SELECTED_OPTIONS_AVAILABLE) 327 | # sanitizer selection is compatible, apply it 328 | add_compile_options(${SANITIZER_COMPILE_OPTIONS_SELECTED}) 329 | add_link_options(${SANITIZER_SELECTED_LINK_OPTIONS}) 330 | else() 331 | message(FATAL_ERROR "Selected sanitizer options not compatible: ${ARGN}") 332 | endif() 333 | endfunction() 334 | 335 | if(SANITIZER_ENABLE_LEGACY_SUPPORT OR USE_SANITIZER) 336 | set(SANITIZER_LEGACY_SUPPORT ON) 337 | 338 | # The older variants used to add this flag, but since MSVC doesn't support it, 339 | # do a check and add it only if available 340 | set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET}) 341 | set(CMAKE_REQUIRED_QUIET TRUE) 342 | unset(SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS) 343 | check_cxx_compiler_flag(-fno-omit-frame-pointer 344 | SANITIZER_OMIT_FRAME_POINTER_AVAILABLE) 345 | set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}") 346 | if(SANITIZER_OMIT_FRAME_POINTER_AVAILABLE) 347 | set(SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS -fno-omit-frame-pointer) 348 | endif() 349 | 350 | set_sanitizer_options(address DEFAULT 351 | ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 352 | set_sanitizer_options(leak DEFAULT ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 353 | set_sanitizer_options(memory DEFAULT 354 | ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 355 | set_sanitizer_options( 356 | memorywithorigins DEFAULT SANITIZER memory 357 | ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS} -fsanitize-memory-track-origins) 358 | set_sanitizer_options(undefined DEFAULT 359 | ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 360 | set_sanitizer_options(thread DEFAULT 361 | ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 362 | set_sanitizer_options(cfi DEFAULT ${SANITIZER_LEGACY_DEFAULT_COMMON_OPTIONS}) 363 | 364 | set(USE_SANITIZER 365 | "" 366 | CACHE 367 | STRING 368 | "(DEPRECATED) Compile with sanitizers. Available sanitizers are: ${SANITIZERS_AVAILABLE_LIST}" 369 | ) 370 | 371 | if(USE_SANITIZER) 372 | add_sanitizer_support(${USE_SANITIZER}) 373 | endif() 374 | 375 | unset(SANITIZER_LEGACY_SUPPORT) 376 | endif() 377 | -------------------------------------------------------------------------------- /tools.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018-2023 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | # CLANG-TIDY 17 | find_program(CLANG_TIDY_EXE NAMES "clang-tidy") 18 | set(CLANG_TIDY_MESSAGE_OUTPUT # Control output messages to occur only once 19 | FALSE 20 | CACHE INTERNAL FALSE) 21 | mark_as_advanced(FORCE CLANG_TIDY_EXE CMAKE_C_CLANG_TIDY CMAKE_CXX_CLANG_TIDY) 22 | 23 | # Adds clang-tidy to code compiled after this macro. All arguments are added to 24 | # the clang-tidy application call in the form of `clang-tidy ${ARGN}`. 25 | # 26 | # If the clang-tidy application is not found, the macro will cause CMake to 27 | # produce an error and not generate. 28 | # 29 | # Options provided can be changed by calling the macro again with the new 30 | # arguments. 31 | macro(clang_tidy) 32 | # Only want to output whether clang-tidy was found once 33 | if(NOT CLANG_TIDY_MESSAGE_OUTPUT) 34 | set(CLANG_TIDY_MESSAGE_OUTPUT TRUE) 35 | if(CLANG_TIDY_EXE) 36 | message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") 37 | else() 38 | message(SEND_ERROR "clang-tidy not found!") 39 | endif() 40 | endif() 41 | 42 | # Only pass the options if the tool was found 43 | if(CLANG_TIDY_EXE) 44 | set(CMAKE_C_CLANG_TIDY 45 | ${CLANG_TIDY_EXE} ${ARGN} 46 | CACHE STRING "" FORCE) 47 | set(CMAKE_CXX_CLANG_TIDY 48 | ${CLANG_TIDY_EXE} ${ARGN} 49 | CACHE STRING "" FORCE) 50 | endif() 51 | endmacro() 52 | 53 | # Clears clang-tidy so it is not called on any following defined code 54 | # compilation. clang-tidy can be re-enabled by another call to `clang_tidy()`. 55 | macro(reset_clang_tidy) 56 | set(CMAKE_C_CLANG_TIDY 57 | "" 58 | CACHE STRING "" FORCE) 59 | set(CMAKE_CXX_CLANG_TIDY 60 | "" 61 | CACHE STRING "" FORCE) 62 | endmacro() 63 | 64 | # INCLUDE-WHAT-YOU-USE 65 | find_program(IWYU_EXE NAMES "include-what-you-use") 66 | set(IWYU_MESSAGE_OUTPUT # Control output messages to occur only once 67 | FALSE 68 | CACHE INTERNAL FALSE) 69 | mark_as_advanced(FORCE IWYU_EXE CMAKE_C_INCLUDE_WHAT_YOU_USE 70 | CMAKE_CXX_INCLUDE_WHAT_YOU_USE) 71 | 72 | # Adds include-what-you-use to code compiled after this macro. All arguments are 73 | # added to the include-what-you-use application call in the form of 74 | # `include-what-you-use ${ARGN}`. 75 | # 76 | # If the include-what-you-use application is not found, the macro will cause 77 | # CMake to produce an error and not generate. 78 | # 79 | # Options provided can be changed by calling the macro again with the new 80 | # arguments. 81 | macro(include_what_you_use) 82 | # Only want to output whether clang-tidy was found once 83 | if(NOT IWYU_MESSAGE_OUTPUT) 84 | set(IWYU_MESSAGE_OUTPUT TRUE) 85 | if(IWYU_EXE) 86 | message(STATUS "include-what-you-use found: ${IWYU_EXE}") 87 | else() 88 | message(SEND_ERROR "include-what-you-use not found!") 89 | endif() 90 | endif() 91 | 92 | # Only pass the options if the tool was found 93 | if(IWYU_EXE) 94 | set(CMAKE_C_INCLUDE_WHAT_YOU_USE 95 | ${IWYU_EXE} ${ARGN} 96 | CACHE STRING "" FORCE) 97 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE 98 | ${IWYU_EXE} ${ARGN} 99 | CACHE STRING "" FORCE) 100 | endif() 101 | endmacro() 102 | 103 | # Clears include-what-you-use so it is not called on any following defined code 104 | # compilation. It can be re-enabled by another call to `include_what_you_use()`. 105 | macro(reset_include_what_you_use) 106 | set(CMAKE_C_INCLUDE_WHAT_YOU_USE 107 | "" 108 | CACHE STRING "" FORCE) 109 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE 110 | "" 111 | CACHE STRING "" FORCE) 112 | endmacro() 113 | 114 | # CPPCHECK 115 | find_program(CPPCHECK_EXE NAMES "cppcheck") 116 | set(CPPCHECK_MESSAGE_OUTPUT # Control output messages to occur only once 117 | FALSE 118 | CACHE INTERNAL FALSE) 119 | mark_as_advanced(FORCE CPPCHECK_EXE CMAKE_C_CPPCHECK CMAKE_CXX_CPPCHECK) 120 | 121 | # Adds cppcheck to code compiled after this macro. All arguments are added to 122 | # the cppcheck application call in the form of `cppcheck ${ARGN}`. 123 | # 124 | # If the include-what-you-use application is not found, the macro will cause 125 | # CMake to produce an error and not generate. 126 | # 127 | # Options provided can be changed by calling the macro again with the new 128 | # arguments. 129 | macro(cppcheck) 130 | # Only want to output whether clang-tidy was found once 131 | if(NOT CPPCHECK_MESSAGE_OUTPUT) 132 | set(CPPCHECK_MESSAGE_OUTPUT TRUE) 133 | if(CPPCHECK_EXE) 134 | message(STATUS "cppcheck found: ${CPPCHECK_EXE}") 135 | else() 136 | message(SEND_ERROR "cppcheck not found!") 137 | endif() 138 | endif() 139 | 140 | # Only pass the options if the tool was found 141 | if(CPPCHECK_EXE) 142 | set(CMAKE_C_CPPCHECK 143 | ${CPPCHECK_EXE} ${ARGN} 144 | CACHE STRING "" FORCE) 145 | set(CMAKE_CXX_CPPCHECK 146 | ${CPPCHECK_EXE} ${ARGN} 147 | CACHE STRING "" FORCE) 148 | endif() 149 | endmacro() 150 | 151 | # Clears include-what-you-use so it is not called on any following defined code 152 | # compilation. It can be re-enabled by another call to `cppcheck()`. 153 | macro(reset_cppcheck) 154 | set(CMAKE_C_CPPCHECK 155 | "" 156 | CACHE STRING "" FORCE) 157 | set(CMAKE_CXX_CPPCHECK 158 | "" 159 | CACHE STRING "" FORCE) 160 | endmacro() 161 | --------------------------------------------------------------------------------