├── vcpkg.txt ├── tests └── Example │ ├── main.cpp │ ├── Example.cpp │ └── CMakeLists.txt ├── .gitignore ├── .clang-format ├── include └── Example │ └── Example.hpp ├── modules └── FindArgAgg.cmake ├── src ├── Example │ ├── Example.cpp │ └── CMakeLists.txt └── ExampleCli │ ├── CMakeLists.txt │ └── main.cpp ├── CMakeLists.txt ├── README.md └── .github └── workflows └── build.yml /vcpkg.txt: -------------------------------------------------------------------------------- 1 | argagg catch2 fmt -------------------------------------------------------------------------------- /tests/Example/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | temp/ 3 | install/ 4 | out/ 5 | .vscode/ 6 | .vs/ 7 | *.kdev4 8 | *~ 9 | *.zip 10 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | ColumnLimit: 120 3 | PointerAlignment: Left 4 | IndentWidth: 4 5 | TabWidth: 4 6 | UseTab: Never 7 | AccessModifierOffset: -4 8 | AllowShortFunctionsOnASingleLine: None -------------------------------------------------------------------------------- /tests/Example/Example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace Example; 5 | 6 | TEST_CASE("Foo") { 7 | Foo foo("."); 8 | const auto files = foo.get(); 9 | REQUIRE(files.size() > 0); 10 | } 11 | -------------------------------------------------------------------------------- /include/Example/Example.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Example { 7 | class Foo { 8 | public: 9 | explicit Foo(const std::filesystem::path& path); 10 | virtual ~Foo(); 11 | 12 | std::vector get() const; 13 | 14 | private: 15 | std::filesystem::path path; 16 | }; 17 | } // namespace Example 18 | -------------------------------------------------------------------------------- /modules/FindArgAgg.cmake: -------------------------------------------------------------------------------- 1 | include(FindPackageHandleStandardArgs) 2 | 3 | if(NOT TARGET ArgAgg) 4 | find_path(ARGAGG_INCLUDE_DIR NAMES argagg/argagg.hpp) 5 | mark_as_advanced(FORCE ARGAGG_INCLUDE_DIR) 6 | add_library(ArgAgg INTERFACE IMPORTED) 7 | set_target_properties(ArgAgg PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${ARGAGG_INCLUDE_DIR}) 8 | endif() 9 | 10 | find_package_handle_standard_args(ArgAgg DEFAULT_MSG ARGAGG_INCLUDE_DIR) 11 | -------------------------------------------------------------------------------- /src/Example/Example.cpp: -------------------------------------------------------------------------------- 1 | #include "../../include/Example/Example.hpp" 2 | #include 3 | 4 | using namespace Example; 5 | namespace fs = std::filesystem; 6 | 7 | Foo::Foo(const fs::path& path) : path(path) { 8 | } 9 | 10 | Foo::~Foo() = default; 11 | 12 | std::vector Foo::get() const { 13 | std::vector ret; 14 | for (const auto& p : fs::directory_iterator(path)) { 15 | ret.push_back(p); 16 | } 17 | return ret; 18 | } 19 | -------------------------------------------------------------------------------- /tests/Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | # Project source files 4 | file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") 5 | 6 | # Add the project source files 7 | add_executable(${PROJECT_NAME}Tests ${SOURCES}) 8 | set_target_properties(${PROJECT_NAME}Tests PROPERTIES CXX_STANDARD 17) 9 | add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests) 10 | 11 | # Find dependencies 12 | find_package(Catch2 REQUIRED) 13 | 14 | # Add dependencies to target 15 | target_link_libraries(${PROJECT_NAME}Tests PRIVATE ${PROJECT_NAME} Catch2::Catch2) 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(CMAKE_MODULE_PATH 3 | ${CMAKE_MODULE_PATH} 4 | "${CMAKE_CURRENT_LIST_DIR}/modules" 5 | ) 6 | 7 | project(Example) 8 | string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER) 9 | 10 | option(EXAMPLE_TESTS "Build ${PROJECT_NAME} tests" ON) 11 | 12 | set(CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "Select build type") 13 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel") 14 | 15 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/${PROJECT_NAME}) 16 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/${PROJECT_NAME}Cli) 17 | if(EXAMPLE_TESTS) 18 | enable_testing() 19 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests/${PROJECT_NAME}) 20 | endif() 21 | 22 | -------------------------------------------------------------------------------- /src/Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | # Project source files 4 | set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../include") 5 | file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") 6 | file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/../../include/${PROJECT_NAME}/*.hpp") 7 | 8 | # Add the project source files 9 | add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) 10 | set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) 11 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME_LOWER}) 12 | target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIR}) 13 | 14 | # Install target 15 | install(TARGETS ${PROJECT_NAME} 16 | EXPORT ${PROJECT_NAME} 17 | ARCHIVE DESTINATION lib 18 | LIBRARY DESTINATION lib 19 | RUNTIME DESTINATION bin 20 | ) 21 | 22 | # Install target 23 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../include/${PROJECT_NAME}" 24 | DESTINATION "include" FILES_MATCHING PATTERN "*.hpp" 25 | ) 26 | 27 | # Find dependencies 28 | find_package(fmt CONFIG REQUIRED) 29 | 30 | # Add dependencies to target 31 | target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt fmt::fmt-header-only) 32 | 33 | -------------------------------------------------------------------------------- /src/ExampleCli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | # Project source files 4 | file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") 5 | 6 | # Add the project source files 7 | add_executable(${PROJECT_NAME}Cli ${SOURCES}) 8 | set_target_properties(${PROJECT_NAME}Cli PROPERTIES OUTPUT_NAME ${PROJECT_NAME_LOWER}) 9 | set_target_properties(${PROJECT_NAME}Cli PROPERTIES CXX_STANDARD 17) 10 | 11 | # Get the version string based on the git tags 12 | # and add it as macro definition 13 | execute_process( 14 | COMMAND git describe --always 15 | OUTPUT_VARIABLE VERSION 16 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE 17 | ) 18 | if(NOT VERSION) 19 | set(VERSION "v1.0.0") 20 | endif() 21 | message("Setting ${PROJECT_NAME}Cli version to: ${VERSION}") 22 | target_compile_definitions(${PROJECT_NAME}Cli PRIVATE VERSION=${VERSION}) 23 | 24 | # Install target 25 | install(TARGETS ${PROJECT_NAME}Cli 26 | EXPORT ${PROJECT_NAME} 27 | ARCHIVE DESTINATION lib 28 | LIBRARY DESTINATION lib 29 | RUNTIME DESTINATION bin 30 | ) 31 | 32 | # Find dependencies 33 | find_package(ArgAgg REQUIRED) 34 | 35 | # Add dependencies to target 36 | target_link_libraries(${PROJECT_NAME}Cli PRIVATE ${PROJECT_NAME} ArgAgg) 37 | 38 | -------------------------------------------------------------------------------- /src/ExampleCli/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define xstr(a) str(a) 7 | #define str(a) #a 8 | 9 | #ifdef VERSION 10 | static const std::string version = xstr(VERSION); 11 | #else 12 | static const std::string version = "unknown"; 13 | #endif 14 | 15 | static argagg::parser argparser{{{"help", {"-h", "--help"}, "Shows this help message", 0}, 16 | {"version", {"-v", "--version"}, "Get the build version", 0}, 17 | {"foo", {"-f", "--foo"}, "Do the foo thing", 1}}}; 18 | 19 | using namespace Example; 20 | 21 | int main(const int argc, char* argv[]) { 22 | try { 23 | const auto args = argparser.parse(argc, argv); 24 | if (args["help"]) { 25 | std::cerr << argparser; 26 | } 27 | 28 | else if (args["version"]) { 29 | std::cout << version << std::endl; 30 | } 31 | 32 | else if (args["foo"]) { 33 | Foo foo(args["version"].as()); 34 | for (const auto& p : foo.get()) { 35 | std::cout << fmt::format("Found: {}", p.string()) << std::endl; 36 | } 37 | } 38 | 39 | else { 40 | std::cerr << argparser; 41 | return EXIT_FAILURE; 42 | } 43 | 44 | return EXIT_SUCCESS; 45 | 46 | } catch (std::exception& e) { 47 | std::cerr << e.what() << std::endl; 48 | return EXIT_FAILURE; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Vcpkg template with GitHub actions 2 | 3 | [![build](https://github.com/matusnovak/cpp-vcpkg-template/workflows/build/badge.svg)](https://github.com/matusnovak/cpp-vcpkg-template/actions) 4 | 5 | This is a minimum working template project for C++ with vcpkg and GitHub actions. This template uses the minimum amount of configuration to create a working C++ project out of the box. You don't need to configure anything (just rename `example` occurances to whatever you want), it just works. 6 | 7 | **Features:** 8 | 9 | * Uses CMake 10 | * Each commit is automatically compiled on Windows (x86 + x64), Linux, and OSX via GitHub actions. 11 | * Each new tag is automatically released into [GitHub Releases](https://github.com/matusnovak/cpp-vcpkg-template/releases) with the built binary, library, and header files. 12 | * Uses vcpkg to handle dependencies and also uses cache on the GitHub actions to cache vcpkg install directory. This is done based on the vcpkg.txt file via hashing. 13 | * Automatically creates a log on the release. This log is created from the last tag to the latest tag (latest tag = the tag currently being compiled). This ensures that the releases contain the all of commit logs between releases. 14 | * The example project is structured as: library + cli + tests 15 | * Everything is automatic, minimal configuration, works out of the box. 16 | * Releases are packaged with `bin`, `include`, and `lib` folders that contain their respective files. The `bin` contains the binary created in the `src/ExampleCli` folder. The `include` folder contains the `include` directory. The `lib` folder contains the static library created in `src/Example`. 17 | * Testing via Catch2 18 | 19 | ## F.A.Q 20 | 21 | #### _How do I use this?_ 22 | 23 | Fork (prefer to use the template button) into your GitHub account. The only thing you will need to do is to rename all occuranced of `example` word to whatever you want to name your new awesome project (unless you want your project to be called "example"). Note that you have to rename both `example` and `Example` occurances. That's it. Your GitHub actions will enable automatically. No additional configuration needed for the GitHub actions workflow. 24 | 25 | #### _Why another C++ template project?_ 26 | 27 | Find me a template project that does not have 10K lines of unnecessary configuration, that works out of the box, publishes the artifacts to GitHub releases with automatic changelog, uses no scripting language to do it, and is configured to automatically compiled to Windows (x64 + x86) + Linux + OSX. 28 | 29 | #### _Why the static library and the CLI target?_ 30 | 31 | The `ExampleCli` is just an example to create a binary executable. I wanted to create a C++ example project where the output is a binary executable, but to have tests at the same time. You can compile the tests with the same source files as the output executable, but you would be compiling the source files twice -> for creating the executable and for creating the tests. Splitting it into a static library and an executable ensures that the core logic is in the library which you can link in your tests. Reduces the compile time in big projects a lot. The "CLI" here is just an example usage of that binary. It can be a GUI app, whatever. 32 | 33 | #### _Why CMake?_ 34 | 35 | It's the best thing we have at the moment, plus it works with vcpkg, and works out of the box with Visual Studio, CLion, and many other editors. 36 | 37 | #### _What is vcpkg?_ 38 | 39 | A C++ library manager made by Microsoft: [https://github.com/microsoft/vcpkg](https://github.com/microsoft/vcpkg) 40 | 41 | 42 | ## License 43 | 44 | This is free and unencumbered software released into the public domain. 45 | 46 | Anyone is free to copy, modify, publish, use, compile, sell, or 47 | distribute this software, either in source code form or as a compiled 48 | binary, for any purpose, commercial or non-commercial, and by any 49 | means. 50 | 51 | In jurisdictions that recognize copyright laws, the author or authors 52 | of this software dedicate any and all copyright interest in the 53 | software to the public domain. We make this dedication for the benefit 54 | of the public at large and to the detriment of our heirs and 55 | successors. We intend this dedication to be an overt act of 56 | relinquishment in perpetuity of all present and future rights to this 57 | software under copyright law. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 60 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 61 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 62 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 63 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 64 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 65 | OTHER DEALINGS IN THE SOFTWARE. 66 | 67 | For more information, please refer to 68 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | tags: 7 | - '*' 8 | pull_request: 9 | branches: 10 | - 'master' 11 | 12 | jobs: 13 | build: 14 | name: ${{ matrix.name }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - name: 'Windows x64' 21 | os: windows-latest 22 | triplet: x64-windows 23 | vcpkg_dir: 'C:\vcpkg' 24 | suffix: 'windows-win32' 25 | generator: 'Visual Studio 16 2019' 26 | arch: '-A x64' 27 | - name: 'Windows x86' 28 | os: windows-latest 29 | triplet: x86-windows 30 | vcpkg_dir: 'C:\vcpkg' 31 | suffix: 'windows-win64' 32 | generator: 'Visual Studio 16 2019' 33 | arch: '-A Win32' 34 | - name: 'Linux x64' 35 | os: ubuntu-latest 36 | triplet: x64-linux 37 | suffix: 'linux-x86_64' 38 | vcpkg_dir: '/usr/local/share/vcpkg' 39 | generator: 'Unix Makefiles' 40 | arch: '' 41 | - name: 'Mac OSX x64' 42 | os: macos-latest 43 | triplet: x64-osx 44 | suffix: 'osx-x86_64' 45 | vcpkg_dir: '/usr/local/share/vcpkg' 46 | generator: 'Unix Makefiles' 47 | arch: '' 48 | 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v1 52 | with: 53 | submodules: true 54 | 55 | # # Enable this if you want specific version of vcpkg 56 | # # For example tag "2020.06" 57 | # - name: Configure Vcpkg 58 | # shell: bash 59 | # run: | 60 | # cd ${{ matrix.vcpkg_dir }} 61 | # git fetch origin --tags 62 | # git reset --hard 2020.06 63 | # if [ "$RUNNER_OS" == "Windows" ]; then 64 | # ./bootstrap-vcpkg.bat 65 | # else 66 | # ./bootstrap-vcpkg.sh 67 | # fi 68 | 69 | - name: Cache vcpkg 70 | uses: actions/cache@v2 71 | with: 72 | path: '${{ matrix.vcpkg_dir }}/installed' 73 | key: vcpkg-${{ matrix.triplet }}-${{ hashFiles('vcpkg.txt') }} 74 | restore-keys: | 75 | vcpkg-${{ matrix.triplet }}- 76 | 77 | - name: Install vcpkg packages 78 | shell: bash 79 | run: | 80 | vcpkg install --triplet ${{ matrix.triplet }} $(cat vcpkg.txt) 81 | 82 | - name: Configure 83 | shell: bash 84 | run: | 85 | mkdir build 86 | mkdir install 87 | if [ "$RUNNER_OS" == "Windows" ]; then 88 | cmake \ 89 | -B ./build \ 90 | -G "${{ matrix.generator }}" ${{ matrix.arch }} \ 91 | -DCMAKE_INSTALL_PREFIX=./install \ 92 | -DCMAKE_BUILD_TYPE=MinSizeRel \ 93 | -DEXAMPLE_TESTS=ON \ 94 | -DCMAKE_TOOLCHAIN_FILE=${{ matrix.vcpkg_dir }}/scripts/buildsystems/vcpkg.cmake \ 95 | . 96 | else 97 | if [ "$RUNNER_OS" == "Linux" ]; then 98 | export CC=/usr/bin/gcc-9 99 | export CXX=/usr/bin/g++-9 100 | fi 101 | cmake \ 102 | -B ./build \ 103 | -G "${{ matrix.generator }}" \ 104 | -DCMAKE_INSTALL_PREFIX=./install \ 105 | -DCMAKE_BUILD_TYPE=MinSizeRel \ 106 | -DEXAMPLE_TESTS=ON \ 107 | -DCMAKE_TOOLCHAIN_FILE=${{ matrix.vcpkg_dir }}/scripts/buildsystems/vcpkg.cmake \ 108 | . 109 | fi 110 | 111 | - name: Compile 112 | shell: bash 113 | run: | 114 | if [ "$RUNNER_OS" == "Windows" ]; then 115 | cmake --build ./build --target INSTALL --config MinSizeRel 116 | else 117 | cmake --build ./build --target install --config MinSizeRel 118 | fi 119 | 120 | - name: Tests 121 | shell: bash 122 | run: cd build && ctest -C MinSizeRel --verbose 123 | 124 | - name: List runtime dependencies 125 | shell: bash 126 | run: | 127 | if [ "$RUNNER_OS" == "Linux" ]; then 128 | ldd ./install/bin/example 129 | elif [ "$RUNNER_OS" == "macOS" ]; then 130 | otool -L ./install/bin/example 131 | fi 132 | 133 | - name: Package 134 | id: create_artifact 135 | shell: bash 136 | run: | 137 | mkdir release 138 | if [ "$RUNNER_OS" == "Windows" ]; then 139 | ls ./install 140 | 7z a -r example.zip ./install/* 141 | else 142 | cd ./install 143 | zip -r ./../example.zip * 144 | cd .. 145 | fi 146 | name=example-${{ matrix.suffix }}-$(git describe --always).zip 147 | mv -v ./example.zip release/${name} 148 | echo "::set-output name=name::${name}" 149 | echo "::set-output name=path::release/${name}" 150 | 151 | - name: Upload artifacts 152 | uses: actions/upload-artifact@v1 153 | with: 154 | name: Release 155 | path: release 156 | 157 | - name: Create Changelog 158 | id: create_changelog 159 | if: startsWith(github.ref, 'refs/tags/v') 160 | shell: bash 161 | run: | 162 | last_tag=$(git describe --tags --abbrev=0 @^ || true) 163 | if [ -z "$last_tag" ]; then 164 | git log --oneline --format="%C(auto) %h %s" > changelog.txt 165 | else 166 | git log --oneline --format="%C(auto) %h %s" ${last_tag}..@ > changelog.txt 167 | fi 168 | cat changelog.txt 169 | 170 | - name: Release 171 | uses: ncipollo/release-action@v1 172 | if: startsWith(github.ref, 'refs/tags/v') 173 | with: 174 | artifacts: ${{ steps.create_artifact.outputs.path }} 175 | allowUpdates: true 176 | artifactContentType: application/zip 177 | bodyFile: changelog.txt 178 | draft: false 179 | omitBodyDuringUpdate: true 180 | omitNameDuringUpdate: true 181 | prerelease: false 182 | token: ${{ secrets.GITHUB_TOKEN }} 183 | --------------------------------------------------------------------------------