├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── bench └── run.cpp ├── cmake └── mason.cmake ├── codecov.yml ├── include ├── hello_world.hpp └── hello_world │ ├── exclaim.hpp │ ├── expensive.hpp │ └── version.hpp ├── scripts ├── clang-tidy.sh ├── coverage.sh ├── format.sh ├── liftoff.sh └── setup.sh └── test └── test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # Mapbox.Variant C/C+ style 3 | Language: Cpp 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 0 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: false 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 4 88 | UseTab: Never 89 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # clang-analyzer-core.uninitialized.UndefReturn can be removed once LLVM is upgrade to fix 3 | # https://bugs.llvm.org/show_bug.cgi?id=39201 4 | Checks: '*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-llvmlibc*,-cppcoreguidelines-macro-usage,-fuchsia*,-clang-analyzer-core.uninitialized.UndefReturn' 5 | WarningsAsErrors: '*' 6 | HeaderFilterRegex: '\/include\/' 7 | AnalyzeTemporaryDtors: false 8 | CheckOptions: 9 | - key: google-readability-braces-around-statements.ShortStatementLines 10 | value: '1' 11 | - key: google-readability-function-size.StatementThreshold 12 | value: '800' 13 | - key: google-readability-namespace-comments.ShortNamespaceLines 14 | value: '10' 15 | - key: google-readability-namespace-comments.SpacesBeforeComments 16 | value: '2' 17 | - key: modernize-loop-convert.MaxCopySize 18 | value: '16' 19 | - key: modernize-loop-convert.MinConfidence 20 | value: reasonable 21 | - key: modernize-loop-convert.NamingStyle 22 | value: CamelCase 23 | - key: modernize-pass-by-value.IncludeStyle 24 | value: llvm 25 | - key: modernize-replace-auto-ptr.IncludeStyle 26 | value: llvm 27 | - key: modernize-use-nullptr.NullMacros 28 | value: 'NULL' 29 | ... 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: mapbox/hpp-skel 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-22.04 13 | permissions: 14 | id-token: write 15 | contents: read 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | cxx: ['g++-4.9', 'g++-5', 'clang++'] 21 | cmake-version: ['3.15.2'] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Install packages 26 | run: | 27 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 28 | sudo apt update 29 | sudo apt-get install -y libstdc++6 libstdc++-5-dev g++-4.9 g++-5 30 | 31 | - name: Install and setup 32 | run: | 33 | ./scripts/setup.sh --config local.env 34 | source local.env 35 | mason install cmake ${{ matrix.cmake-version }} 36 | mason link cmake ${{ matrix.cmake-version }} 37 | which cmake 38 | 39 | - name: Run tests 40 | run: | 41 | make release 42 | make test 43 | make clean 44 | make debug 45 | make test 46 | make clean 47 | env: 48 | CXX: ${{ matrix.cxx }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | mason_packages 3 | cmake-build 4 | *.profdata 5 | *.profraw 6 | .toolchain 7 | .mason 8 | local.env -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 11/17/2017 2 | 3 | * Update contributing and changelog 4 | 5 | # 11/16/2017 6 | 7 | * Liftoff [docs and fix](https://github.com/mapbox/hpp-skel/pull/57) 8 | * ]iwyu fix](https://github.com/mapbox/hpp-skel/pull/59) 9 | 10 | # 10/30/2017 11 | 12 | * [Add docs](https://github.com/mapbox/hpp-skel/pull/56/files) for how to set sanitizer flags 13 | 14 | # 10/23/2017 15 | 16 | * Do not [sort includes with clang format](https://github.com/mapbox/hpp-skel/pull/55) 17 | 18 | # 10/20/2017 19 | 20 | * Add [CC0 license](https://github.com/mapbox/hpp-skel/pull/54) 21 | 22 | # 10/17/2017 23 | 24 | * Add better [default warnings](https://github.com/mapbox/hpp-skel/pull/52) and update compiler flags per https://github.com/mapbox/cpp/issues/37#issuecomment-336200744 25 | 26 | # 10/12/2017 27 | 28 | * Add compiler flags/warnings per https://github.com/mapbox/cpp/issues/37 29 | 30 | # 09/12/2017 31 | 32 | * Add multithread benchmarking: 33 | * inline headers 34 | * collapse both benchmarks into one run.cpp 35 | * call binary directly from Makefile 36 | * [add ignores to codecov](https://github.com/mapbox/hpp-skel/pull/49) 37 | * [clang format fixes](https://github.com/mapbox/hpp-skel/pull/50) 38 | * Add clant-tidy NOLINT 39 | * anti-optimization and clobber methods 40 | 41 | # 08/25/2017 42 | 43 | * Add clang-format 44 | * Transition tests to use catch 45 | * [Port to cmake](https://github.com/mapbox/hpp-skel/pull/24) 46 | * [Add setup.sh and local coverage reporting](https://github.com/mapbox/hpp-skel/pull/27) 47 | * Add [first iteration](https://github.com/mapbox/hpp-skel/pull/29) of "liftoff" script 48 | * [Glob tests in cmake](https://github.com/mapbox/hpp-skel/pull/30) for added flexibility 49 | * [Disable OSX testing](https://github.com/mapbox/hpp-skel/pull/35) for now 50 | 51 | # 08/23/2017 52 | 53 | * move include directory up a level, now just /include/hello_world.cpp 54 | * update test directory structure to just use test.cpp instead of splitting into unit tests 55 | * default to Debug build, standardize build location to /build/Release and /build/Debug [#18](https://github.com/mapbox/hpp-skel/issues/18) 56 | * `make` command builds by default, tests are run with `make test` 57 | * add a version file [#19](https://github.com/mapbox/hpp-skel/issues/19) 58 | 59 | ## 08/25/2016 60 | 61 | * nest hello_world within include directory named after library 62 | 63 | ## 08/18/2016 64 | 65 | * add debug/release builds + more aggressive warnings 66 | * default to Release 67 | * Create test alias 68 | * c++11 as default 69 | * add `include_dirs.js` for NPM publishing and requiring 70 | 71 | ## 07/20/2016 72 | 73 | * the hpp-skel is born :baby: 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(hpp_skel LANGUAGES CXX) 3 | set(CMAKE_CXX_STANDARD 14) 4 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 5 | set(CMAKE_CXX_EXTENSIONS OFF) 6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 7 | 8 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/mason.cmake) 9 | 10 | option(WERROR "Add -Werror flag to build (turns warnings into errors)" ON) 11 | 12 | # configure optimization 13 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 14 | set(OPTIMIZATION_FLAGS "-O0 -DDEBUG") 15 | message("-- Configuring debug build") 16 | else() 17 | set(OPTIMIZATION_FLAGS "-O3 -DNDEBUG") 18 | message("-- Configuring release build") 19 | endif() 20 | 21 | # Enable extra warnings to adhere to https://github.com/mapbox/cpp/issues/37 22 | set(DESIRED_WARNINGS "-Wall -Wextra -Wconversion -Wunreachable-code -Wuninitialized -pedantic-errors -Wold-style-cast -Wno-error=unused-variable -Wshadow -Wfloat-equal -Weffc++") 23 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 24 | set(DESIRED_WARNINGS "${DESIRED_WARNINGS} -Wmost") 25 | endif() 26 | 27 | # Note: -D_GLIBCXX_USE_CXX11_ABI=0 is needed to support mason packages that are precompiled libs 28 | # Currently we only depend on a header only library, but this will help avoid issues when more libs are added via mason 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPTIMIZATION_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0 ${DESIRED_WARNINGS}") 30 | 31 | if (WERROR) 32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") 33 | endif() 34 | 35 | # mason_use is a mason function within the mason.cmake file and provides ready-to-go vars, like "STATIC_LIBS" and "INCLUDE_DIRS" 36 | mason_use(catch VERSION 2.12.1 HEADER_ONLY) 37 | include_directories(SYSTEM ${MASON_PACKAGE_catch_INCLUDE_DIRS}) 38 | 39 | mason_use(benchmark VERSION 1.4.1) 40 | include_directories(SYSTEM ${MASON_PACKAGE_benchmark_INCLUDE_DIRS}) 41 | 42 | include_directories("${PROJECT_SOURCE_DIR}/include") 43 | 44 | file(GLOB TEST_SOURCES test/*.cpp) 45 | add_executable(unit-tests ${TEST_SOURCES}) 46 | 47 | # libbenchmark.a supports threads and therefore needs pthread support 48 | find_package(Threads REQUIRED) 49 | file(GLOB BENCH_SOURCES bench/*.cpp) 50 | add_executable(bench-tests ${BENCH_SOURCES}) 51 | 52 | # link benchmark static library to the bench-tests binary so the bench tests know where to find the benchmark impl code 53 | target_link_libraries(bench-tests ${MASON_PACKAGE_benchmark_STATIC_LIBS} ${CMAKE_THREAD_LIBS_INIT}) 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for getting involved and contributing to the skel :tada: Below are a few things to setup when submitting a PR. 4 | 5 | ## Code comments 6 | 7 | If adding new code, be sure to include relevant code comments. Code comments are a great way for others to learn from your code. This is especially true within the skeleton, since it is made for learning. 8 | 9 | ## Update Documentation 10 | 11 | Be sure to update any documentation relevant to your change. This includes updating the [CHANGELOG.md](https://github.com/mapbox/hpp-skel/blob/master/CHANGELOG.md). 12 | 13 | ## Code Formatting 14 | 15 | We use [this script](/scripts/format.sh#L20) to install a consistent version of [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) to format the code base. The format is automatically checked via a Travis CI build as well. Run the following script locally to ensure formatting is ready to merge: 16 | 17 | make format 18 | 19 | We also use [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) as a C++ linter. Run the following command to lint and ensure your code is ready to merge: 20 | 21 | make tidy 22 | 23 | These commands are set from within [the Makefile](./Makefile). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Whether to turn compiler warnings into errors 3 | export WERROR ?= true 4 | export BUILD_DIR ?= cmake-build 5 | 6 | default: release 7 | 8 | release: 9 | mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Release -DWERROR=$(WERROR) && VERBOSE=1 cmake --build . 10 | 11 | debug: 12 | mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Debug -DWERROR=$(WERROR) && VERBOSE=1 cmake --build . 13 | 14 | test: 15 | @if [ -f ./$(BUILD_DIR)/unit-tests ]; then ./$(BUILD_DIR)/unit-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi 16 | 17 | bench: 18 | @if [ -f ./$(BUILD_DIR)/bench-tests ]; then ./$(BUILD_DIR)/bench-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi 19 | 20 | tidy: 21 | ./scripts/clang-tidy.sh 22 | 23 | coverage: 24 | ./scripts/coverage.sh 25 | 26 | clean: 27 | rm -rf ./$(BUILD_DIR) 28 | # remove remains from running 'make coverage' 29 | rm -f *.profraw 30 | rm -f *.profdata 31 | @echo "run 'make distclean' to also clear mason_packages, .mason, and .toolchain directories" 32 | 33 | distclean: clean 34 | rm -rf mason_packages 35 | # remove remains from running './scripts/setup.sh' 36 | rm -rf .mason 37 | rm -rf .toolchain 38 | rm -f local.env 39 | 40 | format: 41 | ./scripts/format.sh 42 | 43 | .PHONY: test bench -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Skeleton for C++ header-only libraries that can be included into other C++ projects. This repository itself is a helper library. To use it for a specific project, edit filenames and tests accordingly. 2 | 3 | [![Build Status](https://travis-ci.com/mapbox/hpp-skel.svg?branch=master)](https://travis-ci.com/mapbox/hpp-skel) 4 | [![codecov](https://codecov.io/gh/mapbox/hpp-skel/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/hpp-skel) 5 | 6 | ![dancing skel](https://raw.githubusercontent.com/mapbox/cpp/master/assets/hpp-skel-readme_blue.png) 7 | 8 | ## Usage 9 | 10 | ```cpp 11 | #include 12 | 13 | using namespace hello_world; 14 | 15 | // exclaim a string 16 | std::string value = exclaim("hello"); 17 | std::clog << value; // => hello! 18 | ``` 19 | 20 | ## Test 21 | 22 | ```shell 23 | # build test binaries 24 | make 25 | 26 | # run tests 27 | make test 28 | 29 | # run bench tests 30 | make bench 31 | ``` 32 | 33 | The default test binaries will be built in release mode. You can make Debug test binaries as well: 34 | 35 | ```shell 36 | make clean 37 | make debug 38 | make test 39 | ``` 40 | 41 | Enable additional sanitizers to catch hard-to-find bugs, for example: 42 | 43 | ```shell 44 | export LDFLAGS="-fsanitize=address,undefined,integer" 45 | export CXXFLAGS="-fsanitize=address,undefined,integer" 46 | 47 | make 48 | ``` 49 | 50 | ## Customize 51 | Easily use this skeleton as a starting off point. 52 | 53 | ### Start new project 54 | ``` 55 | # Clone hpp-skel locally 56 | 57 | git clone git@github.com:mapbox/hpp-skel.git 58 | cd hpp-skel/ 59 | 60 | # Create your new repo on GitHub and have the remote repo url handy for liftoff 61 | # Then run the liftoff script from within your local hpp-skel root directory. 62 | # 63 | # This will: 64 | # - prompt you for the new name of your project and the new remote repo url 65 | # - automatically create a new directory for your new project repo 66 | # - create a new branch called "hpp-skel-port" within your new repo directory 67 | # - add, commit, and push that branch to your new repo 68 | 69 | ./scripts/liftoff.sh 70 | 71 | ``` 72 | 73 | ### Add custom code 74 | Once your project has ported hpp-skel, follow these steps to integrate your own code: 75 | 76 | - Create a dir in `./include` to hold your custom code. See the example code within [`./include`](https://github.com/mapbox/hpp-skel/tree/master/include) for reference. 77 | - Create a new file within your new directory, and add your custom method or class. 78 | - Create a module header file (see [hello_world.hpp](https://github.com/mapbox/hpp-skel/blob/master/include/hello_world.hpp)), and `#include` your new method or class. Make sure this file matches the name of the directory you created in the first step above. 79 | - Run `make` and see what surprises await on your new journey :boat: 80 | - If it compiles succesfully, congrats :tada: If not, dont fret. Take a breath and read the error message. 81 | - To start putting your header lib to work, setup a test to make sure it is working as expected. 82 | 83 | ### Setup tests 84 | Since header-only libraries are _not_ normally compiled themselves, to test them you need to [#include](https://github.com/mapbox/cpp/blob/master/glossary.md#include) them in a `.cpp` file (aka a [translation unit](https://github.com/mapbox/cpp/blob/master/glossary.md#translation-unit)) to compile and run their code. We recommend using [Catch](https://github.com/catchorg/Catch2) to make writing this `.cpp` file easy. 85 | 86 | - Create a file in `/test` directory, and add the following (be sure to update relevant lines with the name of the header you created above): 87 | ``` cpp 88 | #include 89 | #define CATCH_CONFIG_MAIN 90 | #include 91 | 92 | TEST_CASE("test_my_header") 93 | { 94 | // Your test logic here 95 | } 96 | ``` 97 | - Fill in the TEST_CASE with relevant [Catch](https://github.com/catchorg/Catch2) logic (see [test.cpp](https://github.com/mapbox/hpp-skel/blob/master/test/test.cpp) for examples). 98 | - Tip: When calling your header method/class, make sure the namespace matches your header. For example 99 | ``` cpp 100 | // "hello_world" is the namespace 101 | // "exclaim" is the method 102 | 103 | std::string value = hello_world::exclaim("hello"); 104 | ``` 105 | - Run `make test` to compile and run your test 106 | 107 | ## Benchmarks 108 | This skeleton uses [Google Benchmark](https://github.com/google/benchmark) to measure performance, and includes a [couple benchmark tests](https://github.com/mapbox/hpp-skel/blob/master/bench/run.cpp) to get you up and running quickly: 109 | - `BM_exlaim()`: Pretty barebone, simply testing string creation 110 | - `BM_expensive()`: Expensive allocation of std::map, querying, and string comparison, therefore threads are busy. This benchmark accepts an arg to specify amount of work you'd like the script to do (how big the map will be, how many times to convert an int to a string, and how many times to run the map lookup) 111 | 112 | #### Some notes on Google Benchmark results: 113 | - Number of Iterations is automatically set, based on how many iterations it takes to obtain sufficient data for the bench. 114 | - Results provide both wall time and CPU time. You may notice, the more threads used for the expensive code (up until the number of available cores on the machine), the longer CPU time will be and the shorter wall time will be. This is because the more code is shared between threads, the faster it can run in total time; the more threads doing work, the more processes running, therefore longer CPU time. 115 | 116 | #### Compiler Optimization 117 | To obtain a true benchmark, it may be necessary to prevent the compiler from optimizing away a value or expression. The skeleton uses Google Benchmark's internal functions to manage this. See https://github.com/google/benchmark#preventing-optimisation for more details. 118 | 119 | ## Publishing 120 | 121 | We recommend publishing header files to [Mason](https://github.com/mapbox/mason), the C++ packaging manager. Binaries can be downloaded by project name and version number. In order to publish to Mason you must request the publish via a Pull Request to the [`scripts/` directory](https://github.com/mapbox/mason/tree/master/scripts) with your project materials. 122 | 123 | Mason packages can be downloaded to your project by using the `mason install` command. This is best set up in a Makefile ([example](https://github.com/mapbox/geometry.hpp/blob/23b7fe66b11a4b7830c797817efe19660806d851/Makefile#L10)). 124 | 125 | Of course, you can always copy and paste this repo into your vendor path for your project. :scissors: 126 | 127 | ## Versioning 128 | 129 | This library is semantically versioned using the /include/hello_world/version.cpp file. This defines a number of macros that can be used to check the current major, minor, or patch versions, as well as the full version string. 130 | 131 | Here's how a downstream client would check for a particular version to use specific API methods 132 | ```cpp 133 | #if HELLOWORLD_VERSION_MAJOR > 2 134 | // use version 2 api 135 | #else 136 | // use older verion apis 137 | #endif 138 | ``` 139 | 140 | Here's how to check the version string 141 | ```cpp 142 | std::cout << "version: " << HELLOWORLD_VERSION_STRING << "/n"; 143 | // => version: 0.2.0 144 | ``` 145 | 146 | And lastly, mathematically checking for a specific version: 147 | ```cpp 148 | #if HELLOWORLD_VERSION_CODE > 20001 149 | // use feature provided in v2.0.1 150 | #endif 151 | ``` 152 | 153 | # Contributing and License 154 | 155 | Contributors are welcome! :sparkles: This repo exists as a place to gather C++ Header Library knowledge that will benefit the larger community. Please contribute your knowledge if you'd like. 156 | 157 | hpp-skel is licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/). Attribution is not required, but definitely welcome! If your project uses this skeleton, please add the hpp-skel badge to your readme so that others can learn about the resource. 158 | 159 | [![badge](https://mapbox.s3.amazonaws.com/cpp-assets/hpp-skel-badge_blue.svg)](https://github.com/mapbox/hpp-skel) 160 | 161 | To include the badge, paste this into your README.md file: 162 | ``` 163 | [![badge](https://mapbox.s3.amazonaws.com/cpp-assets/hpp-skel-badge_blue.svg)](https://github.com/mapbox/hpp-skel) 164 | ``` 165 | 166 | See [CONTRIBUTING](CONTRIBUTING.md) and [LICENSE](LICENSE.md) for more info. 167 | -------------------------------------------------------------------------------- /bench/run.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void BM_expensive(benchmark::State& state) // NOLINT google-runtime-references 5 | { 6 | if (state.thread_index == 0) 7 | { 8 | // Setup code here. 9 | } 10 | while (state.KeepRunning()) 11 | { 12 | std::string value = hello_world::expensive(static_cast(state.range(0))); 13 | benchmark::DoNotOptimize(value.data()); 14 | benchmark::ClobberMemory(); 15 | } 16 | if (state.thread_index == 0) 17 | { 18 | // Teardown code here. 19 | } 20 | } 21 | 22 | static void BM_exclaim(benchmark::State& state) // NOLINT google-runtime-references 23 | { 24 | while (state.KeepRunning()) 25 | { 26 | std::string value = hello_world::exclaim("hello"); 27 | benchmark::DoNotOptimize(value.data()); 28 | benchmark::ClobberMemory(); 29 | } 30 | } 31 | 32 | auto main(int argc, char* argv[]) -> int 33 | { 34 | benchmark::RegisterBenchmark("BM_exclaim", BM_exclaim)->Threads(2)->Threads(4)->Threads(8); // NOLINT clang-analyzer-cplusplus.NewDeleteLeaks 35 | benchmark::RegisterBenchmark("BM_expensive", BM_expensive)->Threads(2)->Threads(4)->Threads(8)->Arg(1000000); // NOLINT clang-analyzer-cplusplus.NewDeleteLeaks 36 | benchmark::Initialize(&argc, argv); 37 | benchmark::RunSpecifiedBenchmarks(); 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /cmake/mason.cmake: -------------------------------------------------------------------------------- 1 | string(RANDOM LENGTH 16 MASON_INVOCATION) 2 | 3 | # Directory where Mason packages are located; typically ends with mason_packages 4 | if (NOT MASON_PACKAGE_DIR) 5 | set(MASON_PACKAGE_DIR "${CMAKE_SOURCE_DIR}/mason_packages") 6 | endif() 7 | 8 | # URL prefix of where packages are located. 9 | if (NOT MASON_REPOSITORY) 10 | set(MASON_REPOSITORY "https://mason-binaries.s3.amazonaws.com") 11 | endif() 12 | 13 | # Path to Mason executable 14 | if (NOT MASON_COMMAND) 15 | set(MASON_COMMAND "${CMAKE_SOURCE_DIR}/.mason/mason") 16 | endif() 17 | 18 | # Determine platform 19 | # we call uname -s manually here since 20 | # CMAKE_HOST_SYSTEM_NAME will not be defined before the project() call 21 | execute_process( 22 | COMMAND uname -s 23 | OUTPUT_VARIABLE UNAME_S 24 | OUTPUT_STRIP_TRAILING_WHITESPACE) 25 | 26 | if(NOT MASON_PLATFORM) 27 | if (UNAME_S STREQUAL "Darwin") 28 | set(MASON_PLATFORM "macos") 29 | else() 30 | set(MASON_PLATFORM "linux") 31 | endif() 32 | endif() 33 | 34 | 35 | # Determine platform version string 36 | if(MASON_PLATFORM STREQUAL "ios") 37 | set(MASON_PLATFORM_VERSION "8.0") # Deployment target version 38 | elseif(MASON_PLATFORM STREQUAL "android") 39 | if (ANDROID_ABI STREQUAL "armeabi") 40 | set(MASON_PLATFORM_VERSION "arm-v5-9") 41 | elseif(ANDROID_ABI STREQUAL "arm64-v8a") 42 | set(MASON_PLATFORM_VERSION "arm-v8-21") 43 | elseif(ANDROID_ABI STREQUAL "x86") 44 | set(MASON_PLATFORM_VERSION "x86-9") 45 | elseif(ANDROID_ABI STREQUAL "x86_64") 46 | set(MASON_PLATFORM_VERSION "x86-64-21") 47 | elseif(ANDROID_ABI STREQUAL "mips") 48 | set(MASON_PLATFORM_VERSION "mips-9") 49 | elseif(ANDROID_ABI STREQUAL "mips64") 50 | set(MASON_PLATFORM_VERSION "mips64-21") 51 | else() 52 | set(MASON_PLATFORM_VERSION "arm-v7-9") 53 | endif() 54 | elseif(NOT MASON_PLATFORM_VERSION) 55 | execute_process( 56 | COMMAND uname -m 57 | OUTPUT_VARIABLE MASON_PLATFORM_VERSION 58 | OUTPUT_STRIP_TRAILING_WHITESPACE) 59 | endif() 60 | 61 | if(MASON_PLATFORM STREQUAL "macos") 62 | set(MASON_PLATFORM "osx") 63 | endif() 64 | 65 | set(ENV{MASON_PLATFORM} "${MASON_PLATFORM}") 66 | set(ENV{MASON_PLATFORM_VERSION} "${MASON_PLATFORM_VERSION}") 67 | 68 | include(CMakeParseArguments) 69 | 70 | function(mason_use _PACKAGE) 71 | if(NOT _PACKAGE) 72 | message(FATAL_ERROR "[Mason] No package name given") 73 | endif() 74 | 75 | cmake_parse_arguments("" "HEADER_ONLY" "VERSION" "" ${ARGN}) 76 | 77 | if(_UNPARSED_ARGUMENTS) 78 | message(FATAL_ERROR "[Mason] mason_use() called with unrecognized arguments: ${_UNPARSED_ARGUMENTS}") 79 | endif() 80 | 81 | if(NOT _VERSION) 82 | message(FATAL_ERROR "[Mason] Specifying a version is required") 83 | endif() 84 | 85 | if(MASON_PACKAGE_${_PACKAGE}_INVOCATION STREQUAL "${MASON_INVOCATION}") 86 | # Check that the previous invocation of mason_use didn't select another version of this package 87 | if(NOT MASON_PACKAGE_${_PACKAGE}_VERSION STREQUAL ${_VERSION}) 88 | message(FATAL_ERROR "[Mason] Already using ${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_VERSION}. Cannot select version ${_VERSION}.") 89 | endif() 90 | else() 91 | if(_HEADER_ONLY) 92 | set(_PLATFORM_ID "headers") 93 | else() 94 | set(_PLATFORM_ID "${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}") 95 | endif() 96 | 97 | set(_SLUG "${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}") 98 | set(_INSTALL_PATH "${MASON_PACKAGE_DIR}/${_SLUG}") 99 | file(RELATIVE_PATH _INSTALL_PATH_RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${_INSTALL_PATH}") 100 | 101 | if(NOT EXISTS "${_INSTALL_PATH}") 102 | set(_CACHE_PATH "${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz") 103 | if (NOT EXISTS "${_CACHE_PATH}") 104 | # Download the package 105 | set(_URL "${MASON_REPOSITORY}/${_SLUG}.tar.gz") 106 | message(STATUS "[Mason] Downloading package ${_URL}...") 107 | 108 | set(_FAILED) 109 | set(_ERROR) 110 | # Note: some CMake versions are compiled without SSL support 111 | get_filename_component(_CACHE_DIR "${_CACHE_PATH}" DIRECTORY) 112 | file(MAKE_DIRECTORY "${_CACHE_DIR}") 113 | execute_process( 114 | COMMAND curl --retry 3 -s -f -S -L "${_URL}" -o "${_CACHE_PATH}.tmp" 115 | RESULT_VARIABLE _FAILED 116 | ERROR_VARIABLE _ERROR) 117 | if(_FAILED) 118 | message(FATAL_ERROR "[Mason] Failed to download ${_URL}: ${_ERROR}") 119 | else() 120 | # We downloaded to a temporary file to prevent half-finished downloads 121 | file(RENAME "${_CACHE_PATH}.tmp" "${_CACHE_PATH}") 122 | endif() 123 | endif() 124 | 125 | # Unpack the package 126 | message(STATUS "[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}...") 127 | file(MAKE_DIRECTORY "${_INSTALL_PATH}") 128 | execute_process( 129 | COMMAND ${CMAKE_COMMAND} -E tar xzf "${_CACHE_PATH}" 130 | WORKING_DIRECTORY "${_INSTALL_PATH}") 131 | endif() 132 | 133 | # Create a config file if it doesn't exist in the package 134 | # TODO: remove this once all packages have a mason.ini file 135 | if(NOT EXISTS "${_INSTALL_PATH}/mason.ini") 136 | # Change pkg-config files 137 | file(GLOB_RECURSE _PKGCONFIG_FILES "${_INSTALL_PATH}/*.pc") 138 | foreach(_PKGCONFIG_FILE IN ITEMS ${_PKGCONFIG_FILES}) 139 | file(READ "${_PKGCONFIG_FILE}" _PKGCONFIG_FILE_CONTENT) 140 | string(REGEX REPLACE "(^|\n)prefix=[^\n]*" "\\1prefix=${_INSTALL_PATH}" _PKGCONFIG_FILE_CONTENT "${_PKGCONFIG_FILE_CONTENT}") 141 | file(WRITE "${_PKGCONFIG_FILE}" "${_PKGCONFIG_FILE_CONTENT}") 142 | endforeach() 143 | 144 | if(NOT EXISTS "${MASON_COMMAND}") 145 | message(FATAL_ERROR "[Mason] Could not find Mason command at ${MASON_COMMAND}") 146 | endif() 147 | 148 | set(_FAILED) 149 | set(_ERROR) 150 | execute_process( 151 | COMMAND ${MASON_COMMAND} config ${_PACKAGE} ${_VERSION} 152 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 153 | OUTPUT_FILE "${_INSTALL_PATH}/mason.ini" 154 | RESULT_VARIABLE _FAILED 155 | ERROR_VARIABLE _ERROR) 156 | if(_FAILED) 157 | message(FATAL_ERROR "[Mason] Could not get configuration for package ${_PACKAGE} ${_VERSION}: ${_ERROR}") 158 | endif() 159 | endif() 160 | 161 | set(MASON_PACKAGE_${_PACKAGE}_PREFIX "${_INSTALL_PATH}" CACHE STRING "${_PACKAGE} ${_INSTALL_PATH}" FORCE) 162 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_PREFIX) 163 | 164 | # Load the configuration from the ini file 165 | file(STRINGS "${_INSTALL_PATH}/mason.ini" _CONFIG_FILE) 166 | foreach(_LINE IN LISTS _CONFIG_FILE) 167 | string(REGEX MATCH "^([a-z_]+) *= *" _KEY "${_LINE}") 168 | if (_KEY) 169 | string(LENGTH "${_KEY}" _KEY_LENGTH) 170 | string(SUBSTRING "${_LINE}" ${_KEY_LENGTH} -1 _VALUE) 171 | string(REGEX REPLACE ";.*$" "" _VALUE "${_VALUE}") # Trim trailing commas 172 | string(REPLACE "{prefix}" "${_INSTALL_PATH}" _VALUE "${_VALUE}") 173 | string(STRIP "${_VALUE}" _VALUE) 174 | string(REPLACE "=" "" _KEY "${_KEY}") 175 | string(STRIP "${_KEY}" _KEY) 176 | string(TOUPPER "${_KEY}" _KEY) 177 | if(_KEY STREQUAL "INCLUDE_DIRS" OR _KEY STREQUAL "STATIC_LIBS" ) 178 | separate_arguments(_VALUE) 179 | endif() 180 | set(MASON_PACKAGE_${_PACKAGE}_${_KEY} "${_VALUE}" CACHE STRING "${_PACKAGE} ${_KEY}" FORCE) 181 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_${_KEY}) 182 | endif() 183 | endforeach() 184 | 185 | # Compare version in the package to catch errors early on 186 | if(NOT _VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_VERSION) 187 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has version '${MASON_PACKAGE_${_PACKAGE}_VERSION}', but required '${_VERSION}'") 188 | endif() 189 | 190 | if(NOT _PACKAGE STREQUAL MASON_PACKAGE_${_PACKAGE}_NAME) 191 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has name '${MASON_PACKAGE_${_PACKAGE}_NAME}', but required '${_NAME}'") 192 | endif() 193 | 194 | if(NOT _HEADER_ONLY) 195 | if(NOT MASON_PLATFORM STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM) 196 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform '${MASON_PACKAGE_${_PACKAGE}_PLATFORM}', but required '${MASON_PLATFORM}'") 197 | endif() 198 | 199 | if(NOT MASON_PLATFORM_VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION) 200 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform version '${MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION}', but required '${MASON_PLATFORM_VERSION}'") 201 | endif() 202 | endif() 203 | 204 | # Concatenate the static libs and libraries 205 | set(_LIBRARIES) 206 | list(APPEND _LIBRARIES ${MASON_PACKAGE_${_PACKAGE}_STATIC_LIBS} ${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}) 207 | set(MASON_PACKAGE_${_PACKAGE}_LIBRARIES "${_LIBRARIES}" CACHE STRING "${_PACKAGE} _LIBRARIES" FORCE) 208 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARIES) 209 | 210 | if(NOT _HEADER_ONLY) 211 | string(REGEX MATCHALL "(^| +)-L *([^ ]+)" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}") 212 | string(REGEX REPLACE "(^| +)-L *" "\\1" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}") 213 | set(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" CACHE STRING "${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" FORCE) 214 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS) 215 | endif() 216 | 217 | # Store invocation ID to prevent different versions of the same package in one invocation 218 | set(MASON_PACKAGE_${_PACKAGE}_INVOCATION "${MASON_INVOCATION}" CACHE INTERNAL "${_PACKAGE} invocation ID" FORCE) 219 | endif() 220 | endfunction() 221 | 222 | macro(target_add_mason_package _TARGET _VISIBILITY _PACKAGE) 223 | if (NOT MASON_PACKAGE_${_PACKAGE}_INVOCATION) 224 | message(FATAL_ERROR "[Mason] Package ${_PACKAGE} has not been initialized yet") 225 | endif() 226 | 227 | target_include_directories(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_INCLUDE_DIRS}") 228 | target_compile_definitions(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_DEFINITIONS}") 229 | target_compile_options(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_OPTIONS}") 230 | target_link_libraries(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_LIBRARIES}") 231 | endmacro() 232 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "bench" 3 | - "test" -------------------------------------------------------------------------------- /include/hello_world.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_WORLD_HPP 2 | #define HELLO_WORLD_HPP 3 | 4 | /* The hello_world namespace. 5 | * 6 | * Hello World implements a standard namespace with a few available functions. 7 | */ 8 | #include "hello_world/exclaim.hpp" 9 | #include "hello_world/expensive.hpp" 10 | #include "hello_world/version.hpp" 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/hello_world/exclaim.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_WORLD_EXCLAIM_HPP 2 | #define HELLO_WORLD_EXCLAIM_HPP 3 | 4 | #include 5 | 6 | namespace hello_world { 7 | 8 | /* Exclaim a string. Part of the namespace. 9 | * @message the message you would like to exclaim. 10 | * 11 | * exclaim adds an exclamation point to the beginning and end of a string. 12 | * 13 | * @return your message with an exclamation point on each end. 14 | */ 15 | inline auto exclaim(const std::string& message) -> std::string 16 | { 17 | std::string response = message + "!"; 18 | return response; 19 | } 20 | 21 | } // namespace hello_world 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/hello_world/expensive.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_WORLD_EXPENSIVE_HPP 2 | #define HELLO_WORLD_EXPENSIVE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace hello_world { 8 | 9 | // Expensive allocation of std::map, querying, and string comparison, 10 | // therefore threads are busy. This example is primarily used for benchmarking. 11 | inline auto expensive(std::size_t work_to_do) -> std::string 12 | { 13 | 14 | std::map container; 15 | 16 | for (std::size_t i = 0; i < work_to_do; ++i) 17 | { 18 | container.emplace(i, std::to_string(i)); 19 | } 20 | 21 | for (std::size_t i = 0; i < work_to_do; ++i) 22 | { 23 | std::string const& item = container[i]; 24 | if (item != std::to_string(i)) 25 | { 26 | // Marked NOLINT to avoid clang-tidy cert-err60-cpp error which we cannot 27 | // avoid on some linux distros where std::runtime_error is not properly 28 | // marked noexcept. Details at https://www.securecoding.cert.org/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible 29 | throw std::runtime_error("Uh oh, this should never happen"); // NOLINT 30 | } 31 | } 32 | 33 | std::string result = "Expensive work is finished"; 34 | 35 | return result; 36 | } 37 | } // namespace hello_world 38 | #endif 39 | -------------------------------------------------------------------------------- /include/hello_world/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_WORLD_VERSION_HPP 2 | #define HELLO_WORLD_VERSION_HPP 3 | 4 | #pragma once 5 | 6 | /// The major version number 7 | #define HELLOWORLD_VERSION_MAJOR 1 8 | 9 | /// The minor version number 10 | #define HELLOWORLD_VERSION_MINOR 0 11 | 12 | /// The patch number 13 | #define HELLOWORLD_VERSION_PATCH 0 14 | 15 | /// The complete version number 16 | #define HELLOWORLD_VERSION_CODE (HELLOWORLD_VERSION_MAJOR * 10000 + HELLOWORLD_VERSION_MINOR * 100 + HELLOWORLD_VERSION_PATCH) 17 | 18 | /// Version number as string 19 | #define HELLOWORLD_VERSION_STRING "1.0.0" 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /scripts/clang-tidy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | # https://clang.llvm.org/extra/clang-tidy/ 7 | 8 | # to speed up re-runs, only re-create environment if needed 9 | if [[ ! -f local.env ]]; then 10 | # automatically setup environment 11 | ./scripts/setup.sh --config local.env 12 | fi 13 | 14 | # source the environment 15 | source local.env 16 | 17 | PATH_TO_CLANG_TIDY_SCRIPT="$(pwd)/mason_packages/.link/share/run-clang-tidy.py" 18 | 19 | # to speed up re-runs, only install clang-tidy if needed 20 | if [[ ! -f PATH_TO_CLANG_TIDY_SCRIPT ]]; then 21 | # The MASON_LLVM_RELEASE variable comes from `local.env` 22 | mason install clang-tidy ${MASON_LLVM_RELEASE} 23 | # We link the tools to make it easy to know ${PATH_TO_CLANG_TIDY_SCRIPT} 24 | mason link clang-tidy ${MASON_LLVM_RELEASE} 25 | fi 26 | 27 | # build the compile_commands.json file if it does not exist 28 | if [[ ! -f cmake-build/compile_commands.json ]]; then 29 | # the build automatically puts the compile commands in the ./build directory 30 | make 31 | 32 | fi 33 | 34 | # change into the build directory so that clang-tidy can find the files 35 | # at the right paths (since this is where the actual build happens) 36 | cd cmake-build 37 | ${PATH_TO_CLANG_TIDY_SCRIPT} -fix 38 | 39 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | # http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation 7 | # https://www.bignerdranch.com/blog/weve-got-you-covered/ 8 | 9 | # automatically setup environment 10 | 11 | ./scripts/setup.sh --config local.env 12 | source local.env 13 | 14 | make clean 15 | export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" 16 | export LDFLAGS="-fprofile-instr-generate" 17 | mason install llvm-cov ${MASON_LLVM_RELEASE} 18 | mason link llvm-cov ${MASON_LLVM_RELEASE} 19 | make debug 20 | rm -f *profraw 21 | rm -f *gcov 22 | rm -f *profdata 23 | LLVM_PROFILE_FILE="code-%p.profraw" make test 24 | CXX_MODULE="./cmake-build/unit-tests" 25 | llvm-profdata merge -output=code.profdata code-*.profraw 26 | llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color 27 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color 28 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html 29 | echo "open /tmp/coverage.html for HTML version of this report" 30 | -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | : ' 7 | 8 | Runs clang-format on the code in include/ 9 | 10 | Return `1` if there are files to be formatted, and automatically formats them. 11 | 12 | Returns `0` if everything looks properly formatted. 13 | 14 | ' 15 | # Set up the environment by installing mason and clang++ 16 | ./scripts/setup.sh --config local.env 17 | source local.env 18 | 19 | # Add clang-format as a dep 20 | mason install clang-format ${MASON_LLVM_RELEASE} 21 | mason link clang-format ${MASON_LLVM_RELEASE} 22 | 23 | # Run clang-format on all cpp and hpp files in the /src directory 24 | find include/ bench/ test/ -type f -name '*.hpp' -or -name '*.cpp' \ 25 | | xargs -I{} clang-format -i -style=file {} 26 | 27 | # Print list of modified files 28 | dirty=$(git ls-files --modified include/ bench/ test/) 29 | 30 | if [[ $dirty ]]; then 31 | echo "The following files have been modified:" 32 | echo $dirty 33 | exit 1 34 | else 35 | exit 0 36 | fi -------------------------------------------------------------------------------- /scripts/liftoff.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # First create new repo on GitHub and copy the SSH repo url 6 | # Then run "./scripts/liftoff.sh" from within your local hpp-skel root directory 7 | # and it will create your new local project repo side by side with hpp-skel directory 8 | 9 | echo "What is the name of your new project? " 10 | read name 11 | echo "What is the remote repo url for your new project? " 12 | read url 13 | 14 | mkdir ../$name 15 | cp -R ../hpp-skel/. ../$name/ 16 | cd ../$name/ 17 | rm -rf .git 18 | git init 19 | 20 | git checkout -b hpp-skel-port 21 | git add . 22 | git commit -m "Port from hpp-skel" 23 | git remote add origin $url 24 | git push -u origin hpp-skel-port 25 | 26 | # Perhaps useful for fresh start 27 | # rm -rf include/hello_world 28 | # rm -rf include/hello_world.hpp 29 | # cp /dev/null CHANGELOG.md 30 | # cp /dev/null README.md -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | export MASON_RELEASE="${_MASON_RELEASE:-v0.23.0}" 7 | export MASON_LLVM_RELEASE="${_MASON_LLVM_RELEASE:-11.0.0}" 8 | 9 | PLATFORM=$(uname | tr A-Z a-z) 10 | if [[ ${PLATFORM} == 'darwin' ]]; then 11 | PLATFORM="osx" 12 | fi 13 | 14 | MASON_URL="https://s3.amazonaws.com/mason-binaries/${PLATFORM}-$(uname -m)" 15 | 16 | llvm_toolchain_dir="$(pwd)/.toolchain" 17 | 18 | function run() { 19 | local config=${1} 20 | # unbreak bash shell due to rvm bug on osx: https://github.com/direnv/direnv/issues/210#issuecomment-203383459 21 | # this impacts any usage of scripts that are source'd (like this one) 22 | if [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then 23 | echo 'shell_session_update() { :; }' > ~/.direnvrc 24 | fi 25 | 26 | # 27 | # COMPILER TOOLCHAIN 28 | # 29 | 30 | # We install clang++ without the mason client for a couple reasons: 31 | # 1) decoupling makes it viable to use a custom branch of mason that might 32 | # modify the upstream s3 bucket in a such a way that does not give 33 | # it access to build tools like clang++ 34 | # 2) Allows us to short-circuit and use a global clang++ install if it 35 | # is available to save space for local builds. 36 | GLOBAL_CLANG="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/clang++/${MASON_LLVM_RELEASE}" 37 | GLOBAL_LLVM="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/llvm/${MASON_LLVM_RELEASE}" 38 | if [[ -d ${GLOBAL_LLVM} ]]; then 39 | echo "Detected '${GLOBAL_LLVM}/bin/clang++', using it" 40 | local llvm_toolchain_dir=${GLOBAL_LLVM} 41 | elif [[ -d ${GLOBAL_CLANG} ]]; then 42 | echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it" 43 | local llvm_toolchain_dir=${GLOBAL_CLANG} 44 | elif [[ ! -d ${llvm_toolchain_dir} ]]; then 45 | BINARY="${MASON_URL}/clang++/${MASON_LLVM_RELEASE}.tar.gz" 46 | echo "Downloading ${BINARY}" 47 | mkdir -p ${llvm_toolchain_dir} 48 | curl -sSfL ${BINARY} | tar --gunzip --extract --strip-components=1 --directory=${llvm_toolchain_dir} 49 | fi 50 | 51 | # 52 | # MASON 53 | # 54 | 55 | function setup_mason() { 56 | local install_dir=${1} 57 | local mason_release=${2} 58 | mkdir -p ${install_dir} 59 | curl -sSfL https://github.com/mapbox/mason/archive/${mason_release}.tar.gz | tar --gunzip --extract --strip-components=1 --directory=${install_dir} 60 | } 61 | 62 | setup_mason $(pwd)/.mason ${MASON_RELEASE} 63 | 64 | # 65 | # ENV SETTINGS 66 | # 67 | 68 | echo "export PATH=${llvm_toolchain_dir}/bin:$(pwd)/.mason:$(pwd)/mason_packages/.link/bin:"'${PATH}' > ${config} 69 | echo "export CXX=${CXX:-${llvm_toolchain_dir}/bin/clang++}" >> ${config} 70 | echo "export MASON_RELEASE=${MASON_RELEASE}" >> ${config} 71 | echo "export MASON_LLVM_RELEASE=${MASON_LLVM_RELEASE}" >> ${config} 72 | # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso 73 | RT_BASE=${llvm_toolchain_dir}/lib/clang/${MASON_LLVM_RELEASE}/lib/$(uname | tr A-Z a-z)/libclang_rt 74 | if [[ $(uname -s) == 'Darwin' ]]; then 75 | RT_PRELOAD=${RT_BASE}.asan_osx_dynamic.dylib 76 | else 77 | RT_PRELOAD=${RT_BASE}.asan-x86_64.so 78 | fi 79 | echo "export MASON_LLVM_RT_PRELOAD=${RT_PRELOAD}" >> ${config} 80 | SUPPRESSION_FILE="/tmp/leak_suppressions.txt" 81 | # Add suppressions as needed 82 | #echo "leak:__strdup" > ${SUPPRESSION_FILE} 83 | echo "export ASAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config} 84 | echo "export MSAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config} 85 | echo "export UBSAN_OPTIONS=print_stacktrace=1" >> ${config} 86 | if [[ -f ${SUPPRESSION_FILE} ]]; then 87 | echo "export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE}" >> ${config} 88 | fi 89 | echo "export ASAN_OPTIONS=symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1" >> ${config} 90 | echo 'export MASON_SANITIZE="-fsanitize=address,undefined -fno-sanitize=vptr,function"' >> ${config} 91 | echo 'export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"' >> ${config} 92 | echo 'export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}"' >> ${config} 93 | 94 | exit 0 95 | } 96 | 97 | function usage() { 98 | >&2 echo "Usage" 99 | >&2 echo "" 100 | >&2 echo "$ ./scripts/setup.sh --config local.env" 101 | >&2 echo "$ source local.env" 102 | >&2 echo "" 103 | exit 1 104 | } 105 | 106 | if [[ ! ${1:-} ]]; then 107 | usage 108 | fi 109 | 110 | # https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash 111 | for i in "$@" 112 | do 113 | case $i in 114 | --config) 115 | if [[ ! ${2:-} ]]; then 116 | usage 117 | fi 118 | shift 119 | run $@ 120 | ;; 121 | -h | --help) 122 | usage 123 | shift 124 | ;; 125 | *) 126 | usage 127 | ;; 128 | esac 129 | done 130 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define CATCH_CONFIG_MAIN 3 | #include 4 | 5 | TEST_CASE("test version") 6 | { 7 | REQUIRE(HELLOWORLD_VERSION_STRING == std::string("1.0.0")); 8 | } 9 | 10 | TEST_CASE("test_exclaim") 11 | { 12 | std::string value = hello_world::exclaim("hello"); 13 | REQUIRE(value == std::string("hello!")); 14 | } 15 | 16 | TEST_CASE("test_expensive") 17 | { 18 | std::size_t work = 100; 19 | std::string result = hello_world::expensive(work); 20 | REQUIRE(result == std::string("Expensive work is finished")); 21 | } --------------------------------------------------------------------------------