diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ffd5aa9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,98 @@ +AccessModifierOffset: -2 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: true + SplitEmptyRecord: true +BreakAfterJavaFieldAnnotations: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: true +ForEachMacros: +- foreach +- Q_FOREACH +- BOOST_FOREACH +IncludeCategories: +- Priority: 2 + Regex: ^"(llvm|llvm-c|clang|clang-c)/ +- Priority: 3 + Regex: ^(<|"(gtest|gmock|isl|json)/) +- Priority: 1 + Regex: .* +IncludeIsMainRegex: (Test)?$ +IndentCaseLabels: false +IndentWidth: 2 +IndentWrappedFunctionNames: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +Language: Cpp +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +ObjCBlockIndentWidth: 7 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 0 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++20 +TabWidth: 8 +UseTab: Never + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..cdfaf8b --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,30 @@ +--- +Checks: "*, + -abseil-*, + -altera-*, + -android-*, + -fuchsia-*, + -google-*, + -llvm*, + -modernize-use-trailing-return-type, + -zircon-*, + -readability-else-after-return, + -readability-static-accessed-through-instance, + -readability-avoid-const-params-in-decls, + -cppcoreguidelines-non-private-member-variables-in-classes, + -misc-non-private-member-variables-in-classes, +" +WarningsAsErrors: '' +HeaderFilterRegex: '' +FormatStyle: none + +CheckOptions: + - key: readability-identifier-length.IgnoredVariableNames + value: 'x|y|z' + - key: readability-identifier-length.IgnoredParameterNames + value: 'x|y|z' + + + + + diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 0000000..33bfdae --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,21 @@ +additional_commands: + foo: + flags: + - BAR + - BAZ + kwargs: + DEPENDS: '*' + HEADERS: '*' + SOURCES: '*' +bullet_char: '*' +dangle_parens: false +enum_char: . +line_ending: unix +line_width: 120 +max_pargs_hwrap: 3 +separate_ctrl_name_with_space: false +separate_fn_name_with_space: false +tab_size: 2 + +markup: + enable_markup: false diff --git a/.devcontainer/.dockerignore b/.devcontainer/.dockerignore new file mode 100644 index 0000000..de40747 --- /dev/null +++ b/.devcontainer/.dockerignore @@ -0,0 +1,34 @@ +# Build directories and binary files +build/ +out/ +cmake-build-*/ + +# User spesific settings +CMakeUserPresets.json + +# IDE files +.vs/ +.idea/ +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.swp +*~ +_ReSharper* +*.log + +# OS Generated Files +.DS_Store +.AppleDouble +.LSOverride +._* +.Spotlight-V100 +.Trashes +.Trash-* +$RECYCLE.BIN/ +.TemporaryItems +ehthumbs.db +Thumbs.db +Dockerfile \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..e948180 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,120 @@ +# [Choice] bionic (18.04), focal (20.04) +ARG VARIANT="focal" +FROM ubuntu:${VARIANT} + +# Restate the variant to use it later on in the llvm and cmake installations +ARG VARIANT + +# Install necessary packages available from standard repos +RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends \ + software-properties-common wget apt-utils file zip \ + openssh-client gpg-agent socat rsync \ + make ninja-build git \ + python3 python3-pip + +# Install conan +RUN python3 -m pip install --upgrade pip setuptools && \ + python3 -m pip install conan && \ + conan --version + +# By default, anything you run in Docker is done as superuser. +# Conan runs some install commands as superuser, and will prepend `sudo` to +# these commands, unless `CONAN_SYSREQUIRES_SUDO=0` is in your env variables. +ENV CONAN_SYSREQUIRES_SUDO 0 +# Some packages request that Conan use the system package manager to install +# a few dependencies. This flag allows Conan to proceed with these installations; +# leaving this flag undefined can cause some installation failures. +ENV CONAN_SYSREQUIRES_MODE enabled + +# User-settable versions: +# This Dockerfile should support gcc-[7, 8, 9, 10, 11] and clang-[10, 11, 12, 13] +# Earlier versions of clang will require significant modifications to the IWYU section +ARG GCC_VER="11" +# Add gcc-${GCC_VER} +RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends \ + gcc-${GCC_VER} g++-${GCC_VER} gdb + +# Set gcc-${GCC_VER} as default gcc +RUN update-alternatives --install /usr/bin/gcc gcc $(which gcc-${GCC_VER}) 100 +RUN update-alternatives --install /usr/bin/g++ g++ $(which g++-${GCC_VER}) 100 + +ARG LLVM_VER="13" +# Add clang-${LLVM_VER} +ARG LLVM_URL="http://apt.llvm.org/${VARIANT}/" +ARG LLVM_PKG="llvm-toolchain-${VARIANT}-${LLVM_VER}" +RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - 2>/dev/null && \ + add-apt-repository -y "deb ${LLVM_URL} ${LLVM_PKG} main" && \ + apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends \ + clang-${LLVM_VER} lldb-${LLVM_VER} lld-${LLVM_VER} clangd-${LLVM_VER} \ + llvm-${LLVM_VER}-dev libclang-${LLVM_VER}-dev clang-tidy-${LLVM_VER} + +# Set the default clang-tidy, so CMake can find it +RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy $(which clang-tidy-${LLVM_VER}) 1 + +# Set clang-${LLVM_VER} as default clang +RUN update-alternatives --install /usr/bin/clang clang $(which clang-${LLVM_VER}) 100 +RUN update-alternatives --install /usr/bin/clang++ clang++ $(which clang++-${LLVM_VER}) 100 + +# Add current cmake/ccmake, from Kitware +ARG CMAKE_URL="https://apt.kitware.com/ubuntu/" +ARG CMAKE_PKG=${VARIANT} +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \ + | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null && \ + apt-add-repository -y "deb ${CMAKE_URL} ${CMAKE_PKG} main" && \ + apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends cmake cmake-curses-gui + +# Install editors +RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends \ + neovim emacs nano + +# Install optional dependecies +RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ + apt-get install -y --no-install-recommends \ + doxygen graphviz ccache cppcheck + +# Install include-what-you-use +ENV IWYU /home/iwyu +ENV IWYU_BUILD ${IWYU}/build +ENV IWYU_SRC ${IWYU}/include-what-you-use +RUN mkdir -p ${IWYU_BUILD} && \ + git clone --branch clang_${LLVM_VER} \ + https://github.com/include-what-you-use/include-what-you-use.git \ + ${IWYU_SRC} +RUN CC=clang-${LLVM_VER} CXX=clang++-${LLVM_VER} cmake -S ${IWYU_SRC} \ + -B ${IWYU_BUILD} \ + -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=/usr/lib/llvm-${LLVM_VER} && \ + cmake --build ${IWYU_BUILD} -j && \ + cmake --install ${IWYU_BUILD} + +# Per https://github.com/include-what-you-use/include-what-you-use#how-to-install: +# `You need to copy the Clang include directory to the expected location before +# running (similarly, use include-what-you-use -print-resource-dir to learn +# exactly where IWYU wants the headers).` +RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) +RUN ln -s $(readlink -f /usr/lib/clang/${LLVM_VER}/include) \ + $(include-what-you-use -print-resource-dir 2>/dev/null)/include + +## Cleanup cached apt data we don't need anymore +RUN apt-get autoremove -y && apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Allow the user to set compiler defaults +ARG USE_CLANG +# if --build-arg USE_CLANG=1, set CC to 'clang' or set to null otherwise. +ENV CC=${USE_CLANG:+"clang"} +ENV CXX=${USE_CLANG:+"clang++"} +# if CC is null, set it to 'gcc' (or leave as is otherwise). +ENV CC=${CC:-"gcc"} +ENV CXX=${CXX:-"g++"} + +# Include project +#ADD . /workspaces/cpp_starter_project +#WORKDIR /workspaces/cpp_starter_project + +CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..8b79bed --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,52 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/cpp +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick an Ubuntu OS version. Options: [bionic, focal]. Default: focal + // Update 'GCC_VER' to pick a gcc and g++ version. Options: [7, 8, 9, 10, 11]. Default: 11 + // Update 'LLVM_VER' to pick clang version. Options: [10, 11, 12, 13]. Default: 13 + // Update 'USE_CLANG' to set clang as the default C and C++ compiler. Options: [1, null]. Default null + // "args": { + // "VARIANT": "focal", + // "GCC_VER": "11", + // "LLVM_VER": "13" + // } + }, + "runArgs": [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined" + ], + // Set *default* container specific settings.json values on container create. + "settings": { + "cmake.configureOnOpen": true, + "editor.formatOnSave": true + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "twxs.cmake", + "ms-vscode.cpptools-themes", + "cschlosser.doxdocgen", + "eamodio.gitlens", + "ms-python.python", + "ms-python.vscode-pylance", + "mutantdino.resourcemonitor" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + //"postCreateCommand": "uname -a", + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + //"remoteUser": "vscode", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/${localWorkspaceFolderBasename},type=bind,consistency=delegated", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "git": "latest", + "git-lfs": "latest", + "powershell": "latest" + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..23eae36 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +############################### +# Git Line Endings # +############################### + +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf +*.{vcxproj,vcxproj.filters} text eol=crlf + +############################### +# Git Large File System (LFS) # +############################### + +# Archives +#*.7z filter=lfs diff=lfs merge=lfs -text +#*.br filter=lfs diff=lfs merge=lfs -text +#*.gz filter=lfs diff=lfs merge=lfs -text +#*.tar filter=lfs diff=lfs merge=lfs -text +#*.zip filter=lfs diff=lfs merge=lfs -text + +# Documents +#*.pdf filter=lfs diff=lfs merge=lfs -text + +# Images +#*.gif filter=lfs diff=lfs merge=lfs -text +#*.ico filter=lfs diff=lfs merge=lfs -text +#*.jpg filter=lfs diff=lfs merge=lfs -text +#*.pdf filter=lfs diff=lfs merge=lfs -text +#*.png filter=lfs diff=lfs merge=lfs -text +#*.psd filter=lfs diff=lfs merge=lfs -text +#*.webp filter=lfs diff=lfs merge=lfs -text + +# Fonts +#*.woff2 filter=lfs diff=lfs merge=lfs -text + +# Other +#*.exe filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c7270da --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: lefticus +patreon: lefticus diff --git a/.github/actions/setup_cache/action.yml b/.github/actions/setup_cache/action.yml new file mode 100644 index 0000000..7bf260e --- /dev/null +++ b/.github/actions/setup_cache/action.yml @@ -0,0 +1,32 @@ + +name: 'setup_cache' +description: 'sets up the shared cache' +inputs: + compiler: + required: true + type: string + build_type: + required: true + type: string + generator: + required: true + type: string + packaging_maintainer_mode: + required: true + type: string + + +runs: + using: "composite" + steps: + - name: Cache + uses: actions/cache@v3 + with: + # You might want to add .ccache to your cache configuration? + path: | + ~/.cache/pip + ~/.ccache + key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.packaging_maintainer_mode }}-${{ hashFiles('**/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }} + diff --git a/.github/constants.env b/.github/constants.env new file mode 100644 index 0000000..5c8528a --- /dev/null +++ b/.github/constants.env @@ -0,0 +1 @@ +PROJECT_NAME=myproject diff --git a/.github/template/README.md b/.github/template/README.md new file mode 100644 index 0000000..c7b5b7e --- /dev/null +++ b/.github/template/README.md @@ -0,0 +1,16 @@ +# %%myproject%% + +[![ci](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/%%myorg%%/%%myproject%%/branch/main/graph/badge.svg)](https://codecov.io/gh/%%myorg%%/%%myproject%%) +[![CodeQL](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml) + +## About %%myproject%% +%%description%% + + +## More Details + + * [Dependency Setup](README_dependencies.md) + * [Building Details](README_building.md) + * [Troubleshooting](README_troubleshooting.md) + * [Docker](README_docker.md) diff --git a/.github/template/removal-list b/.github/template/removal-list new file mode 100644 index 0000000..4f63e51 --- /dev/null +++ b/.github/template/removal-list @@ -0,0 +1,2 @@ +LICENSE +.github/FUNDING.yml \ No newline at end of file diff --git a/.github/template/template_name b/.github/template/template_name new file mode 100644 index 0000000..eb23e25 --- /dev/null +++ b/.github/template/template_name @@ -0,0 +1 @@ +cmake_template diff --git a/.github/template/template_repository b/.github/template/template_repository new file mode 100644 index 0000000..5494e8a --- /dev/null +++ b/.github/template/template_repository @@ -0,0 +1 @@ +cpp-best-practices/cmake_template diff --git a/.github/workflows/auto-clang-format.yml b/.github/workflows/auto-clang-format.yml new file mode 100644 index 0000000..b4e0a1b --- /dev/null +++ b/.github/workflows/auto-clang-format.yml @@ -0,0 +1,23 @@ +name: auto-clang-format +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.13 + with: + source: '.' + exclude: './third_party ./external' + extensions: 'h,cpp,hpp' + clangFormatVersion: 12 + inplace: True + - uses: EndBug/add-and-commit@v4 + with: + author_name: Clang Robot + author_email: robot@example.com + message: ':art: Committing clang-format changes' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..db6c611 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,205 @@ +name: ci +on: + pull_request: + release: + types: [published] + push: + tags: + branches: + - main + - develop + +env: + CLANG_TIDY_VERSION: "15.0.2" + VERBOSE: 1 + + +jobs: + Test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + + # Recommendations: + # * support at least 2 operating systems + # * support at least 2 compilers + # * make sure all supported configurations for your project are built + # + # Disable/enable builds in this list to meet the above recommendations + # and your own projects needs + matrix: + os: + - ubuntu-20.04 + - macos-10.15 + - windows-2019 + compiler: + # you can specify the version after `-` like "llvm-15.0.2". + - llvm-15.0.2 + - gcc-11 + generator: + - "Ninja Multi-Config" + build_type: + - Release + - Debug + packaging_maintainer_mode: + - ON + - OFF + build_shared: + - OFF + + exclude: + # mingw is determined by this author to be too buggy to support + - os: windows-2019 + compiler: gcc-11 + + include: + # Add appropriate variables for gcov version required. This will intentionally break + # if you try to use a compiler that does not have gcov set + - compiler: gcc-11 + gcov_executable: gcov + enable_ipo: On + + - compiler: llvm-15.0.2 + enable_ipo: Off + gcov_executable: "llvm-cov gcov" + + - os: macos-10.15 + enable_ipo: Off + + # Set up preferred package generators, for given build configurations + - build_type: Release + packaging_maintainer_mode: OFF + package_generator: TBZ2 + + # This exists solely to make sure a non-multiconfig build works + - os: ubuntu-20.04 + compiler: gcc-11 + generator: "Unix Makefiles" + build_type: Debug + gcov_executable: gcov + packaging_maintainer_mode: On + enable_ipo: Off + + # Windows msvc builds + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Debug + packaging_maintainer_mode: On + enable_ipo: On + + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: On + enable_ipo: On + + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Debug + packaging_maintainer_mode: Off + + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: Off + package_generator: ZIP + + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: On + enable_ipo: On + build_shared: On + + + steps: + - name: Check for llvm version mismatches + if: ${{ contains(matrix.compiler, 'llvm') && !contains(matrix.compiler, env.CLANG_TIDY_VERSION) }} + uses: actions/github-script@v3 + with: + script: | + core.setFailed('There is a mismatch between configured llvm compiler and clang-tidy version chosen') + + - uses: actions/checkout@v3 + + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} + generator: ${{ matrix.generator }} + + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows' )}} + + cmake: true + ninja: true + vcpkg: false + ccache: true + clangtidy: ${{ env.CLANG_TIDY_VERSION }} + + + cppcheck: true + + gcovr: true + opencppcoverage: true + + - name: Configure CMake + run: | + cmake -S . -B ./build -G "${{matrix.generator}}" -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} + + - name: Build + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build ./build --config ${{matrix.build_type}} + + - name: Unix - Test and coverage + if: runner.os != 'Windows' + working-directory: ./build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: | + ctest -C ${{matrix.build_type}} + gcovr -j ${{env.nproc}} --delete --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' + + - name: Windows - Test and coverage + if: runner.os == 'Windows' + working-directory: ./build + run: | + OpenCppCoverage.exe --export_type cobertura:coverage.xml --cover_children -- ctest -C ${{matrix.build_type}} + + - name: CPack + if: matrix.package_generator != '' + working-directory: ./build + run: | + cpack -C ${{matrix.build_type}} -G ${{matrix.package_generator}} + + - name: Publish Tagged Release + uses: softprops/action-gh-release@v1 + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.package_generator != '' }} + with: + files: | + build/*-*${{ matrix.build_type }}*-*.* + + + - name: Publish to codecov + uses: codecov/codecov-action@v2 + with: + flags: ${{ runner.os }} + name: ${{ runner.os }}-coverage + files: ./build/coverage.xml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..e21dc29 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,107 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main, develop ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main, develop ] + schedule: + - cron: '38 0 * * 5' + + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + compiler: + # you can specify the version after `-` like "llvm-13.0.0". + - gcc-11 + generator: + - "Ninja Multi-Config" + build_type: + - Debug + packaging_maintainer_mode: + - ON + + + steps: + - uses: actions/checkout@v3 + + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} + generator: ${{ matrix.generator }} + + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows' )}} + + cmake: true + ninja: true + vcpkg: false + ccache: true + clangtidy: false + + cppcheck: false + + gcovr: false + opencppcoverage: false + + # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage + # has meaningful results + - name: Configure CMake + run: | + cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + + - name: Build + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build ./build --config ${{matrix.build_type}} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/template-janitor.yml b/.github/workflows/template-janitor.yml new file mode 100644 index 0000000..bc56ba0 --- /dev/null +++ b/.github/workflows/template-janitor.yml @@ -0,0 +1,247 @@ +# This workflow should cleanup everything unneeded from the template project + +name: Template Janitor + +on: + pull_request: + release: + types: [published] + push: + branches: + - main + - develop +permissions: + contents: write + +env: + TEMPLATES_PATH: ".github/template" + + +jobs: + + template-cleanup: + name: Cleanup after create + runs-on: ubuntu-latest + strategy: + matrix: + compiler: + - gcc-11 + generator: + - "Unix Makefiles" + build_type: + - Debug + developer_mode: + - OFF + + steps: + - uses: actions/checkout@v3 + + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + developer_mode: ${{ matrix.developer_mode }} + generator: ${{ matrix.generator }} + + - name: Get organization and project name + run: | + echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV + echo "NEW_PROJECT=${{ github.event.repository.name }}" >> $GITHUB_ENV + echo "NEW_URL=${{ github.repositoryUrl }}" >> $GITHUB_ENV + + - uses: octokit/request-action@v2.x + id: get_repo_meta + with: + route: GET /repos/{owner}/{repo} + owner: ${{ env.NEW_ORG }} + repo: ${{ env.NEW_PROJECT }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Use testing variables if still a template + if: fromJson(steps.get_repo_meta.outputs.data).is_template == true + run: | + # This name is unsafe because it is not a valid C++ identifier + echo "NEW_PROJECT=my-unsafe.project" >> $GITHUB_ENV + + - name: Add safe replacement variable versions + run: | + # hyphens and dots in c++ identifiers are forbidden. Use underscores instead. + NEW_SAFE_PROJECT=$(echo ${{ env.NEW_PROJECT }} | sed "s/-/_/g" | sed "s/\./_/g" ) + echo "NEW_SAFE_PROJECT=$NEW_SAFE_PROJECT" >> $GITHUB_ENV + + # Rename all cpp_starter_project occurences to current repository and remove this workflow + - name: Insert new org and project + run: | + # rename the CMake project to match the github project + find src include test fuzz_test cmake -type f -exec sed -i "s/myproject/${{ env.NEW_SAFE_PROJECT }}/gi" .github/constants.env CMakeLists.txt Dependencies.cmake ProjectOptions.cmake .github/workflows/ci.yml .github/workflows/codeql-analysis.yml configured_files/config.hpp.in {} + + + # Update URL placeholders for project + sed -i "s|%%myurl%%|${{ fromJson(steps.get_repo_meta.outputs.data).html_url }}|gi" CMakeLists.txt + + # fill in placeholders of readme and move it into place + sed -i "s/%%myorg%%/${{ env.NEW_ORG }}/g" ${{ env.TEMPLATES_PATH }}/README.md + sed -i "s/%%myproject%%/${{ env.NEW_PROJECT }}/g" ${{ env.TEMPLATES_PATH }}/README.md + sed -i "s|%%description%%|${{ fromJson(steps.get_repo_meta.outputs.data).description }}|g" ${{ env.TEMPLATES_PATH }}/README.md + mv include/myproject include/${{ env.NEW_SAFE_PROJECT }} + cp ${{ env.TEMPLATES_PATH }}/README.md README.md + + - name: Print diff after replacement + run: | + # Exclude the README as that is checked separately! + git diff ':!README.md' + # following should not have any diffs + diff ${{ env.TEMPLATES_PATH }}/README.md README.md + + - name: Remove unwanted files + run: | + # No tests needed as this will fail if any file from the list is missing/misspelled + xargs rm -r < ${{ env.TEMPLATES_PATH }}/removal-list + + - name: Clean up before commit and push + run: | + rm -r ${{ env.TEMPLATES_PATH }} + + # Can we get that from a variable? + # Remove this workflow as it has fulfilled its purpose + rm .github/workflows/template-janitor.yml + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + + cmake: true + ninja: false + vcpkg: false + ccache: false + clangtidy: false + + cppcheck: false + + gcovr: false + opencppcoverage: false + + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + + + - name: Test simple configuration to make sure nothing broke + run: | + cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.NEW_SAFE_PROJECT }}_PACKAGING_MAINTAINER_MODE:BOOL=ON + # Build it because we may have broken something in the cpp/hpp files + cmake --build build + + - uses: EndBug/add-and-commit@v9 + # only commit and push if we are not a template project anymore! + if: fromJson(steps.get_repo_meta.outputs.data).is_template != true + with: + add: -A + author_name: Template Janitor + author_email: template.janitor@example.com + message: 'Cleanup template and initialize repository' + pathspec_error_handling: exitImmediately + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + + template-rename: + name: Renames template when a new name is detected + runs-on: ubuntu-latest + strategy: + matrix: + compiler: + - gcc-11 + generator: + - "Unix Makefiles" + build_type: + - Debug + developer_mode: + - OFF + + steps: + - uses: actions/checkout@v3 + + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + developer_mode: ${{ matrix.developer_mode }} + generator: ${{ matrix.generator }} + + - name: Get organization and project name + run: | + echo "TEST_RUN=false" >> $GITHUB_ENV + echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV + echo "NEW_PROJECT=${{ github.event.repository.name }}" >> $GITHUB_ENV + echo "NEW_REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV + echo "TEMPLATE_NAME=`cat ${{ env.TEMPLATES_PATH }}/template_name`" >> $GITHUB_ENV + echo "TEMPLATE_REPOSITORY=`cat ${{ env.TEMPLATES_PATH }}/template_repository`" >> $GITHUB_ENV + + - uses: octokit/request-action@v2.x + id: get_repo_meta + with: + route: GET /repos/{owner}/{repo} + owner: ${{ env.NEW_ORG }} + repo: ${{ env.NEW_PROJECT }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup fake test org/project names if project didn't change + if: env.TEMPLATE_NAME == env.NEW_PROJECT + run: | + echo "TEST_RUN=true" >> $GITHUB_ENV + echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV + echo "NEW_PROJECT=TEST_PROJECT" >> $GITHUB_ENV + echo "NEW_REPOSITORY=TEST_REPOSITORY" >> $GITHUB_ENV + + + # Rename all cpp_starter_project occurrences to current repository and remove this workflow + - name: Update repository to match new template information + run: | + # Update the README and template files to match the new org / repository names + sed -i "s|${{ env.TEMPLATE_REPOSITORY }}|${{ env.NEW_REPOSITORY }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_repository + sed -i "s|${{ env.TEMPLATE_NAME }}|${{ env.NEW_PROJECT }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_name + + - name: Print diff after template name replacement + run: | + git diff + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: gcc + + cmake: true + ninja: false + vcpkg: false + ccache: false + clangtidy: false + + cppcheck: false + + gcovr: false + opencppcoverage: false + + + - name: Test simple configuration to make sure nothing broke (default compiler,cmake,developer_mode OFF) + run: | + cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=ON + + - uses: EndBug/add-and-commit@v9 + # only commit and push if we are a template and project name has changed + if: fromJson(steps.get_repo_meta.outputs.data).is_template == true && env.TEST_RUN == 'false' + with: + add: -A + author_name: Template Janitor + author_email: template.janitor@example.com + message: 'Change Template Name' + pathspec_error_handling: exitImmediately + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3f1df0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Build directories and binary files +build/ +out/ +cmake-build-*/ +conan-cache/ + +# User spesific settings +CMakeUserPresets.json + +# IDE files +.vs/ +.idea/ +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.swp +*~ +_ReSharper* +*.log + +# OS Generated Files +.DS_Store +.AppleDouble +.LSOverride +._* +.Spotlight-V100 +.Trashes +.Trash-* +$RECYCLE.BIN/ +.TemporaryItems +ehthumbs.db +Thumbs.db diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..727a39a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,49 @@ +image: ubuntu:latest + +stages: + - test + +.setup_linux: &setup_linux | + DEBIAN_FRONTEND=noninteractive + + # set time-zone + TZ=Canada/Pacific + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + # for downloading + apt-get update -qq + apt-get install -y --no-install-recommends curl gnupg ca-certificates + + # keys used by apt + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32 + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F + +.setup_cpp: &setup_cpp | + curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.10.0/setup_cpp_linux" + chmod +x setup_cpp_linux + ./setup_cpp_linux --compiler $compiler --cmake true --ninja true --conan true --ccache true --clangtidy true --clangformat true --cppcheck true + source ~/.profile + +.test: &test | + # Build and Test + cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo + cmake --build ./build --config RelWithDebInfo + +test_linux_llvm: + stage: test + variables: + compiler: llvm + script: + - *setup_linux + - *setup_cpp + - *test + +test_linux_gcc: + stage: test + variables: + compiler: gcc + script: + - *setup_linux + - *setup_cpp + - *test diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..45e19f0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,121 @@ +cmake_minimum_required(VERSION 3.21) + +# This template attempts to be "fetch_content"-able +# so that it works well with tools like CPM or other +# manual dependency management + +# Only set the cxx_standard if it is not set by someone else +if (NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +# strongly encouraged to enable this globally to avoid conflicts between +# -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example +# when compiling with PCH enabled +set(CMAKE_CXX_EXTENSIONS OFF) + +# Set the project name and language +project( + myproject + VERSION 0.0.1 + DESCRIPTION "" + HOMEPAGE_URL "%%myurl%%" + LANGUAGES CXX C) + +include(cmake/PreventInSourceBuilds.cmake) +include(ProjectOptions.cmake) + + +myproject_setup_options() + +myproject_global_options() +include(Dependencies.cmake) +myproject_setup_dependencies() + +myproject_local_options() + +# don't know if this should be set globally from here or not... +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +set(GIT_SHA + "Unknown" + CACHE STRING "SHA this build was generated from") +string( + SUBSTRING "${GIT_SHA}" + 0 + 8 + GIT_SHORT_SHA) + +target_compile_features(myproject_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) + +add_library(myproject::myproject_options ALIAS myproject_options) +add_library(myproject::myproject_warnings ALIAS myproject_warnings) + +#add_library(myproject::myproject_options INTERFACE IMPORTED) +#add_library(myproject::myproject_warnings INTERFACE IMPORTED) + +# configure files based on CMake configuration options +add_subdirectory(configured_files) + +# Adding the src: +add_subdirectory(src) + +# Don't even look at tests if we're not top level +if(NOT PROJECT_IS_TOP_LEVEL) + return() +endif() + +# Adding the tests: +include(CTest) + +if(BUILD_TESTING) + message(AUTHOR_WARNING "Building Tests. Be sure to check out test/constexpr_tests.cpp for constexpr testing") + add_subdirectory(test) +endif() + + +if(myproject_BUILD_FUZZ_TESTS) + message(AUTHOR_WARNING "Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html") + if (NOT myproject_ENABLE_ADDRESS_SANITIZER AND NOT myproject_ENABLE_THREAD_SANITIZER) + message(WARNING "You need asan or tsan enabled for meaningful fuzz testing") + endif() + add_subdirectory(fuzz_test) + +endif() + +# If MSVC is being used, and ASAN is enabled, we need to set the debugger environment +# so that it behaves well with MSVC's debugger, and we can run the target from visual studio +if(MSVC) + get_all_installable_targets(all_targets) + message("all_targets=${all_targets}") + set_target_properties(${all_targets} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$(VC_ExecutablePath_x64);%PATH%") +endif() + +# set the startup project for the "play" button in MSVC +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT intro) + +if(CMAKE_SKIP_INSTALL_RULES) + return() +endif() + +include(cmake/PackageProject.cmake) + +# Add other targets that you want installed here, by default we just package the one executable +# we know we want to ship +myproject_package_project( + TARGETS + intro + myproject_options + myproject_warnings + # FIXME: this does not work! CK + # PRIVATE_DEPENDENCIES_CONFIGURED project_options project_warnings +) + +# Experience shows that explicit package naming can help make it easier to sort +# out potential ABI related issues before they start, while helping you +# track a build to a specific GIT SHA +set(CPACK_PACKAGE_FILE_NAME + "${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}-${GIT_SHORT_SHA}-${CMAKE_SYSTEM_NAME}-${CMAKE_BUILD_TYPE}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" +) + +include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..5871489 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,254 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "conf-common", + "description": "General settings that apply to all configurations", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}" + }, + { + "name": "conf-windows-common", + "description": "Windows settings for MSBuild toolchain that apply to msvc and clang", + "hidden": true, + "inherits": "conf-common", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "architecture": { + "value": "x64", + "strategy": "external" + }, + "toolset": { + "value": "host=x64", + "strategy": "external" + }, + "cacheVariables": { + "ENABLE_CPPCHECK_DEFAULT": "FALSE", + "ENABLE_CLANG_TIDY_DEFAULT": "FALSE" + } + }, + { + "name": "conf-unixlike-common", + "description": "Unix-like OS settings for gcc and clang toolchains", + "hidden": true, + "inherits": "conf-common", + "condition": { + "type": "inList", + "string": "${hostSystemName}", + "list": [ + "Linux", + "Darwin" + ] + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "windows-msvc-debug-developer-mode", + "displayName": "msvc Debug (Developer Mode)", + "description": "Target Windows with the msvc compiler, debug build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_DEVELOPER_MODE": "ON" + } + }, + { + "name": "windows-msvc-release-developer-mode", + "displayName": "msvc Release (Developer Mode)", + "description": "Target Windows with the msvc compiler, release build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "ENABLE_DEVELOPER_MODE": "ON" + } + }, + { + "name": "windows-msvc-debug-user-mode", + "displayName": "msvc Debug (User Mode)", + "description": "Target Windows with the msvc compiler, debug build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_DEVELOPER_MODE": "OFF" + } + }, + { + "name": "windows-msvc-release-user-mode", + "displayName": "msvc Release (User Mode)", + "description": "Target Windows with the msvc compiler, release build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "ENABLE_DEVELOPER_MODE": "OFF" + } + }, + { + "name": "windows-clang-debug", + "displayName": "clang Debug", + "description": "Target Windows with the clang compiler, debug build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "Debug" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-clang-x64" + } + } + }, + { + "name": "windows-clang-release", + "displayName": "clang Release", + "description": "Target Windows with the clang compiler, release build type", + "inherits": "conf-windows-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-clang-x64" + } + } + }, + { + "name": "unixlike-gcc-debug", + "displayName": "gcc Debug", + "description": "Target Unix-like OS with the gcc compiler, debug build type", + "inherits": "conf-unixlike-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unixlike-gcc-release", + "displayName": "gcc Release", + "description": "Target Unix-like OS with the gcc compiler, release build type", + "inherits": "conf-unixlike-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "unixlike-clang-debug", + "displayName": "clang Debug", + "description": "Target Unix-like OS with the clang compiler, debug build type", + "inherits": "conf-unixlike-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unixlike-clang-release", + "displayName": "clang Release", + "description": "Target Unix-like OS with the clang compiler, release build type", + "inherits": "conf-unixlike-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + } + ], + "testPresets": [ + { + "name": "test-common", + "description": "Test CMake settings that apply to all configurations", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + }, + { + "name": "test-windows-msvc-debug-developer-mode", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "windows-msvc-debug-developer-mode" + }, + { + "name": "test-windows-msvc-release-developer-mode", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "windows-msvc-release-developer-mode" + }, + { + "name": "test-windows-clang-debug", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "windows-clang-debug" + }, + { + "name": "test-windows-clang-release", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "windows-clang-release" + }, + { + "name": "test-unixlike-gcc-debug", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "unixlike-gcc-debug" + }, + { + "name": "test-unixlike-gcc-release", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "unixlike-gcc-release" + }, + { + "name": "test-unixlike-clang-debug", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "unixlike-clang-debug" + }, + { + "name": "test-unixlike-clang-release", + "displayName": "Strict", + "description": "Enable output and stop on failure", + "inherits": "test-common", + "configurePreset": "unixlike-clang-release" + } + ] +} \ No newline at end of file diff --git a/Dependencies.cmake b/Dependencies.cmake new file mode 100644 index 0000000..a84378d --- /dev/null +++ b/Dependencies.cmake @@ -0,0 +1,43 @@ +include(cmake/CPM.cmake) + +# Done as a function so that updates to variables like +# CMAKE_CXX_FLAGS don't propagate out to other +# targets +function(myproject_setup_dependencies) + + # For each dependency, see if it's + # already been provided to us by a parent project + + if(NOT TARGET fmtlib::fmtlib) + cpmaddpackage("gh:fmtlib/fmt#9.1.0") + endif() + + if(NOT TARGET spdlog::spdlog) + cpmaddpackage( + NAME + spdlog + VERSION + 1.11.0 + GITHUB_REPOSITORY + "gabime/spdlog" + OPTIONS + "SPDLOG_FMT_EXTERNAL ON") + endif() + + if(NOT TARGET Catch2::Catch2WithMain) + cpmaddpackage("gh:catchorg/Catch2@3.3.2") + endif() + + if(NOT TARGET CLI11::CLI11) + cpmaddpackage("gh:CLIUtils/CLI11@2.3.2") + endif() + + if(NOT TARGET ftxui::screen) + cpmaddpackage("gh:ArthurSonzogni/FTXUI#e23dbc7473654024852ede60e2121276c5aab660") + endif() + + if(NOT TARGET tools::tools) + cpmaddpackage("gh:lefticus/tools#update_build_system") + endif() + +endfunction() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake new file mode 100644 index 0000000..2709aa1 --- /dev/null +++ b/ProjectOptions.cmake @@ -0,0 +1,200 @@ +include(cmake/SystemLink.cmake) +include(cmake/LibFuzzer.cmake) +include(CMakeDependentOption) +include(CheckCXXCompilerFlag) + + +macro(myproject_supports_sanitizers) + if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32) + set(SUPPORTS_UBSAN ON) + else() + set(SUPPORTS_UBSAN OFF) + endif() + + if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32) + set(SUPPORTS_ASAN OFF) + else() + set(SUPPORTS_ASAN ON) + endif() +endmacro() + +macro(myproject_setup_options) + option(myproject_ENABLE_HARDENING "Enable hardening" ON) + option(myproject_ENABLE_COVERAGE "Enable coverage reporting" OFF) + cmake_dependent_option( + myproject_ENABLE_GLOBAL_HARDENING + "Attempt to push hardening options to built dependencies" + ON + myproject_ENABLE_HARDENING + OFF) + + myproject_supports_sanitizers() + + if(NOT PROJECT_IS_TOP_LEVEL OR myproject_PACKAGING_MAINTAINER_MODE) + option(myproject_ENABLE_IPO "Enable IPO/LTO" OFF) + option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) + option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) + option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) + option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) + option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) + option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) + option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) + option(myproject_ENABLE_CACHE "Enable ccache" OFF) + else() + option(myproject_ENABLE_IPO "Enable IPO/LTO" ON) + option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" ON) + option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) + option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" ${SUPPORTS_ASAN}) + option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" ${SUPPORTS_UBSAN}) + option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) + option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) + option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) + option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) + option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) + option(myproject_ENABLE_CACHE "Enable ccache" ON) + endif() + + if(NOT PROJECT_IS_TOP_LEVEL) + mark_as_advanced( + myproject_ENABLE_IPO + myproject_WARNINGS_AS_ERRORS + myproject_ENABLE_USER_LINKER + myproject_ENABLE_SANITIZER_ADDRESS + myproject_ENABLE_SANITIZER_LEAK + myproject_ENABLE_SANITIZER_UNDEFINED + myproject_ENABLE_SANITIZER_THREAD + myproject_ENABLE_SANITIZER_MEMORY + myproject_ENABLE_UNITY_BUILD + myproject_ENABLE_CLANG_TIDY + myproject_ENABLE_CPPCHECK + myproject_ENABLE_COVERAGE + myproject_ENABLE_PCH + myproject_ENABLE_CACHE) + endif() + + myproject_check_libfuzzer_support(LIBFUZZER_SUPPORTED) + if(LIBFUZZER_SUPPORTED AND (myproject_ENABLE_SANITIZER_ADDRESS OR myproject_ENABLE_SANITIZER_THREAD OR myproject_ENABLE_SANITIZER_UNDEFINED)) + set(DEFAULT_FUZZER ON) + else() + set(DEFAULT_FUZZER OFF) + endif() + + option(myproject_BUILD_FUZZ_TESTS "Enable fuzz testing executable" ${DEFAULT_FUZZER}) + +endmacro() + +macro(myproject_global_options) + if(myproject_ENABLE_IPO) + include(cmake/InterproceduralOptimization.cmake) + myproject_enable_ipo() + endif() + + myproject_supports_sanitizers() + + if(myproject_ENABLE_HARDENING AND myproject_ENABLE_GLOBAL_HARDENING) + include(cmake/Hardening.cmake) + if(NOT SUPPORTS_UBSAN + OR myproject_ENABLE_SANITIZER_UNDEFINED + OR myproject_ENABLE_SANITIZER_ADDRESS + OR myproject_ENABLE_SANITIZER_THREAD + OR myproject_ENABLE_SANITIZER_LEAK) + set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) + else() + set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) + endif() + message("${myproject_ENABLE_HARDENING} ${ENABLE_UBSAN_MINIMAL_RUNTIME} ${myproject_ENABLE_SANITIZER_UNDEFINED}") + myproject_enable_hardening(myproject_options ON ${ENABLE_UBSAN_MINIMAL_RUNTIME}) + endif() +endmacro() + +macro(myproject_local_options) + if(PROJECT_IS_TOP_LEVEL) + include(cmake/StandardProjectSettings.cmake) + endif() + + add_library(myproject_warnings INTERFACE) + add_library(myproject_options INTERFACE) + + include(cmake/CompilerWarnings.cmake) + myproject_set_project_warnings( + myproject_warnings + ${myproject_WARNINGS_AS_ERRORS} + "" + "" + "" + "") + + if(myproject_ENABLE_USER_LINKER) + include(cmake/Linker.cmake) + configure_linker(myproject_options) + endif() + + include(cmake/Sanitizers.cmake) + myproject_enable_sanitizers( + myproject_options + ${myproject_ENABLE_SANITIZER_ADDRESS} + ${myproject_ENABLE_SANITIZER_LEAK} + ${myproject_ENABLE_SANITIZER_UNDEFINED} + ${myproject_ENABLE_SANITIZER_THREAD} + ${myproject_ENABLE_SANITIZER_MEMORY}) + + set_target_properties(myproject_options PROPERTIES UNITY_BUILD ${myproject_ENABLE_UNITY_BUILD}) + + if(myproject_ENABLE_PCH) + target_precompile_headers( + myproject_options + INTERFACE + + + ) + endif() + + if(myproject_ENABLE_CACHE) + include(cmake/Cache.cmake) + myproject_enable_cache() + endif() + + include(cmake/StaticAnalyzers.cmake) + if(myproject_ENABLE_CLANG_TIDY) + myproject_enable_clang_tidy(myproject_options ${myproject_WARNINGS_AS_ERRORS}) + endif() + + if(myproject_ENABLE_CPPCHECK) + myproject_enable_cppcheck(${myproject_WARNINGS_AS_ERRORS} "" # override cppcheck options + ) + endif() + + if(myproject_ENABLE_COVERAGE) + include(cmake/Tests.cmake) + myproject_enable_coverage(myproject_options) + endif() + + if(myproject_WARNINGS_AS_ERRORS) + check_cxx_compiler_flag("-Wl,--fatal-warnings" LINKER_FATAL_WARNINGS) + if(LINKER_FATAL_WARNINGS) + # This is not working consistently, so disabling for now + # target_link_options(myproject_options INTERFACE -Wl,--fatal-warnings) + endif() + endif() + + if(myproject_ENABLE_HARDENING AND NOT myproject_ENABLE_GLOBAL_HARDENING) + include(cmake/Hardening.cmake) + if(NOT SUPPORTS_UBSAN + OR myproject_ENABLE_SANITIZER_UNDEFINED + OR myproject_ENABLE_SANITIZER_ADDRESS + OR myproject_ENABLE_SANITIZER_THREAD + OR myproject_ENABLE_SANITIZER_LEAK) + set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) + else() + set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) + endif() + myproject_enable_hardening(myproject_options OFF ${ENABLE_UBSAN_MINIMAL_RUNTIME}) + endif() + +endmacro() diff --git a/README.md b/README.md new file mode 100644 index 0000000..09384f9 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# cmake_template + +[![ci](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/cpp-best-practices/cmake_template/branch/main/graph/badge.svg)](https://codecov.io/gh/cpp-best-practices/cmake_template) +[![CodeQL](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml) + +## About cmake_template + +This is a C++ Best Practices GitHub template for getting up and running with C++ quickly. + +By default (collectively known as `ENABLE_DEVELOPER_MODE`) + + * Address Sanitizer and Undefined Behavior Sanitizer enabled where possible + * Warnings as errors + * clang-tidy and cppcheck static analysis + * CPM for dependencies + +It includes + + * a basic CLI example + * examples for fuzz, unit, and constexpr testing + * large github action testing matrix + +It requires + + * cmake + * a compiler + + +This project gets you started with a simple example of using FTXUI, which happens to also be a game. + + +## Getting Started + +### Use the Github template +First, click the green `Use this template` button near the top of this page. +This will take you to Github's ['Generate Repository'](https://github.com/cpp-best-practices/cmake_template/generate) page. +Fill in a repository name and short description, and click 'Create repository from template'. +This will allow you to create a new repository in your Github account, +prepopulated with the contents of this project. + +After creating the project please wait until the cleanup workflow has finished +setting up your project and commited the changes. + +Now you can clone the project locally and get to work! + + git clone https://github.com//.git + +## More Details + + * [Dependency Setup](README_dependencies.md) + * [Building Details](README_building.md) + * [Docker](README_docker.md) + +## Testing + +See [Catch2 tutorial](https://github.com/catchorg/Catch2/blob/master/docs/tutorial.md) + +## Fuzz testing + +See [libFuzzer Tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) + + diff --git a/README_building.md b/README_building.md new file mode 100644 index 0000000..99ceacf --- /dev/null +++ b/README_building.md @@ -0,0 +1,192 @@ +## Build Instructions + +A full build has different steps: +1) Specifying the compiler using environment variables +2) Configuring the project +3) Building the project + +For the subsequent builds, in case you change the source code, you only need to repeat the last step. + +### (1) Specify the compiler using environment variables + +By default (if you don't set environment variables `CC` and `CXX`), the system default compiler will be used. + +CMake uses the environment variables CC and CXX to decide which compiler to use. So to avoid the conflict issues only specify the compilers using these variables. + + +
+Commands for setting the compilers + +- Debian/Ubuntu/MacOS: + + Set your desired compiler (`clang`, `gcc`, etc): + + - Temporarily (only for the current shell) + + Run one of the followings in the terminal: + + - clang + + CC=clang CXX=clang++ + + - gcc + + CC=gcc CXX=g++ + + - Permanent: + + Open `~/.bashrc` using your text editor: + + gedit ~/.bashrc + + Add `CC` and `CXX` to point to the compilers: + + export CC=clang + export CXX=clang++ + + Save and close the file. + +- Windows: + + - Permanent: + + Run one of the followings in PowerShell: + + - Visual Studio generator and compiler (cl) + + [Environment]::SetEnvironmentVariable("CC", "cl.exe", "User") + [Environment]::SetEnvironmentVariable("CXX", "cl.exe", "User") + refreshenv + + Set the architecture using [vcvarsall](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=vs-2019#vcvarsall-syntax): + + vcvarsall.bat x64 + + - clang + + [Environment]::SetEnvironmentVariable("CC", "clang.exe", "User") + [Environment]::SetEnvironmentVariable("CXX", "clang++.exe", "User") + refreshenv + + - gcc + + [Environment]::SetEnvironmentVariable("CC", "gcc.exe", "User") + [Environment]::SetEnvironmentVariable("CXX", "g++.exe", "User") + refreshenv + + + - Temporarily (only for the current shell): + + $Env:CC="clang.exe" + $Env:CXX="clang++.exe" + +
+ +### (2) Configure your build + +To configure the project, you could use `cmake`, or `ccmake` or `cmake-gui`. Each of them are explained in the following: + +#### (2.a) Configuring via cmake: +With Cmake directly: + + cmake -S . -B ./build + +Cmake will automatically create the `./build` folder if it does not exist, and it wil configure the project. + +Instead, if you have CMake version 3.21+, you can use one of the configuration presets that are listed in the CmakePresets.json file. + + cmake . --preset + cmake --build + +#### (2.b) Configuring via ccmake: + +With the Cmake Curses Dialog Command Line tool: + + ccmake -S . -B ./build + +Once `ccmake` has finished setting up, press 'c' to configure the project, +press 'g' to generate, and 'q' to quit. + +#### (2.c) Configuring via cmake-gui: + +To use the GUI of the cmake: + +2.c.1) Open cmake-gui from the project directory: +``` +cmake-gui . +``` +2.c.2) Set the build directory: + +![build_dir](https://user-images.githubusercontent.com/16418197/82524586-fa48e380-9af4-11ea-8514-4e18a063d8eb.jpg) + +2.c.3) Configure the generator: + +In cmake-gui, from the upper menu select `Tools/Configure`. + +**Warning**: if you have set `CC` and `CXX` always choose the `use default native compilers` option. This picks `CC` and `CXX`. Don't change the compiler at this stage! + +
+Windows - MinGW Makefiles + +Choose MinGW Makefiles as the generator: + +mingw + +
+ +
+Windows - Visual Studio generator and compiler + +You should have already set `C` and `CXX` to `cl.exe`. + +Choose "Visual Studio 16 2019" as the generator: + +default_vs + +
+ +
+ +Windows - Visual Studio generator and Clang Compiler + +You should have already set `C` and `CXX` to `clang.exe` and `clang++.exe`. + +Choose "Visual Studio 16 2019" as the generator. To tell Visual studio to use `clang-cl.exe`: +- If you use the LLVM that is shipped with Visual Studio: write `ClangCl` under "optional toolset to use". + +visual_studio + +- If you use an external LLVM: write [`LLVM_v142`](https://github.com/zufuliu/llvm-utils#llvm-for-visual-studio-2017-and-2019) + under "optional toolset to use". + +visual_studio + +
+
+ +2.c.4) Choose the Cmake options and then generate: + +![generate](https://user-images.githubusercontent.com/16418197/82781591-c97feb80-9e1f-11ea-86c8-f2748b96f516.png) + +### (3) Build the project +Once you have selected all the options you would like to use, you can build the +project (all targets): + + cmake --build ./build + +For Visual Studio, give the build configuration (Release, RelWithDeb, Debug, etc) like the following: + + cmake --build ./build -- /p:configuration=Release + + +### Running the tests + +You can use the `ctest` command run the tests. + +```shell +cd ./build +ctest -C Debug +cd ../ +``` + + diff --git a/README_dependencies.md b/README_dependencies.md new file mode 100644 index 0000000..430cafb --- /dev/null +++ b/README_dependencies.md @@ -0,0 +1,192 @@ +## Dependencies + +Note about install commands: +- for Windows, we use [choco](https://chocolatey.org/install). +- for MacOS, we use [brew](https://brew.sh/). +- In case of an error in cmake, make sure that the dependencies are on the PATH. + + +### Too Long, Didn't Install + +This is a really long list of dependencies, and it's easy to mess up. That's why: + +#### Docker +We have a Docker image that's already set up for you. See the [Docker instructions](./README_docker.md). + +#### Setup-cpp + +We have [setup-cpp](https://github.com/aminya/setup-cpp) that is a cross-platform tool to install all the compilers and dependencies on the system. + +Please check [the setup-cpp documentation](https://github.com/aminya/setup-cpp) for more information. + +For example, on Windows, you can run the following to install llvm, cmake, ninja, ccache, and cppcheck. +```ps1 +# windows example (open shell as admin) +curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.5.7/setup_cpp_windows.exe" +./setup_cpp_windows --compiler llvm --cmake true --ninja true --ccache true --cppcheck true + +RefreshEnv.cmd # reload the environment +``` + +### Necessary Dependencies +1. A C++ compiler that supports C++17. +See [cppreference.com](https://en.cppreference.com/w/cpp/compiler_support) +to see which features are supported by each compiler. +The following compilers should work: + + * [gcc 7+](https://gcc.gnu.org/) +
+ Install command + + - Debian/Ubuntu: + + sudo apt install build-essential + + - Windows: + + choco install mingw -y + + - MacOS: + + brew install gcc +
+ + * [clang 6+](https://clang.llvm.org/) +
+ Install command + + - Debian/Ubuntu: + + bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" + + - Windows: + + Visual Studio 2019 ships with LLVM (see the Visual Studio section). However, to install LLVM separately: + + choco install llvm -y + + llvm-utils for using external LLVM with Visual Studio generator: + + git clone https://github.com/zufuliu/llvm-utils.git + cd llvm-utils/VS2017 + .\install.bat + + - MacOS: + + brew install llvm +
+ + * [Visual Studio 2019 or higher](https://visualstudio.microsoft.com/) +
+ Install command + Environment setup + + On Windows, you need to install Visual Studio 2019 because of the SDK and libraries that ship with it. + + Visual Studio IDE - 2019 Community (installs Clang too): + + choco install -y visualstudio2019community --package-parameters "add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --includeOptional --passive --locale en-US" + + Put MSVC compiler, Clang compiler, and vcvarsall.bat on the path: + + choco install vswhere -y + refreshenv + + # change to x86 for 32bit + $clpath = vswhere -products * -latest -prerelease -find **/Hostx64/x64/* + $clangpath = vswhere -products * -latest -prerelease -find **/Llvm/bin/* + $vcvarsallpath = vswhere -products * -latest -prerelease -find **/Auxiliary/Build/* + + $path = [System.Environment]::GetEnvironmentVariable("PATH", "User") + [Environment]::SetEnvironmentVariable("Path", $path + ";$clpath" + ";$clangpath" + ";$vcvarsallpath", "User") + refreshenv + +
+ + +2. [CMake 3.21+](https://cmake.org/) +
+ Install Command + + - Debian/Ubuntu: + + sudo apt-get install cmake + + - Windows: + + choco install cmake -y + + - MacOS: + + brew install cmake + +
+ +### Optional Dependencies +#### C++ Tools + * [Doxygen](http://doxygen.nl/) +
+ Install Command + + - Debian/Ubuntu: + + sudo apt-get install doxygen + sudo apt-get install graphviz + + - Windows: + + choco install doxygen.install -y + choco install graphviz -y + + - MacOS: + + brew install doxygen + brew install graphviz + +
+ + + * [ccache](https://ccache.dev/) +
+ Install Command + + - Debian/Ubuntu: + + sudo apt-get install ccache + + - Windows: + + choco install ccache -y + + - MacOS: + + brew install ccache + +
+ + + * [Cppcheck](http://cppcheck.sourceforge.net/) +
+ Install Command + + - Debian/Ubuntu: + + sudo apt-get install cppcheck + + - Windows: + + choco install cppcheck -y + + - MacOS: + + brew install cppcheck + +
+ + + * [include-what-you-use](https://include-what-you-use.org/) +
+ Install Command + + Follow instructions here: + https://github.com/include-what-you-use/include-what-you-use#how-to-install +
diff --git a/README_docker.md b/README_docker.md new file mode 100644 index 0000000..b1f5bf5 --- /dev/null +++ b/README_docker.md @@ -0,0 +1,71 @@ +## Docker Instructions + +If you have [Docker](https://www.docker.com/) installed, you can run this +in your terminal, when the Dockerfile is inside the `.devcontainer` directory: + +```bash +docker build -f ./.devcontainer/Dockerfile --tag=my_project:latest . +docker run -it my_project:latest +``` + +This command will put you in a `bash` session in a Ubuntu 20.04 Docker container, +with all of the tools listed in the [Dependencies](#dependencies) section already installed. +Additionally, you will have `g++-11` and `clang++-13` installed as the default +versions of `g++` and `clang++`. + +If you want to build this container using some other versions of gcc and clang, +you may do so with the `GCC_VER` and `LLVM_VER` arguments: + +```bash +docker build --tag=myproject:latest --build-arg GCC_VER=10 --build-arg LLVM_VER=11 . +``` + +The CC and CXX environment variables are set to GCC version 11 by default. +If you wish to use clang as your default CC and CXX environment variables, you +may do so like this: + +```bash +docker build --tag=my_project:latest --build-arg USE_CLANG=1 . +``` + +You will be logged in as root, so you will see the `#` symbol as your prompt. +You will be in a directory that contains a copy of the `cpp_starter_project`; +any changes you make to your local copy will not be updated in the Docker image +until you rebuild it. +If you need to mount your local copy directly in the Docker image, see +[Docker volumes docs](https://docs.docker.com/storage/volumes/). +TLDR: + +```bash +docker run -it \ + -v absolute_path_on_host_machine:absolute_path_in_guest_container \ + my_project:latest +``` + +You can configure and build [as directed above](#build) using these commands: + +```bash +/starter_project# mkdir build +/starter_project# cmake -S . -B ./build +/starter_project# cmake --build ./build +``` + +You can configure and build using `clang-13`, without rebuilding the container, +with these commands: + +```bash +/starter_project# mkdir build +/starter_project# CC=clang CXX=clang++ cmake -S . -B ./build +/starter_project# cmake --build ./build +``` + +The `ccmake` tool is also installed; you can substitute `ccmake` for `cmake` to +configure the project interactively. +All of the tools this project supports are installed in the Docker image; +enabling them is as simple as flipping a switch using the `ccmake` interface. +Be aware that some of the sanitizers conflict with each other, so be sure to +run them separately. + +A script called `build_examples.sh` is provided to help you to build the example +GUI projects in this container. + diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..a3086b7 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,33 @@ +set(CPM_DOWNLOAD_VERSION 0.38.1) + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +function(download_cpm) + message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") + file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} + ) +endfunction() + +if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) + download_cpm() +else() + # resume download if it previously failed + file(READ ${CPM_DOWNLOAD_LOCATION} check) + if("${check}" STREQUAL "") + download_cpm() + endif() + unset(check) +endif() + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake new file mode 100644 index 0000000..2164c10 --- /dev/null +++ b/cmake/Cache.cmake @@ -0,0 +1,33 @@ +# Enable cache if available +function(myproject_enable_cache) + set(CACHE_OPTION + "ccache" + CACHE STRING "Compiler cache to be used") + set(CACHE_OPTION_VALUES "ccache" "sccache") + set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) + list( + FIND + CACHE_OPTION_VALUES + ${CACHE_OPTION} + CACHE_OPTION_INDEX) + + if(${CACHE_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" + ) + endif() + + find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) + if(CACHE_BINARY) + message(STATUS "${CACHE_BINARY} found and enabled") + set(CMAKE_CXX_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH "CXX compiler cache used") + set(CMAKE_C_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH "C compiler cache used") + else() + message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") + endif() +endfunction() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..edb526a --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,115 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md + +function( + myproject_set_project_warnings + project_name + WARNINGS_AS_ERRORS + MSVC_WARNINGS + CLANG_WARNINGS + GCC_WARNINGS + CUDA_WARNINGS) + if("${MSVC_WARNINGS}" STREQUAL "") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + endif() + + if("${CLANG_WARNINGS}" STREQUAL "") + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + ) + endif() + + if("${GCC_WARNINGS}" STREQUAL "") + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + endif() + + if("${CUDA_WARNINGS}" STREQUAL "") + set(CUDA_WARNINGS + -Wall + -Wextra + -Wunused + -Wconversion + -Wshadow + # TODO add more Cuda warnings + ) + endif() + + if(WARNINGS_AS_ERRORS) + message(TRACE "Warnings are treated as errors") + list(APPEND CLANG_WARNINGS -Werror) + list(APPEND GCC_WARNINGS -Werror) + list(APPEND MSVC_WARNINGS /WX) + endif() + + if(MSVC) + set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") + # TODO support Intel compiler + endif() + + # use the same warning flags for C + set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") + + set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") + + target_compile_options( + ${project_name} + INTERFACE # C++ warnings + $<$:${PROJECT_WARNINGS_CXX}> + # C warnings + $<$:${PROJECT_WARNINGS_C}> + # Cuda warnings + $<$:${PROJECT_WARNINGS_CUDA}>) +endfunction() diff --git a/cmake/Cuda.cmake b/cmake/Cuda.cmake new file mode 100644 index 0000000..d784a88 --- /dev/null +++ b/cmake/Cuda.cmake @@ -0,0 +1,48 @@ +# ! target_link_cuda +# A function that links Cuda to the given target +# +# # Example +# add_executable(main_cuda main.cu) +# target_compile_features(main_cuda PRIVATE cxx_std_17) +# target_link_libraries(main_cuda PRIVATE project_options project_warnings) +# target_link_cuda(main_cuda) +# +macro(myproject_target_link_cuda target) + # optional named CUDA_WARNINGS + set(oneValueArgs CUDA_WARNINGS) + cmake_parse_arguments( + _cuda_args + "" + "${oneValueArgs}" + "" + ${ARGN}) + + # add CUDA to cmake language + enable_language(CUDA) + + # use the same C++ standard if not specified + if("${CMAKE_CUDA_STANDARD}" STREQUAL "") + set(CMAKE_CUDA_STANDARD "${CMAKE_CXX_STANDARD}") + endif() + + # -fPIC + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) + + # We need to explicitly state that we need all CUDA files in the + # ${target} library to be built with -dc as the member functions + # could be called by other libraries and executables + set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION ON) + + if(APPLE) + # We need to add the path to the driver (libcuda.dylib) as an rpath, + # so that the static cuda runtime can find it at runtime. + set_property(TARGET ${target} PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) + endif() + + if(WIN32 AND "$ENV{VSCMD_VER}" STREQUAL "") + message( + WARNING + "Compiling Cuda on Windows outside the Visual Studio Commant prompt or without running `vcvarsall.bat x64` probably fails" + ) + endif() +endmacro() diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake new file mode 100644 index 0000000..ed90c56 --- /dev/null +++ b/cmake/Doxygen.cmake @@ -0,0 +1,54 @@ +# Enable doxygen doc builds of source +function(myproject_enable_doxygen DOXYGEN_THEME) + # If not specified, use the top readme file as the first page + if((NOT DOXYGEN_USE_MDFILE_AS_MAINPAGE) AND EXISTS "${PROJECT_SOURCE_DIR}/README.md") + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${PROJECT_SOURCE_DIR}/README.md") + endif() + + # set better defaults for doxygen + is_verbose(_is_verbose) + if(NOT ${_is_verbose}) + set(DOXYGEN_QUIET YES) + endif() + set(DOXYGEN_CALLER_GRAPH YES) + set(DOXYGEN_CALL_GRAPH YES) + set(DOXYGEN_EXTRACT_ALL YES) + set(DOXYGEN_GENERATE_TREEVIEW YES) + # svg files are much smaller than jpeg and png, and yet they have higher quality + set(DOXYGEN_DOT_IMAGE_FORMAT svg) + set(DOXYGEN_DOT_TRANSPARENT YES) + + # If not specified, exclude the vcpkg files and the files CMake downloads under _deps (like project_options) + if(NOT DOXYGEN_EXCLUDE_PATTERNS) + set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/*" "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") + endif() + + if("${DOXYGEN_THEME}" STREQUAL "") + set(DOXYGEN_THEME "awesome-sidebar") + endif() + + if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + # use a modern doxygen theme + # https://github.com/jothepro/doxygen-awesome-css v1.6.1 + FetchContent_Declare(_doxygen_theme + URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v1.6.1.zip) + FetchContent_MakeAvailable(_doxygen_theme) + if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + set(DOXYGEN_HTML_EXTRA_STYLESHEET "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome.css") + endif() + if("${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_HTML_EXTRA_STYLESHEET} + "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome-sidebar-only.css") + endif() + else() + # use the original doxygen theme + endif() + + # find doxygen and dot if available + find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot) + + # add doxygen-docs target + message(STATUS "Adding `doxygen-docs` target that builds the documentation.") + doxygen_add_docs(doxygen-docs ALL ${PROJECT_SOURCE_DIR} + COMMENT "Generating documentation - entry file: ${CMAKE_CURRENT_BINARY_DIR}/html/index.html") +endfunction() diff --git a/cmake/Hardening.cmake b/cmake/Hardening.cmake new file mode 100644 index 0000000..5f61d22 --- /dev/null +++ b/cmake/Hardening.cmake @@ -0,0 +1,98 @@ +include(CheckCXXCompilerFlag) + +macro( + myproject_enable_hardening + target + global + ubsan_minimal_runtime) + + message(STATUS "** Enabling Hardening (Target ${target}) **") + + if(MSVC) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} /sdl /DYNAMICBASE /guard:cf") + message(STATUS "*** MSVC flags: /sdl /DYNAMICBASE /guard:cf /NXCOMPAT /CETCOMPAT") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} /NXCOMPAT /CETCOMPAT") + + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU") + set(NEW_CXX_DEFINITIONS "${NEW_CXX_DEFINITIONS} -D_GLIBCXX_ASSERTIONS") + message(STATUS "*** GLIBC++ Assertions (vector[], string[], ...) enabled") + + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3") + message(STATUS "*** g++/clang _FORTIFY_SOURCE=3 enabled") + + # check_cxx_compiler_flag(-fpie PIE) + #if(PIE) + # set(NEW_COMPILE_OPTIONS ${NEW_COMPILE_OPTIONS} -fpie) + # set(NEW_LINK_OPTIONS ${NEW_LINK_OPTIONS} -pie) + # + # message(STATUS "*** g++/clang PIE mode enabled") + #else() + # message(STATUS "*** g++/clang PIE mode NOT enabled (not supported)") + #endif() + + check_cxx_compiler_flag(-fstack-protector-strong STACK_PROTECTOR) + if(STACK_PROTECTOR) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-protector-strong") + message(STATUS "*** g++/clang -fstack-protector-strong enabled") + else() + message(STATUS "*** g++/clang -fstack-protector-strong NOT enabled (not supported)") + endif() + + check_cxx_compiler_flag(-fcf-protection CF_PROTECTION) + if(CF_PROTECTION) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fcf-protection") + message(STATUS "*** g++/clang -fcf-protection enabled") + else() + message(STATUS "*** g++/clang -fcf-protection NOT enabled (not supported)") + endif() + + check_cxx_compiler_flag(-fstack-clash-protection CLASH_PROTECTION) + if(CLASH_PROTECTION) + if(LINUX OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-clash-protection") + message(STATUS "*** g++/clang -fstack-clash-protection enabled") + else() + message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (clang on non-Linux)") + endif() + else() + message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (not supported)") + endif() + endif() + + if(${ubsan_minimal_runtime}) + check_cxx_compiler_flag("-fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-minimal-runtime" + MINIMAL_RUNTIME) + if(MINIMAL_RUNTIME) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") + + if(NOT ${global}) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fno-sanitize-recover=undefined") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fno-sanitize-recover=undefined") + else() + message(STATUS "** not enabling -fno-sanitize-recover=undefined for global consumption") + endif() + + message(STATUS "*** ubsan minimal runtime enabled") + else() + message(STATUS "*** ubsan minimal runtime NOT enabled (not supported)") + endif() + else() + message(STATUS "*** ubsan minimal runtime NOT enabled (not requested)") + endif() + + message(STATUS "** Hardening Compiler Flags: ${NEW_COMPILE_OPTIONS}") + message(STATUS "** Hardening Linker Flags: ${NEW_LINK_OPTIONS}") + message(STATUS "** Hardening Compiler Defines: ${NEW_CXX_DEFINITIONS}") + + if(${global}) + message(STATUS "** Setting hardening options globally for all dependencies") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_COMPILE_OPTIONS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${NEW_LINK_OPTIONS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_CXX_DEFINITIONS}") + else() + target_compile_options(${target} INTERFACE ${NEW_COMPILE_OPTIONS}) + target_link_options(${target} INTERFACE ${NEW_LINK_OPTIONS}) + target_compile_definitions(${target} INTERFACE ${NEW_CXX_DEFINITIONS}) + endif() +endmacro() diff --git a/cmake/InterproceduralOptimization.cmake b/cmake/InterproceduralOptimization.cmake new file mode 100644 index 0000000..7ea4daf --- /dev/null +++ b/cmake/InterproceduralOptimization.cmake @@ -0,0 +1,9 @@ +macro(myproject_enable_ipo) + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + else() + message(SEND_ERROR "IPO is not supported: ${output}") + endif() +endmacro() diff --git a/cmake/LibFuzzer.cmake b/cmake/LibFuzzer.cmake new file mode 100644 index 0000000..7ab5eea --- /dev/null +++ b/cmake/LibFuzzer.cmake @@ -0,0 +1,17 @@ +function(myproject_check_libfuzzer_support var_name) + set(LibFuzzerTestSource + " +#include + +extern \"C\" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) { + return 0; +} + ") + + include(CheckCXXSourceCompiles) + + set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer") + set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=fuzzer") + check_cxx_source_compiles("${LibFuzzerTestSource}" ${var_name}) + +endfunction() diff --git a/cmake/Linker.cmake b/cmake/Linker.cmake new file mode 100644 index 0000000..067aac6 --- /dev/null +++ b/cmake/Linker.cmake @@ -0,0 +1,31 @@ +macro(myproject_configure_linker project_name) + include(CheckCXXCompilerFlag) + + set(USER_LINKER_OPTION + "lld" + CACHE STRING "Linker to be used") + set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") + set_property(CACHE USER_LINKER_OPTION PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES}) + list( + FIND + USER_LINKER_OPTION_VALUES + ${USER_LINKER_OPTION} + USER_LINKER_OPTION_INDEX) + + if(${USER_LINKER_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}") + endif() + + if(NOT ENABLE_USER_LINKER) + return() + endif() + + set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") + + check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) + if(CXX_SUPPORTS_USER_LINKER) + target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) + endif() +endmacro() diff --git a/cmake/PackageProject.cmake b/cmake/PackageProject.cmake new file mode 100644 index 0000000..f831426 --- /dev/null +++ b/cmake/PackageProject.cmake @@ -0,0 +1,190 @@ +# Uses ycm (permissive BSD-3-Clause license) and ForwardArguments (permissive MIT license) + +function(myproject_package_project) + cmake_policy(SET CMP0103 NEW) # disallow multiple calls with the same NAME + + set(_options ARCH_INDEPENDENT # default to false + ) + set(_oneValueArgs + # default to the project_name: + NAME + COMPONENT + # default to project version: + VERSION + # default to semver + COMPATIBILITY + # default to ${CMAKE_BINARY_DIR} + CONFIG_EXPORT_DESTINATION + # default to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${NAME} suitable for vcpkg, etc. + CONFIG_INSTALL_DESTINATION) + set(_multiValueArgs + # recursively found for the current folder if not specified + TARGETS + # a list of public/interface include directories or files + PUBLIC_INCLUDES + # the names of the INTERFACE/PUBLIC dependencies that are found using `CONFIG` + PUBLIC_DEPENDENCIES_CONFIGURED + # the INTERFACE/PUBLIC dependencies that are found by any means using `find_dependency`. + # the arguments must be specified within double quotes (e.g. " 1.0.0 EXACT" or " CONFIG"). + PUBLIC_DEPENDENCIES + # the names of the PRIVATE dependencies that are found using `CONFIG`. Only included when BUILD_SHARED_LIBS is OFF. + PRIVATE_DEPENDENCIES_CONFIGURED + # PRIVATE dependencies that are only included when BUILD_SHARED_LIBS is OFF + PRIVATE_DEPENDENCIES) + + cmake_parse_arguments( + _PackageProject + "${_options}" + "${_oneValueArgs}" + "${_multiValueArgs}" + "${ARGN}") + + # Set default options + include(GNUInstallDirs) # Define GNU standard installation directories such as CMAKE_INSTALL_DATADIR + + # set default packaged targets + if(NOT _PackageProject_TARGETS) + get_all_installable_targets(_PackageProject_TARGETS) + message(STATUS "package_project: considering ${_PackageProject_TARGETS} as the exported targets") + endif() + + # default to the name of the project or the given name + if("${_PackageProject_NAME}" STREQUAL "") + set(_PackageProject_NAME ${PROJECT_NAME}) + endif() + # ycm args + set(_PackageProject_NAMESPACE "${_PackageProject_NAME}::") + set(_PackageProject_VARS_PREFIX ${_PackageProject_NAME}) + set(_PackageProject_EXPORT ${_PackageProject_NAME}) + + # default version to the project version + if("${_PackageProject_VERSION}" STREQUAL "") + set(_PackageProject_VERSION ${PROJECT_VERSION}) + endif() + + # default compatibility to SameMajorVersion + if("${_PackageProject_COMPATIBILITY}" STREQUAL "") + set(_PackageProject_COMPATIBILITY "SameMajorVersion") + endif() + + # default to the build directory + if("${_PackageProject_CONFIG_EXPORT_DESTINATION}" STREQUAL "") + set(_PackageProject_CONFIG_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}") + endif() + set(_PackageProject_EXPORT_DESTINATION "${_PackageProject_CONFIG_EXPORT_DESTINATION}") + + # use datadir (works better with vcpkg, etc) + if("${_PackageProject_CONFIG_INSTALL_DESTINATION}" STREQUAL "") + set(_PackageProject_CONFIG_INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/${_PackageProject_NAME}") + endif() + # ycm args + set(_PackageProject_INSTALL_DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") + + # Installation of the public/interface includes + if(NOT + "${_PackageProject_PUBLIC_INCLUDES}" + STREQUAL + "") + foreach(_INC ${_PackageProject_PUBLIC_INCLUDES}) + # make include absolute + if(NOT IS_ABSOLUTE ${_INC}) + set(_INC "${CMAKE_CURRENT_SOURCE_DIR}/${_INC}") + endif() + # install include + if(IS_DIRECTORY ${_INC}) + # the include directories are directly installed to the install destination. If you want an `include` folder in the install destination, name your include directory as `include` (or install it manually using `install()` command). + install(DIRECTORY ${_INC} DESTINATION "./") + else() + install(FILES ${_INC} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + endif() + endforeach() + endif() + + # Append the configured public dependencies + if(NOT + "${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}" + STREQUAL + "") + set(_PUBLIC_DEPENDENCIES_CONFIG) + foreach(DEP ${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}) + list(APPEND _PUBLIC_DEPENDENCIES_CONFIG "${DEP} CONFIG") + endforeach() + endif() + list(APPEND _PackageProject_PUBLIC_DEPENDENCIES ${_PUBLIC_DEPENDENCIES_CONFIG}) + # ycm arg + set(_PackageProject_DEPENDENCIES ${_PackageProject_PUBLIC_DEPENDENCIES}) + + # Append the configured private dependencies + if(NOT + "${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}" + STREQUAL + "") + set(_PRIVATE_DEPENDENCIES_CONFIG) + foreach(DEP ${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}) + list(APPEND _PRIVATE_DEPENDENCIES_CONFIG "${DEP} CONFIG") + endforeach() + endif() + # ycm arg + list(APPEND _PackageProject_PRIVATE_DEPENDENCIES ${_PRIVATE_DEPENDENCIES_CONFIG}) + + # Installation of package (compatible with vcpkg, etc) + install( + TARGETS ${_PackageProject_TARGETS} + EXPORT ${_PackageProject_EXPORT} + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${_PackageProject_NAME}" COMPONENT dev) + + # install the usage file + set(_targets_str "") + foreach(_target ${_PackageProject_TARGETS}) + set(_targets_str "${_targets_str} ${_PackageProject_NAMESPACE}${_target}") + endforeach() + set(USAGE_FILE_CONTENT + "The package ${_PackageProject_NAME} provides CMake targets: + + find_package(${_PackageProject_NAME} CONFIG REQUIRED) + target_link_libraries(main PRIVATE ${_targets_str}) + ") + install(CODE "MESSAGE(STATUS \"${USAGE_FILE_CONTENT}\")") + file(WRITE "${_PackageProject_EXPORT_DESTINATION}/usage" "${USAGE_FILE_CONTENT}") + install(FILES "${_PackageProject_EXPORT_DESTINATION}/usage" + DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") + + unset(_PackageProject_TARGETS) + + # download ForwardArguments + FetchContent_Declare( + _fargs + URL https://github.com/polysquare/cmake-forward-arguments/archive/8c50d1f956172edb34e95efa52a2d5cb1f686ed2.zip) + FetchContent_GetProperties(_fargs) + if(NOT _fargs_POPULATED) + FetchContent_Populate(_fargs) + endif() + include("${_fargs_SOURCE_DIR}/ForwardArguments.cmake") + + # prepare the forward arguments for ycm + set(_FARGS_LIST) + cmake_forward_arguments( + _PackageProject + _FARGS_LIST + OPTION_ARGS + "${_options};" + SINGLEVAR_ARGS + "${_oneValueArgs};EXPORT_DESTINATION;INSTALL_DESTINATION;NAMESPACE;VARS_PREFIX;EXPORT" + MULTIVAR_ARGS + "${_multiValueArgs};DEPENDENCIES;PRIVATE_DEPENDENCIES") + + # download ycm + FetchContent_Declare(_ycm URL https://github.com/robotology/ycm/archive/refs/tags/v0.13.0.zip) + FetchContent_GetProperties(_ycm) + if(NOT _ycm_POPULATED) + FetchContent_Populate(_ycm) + endif() + include("${_ycm_SOURCE_DIR}/modules/InstallBasicPackageFiles.cmake") + + install_basic_package_files(${_PackageProject_NAME} "${_FARGS_LIST}") + + include("${_ycm_SOURCE_DIR}/modules/AddUninstallTarget.cmake") +endfunction() diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 0000000..302a0ba --- /dev/null +++ b/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,19 @@ +# +# This function will prevent in-source builds +# +function(myproject_assure_out_of_source_builds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +myproject_assure_out_of_source_builds() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 0000000..e238fa2 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,90 @@ +function( + myproject_enable_sanitizers + project_name + ENABLE_SANITIZER_ADDRESS + ENABLE_SANITIZER_LEAK + ENABLE_SANITIZER_UNDEFINED_BEHAVIOR + ENABLE_SANITIZER_THREAD + ENABLE_SANITIZER_MEMORY) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(SANITIZERS "") + + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + + if(${ENABLE_SANITIZER_LEAK}) + list(APPEND SANITIZERS "leak") + endif() + + if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) + list(APPEND SANITIZERS "undefined") + endif() + + if(${ENABLE_SANITIZER_THREAD}) + if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "thread") + endif() + endif() + + if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + message( + WARNING + "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" + ) + if("address" IN_LIST SANITIZERS + OR "thread" IN_LIST SANITIZERS + OR "leak" IN_LIST SANITIZERS) + message(WARNING "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "memory") + endif() + endif() + elseif(MSVC) + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + if(${ENABLE_SANITIZER_LEAK} + OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} + OR ${ENABLE_SANITIZER_THREAD} + OR ${ENABLE_SANITIZER_MEMORY}) + message(WARNING "MSVC only supports address sanitizer") + endif() + endif() + + list( + JOIN + SANITIZERS + "," + LIST_OF_SANITIZERS) + + if(LIST_OF_SANITIZERS) + if(NOT + "${LIST_OF_SANITIZERS}" + STREQUAL + "") + if(NOT MSVC) + target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + else() + string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir) + if("${index_of_vs_install_dir}" STREQUAL "-1") + message( + SEND_ERROR + "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." + ) + endif() + target_compile_options(${project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /Zi /INCREMENTAL:NO) + target_compile_definitions(${project_name} INTERFACE _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION) + target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) + endif() + endif() + endif() + +endfunction() + + + diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 0000000..b9f4123 --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,45 @@ +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo") +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Enhance error reporting and compiler messages +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(WIN32) + # On Windows cuda nvcc uses cl and not clang + add_compile_options($<$:-fcolor-diagnostics> $<$:-fcolor-diagnostics>) + else() + add_compile_options(-fcolor-diagnostics) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(WIN32) + # On Windows cuda nvcc uses cl and not gcc + add_compile_options($<$:-fdiagnostics-color=always> + $<$:-fdiagnostics-color=always>) + else() + add_compile_options(-fdiagnostics-color=always) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) + add_compile_options(/diagnostics:column) +else() + message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") +endif() + + +# run vcvarsall when msvc is used +include("${CMAKE_CURRENT_LIST_DIR}/VCEnvironment.cmake") +run_vcvarsall() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 0000000..eb3674e --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,109 @@ +macro(myproject_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) + find_program(CPPCHECK cppcheck) + if(CPPCHECK) + + if(CMAKE_GENERATOR MATCHES ".*Visual Studio.*") + set(CPPCHECK_TEMPLATE "vs") + else() + set(CPPCHECK_TEMPLATE "gcc") + endif() + + if("${CPPCHECK_OPTIONS}" STREQUAL "") + # Enable all warnings that are actionable by the user of this toolset + # style should enable the other 3, but we'll be explicit just in case + set(CMAKE_CXX_CPPCHECK + ${CPPCHECK} + --template=${CPPCHECK_TEMPLATE} + --enable=style,performance,warning,portability + --inline-suppr + # We cannot act on a bug/missing feature of cppcheck + --suppress=cppcheckError + --suppress=internalAstError + # if a file does not have an internalAstError, we get an unmatchedSuppression error + --suppress=unmatchedSuppression + # noisy and incorrect sometimes + --suppress=passedByValue + # ignores code that cppcheck thinks is invalid C++ + --suppress=syntaxError + --suppress=preprocessorErrorDirective + --inconclusive) + else() + # if the user provides a CPPCHECK_OPTIONS with a template specified, it will override this template + set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --template=${CPPCHECK_TEMPLATE} ${CPPCHECK_OPTIONS}) + endif() + + if(NOT + "${CMAKE_CXX_STANDARD}" + STREQUAL + "") + set(CMAKE_CXX_CPPCHECK ${CMAKE_CXX_CPPCHECK} --std=c++${CMAKE_CXX_STANDARD}) + endif() + if(${WARNINGS_AS_ERRORS}) + list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) + endif() + else() + message(${WARNING_MESSAGE} "cppcheck requested but executable not found") + endif() +endmacro() + +macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) + + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + if(NOT + CMAKE_CXX_COMPILER_ID + MATCHES + ".*Clang") + + get_target_property(TARGET_PCH ${target} INTERFACE_PRECOMPILE_HEADERS) + + if("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND") + get_target_property(TARGET_PCH ${target} PRECOMPILE_HEADERS) + endif() + + if(NOT ("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND")) + message( + SEND_ERROR + "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file") + endif() + endif() + + # construct the clang-tidy command line + set(CLANG_TIDY_OPTIONS + ${CLANGTIDY} + -extra-arg=-Wno-unknown-warning-option + -extra-arg=-Wno-ignored-optimization-argument + -extra-arg=-Wno-unused-command-line-argument + -p) + # set standard + if(NOT + "${CMAKE_CXX_STANDARD}" + STREQUAL + "") + if("${CLANG_TIDY_OPTIONS_DRIVER_MODE}" STREQUAL "cl") + set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=/std:c++${CMAKE_CXX_STANDARD}) + else() + set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=-std=c++${CMAKE_CXX_STANDARD}) + endif() + endif() + + # set warnings as errors + if(${WARNINGS_AS_ERRORS}) + list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) + endif() + + message("Also setting clang-tidy globally") + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) + else() + message(${WARNING_MESSAGE} "clang-tidy requested but executable not found") + endif() +endmacro() + +macro(myproject_enable_include_what_you_use) + find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) + if(INCLUDE_WHAT_YOU_USE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) + else() + message(${WARNING_MESSAGE} "include-what-you-use requested but executable not found") + endif() +endmacro() diff --git a/cmake/SystemLink.cmake b/cmake/SystemLink.cmake new file mode 100644 index 0000000..000a9ad --- /dev/null +++ b/cmake/SystemLink.cmake @@ -0,0 +1,83 @@ +# Include a system directory (which suppresses its warnings). +function(target_include_system_directories target) + set(multiValueArgs INTERFACE PUBLIC PRIVATE) + cmake_parse_arguments( + ARG + "" + "" + "${multiValueArgs}" + ${ARGN}) + + foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) + foreach(lib_include_dirs IN LISTS ARG_${scope}) + if(NOT MSVC) + # system includes do not work in MSVC + # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/18272# + # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/17904 + set(_SYSTEM SYSTEM) + endif() + if(${scope} STREQUAL "INTERFACE" OR ${scope} STREQUAL "PUBLIC") + target_include_directories( + ${target} + ${_SYSTEM} + ${scope} + "$" + "$") + else() + target_include_directories( + ${target} + ${_SYSTEM} + ${scope} + ${lib_include_dirs}) + endif() + endforeach() + endforeach() + +endfunction() + +# Include the directories of a library target as system directories (which suppresses their warnings). +function( + target_include_system_library + target + scope + lib) + # check if this is a target + if(TARGET ${lib}) + get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) + if(lib_include_dirs) + target_include_system_directories(${target} ${scope} ${lib_include_dirs}) + else() + message(TRACE "${lib} library does not have the INTERFACE_INCLUDE_DIRECTORIES property.") + endif() + endif() +endfunction() + +# Link a library target as a system library (which suppresses its warnings). +function( + target_link_system_library + target + scope + lib) + # Include the directories in the library + target_include_system_library(${target} ${scope} ${lib}) + + # Link the library + target_link_libraries(${target} ${scope} ${lib}) +endfunction() + +# Link multiple library targets as system libraries (which suppresses their warnings). +function(target_link_system_libraries target) + set(multiValueArgs INTERFACE PUBLIC PRIVATE) + cmake_parse_arguments( + ARG + "" + "" + "${multiValueArgs}" + ${ARGN}) + + foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) + foreach(lib IN LISTS ARG_${scope}) + target_link_system_library(${target} ${scope} ${lib}) + endforeach() + endforeach() +endfunction() diff --git a/cmake/Tests.cmake b/cmake/Tests.cmake new file mode 100644 index 0000000..e20c7d4 --- /dev/null +++ b/cmake/Tests.cmake @@ -0,0 +1,6 @@ +function(myproject_enable_coverage project_name) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + target_compile_options(${project_name} INTERFACE --coverage -O0 -g) + target_link_libraries(${project_name} INTERFACE --coverage) + endif() +endfunction() diff --git a/cmake/Utilities.cmake b/cmake/Utilities.cmake new file mode 100644 index 0000000..6fa78b2 --- /dev/null +++ b/cmake/Utilities.cmake @@ -0,0 +1,139 @@ +# find a subtring from a string by a given prefix such as VCVARSALL_ENV_START +function( + find_substring_by_prefix + output + prefix + input) + # find the prefix + string(FIND "${input}" "${prefix}" prefix_index) + if("${prefix_index}" STREQUAL "-1") + message(SEND_ERROR "Could not find ${prefix} in ${input}") + endif() + # find the start index + string(LENGTH "${prefix}" prefix_length) + math(EXPR start_index "${prefix_index} + ${prefix_length}") + + string( + SUBSTRING "${input}" + "${start_index}" + "-1" + _output) + set("${output}" + "${_output}" + PARENT_SCOPE) +endfunction() + +# A function to set environment variables of CMake from the output of `cmd /c set` +function(set_env_from_string env_string) + # replace ; in paths with __sep__ so we can split on ; + string( + REGEX + REPLACE ";" + "__sep__" + env_string_sep_added + "${env_string}") + + # the variables are separated by \r?\n + string( + REGEX + REPLACE "\r?\n" + ";" + env_list + "${env_string_sep_added}") + + foreach(env_var ${env_list}) + # split by = + string( + REGEX + REPLACE "=" + ";" + env_parts + "${env_var}") + + list(LENGTH env_parts env_parts_length) + if("${env_parts_length}" EQUAL "2") + # get the variable name and value + list( + GET + env_parts + 0 + env_name) + list( + GET + env_parts + 1 + env_value) + + # recover ; in paths + string( + REGEX + REPLACE "__sep__" + ";" + env_value + "${env_value}") + + # set env_name to env_value + set(ENV{${env_name}} "${env_value}") + + # update cmake program path + if("${env_name}" EQUAL "PATH") + list(APPEND CMAKE_PROGRAM_PATH ${env_value}) + endif() + endif() + endforeach() +endfunction() + +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} + ${targets} + PARENT_SCOPE) +endfunction() + +function(get_all_installable_targets var) + set(targets) + get_all_targets(targets) + foreach(_target ${targets}) + get_target_property(_target_type ${_target} TYPE) + if(NOT + ${_target_type} + MATCHES + ".*LIBRARY|EXECUTABLE") + list(REMOVE_ITEM targets ${_target}) + endif() + endforeach() + set(${var} + ${targets} + PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property( + subdirectories + DIRECTORY ${dir} + PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + + get_property( + current_targets + DIRECTORY ${dir} + PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +function(is_verbose var) + if("CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "VERBOSE" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "DEBUG" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "TRACE") + set(${var} + ON + PARENT_SCOPE) + else() + set(${var} + OFF + PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/VCEnvironment.cmake b/cmake/VCEnvironment.cmake new file mode 100644 index 0000000..a95cb46 --- /dev/null +++ b/cmake/VCEnvironment.cmake @@ -0,0 +1,71 @@ +include("${CMAKE_CURRENT_LIST_DIR}/Utilities.cmake") + +macro(detect_architecture) + # detect the architecture + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) + if(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^i[3456]86$") + set(VCVARSALL_ARCH x86) + elseif( + CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x64 + OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86_64 + OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL amd64) + set(VCVARSALL_ARCH x64) + elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm) + set(VCVARSALL_ARCH arm) + elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm64 OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL aarch64) + set(VCVARSALL_ARCH arm64) + else() + if(CMAKE_HOST_SYSTEM_PROCESSOR) + set(VCVARSALL_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) + else() + set(VCVARSALL_ARCH x64) + message(STATUS "Unkown architecture CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR_LOWER} - using x64") + endif() + endif() +endmacro() + +# Run vcvarsall.bat and set CMake environment variables +function(run_vcvarsall) + # if MSVC but VSCMD_VER is not set, which means vcvarsall has not run + if(MSVC AND "$ENV{VSCMD_VER}" STREQUAL "") + + # find vcvarsall.bat + get_filename_component(MSVC_DIR ${CMAKE_CXX_COMPILER} DIRECTORY) + find_file( + VCVARSALL_FILE + NAMES vcvarsall.bat + PATHS "${MSVC_DIR}" + "${MSVC_DIR}/.." + "${MSVC_DIR}/../.." + "${MSVC_DIR}/../../../../../../../.." + "${MSVC_DIR}/../../../../../../.." + PATH_SUFFIXES "VC/Auxiliary/Build" "Common7/Tools" "Tools") + + if(EXISTS ${VCVARSALL_FILE}) + # detect the architecture + detect_architecture() + + # run vcvarsall and print the environment variables + message(STATUS "Running `${VCVARSALL_FILE} ${VCVARSALL_ARCH}` to set up the MSVC environment") + execute_process( + COMMAND + "cmd" "/c" ${VCVARSALL_FILE} ${VCVARSALL_ARCH} # + "&&" "call" "echo" "VCVARSALL_ENV_START" # + "&" "set" # + OUTPUT_VARIABLE VCVARSALL_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # parse the output and get the environment variables string + find_substring_by_prefix(VCVARSALL_ENV "VCVARSALL_ENV_START" "${VCVARSALL_OUTPUT}") + + # set the environment variables + set_env_from_string("${VCVARSALL_ENV}") + + else() + message( + WARNING + "Could not find `vcvarsall.bat` for automatic MSVC environment preparation. Please manually open the MSVC command prompt and rebuild the project. + ") + endif() + endif() +endfunction() diff --git a/cmake/_FORTIFY_SOURCE.hpp b/cmake/_FORTIFY_SOURCE.hpp new file mode 100644 index 0000000..f513278 --- /dev/null +++ b/cmake/_FORTIFY_SOURCE.hpp @@ -0,0 +1,8 @@ +#ifdef _FORTIFY_SOURCE +#if _FORTIFY_SOURCE < 3 +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 3 +#endif +#else +#define _FORTIFY_SOURCE 3 +#endif diff --git a/configured_files/CMakeLists.txt b/configured_files/CMakeLists.txt new file mode 100644 index 0000000..2bf896e --- /dev/null +++ b/configured_files/CMakeLists.txt @@ -0,0 +1,7 @@ + +# A very simple example of a configured file that might need to be +# converted to one that is publicly installed in the case that +# you are developing a library +configure_file("config.hpp.in" "${CMAKE_BINARY_DIR}/configured_files/include/internal_use_only/config.hpp" ESCAPE_QUOTES) + + diff --git a/configured_files/config.hpp.in b/configured_files/config.hpp.in new file mode 100644 index 0000000..86d5cb3 --- /dev/null +++ b/configured_files/config.hpp.in @@ -0,0 +1,16 @@ +#ifndef MYPROJECT_CONFIG_HPP +#define MYPROJECT_CONFIG_HPP + +// this is a basic example of how a CMake configured file might look +// in this particular case, we are using it to set the version number of our executable +namespace myproject::cmake { +inline constexpr std::string_view project_name = "@PROJECT_NAME@"; +inline constexpr std::string_view project_version = "@PROJECT_VERSION@"; +inline constexpr int project_version_major { @PROJECT_VERSION_MAJOR@ }; +inline constexpr int project_version_minor { @PROJECT_VERSION_MINOR@ }; +inline constexpr int project_version_patch { @PROJECT_VERSION_PATCH@ }; +inline constexpr int project_version_tweak { @PROJECT_VERSION_TWEAK@ }; +inline constexpr std::string_view git_sha = "@GIT_SHA@"; +}// namespace myproject::cmake + +#endif diff --git a/fuzz_test/CMakeLists.txt b/fuzz_test/CMakeLists.txt new file mode 100644 index 0000000..7a392f5 --- /dev/null +++ b/fuzz_test/CMakeLists.txt @@ -0,0 +1,21 @@ +# A fuzz test runs until it finds an error. This particular one is going to rely on libFuzzer. +# + +find_package(fmt) + +add_executable(fuzz_tester fuzz_tester.cpp) +target_link_libraries( + fuzz_tester + PRIVATE myproject_options + myproject_warnings + fmt::fmt + -coverage + -fsanitize=fuzzer) +target_compile_options(fuzz_tester PRIVATE -fsanitize=fuzzer) + +# Allow short runs during automated testing to see if something new breaks +set(FUZZ_RUNTIME + 10 + CACHE STRING "Number of seconds to run fuzz tests during ctest run") # Default of 10 seconds + +add_test(NAME fuzz_tester_run COMMAND fuzz_tester -max_total_time=${FUZZ_RUNTIME}) diff --git a/fuzz_test/fuzz_tester.cpp b/fuzz_test/fuzz_tester.cpp new file mode 100644 index 0000000..ed0fc4c --- /dev/null +++ b/fuzz_test/fuzz_tester.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +[[nodiscard]] auto sum_values(const uint8_t *Data, size_t Size) +{ + constexpr auto scale = 1000; + + int value = 0; + for (std::size_t offset = 0; offset < Size; ++offset) { + value += static_cast(*std::next(Data, static_cast(offset))) * scale; + } + return value; +} + +// Fuzzer that attempts to invoke undefined behavior for signed integer overflow +// cppcheck-suppress unusedFunction symbolName=LLVMFuzzerTestOneInput +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + fmt::print("Value sum: {}, len{}\n", sum_values(Data, Size), Size); + return 0; +} diff --git a/include/myproject/sample_library.hpp b/include/myproject/sample_library.hpp new file mode 100644 index 0000000..1b2b177 --- /dev/null +++ b/include/myproject/sample_library.hpp @@ -0,0 +1,15 @@ +#ifndef SAMPLE_LIBRARY_HPP +#define SAMPLE_LIBRARY_HPP + +#include + +[[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int) noexcept; + +[[nodiscard]] constexpr int factorial_constexpr(int input) noexcept +{ + if (input == 0) { return 1; } + + return input * factorial_constexpr(input - 1); +} + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..0f92a9d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(sample_library) +add_subdirectory(ftxui_sample) diff --git a/src/ftxui_sample/CMakeLists.txt b/src/ftxui_sample/CMakeLists.txt new file mode 100644 index 0000000..fc5c65e --- /dev/null +++ b/src/ftxui_sample/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(intro main.cpp) + +target_link_libraries( + intro + PRIVATE myproject::myproject_options + myproject::myproject_warnings) + +target_link_system_libraries( + intro + PRIVATE + CLI11::CLI11 + fmt::fmt + spdlog::spdlog + lefticus::tools + ftxui::screen + ftxui::dom + ftxui::component) + +target_include_directories(intro PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") diff --git a/src/ftxui_sample/main.cpp b/src/ftxui_sample/main.cpp new file mode 100644 index 0000000..aa53bea --- /dev/null +++ b/src/ftxui_sample/main.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include + +#include + +#include +#include // for ftxui +#include // for Slider +#include // for ScreenInteractive +#include + +#include + +// This file will be generated automatically when cur_you run the CMake +// configuration step. It creates a namespace called `myproject`. You can modify +// the source template at `configured_files/config.hpp.in`. +#include + +template struct GameBoard +{ + static constexpr std::size_t width = Width; + static constexpr std::size_t height = Height; + + std::array, width> strings; + std::array, width> values{}; + + std::size_t move_count{ 0 }; + + std::string &get_string(std::size_t cur_x, std::size_t cur_y) { return strings.at(cur_x).at(cur_y); } + + + void set(std::size_t cur_x, std::size_t cur_y, bool new_value) + { + get(cur_x, cur_y) = new_value; + + if (new_value) { + get_string(cur_x, cur_y) = " ON"; + } else { + get_string(cur_x, cur_y) = "OFF"; + } + } + + void visit(auto visitor) + { + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { visitor(cur_x, cur_y, *this); } + } + } + + [[nodiscard]] bool get(std::size_t cur_x, std::size_t cur_y) const { return values.at(cur_x).at(cur_y); } + + [[nodiscard]] bool &get(std::size_t cur_x, std::size_t cur_y) { return values.at(cur_x).at(cur_y); } + + GameBoard() + { + visit([](const auto cur_x, const auto cur_y, auto &gameboard) { gameboard.set(cur_x, cur_y, true); }); + } + + void update_strings() + { + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { set(cur_x, cur_y, get(cur_x, cur_y)); } + } + } + + void toggle(std::size_t cur_x, std::size_t cur_y) { set(cur_x, cur_y, !get(cur_x, cur_y)); } + + void press(std::size_t cur_x, std::size_t cur_y) + { + ++move_count; + toggle(cur_x, cur_y); + if (cur_x > 0) { toggle(cur_x - 1, cur_y); } + if (cur_y > 0) { toggle(cur_x, cur_y - 1); } + if (cur_x < width - 1) { toggle(cur_x + 1, cur_y); } + if (cur_y < height - 1) { toggle(cur_x, cur_y + 1); } + } + + [[nodiscard]] bool solved() const + { + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { + if (!get(cur_x, cur_y)) { return false; } + } + } + + return true; + } +}; + + +void consequence_game() +{ + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + GameBoard<3, 3> game_board; + + std::string quit_text; + + const auto update_quit_text = [&quit_text](const auto &game_board_param) { + quit_text = fmt::format("Quit ({} moves)", game_board_param.move_count); + if (game_board_param.solved()) { quit_text += " Solved!"; } + }; + + const auto make_buttons = [&] { + std::vector buttons; + for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { + buttons.push_back(ftxui::Button(&game_board.get_string(cur_x, cur_y), [=, &game_board] { + if (!game_board.solved()) { game_board.press(cur_x, cur_y); } + update_quit_text(game_board); + })); + } + } + return buttons; + }; + + auto buttons = make_buttons(); + + auto quit_button = ftxui::Button(&quit_text, screen.ExitLoopClosure()); + + auto make_layout = [&] { + std::vector rows; + + std::size_t idx = 0; + + for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { + std::vector row; + for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { + row.push_back(buttons[idx]->Render()); + ++idx; + } + rows.push_back(ftxui::hbox(std::move(row))); + } + + rows.push_back(ftxui::hbox({ quit_button->Render() })); + + return ftxui::vbox(std::move(rows)); + }; + + + static constexpr int randomization_iterations = 100; + static constexpr int random_seed = 42; + + std::mt19937 gen32{ random_seed };// NOLINT fixed seed + + // NOLINTNEXTLINE This cannot be const + std::uniform_int_distribution cur_x(static_cast(0), game_board.width - 1); + // NOLINTNEXTLINE This cannot be const + std::uniform_int_distribution cur_y(static_cast(0), game_board.height - 1); + + for (int i = 0; i < randomization_iterations; ++i) { game_board.press(cur_x(gen32), cur_y(gen32)); } + game_board.move_count = 0; + update_quit_text(game_board); + + auto all_buttons = buttons; + all_buttons.push_back(quit_button); + auto container = ftxui::Container::Horizontal(all_buttons); + + auto renderer = ftxui::Renderer(container, make_layout); + + screen.Loop(renderer); +} + +struct Color +{ + lefticus::tools::uint_np8_t R{ static_cast(0) }; + lefticus::tools::uint_np8_t G{ static_cast(0) }; + lefticus::tools::uint_np8_t B{ static_cast(0) }; +}; + +// A simple way of representing a bitmap on screen using only characters +struct Bitmap : ftxui::Node +{ + Bitmap(std::size_t width, std::size_t height)// NOLINT same typed parameters adjacent to each other + : width_(width), height_(height) + {} + + Color &at(std::size_t cur_x, std::size_t cur_y) { return pixels.at(width_ * cur_y + cur_x); } + + void ComputeRequirement() override + { + requirement_ = ftxui::Requirement{ + .min_x = static_cast(width_), .min_y = static_cast(height_ / 2), .selected_box{ 0, 0, 0, 0 } + }; + } + + void Render(ftxui::Screen &screen) override + { + for (std::size_t cur_x = 0; cur_x < width_; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height_ / 2; ++cur_y) { + auto &pixel = screen.PixelAt(box_.x_min + static_cast(cur_x), box_.y_min + static_cast(cur_y)); + pixel.character = "▄"; + const auto &top_color = at(cur_x, cur_y * 2); + const auto &bottom_color = at(cur_x, cur_y * 2 + 1); + pixel.background_color = ftxui::Color{ top_color.R.get(), top_color.G.get(), top_color.B.get() }; + pixel.foreground_color = ftxui::Color{ bottom_color.R.get(), bottom_color.G.get(), bottom_color.B.get() }; + } + } + } + + [[nodiscard]] auto width() const noexcept { return width_; } + + [[nodiscard]] auto height() const noexcept { return height_; } + + [[nodiscard]] auto &data() noexcept { return pixels; } + +private: + std::size_t width_; + std::size_t height_; + + std::vector pixels = std::vector(width_ * height_, Color{}); +}; + +void game_iteration_canvas() +{ + // this should probably have a `bitmap` helper function that does what cur_you expect + // similar to the other parts of FTXUI + auto bm = std::make_shared(50, 50);// NOLINT magic numbers + auto small_bm = std::make_shared(6, 6);// NOLINT magic numbers + + double fps = 0; + + std::size_t max_row = 0; + std::size_t max_col = 0; + + // to do, add total game time clock also, not just current elapsed time + auto game_iteration = [&](const std::chrono::steady_clock::duration elapsed_time) { + // in here we simulate however much game time has elapsed. Update animations, + // run character AI, whatever, update stats, etc + + // this isn't actually timing based for now, it's just updating the display however fast it can + fps = 1.0 + / (static_cast(std::chrono::duration_cast(elapsed_time).count()) + / 1'000'000.0);// NOLINT magic numbers + + for (std::size_t row = 0; row < max_row; ++row) { + for (std::size_t col = 0; col < bm->width(); ++col) { ++(bm->at(col, row).R); } + } + + for (std::size_t row = 0; row < bm->height(); ++row) { + for (std::size_t col = 0; col < max_col; ++col) { ++(bm->at(col, row).G); } + } + + // for the fun of it, let's have a second window doing interesting things + auto &small_bm_pixel = + small_bm->data().at(static_cast(elapsed_time.count()) % small_bm->data().size()); + + switch (elapsed_time.count() % 3) { + case 0: + small_bm_pixel.R += 11;// NOLINT Magic Number + break; + case 1: + small_bm_pixel.G += 11;// NOLINT Magic Number + break; + case 2: + small_bm_pixel.B += 11;// NOLINT Magic Number + break; + } + + + ++max_row; + if (max_row >= bm->height()) { max_row = 0; } + ++max_col; + if (max_col >= bm->width()) { max_col = 0; } + }; + + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + int counter = 0; + + auto last_time = std::chrono::steady_clock::now(); + + auto make_layout = [&] { + // This code actually processes the draw event + const auto new_time = std::chrono::steady_clock::now(); + + ++counter; + // we will dispatch to the game_iteration function, where the work happens + game_iteration(new_time - last_time); + last_time = new_time; + + // now actually draw the game elements + return ftxui::hbox({ bm | ftxui::border, + ftxui::vbox({ ftxui::text("Frame: " + std::to_string(counter)), + ftxui::text("FPS: " + std::to_string(fps)), + small_bm | ftxui::border }) }); + }; + + auto renderer = ftxui::Renderer(make_layout); + + + std::atomic refresh_ui_continue = true; + + // This thread exists to make sure that the event queue has an event to + // process at approximately a rate of 30 FPS + std::thread refresh_ui([&] { + while (refresh_ui_continue) { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1.0s / 30.0);// NOLINT magic numbers + screen.PostEvent(ftxui::Event::Custom); + } + }); + + screen.Loop(renderer); + + refresh_ui_continue = false; + refresh_ui.join(); +} + +// NOLINTNEXTLINE(bugprone-exception-escape) +int main(int argc, const char **argv) +{ + try { + CLI::App app{ fmt::format("{} version {}", myproject::cmake::project_name, myproject::cmake::project_version) }; + + std::optional message; + app.add_option("-m,--message", message, "A message to print back out"); + bool show_version = false; + app.add_flag("--version", show_version, "Show version information"); + + bool is_turn_based = false; + auto *turn_based = app.add_flag("--turn_based", is_turn_based); + + bool is_loop_based = false; + auto *loop_based = app.add_flag("--loop_based", is_loop_based); + + turn_based->excludes(loop_based); + loop_based->excludes(turn_based); + + + CLI11_PARSE(app, argc, argv); + + if (show_version) { + fmt::print("{}\n", myproject::cmake::project_version); + return EXIT_SUCCESS; + } + + if (is_turn_based) { + consequence_game(); + } else { + game_iteration_canvas(); + } + + } catch (const std::exception &e) { + spdlog::error("Unhandled exception in main: {}", e.what()); + } +} diff --git a/src/sample_library/CMakeLists.txt b/src/sample_library/CMakeLists.txt new file mode 100644 index 0000000..b75fe63 --- /dev/null +++ b/src/sample_library/CMakeLists.txt @@ -0,0 +1,27 @@ +include(GenerateExportHeader) + + +add_library(sample_library sample_library.cpp) + + + +add_library(myproject::sample_library ALIAS sample_library) + +target_link_libraries(sample_library PRIVATE myproject_options myproject_warnings) + +target_include_directories(sample_library ${WARNING_GUARD} PUBLIC $ + $) + +target_compile_features(sample_library PUBLIC cxx_std_20) + +set_target_properties( + sample_library + PROPERTIES VERSION ${PROJECT_VERSION} + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES) + +generate_export_header(sample_library EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/myproject/sample_library_export.hpp) + +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(sample_library PUBLIC SAMPLE_LIBRARY_STATIC_DEFINE) +endif() diff --git a/src/sample_library/sample_library.cpp b/src/sample_library/sample_library.cpp new file mode 100644 index 0000000..878deae --- /dev/null +++ b/src/sample_library/sample_library.cpp @@ -0,0 +1,13 @@ +#include + +int factorial(int input) noexcept +{ + int result = 1; + + while (input > 0) { + result *= input; + --input; + } + + return result; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0739f42 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,107 @@ +cmake_minimum_required(VERSION 3.15...3.23) + +project(CmakeConfigPackageTests LANGUAGES CXX) + +# ---- Test as standalone project the exported config package ---- + +if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) + enable_testing() + + find_package(myproject CONFIG REQUIRED) # for intro, project_options, ... + + if(NOT TARGET myproject_options) + message(FATAL_ERROR "Requiered config package not found!") + return() # be strictly paranoid for Template Janitor github action! CK + endif() +endif() + +# ---- Dependencies ---- + +include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) + +# Provide a simple smoke test to make sure that the CLI works and can display a --help message +add_test(NAME cli.has_help COMMAND intro --help) + +# Provide a test to verify that the version being reported from the application +# matches the version given to CMake. This will be important once you package +# your program. Real world shows that this is the kind of simple mistake that is easy +# to make, but also easy to test for. +add_test(NAME cli.version_matches COMMAND intro --version) +set_tests_properties(cli.version_matches PROPERTIES PASS_REGULAR_EXPRESSION "${PROJECT_VERSION}") + +add_executable(tests tests.cpp) +target_link_libraries( + tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) + +if(WIN32 AND BUILD_SHARED_LIBS) + add_custom_command( + TARGET tests + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND_EXPAND_LISTS) +endif() + +# automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX +# to whatever you want, or use different for different binaries +catch_discover_tests( + tests + TEST_PREFIX + "unittests." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "unittests." + OUTPUT_SUFFIX + .xml) + +# Add a file containing a set of constexpr tests +add_executable(constexpr_tests constexpr_tests.cpp) +target_link_libraries( + constexpr_tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) + +catch_discover_tests( + constexpr_tests + TEST_PREFIX + "constexpr." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "constexpr." + OUTPUT_SUFFIX + .xml) + +# Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when +# things go wrong with the constexpr testing +add_executable(relaxed_constexpr_tests constexpr_tests.cpp) +target_link_libraries( + relaxed_constexpr_tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) +target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + +catch_discover_tests( + relaxed_constexpr_tests + TEST_PREFIX + "relaxed_constexpr." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "relaxed_constexpr." + OUTPUT_SUFFIX + .xml) diff --git a/test/constexpr_tests.cpp b/test/constexpr_tests.cpp new file mode 100644 index 0000000..fe36db5 --- /dev/null +++ b/test/constexpr_tests.cpp @@ -0,0 +1,12 @@ +#include + +#include + +TEST_CASE("Factorials are computed with constexpr", "[factorial]") +{ + STATIC_REQUIRE(factorial_constexpr(0) == 1); + STATIC_REQUIRE(factorial_constexpr(1) == 1); + STATIC_REQUIRE(factorial_constexpr(2) == 2); + STATIC_REQUIRE(factorial_constexpr(3) == 6); + STATIC_REQUIRE(factorial_constexpr(10) == 3628800); +} diff --git a/test/tests.cpp b/test/tests.cpp new file mode 100644 index 0000000..5b632e2 --- /dev/null +++ b/test/tests.cpp @@ -0,0 +1,14 @@ +#include + + +#include + + +TEST_CASE("Factorials are computed", "[factorial]") +{ + REQUIRE(factorial(0) == 1); + REQUIRE(factorial(1) == 1); + REQUIRE(factorial(2) == 2); + REQUIRE(factorial(3) == 6); + REQUIRE(factorial(10) == 3628800); +}