├── .clang-format ├── .clang-tidy ├── .codespellrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── BUILDING.md ├── CMakeLists.txt ├── CMakePresets.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HACKING.md ├── README.md ├── cmake ├── coverage.cmake ├── dev-mode.cmake ├── docs-ci.cmake ├── docs.cmake ├── folders.cmake ├── install-config.cmake ├── install-rules.cmake ├── lint-targets.cmake ├── lint.cmake ├── prelude.cmake ├── project-is-top-level.cmake ├── spell-targets.cmake ├── spell.cmake └── variables.cmake ├── docs ├── Doxyfile.in ├── conf.py.in └── pages │ └── about.dox ├── include └── shared │ └── shared.hpp ├── source └── shared.cpp └── test ├── CMakeLists.txt └── source └── shared_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveAssignments: 7 | Enabled: false 8 | AcrossEmptyLines: false 9 | AcrossComments: false 10 | AlignCompound: false 11 | AlignFunctionPointers: false 12 | PadOperators: false 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | AlignFunctionPointers: false 19 | PadOperators: false 20 | AlignConsecutiveDeclarations: 21 | Enabled: false 22 | AcrossEmptyLines: false 23 | AcrossComments: false 24 | AlignCompound: false 25 | AlignFunctionPointers: false 26 | PadOperators: false 27 | AlignConsecutiveMacros: 28 | Enabled: false 29 | AcrossEmptyLines: false 30 | AcrossComments: false 31 | AlignCompound: false 32 | AlignFunctionPointers: false 33 | PadOperators: false 34 | AlignConsecutiveShortCaseStatements: 35 | Enabled: false 36 | AcrossEmptyLines: false 37 | AcrossComments: false 38 | AlignCaseColons: false 39 | AlignEscapedNewlines: DontAlign 40 | AlignOperands: DontAlign 41 | AlignTrailingComments: 42 | Kind: Never 43 | OverEmptyLines: 0 44 | AllowAllArgumentsOnNextLine: true 45 | AllowAllParametersOfDeclarationOnNextLine: true 46 | AllowBreakBeforeNoexceptSpecifier: OnlyWithParen 47 | AllowShortBlocksOnASingleLine: Empty 48 | AllowShortCaseLabelsOnASingleLine: false 49 | AllowShortCompoundRequirementOnASingleLine: true 50 | AllowShortEnumsOnASingleLine: false 51 | AllowShortFunctionsOnASingleLine: Inline 52 | AllowShortIfStatementsOnASingleLine: Never 53 | AllowShortLambdasOnASingleLine: All 54 | AllowShortLoopsOnASingleLine: false 55 | AlwaysBreakAfterDefinitionReturnType: None 56 | AlwaysBreakAfterReturnType: None 57 | AlwaysBreakBeforeMultilineStrings: true 58 | AlwaysBreakTemplateDeclarations: Yes 59 | AttributeMacros: 60 | - __capability 61 | BinPackArguments: false 62 | BinPackParameters: false 63 | BitFieldColonSpacing: Both 64 | BraceWrapping: 65 | AfterCaseLabel: false 66 | AfterClass: true 67 | AfterControlStatement: MultiLine 68 | AfterEnum: true 69 | AfterExternBlock: true 70 | AfterFunction: true 71 | AfterNamespace: true 72 | AfterObjCDeclaration: false 73 | AfterStruct: true 74 | AfterUnion: true 75 | BeforeCatch: false 76 | BeforeElse: false 77 | BeforeLambdaBody: true 78 | BeforeWhile: false 79 | IndentBraces: false 80 | SplitEmptyFunction: true 81 | SplitEmptyRecord: true 82 | SplitEmptyNamespace: true 83 | BreakAdjacentStringLiterals: false 84 | BreakAfterAttributes: Leave 85 | BreakAfterJavaFieldAnnotations: true 86 | BreakArrays: true 87 | BreakBeforeBinaryOperators: NonAssignment 88 | BreakBeforeConceptDeclarations: Always 89 | BreakBeforeBraces: Custom 90 | BreakBeforeInlineASMColon: OnlyMultiline 91 | BreakBeforeTernaryOperators: true 92 | BreakConstructorInitializers: BeforeComma 93 | BreakInheritanceList: BeforeComma 94 | BreakStringLiterals: true 95 | ColumnLimit: 80 96 | CommentPragmas: '^ IWYU pragma:' 97 | CompactNamespaces: false 98 | ConstructorInitializerIndentWidth: 4 99 | ContinuationIndentWidth: 4 100 | Cpp11BracedListStyle: true 101 | DerivePointerAlignment: false 102 | DisableFormat: false 103 | EmptyLineAfterAccessModifier: Never 104 | EmptyLineBeforeAccessModifier: LogicalBlock 105 | ExperimentalAutoDetectBinPacking: false 106 | FixNamespaceComments: true 107 | ForEachMacros: 108 | - foreach 109 | - Q_FOREACH 110 | - BOOST_FOREACH 111 | IfMacros: 112 | - KJ_IF_MAYBE 113 | IncludeBlocks: Regroup 114 | IncludeCategories: 115 | # Standard library headers come before anything else 116 | - Regex: '^<[a-z_]+>' 117 | Priority: -1 118 | SortPriority: 0 119 | CaseSensitive: false 120 | - Regex: '^<.+\.h(pp)?>' 121 | Priority: 1 122 | SortPriority: 0 123 | CaseSensitive: false 124 | - Regex: '^<.*' 125 | Priority: 2 126 | SortPriority: 0 127 | CaseSensitive: false 128 | - Regex: '.*' 129 | Priority: 3 130 | SortPriority: 0 131 | CaseSensitive: false 132 | IncludeIsMainRegex: '' 133 | IncludeIsMainSourceRegex: '' 134 | IndentAccessModifiers: false 135 | IndentCaseBlocks: false 136 | IndentCaseLabels: true 137 | IndentExternBlock: NoIndent 138 | IndentGotoLabels: true 139 | IndentPPDirectives: AfterHash 140 | IndentRequiresClause: true 141 | IndentWidth: 2 142 | IndentWrappedFunctionNames: false 143 | InsertBraces: true 144 | InsertNewlineAtEOF: true 145 | InsertTrailingCommas: Wrapped 146 | IntegerLiteralSeparator: 147 | Binary: 0 148 | BinaryMinDigits: 0 149 | Decimal: 0 150 | DecimalMinDigits: 0 151 | Hex: 0 152 | HexMinDigits: 0 153 | JavaScriptQuotes: Double 154 | JavaScriptWrapImports: true 155 | KeepEmptyLinesAtTheStartOfBlocks: false 156 | KeepEmptyLinesAtEOF: false 157 | LambdaBodyIndentation: Signature 158 | LineEnding: LF 159 | MacroBlockBegin: '' 160 | MacroBlockEnd: '' 161 | MaxEmptyLinesToKeep: 1 162 | NamespaceIndentation: None 163 | ObjCBinPackProtocolList: Never 164 | ObjCBlockIndentWidth: 2 165 | ObjCBreakBeforeNestedBlockParam: true 166 | ObjCSpaceAfterProperty: false 167 | ObjCSpaceBeforeProtocolList: true 168 | PackConstructorInitializers: Never 169 | PenaltyBreakAssignment: 2 170 | PenaltyBreakBeforeFirstCallParameter: 1 171 | PenaltyBreakComment: 300 172 | PenaltyBreakFirstLessLess: 120 173 | PenaltyBreakOpenParenthesis: 0 174 | PenaltyBreakScopeResolution: 500 175 | PenaltyBreakString: 1000 176 | PenaltyBreakTemplateDeclaration: 10 177 | PenaltyExcessCharacter: 1000000 178 | PenaltyIndentedWhitespace: 0 179 | PenaltyReturnTypeOnItsOwnLine: 200 180 | PointerAlignment: Left 181 | PPIndentWidth: -1 182 | QualifierAlignment: Leave 183 | RawStringFormats: 184 | - Language: Cpp 185 | Delimiters: 186 | - cc 187 | - CC 188 | - cpp 189 | - Cpp 190 | - CPP 191 | - 'c++' 192 | - 'C++' 193 | CanonicalDelimiter: '' 194 | BasedOnStyle: google 195 | - Language: TextProto 196 | Delimiters: 197 | - pb 198 | - PB 199 | - proto 200 | - PROTO 201 | EnclosingFunctions: 202 | - EqualsProto 203 | - EquivToProto 204 | - PARSE_PARTIAL_TEXT_PROTO 205 | - PARSE_TEST_PROTO 206 | - PARSE_TEXT_PROTO 207 | - ParseTextOrDie 208 | - ParseTextProtoOrDie 209 | - ParseTestProto 210 | - ParsePartialTestProto 211 | CanonicalDelimiter: '' 212 | BasedOnStyle: google 213 | ReferenceAlignment: Pointer 214 | ReflowComments: true 215 | RemoveBracesLLVM: false 216 | RemoveParentheses: Leave 217 | RemoveSemicolon: false 218 | RequiresClausePosition: OwnLine 219 | RequiresExpressionIndentation: OuterScope 220 | SeparateDefinitionBlocks: Always 221 | ShortNamespaceLines: 1 222 | SkipMacroDefinitionBody: false 223 | SortIncludes: CaseSensitive 224 | SortJavaStaticImport: Before 225 | SortUsingDeclarations: LexicographicNumeric 226 | SpaceAfterCStyleCast: false 227 | SpaceAfterLogicalNot: false 228 | SpaceAfterTemplateKeyword: false 229 | SpaceAroundPointerQualifiers: Default 230 | SpaceBeforeAssignmentOperators: true 231 | SpaceBeforeCaseColon: false 232 | SpaceBeforeCpp11BracedList: true 233 | SpaceBeforeCtorInitializerColon: true 234 | SpaceBeforeInheritanceColon: true 235 | SpaceBeforeJsonColon: false 236 | SpaceBeforeParens: ControlStatementsExceptControlMacros 237 | SpaceBeforeParensOptions: 238 | AfterControlStatements: true 239 | AfterForeachMacros: false 240 | AfterFunctionDefinitionName: false 241 | AfterFunctionDeclarationName: false 242 | AfterIfMacros: false 243 | AfterOverloadedOperator: false 244 | AfterPlacementOperator: true 245 | AfterRequiresInClause: false 246 | AfterRequiresInExpression: false 247 | BeforeNonEmptyParentheses: false 248 | SpaceBeforeRangeBasedForLoopColon: true 249 | SpaceBeforeSquareBrackets: false 250 | SpaceInEmptyBlock: false 251 | SpacesBeforeTrailingComments: 2 252 | SpacesInAngles: Never 253 | SpacesInContainerLiterals: false 254 | SpacesInLineCommentPrefix: 255 | Minimum: 1 256 | Maximum: -1 257 | SpacesInParens: Never 258 | SpacesInParensOptions: 259 | InCStyleCasts: false 260 | InConditionalStatements: false 261 | InEmptyParentheses: false 262 | Other: false 263 | SpacesInSquareBrackets: false 264 | Standard: Auto 265 | StatementAttributeLikeMacros: 266 | - Q_EMIT 267 | StatementMacros: 268 | - Q_UNUSED 269 | - QT_REQUIRE_VERSION 270 | TabWidth: 8 271 | UseTab: Never 272 | VerilogBreakBetweenInstancePorts: true 273 | WhitespaceSensitiveMacros: 274 | - STRINGIZE 275 | - PP_STRINGIZE 276 | - BOOST_PP_STRINGIZE 277 | ... 278 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Enable ALL the things! Except not really 3 | # misc-non-private-member-variables-in-classes: the options don't do anything 4 | # modernize-use-nodiscard: too aggressive, attribute is situationally useful 5 | Checks: "*,\ 6 | -google-readability-todo,\ 7 | -altera-*,\ 8 | -fuchsia-*,\ 9 | fuchsia-multiple-inheritance,\ 10 | -llvm-header-guard,\ 11 | -llvm-include-order,\ 12 | -llvmlibc-*,\ 13 | -modernize-use-nodiscard,\ 14 | -misc-non-private-member-variables-in-classes" 15 | WarningsAsErrors: '' 16 | CheckOptions: 17 | - key: 'bugprone-argument-comment.StrictMode' 18 | value: 'true' 19 | # Prefer using enum classes with 2 values for parameters instead of bools 20 | - key: 'bugprone-argument-comment.CommentBoolLiterals' 21 | value: 'true' 22 | - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts' 23 | value: 'true' 24 | - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression' 25 | value: 'true' 26 | - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison' 27 | value: 'true' 28 | - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn' 29 | value: 'true' 30 | - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment' 31 | value: 'true' 32 | - key: 'readability-uniqueptr-delete-release.PreferResetCall' 33 | value: 'true' 34 | - key: 'cppcoreguidelines-init-variables.MathHeader' 35 | value: '' 36 | - key: 'cppcoreguidelines-narrowing-conversions.PedanticMode' 37 | value: 'true' 38 | - key: 'readability-else-after-return.WarnOnUnfixable' 39 | value: 'true' 40 | - key: 'readability-else-after-return.WarnOnConditionVariables' 41 | value: 'true' 42 | - key: 'readability-inconsistent-declaration-parameter-name.Strict' 43 | value: 'true' 44 | - key: 'readability-qualified-auto.AddConstToQualified' 45 | value: 'true' 46 | - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration' 47 | value: 'true' 48 | # These seem to be the most common identifier styles 49 | - key: 'readability-identifier-naming.AbstractClassCase' 50 | value: 'lower_case' 51 | - key: 'readability-identifier-naming.ClassCase' 52 | value: 'lower_case' 53 | - key: 'readability-identifier-naming.ClassConstantCase' 54 | value: 'lower_case' 55 | - key: 'readability-identifier-naming.ClassMemberCase' 56 | value: 'lower_case' 57 | - key: 'readability-identifier-naming.ClassMethodCase' 58 | value: 'lower_case' 59 | - key: 'readability-identifier-naming.ConstantCase' 60 | value: 'lower_case' 61 | - key: 'readability-identifier-naming.ConstantMemberCase' 62 | value: 'lower_case' 63 | - key: 'readability-identifier-naming.ConstantParameterCase' 64 | value: 'lower_case' 65 | - key: 'readability-identifier-naming.ConstantPointerParameterCase' 66 | value: 'lower_case' 67 | - key: 'readability-identifier-naming.ConstexprFunctionCase' 68 | value: 'lower_case' 69 | - key: 'readability-identifier-naming.ConstexprMethodCase' 70 | value: 'lower_case' 71 | - key: 'readability-identifier-naming.ConstexprVariableCase' 72 | value: 'lower_case' 73 | - key: 'readability-identifier-naming.EnumCase' 74 | value: 'lower_case' 75 | - key: 'readability-identifier-naming.EnumConstantCase' 76 | value: 'lower_case' 77 | - key: 'readability-identifier-naming.FunctionCase' 78 | value: 'lower_case' 79 | - key: 'readability-identifier-naming.GlobalConstantCase' 80 | value: 'lower_case' 81 | - key: 'readability-identifier-naming.GlobalConstantPointerCase' 82 | value: 'lower_case' 83 | - key: 'readability-identifier-naming.GlobalFunctionCase' 84 | value: 'lower_case' 85 | - key: 'readability-identifier-naming.GlobalPointerCase' 86 | value: 'lower_case' 87 | - key: 'readability-identifier-naming.GlobalVariableCase' 88 | value: 'lower_case' 89 | - key: 'readability-identifier-naming.InlineNamespaceCase' 90 | value: 'lower_case' 91 | - key: 'readability-identifier-naming.LocalConstantCase' 92 | value: 'lower_case' 93 | - key: 'readability-identifier-naming.LocalConstantPointerCase' 94 | value: 'lower_case' 95 | - key: 'readability-identifier-naming.LocalPointerCase' 96 | value: 'lower_case' 97 | - key: 'readability-identifier-naming.LocalVariableCase' 98 | value: 'lower_case' 99 | - key: 'readability-identifier-naming.MacroDefinitionCase' 100 | value: 'UPPER_CASE' 101 | - key: 'readability-identifier-naming.MemberCase' 102 | value: 'lower_case' 103 | - key: 'readability-identifier-naming.MethodCase' 104 | value: 'lower_case' 105 | - key: 'readability-identifier-naming.NamespaceCase' 106 | value: 'lower_case' 107 | - key: 'readability-identifier-naming.ParameterCase' 108 | value: 'lower_case' 109 | - key: 'readability-identifier-naming.ParameterPackCase' 110 | value: 'lower_case' 111 | - key: 'readability-identifier-naming.PointerParameterCase' 112 | value: 'lower_case' 113 | - key: 'readability-identifier-naming.PrivateMemberCase' 114 | value: 'lower_case' 115 | - key: 'readability-identifier-naming.PrivateMemberPrefix' 116 | value: 'm_' 117 | - key: 'readability-identifier-naming.PrivateMethodCase' 118 | value: 'lower_case' 119 | - key: 'readability-identifier-naming.ProtectedMemberCase' 120 | value: 'lower_case' 121 | - key: 'readability-identifier-naming.ProtectedMemberPrefix' 122 | value: 'm_' 123 | - key: 'readability-identifier-naming.ProtectedMethodCase' 124 | value: 'lower_case' 125 | - key: 'readability-identifier-naming.PublicMemberCase' 126 | value: 'lower_case' 127 | - key: 'readability-identifier-naming.PublicMethodCase' 128 | value: 'lower_case' 129 | - key: 'readability-identifier-naming.ScopedEnumConstantCase' 130 | value: 'lower_case' 131 | - key: 'readability-identifier-naming.StaticConstantCase' 132 | value: 'lower_case' 133 | - key: 'readability-identifier-naming.StaticVariableCase' 134 | value: 'lower_case' 135 | - key: 'readability-identifier-naming.StructCase' 136 | value: 'lower_case' 137 | - key: 'readability-identifier-naming.TemplateParameterCase' 138 | value: 'CamelCase' 139 | - key: 'readability-identifier-naming.TemplateTemplateParameterCase' 140 | value: 'CamelCase' 141 | - key: 'readability-identifier-naming.TypeAliasCase' 142 | value: 'lower_case' 143 | - key: 'readability-identifier-naming.TypedefCase' 144 | value: 'lower_case' 145 | - key: 'readability-identifier-naming.TypeTemplateParameterCase' 146 | value: 'CamelCase' 147 | - key: 'readability-identifier-naming.UnionCase' 148 | value: 'lower_case' 149 | - key: 'readability-identifier-naming.ValueTemplateParameterCase' 150 | value: 'CamelCase' 151 | - key: 'readability-identifier-naming.VariableCase' 152 | value: 'lower_case' 153 | - key: 'readability-identifier-naming.VirtualMethodCase' 154 | value: 'lower_case' 155 | ... 156 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | builtin = clear,rare,en-GB_to_en-US,names,informal,code 3 | check-filenames = 4 | check-hidden = 5 | skip = */.git,*/build,*/prefix 6 | quiet-level = 2 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-24.04 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: actions/setup-python@v5 20 | with: { python-version: "3.12" } 21 | 22 | - name: Install codespell 23 | run: pip3 install codespell 24 | 25 | - name: Lint 26 | run: cmake -D FORMAT_COMMAND=clang-format-18 -P cmake/lint.cmake 27 | 28 | - name: Spell check 29 | if: always() 30 | run: cmake -P cmake/spell.cmake 31 | 32 | coverage: 33 | needs: [lint] 34 | 35 | runs-on: ubuntu-24.04 36 | 37 | if: github.repository_owner == 'friendlyanon' 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | 42 | - name: Install LCov 43 | run: sudo apt-get update -q 44 | && sudo apt-get install lcov -q -y 45 | 46 | - name: Configure 47 | run: cmake --preset=ci-coverage 48 | 49 | - name: Build 50 | run: cmake --build build/coverage -j 2 51 | 52 | - name: Test 53 | working-directory: build/coverage 54 | run: ctest --output-on-failure --no-tests=error -j 2 55 | 56 | - name: Process coverage info 57 | run: cmake --build build/coverage -t coverage 58 | 59 | - name: Submit to codecov.io 60 | uses: codecov/codecov-action@v4 61 | with: 62 | file: build/coverage/coverage.info 63 | token: ${{ secrets.CODECOV_TOKEN }} 64 | 65 | sanitize: 66 | needs: [lint] 67 | 68 | runs-on: ubuntu-24.04 69 | 70 | env: { CXX: clang++-18 } 71 | 72 | steps: 73 | - uses: actions/checkout@v4 74 | 75 | - name: Configure 76 | run: cmake --preset=ci-sanitize 77 | 78 | - name: Build 79 | run: cmake --build build/sanitize -j 2 80 | 81 | - name: Test 82 | working-directory: build/sanitize 83 | env: 84 | ASAN_OPTIONS: "strict_string_checks=1:\ 85 | detect_stack_use_after_return=1:\ 86 | check_initialization_order=1:\ 87 | strict_init_order=1:\ 88 | detect_leaks=1:\ 89 | halt_on_error=1" 90 | UBSAN_OPTIONS: "print_stacktrace=1:\ 91 | halt_on_error=1" 92 | run: ctest --output-on-failure --no-tests=error -j 2 93 | 94 | test: 95 | needs: [lint] 96 | 97 | strategy: 98 | matrix: 99 | os: [macos-14, ubuntu-24.04, windows-2022] 100 | 101 | type: [shared, static] 102 | 103 | include: 104 | - { type: shared, shared: YES } 105 | - { type: static, shared: NO } 106 | 107 | runs-on: ${{ matrix.os }} 108 | 109 | steps: 110 | - uses: actions/checkout@v4 111 | 112 | - name: Install static analyzers 113 | if: matrix.os == 'ubuntu-24.04' 114 | run: >- 115 | sudo apt-get install clang-tidy-18 cppcheck -y -q 116 | 117 | sudo update-alternatives --install 118 | /usr/bin/clang-tidy clang-tidy 119 | /usr/bin/clang-tidy-18 180 120 | 121 | - name: Setup MultiToolTask 122 | if: matrix.os == 'windows-2022' 123 | run: | 124 | Add-Content "$env:GITHUB_ENV" 'UseMultiToolTask=true' 125 | Add-Content "$env:GITHUB_ENV" 'EnforceProcessCountAcrossBuilds=true' 126 | 127 | - name: Configure 128 | shell: pwsh 129 | run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" 130 | -D BUILD_SHARED_LIBS=${{ matrix.shared }} 131 | 132 | - name: Setup PATH 133 | if: matrix.os == 'windows-2022' && matrix.type == 'shared' 134 | run: Add-Content "$env:GITHUB_PATH" "$(Get-Location)\build\Release" 135 | 136 | - name: Build 137 | run: cmake --build build --config Release -j 2 138 | 139 | - name: Install 140 | run: cmake --install build --config Release --prefix prefix 141 | 142 | - name: Test 143 | working-directory: build 144 | run: ctest --output-on-failure --no-tests=error -C Release -j 2 145 | 146 | docs: 147 | # Deploy docs only when builds succeed 148 | needs: [sanitize, test] 149 | 150 | runs-on: ubuntu-24.04 151 | 152 | if: github.ref == 'refs/heads/master' 153 | && github.event_name == 'push' 154 | && github.repository_owner == 'friendlyanon' 155 | 156 | permissions: 157 | contents: write 158 | 159 | steps: 160 | - uses: actions/checkout@v4 161 | 162 | - uses: actions/setup-python@v5 163 | with: { python-version: "3.12" } 164 | 165 | - name: Install m.css dependencies 166 | run: pip3 install jinja2 Pygments 167 | 168 | - name: Install Doxygen 169 | run: sudo apt-get update -q 170 | && sudo apt-get install doxygen -q -y 171 | 172 | - name: Build docs 173 | run: cmake "-DPROJECT_SOURCE_DIR=$PWD" "-DPROJECT_BINARY_DIR=$PWD/build" 174 | -P cmake/docs-ci.cmake 175 | 176 | - name: Deploy docs 177 | uses: peaceiris/actions-gh-pages@v4 178 | with: 179 | github_token: ${{ secrets.GITHUB_TOKEN }} 180 | publish_dir: build/docs/html 181 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | .idea/ 3 | .vs/ 4 | .vscode/ 5 | build/ 6 | cmake-build-*/ 7 | prefix/ 8 | .clangd 9 | CMakeLists.txt.user 10 | CMakeUserPresets.json 11 | compile_commands.json 12 | env.bat 13 | env.ps1 14 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building with CMake 2 | 3 | ## Build 4 | 5 | This project doesn't require any special command-line flags to build to keep 6 | things simple. 7 | 8 | Here are the steps for building in release mode with a single-configuration 9 | generator, like the Unix Makefiles one: 10 | 11 | ```sh 12 | cmake -S . -B build -D CMAKE_BUILD_TYPE=Release 13 | cmake --build build 14 | ``` 15 | 16 | Here are the steps for building in release mode with a multi-configuration 17 | generator, like the Visual Studio ones: 18 | 19 | ```sh 20 | cmake -S . -B build 21 | cmake --build build --config Release 22 | ``` 23 | 24 | ### Building with MSVC 25 | 26 | Note that MSVC by default is not standards compliant and you need to pass some 27 | flags to make it behave properly. See the `flags-msvc` preset in the 28 | [CMakePresets.json](CMakePresets.json) file for the flags and with what 29 | variable to provide them to CMake during configuration. 30 | 31 | ### Building on Apple Silicon 32 | 33 | CMake supports building on Apple Silicon properly since 3.20.1. Make sure you 34 | have the [latest version][1] installed. 35 | 36 | ## Install 37 | 38 | This project doesn't require any special command-line flags to install to keep 39 | things simple. As a prerequisite, the project has to be built with the above 40 | commands already. 41 | 42 | The below commands require at least CMake 3.15 to run, because that is the 43 | version in which [Install a Project][2] was added. 44 | 45 | Here is the command for installing the release mode artifacts with a 46 | single-configuration generator, like the Unix Makefiles one: 47 | 48 | ```sh 49 | cmake --install build 50 | ``` 51 | 52 | Here is the command for installing the release mode artifacts with a 53 | multi-configuration generator, like the Visual Studio ones: 54 | 55 | ```sh 56 | cmake --install build --config Release 57 | ``` 58 | 59 | ### CMake package 60 | 61 | This project exports a CMake package to be used with the [`find_package`][3] 62 | command of CMake: 63 | 64 | * Package name: `shared` 65 | * Target name: `shared::shared` 66 | 67 | Example usage: 68 | 69 | ```cmake 70 | find_package(shared REQUIRED) 71 | # Declare the imported target as a build requirement using PRIVATE, where 72 | # project_target is a target created in the consuming project 73 | target_link_libraries( 74 | project_target PRIVATE 75 | shared::shared 76 | ) 77 | ``` 78 | 79 | ### Note to packagers 80 | 81 | The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if 82 | the project is configured as a top level project to avoid indirectly including 83 | other libraries when installed to a common prefix. Please review the 84 | [install-rules.cmake](cmake/install-rules.cmake) file for the full set of 85 | install rules. 86 | 87 | [1]: https://cmake.org/download/ 88 | [2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project 89 | [3]: https://cmake.org/cmake/help/latest/command/find_package.html 90 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | include(cmake/prelude.cmake) 4 | 5 | project( 6 | shared 7 | VERSION 0.1.0 8 | DESCRIPTION "Short description" 9 | HOMEPAGE_URL "https://example.com/" 10 | LANGUAGES CXX 11 | ) 12 | 13 | include(cmake/project-is-top-level.cmake) 14 | include(cmake/variables.cmake) 15 | 16 | # ---- Declare library ---- 17 | 18 | add_library( 19 | shared_shared 20 | source/shared.cpp 21 | ) 22 | add_library(shared::shared ALIAS shared_shared) 23 | 24 | include(GenerateExportHeader) 25 | generate_export_header( 26 | shared_shared 27 | BASE_NAME shared 28 | EXPORT_FILE_NAME export/shared/shared_export.hpp 29 | CUSTOM_CONTENT_FROM_VARIABLE pragma_suppress_c4251 30 | ) 31 | 32 | if(NOT BUILD_SHARED_LIBS) 33 | target_compile_definitions(shared_shared PUBLIC SHARED_STATIC_DEFINE) 34 | endif() 35 | 36 | set_target_properties( 37 | shared_shared PROPERTIES 38 | CXX_VISIBILITY_PRESET hidden 39 | VISIBILITY_INLINES_HIDDEN YES 40 | VERSION "${PROJECT_VERSION}" 41 | SOVERSION "${PROJECT_VERSION_MAJOR}" 42 | EXPORT_NAME shared 43 | OUTPUT_NAME shared 44 | ) 45 | 46 | target_include_directories( 47 | shared_shared ${warning_guard} 48 | PUBLIC 49 | "\$" 50 | ) 51 | 52 | target_include_directories( 53 | shared_shared SYSTEM 54 | PUBLIC 55 | "\$" 56 | ) 57 | 58 | target_compile_features(shared_shared PUBLIC cxx_std_17) 59 | 60 | # ---- Install rules ---- 61 | 62 | if(NOT CMAKE_SKIP_INSTALL_RULES) 63 | include(cmake/install-rules.cmake) 64 | endif() 65 | 66 | # ---- Developer mode ---- 67 | 68 | if(NOT shared_DEVELOPER_MODE) 69 | return() 70 | elseif(NOT PROJECT_IS_TOP_LEVEL) 71 | message( 72 | AUTHOR_WARNING 73 | "Developer mode is intended for developers of shared" 74 | ) 75 | endif() 76 | 77 | include(cmake/dev-mode.cmake) 78 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 14, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "cmake-pedantic", 11 | "hidden": true, 12 | "warnings": { 13 | "dev": true, 14 | "deprecated": true, 15 | "uninitialized": true, 16 | "unusedCli": true, 17 | "systemVars": false 18 | }, 19 | "errors": { 20 | "dev": true, 21 | "deprecated": true 22 | } 23 | }, 24 | { 25 | "name": "dev-mode", 26 | "hidden": true, 27 | "inherits": "cmake-pedantic", 28 | "cacheVariables": { 29 | "shared_DEVELOPER_MODE": "ON" 30 | } 31 | }, 32 | { 33 | "name": "cppcheck", 34 | "hidden": true, 35 | "cacheVariables": { 36 | "CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr" 37 | } 38 | }, 39 | { 40 | "name": "clang-tidy", 41 | "hidden": true, 42 | "cacheVariables": { 43 | "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/" 44 | } 45 | }, 46 | { 47 | "name": "ci-std", 48 | "description": "This preset makes sure the project actually builds with at least the specified standard", 49 | "hidden": true, 50 | "cacheVariables": { 51 | "CMAKE_CXX_EXTENSIONS": "OFF", 52 | "CMAKE_CXX_STANDARD": "17", 53 | "CMAKE_CXX_STANDARD_REQUIRED": "ON" 54 | } 55 | }, 56 | { 57 | "name": "flags-gcc-clang", 58 | "description": "These flags are supported by both GCC and Clang", 59 | "hidden": true, 60 | "cacheVariables": { 61 | "CMAKE_CXX_FLAGS": "-D_GLIBCXX_ASSERTIONS=1 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", 62 | "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen", 63 | "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen" 64 | } 65 | }, 66 | { 67 | "name": "flags-appleclang", 68 | "hidden": true, 69 | "cacheVariables": { 70 | "CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast" 71 | } 72 | }, 73 | { 74 | "name": "flags-msvc", 75 | "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard", 76 | "hidden": true, 77 | "cacheVariables": { 78 | "CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc", 79 | "CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf", 80 | "CMAKE_SHARED_LINKER_FLAGS": "/machine:x64 /guard:cf" 81 | } 82 | }, 83 | { 84 | "name": "ci-linux", 85 | "description": "Includes fortification with the CMake default release flags", 86 | "inherits": ["flags-gcc-clang", "ci-std"], 87 | "generator": "Unix Makefiles", 88 | "hidden": true, 89 | "cacheVariables": { 90 | "CMAKE_BUILD_TYPE": "Release", 91 | "CMAKE_CXX_FLAGS_RELEASE": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -O3 -DNDEBUG" 92 | } 93 | }, 94 | { 95 | "name": "ci-darwin", 96 | "inherits": ["flags-appleclang", "ci-std"], 97 | "generator": "Xcode", 98 | "hidden": true 99 | }, 100 | { 101 | "name": "ci-win64", 102 | "inherits": ["flags-msvc", "ci-std"], 103 | "generator": "Visual Studio 17 2022", 104 | "architecture": "x64", 105 | "hidden": true 106 | }, 107 | { 108 | "name": "coverage-linux", 109 | "binaryDir": "${sourceDir}/build/coverage", 110 | "inherits": "ci-linux", 111 | "hidden": true, 112 | "cacheVariables": { 113 | "ENABLE_COVERAGE": "ON", 114 | "CMAKE_BUILD_TYPE": "Coverage", 115 | "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions", 116 | "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage", 117 | "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage" 118 | } 119 | }, 120 | { 121 | "name": "ci-coverage", 122 | "inherits": ["coverage-linux", "dev-mode"], 123 | "cacheVariables": { 124 | "COVERAGE_HTML_COMMAND": "" 125 | } 126 | }, 127 | { 128 | "name": "ci-sanitize", 129 | "binaryDir": "${sourceDir}/build/sanitize", 130 | "inherits": ["ci-linux", "dev-mode"], 131 | "cacheVariables": { 132 | "CMAKE_BUILD_TYPE": "Sanitize", 133 | "CMAKE_CXX_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common" 134 | } 135 | }, 136 | { 137 | "name": "ci-build", 138 | "binaryDir": "${sourceDir}/build", 139 | "hidden": true 140 | }, 141 | { 142 | "name": "ci-multi-config", 143 | "description": "Speed up multi-config generators by generating only one configuration instead of the defaults", 144 | "hidden": true, 145 | "cacheVariables": { 146 | "CMAKE_CONFIGURATION_TYPES": "Release" 147 | } 148 | }, 149 | { 150 | "name": "ci-macos", 151 | "inherits": ["ci-build", "ci-darwin", "dev-mode", "ci-multi-config"] 152 | }, 153 | { 154 | "name": "ci-ubuntu", 155 | "inherits": ["ci-build", "ci-linux", "clang-tidy", "cppcheck", "dev-mode"] 156 | }, 157 | { 158 | "name": "ci-windows", 159 | "inherits": ["ci-build", "ci-win64", "dev-mode", "ci-multi-config"] 160 | } 161 | ] 162 | } 163 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | * You will be judged by your contributions first, and your sense of humor 4 | second. 5 | * Nobody owes you anything. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 7 | 8 | ## Code of Conduct 9 | 10 | Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document. 11 | 12 | ## Getting started 13 | 14 | Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md) 15 | document. 16 | 17 | In addition to he above, if you use the presets file as instructed, then you 18 | should NOT check it into source control, just as the CMake documentation 19 | suggests. 20 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Hacking 2 | 3 | Here is some wisdom to help you build and test this project as a developer and 4 | potential contributor. 5 | 6 | If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md) 7 | guide. 8 | 9 | ## Developer mode 10 | 11 | Build system targets that are only useful for developers of this project are 12 | hidden if the `shared_DEVELOPER_MODE` option is disabled. Enabling this 13 | option makes tests and other developer targets and options available. Not 14 | enabling this option means that you are a consumer of this project and thus you 15 | have no need for these targets and options. 16 | 17 | Developer mode is always set to on in CI workflows. 18 | 19 | ### Presets 20 | 21 | This project makes use of [presets][1] to simplify the process of configuring 22 | the project. As a developer, you are recommended to always have the [latest 23 | CMake version][2] installed to make use of the latest Quality-of-Life 24 | additions. 25 | 26 | You have a few options to pass `shared_DEVELOPER_MODE` to the configure 27 | command, but this project prefers to use presets. 28 | 29 | As a developer, you should create a `CMakeUserPresets.json` file at the root of 30 | the project: 31 | 32 | ```json 33 | { 34 | "version": 2, 35 | "cmakeMinimumRequired": { 36 | "major": 3, 37 | "minor": 14, 38 | "patch": 0 39 | }, 40 | "configurePresets": [ 41 | { 42 | "name": "dev", 43 | "binaryDir": "${sourceDir}/build/dev", 44 | "inherits": ["dev-mode", "ci-"], 45 | "cacheVariables": { 46 | "CMAKE_BUILD_TYPE": "Debug" 47 | } 48 | } 49 | ], 50 | "buildPresets": [ 51 | { 52 | "name": "dev", 53 | "configurePreset": "dev", 54 | "configuration": "Debug" 55 | } 56 | ], 57 | "testPresets": [ 58 | { 59 | "name": "dev", 60 | "configurePreset": "dev", 61 | "configuration": "Debug", 62 | "output": { 63 | "outputOnFailure": true 64 | } 65 | } 66 | ] 67 | } 68 | ``` 69 | 70 | You should replace `` in your newly created presets file with the name of 71 | the operating system you have, which may be `win64`, `linux` or `darwin`. You 72 | can see what these correspond to in the 73 | [`CMakePresets.json`](CMakePresets.json) file. 74 | 75 | `CMakeUserPresets.json` is also the perfect place in which you can put all 76 | sorts of things that you would otherwise want to pass to the configure command 77 | in the terminal. 78 | 79 | > **Note** 80 | > Some editors are pretty greedy with how they open projects with presets. 81 | > Some just randomly pick a preset and start configuring without your consent, 82 | > which can be confusing. Make sure that your editor configures when you 83 | > actually want it to, for example in CLion you have to make sure only the 84 | > `dev-dev preset` has `Enable profile` ticked in 85 | > `File > Settings... > Build, Execution, Deployment > CMake` and in Visual 86 | > Studio you have to set the option `Never run configure step automatically` 87 | > in `Tools > Options > CMake` **prior to opening the project**, after which 88 | > you can manually configure using `Project > Configure Cache`. 89 | 90 | ### Configure, build and test 91 | 92 | If you followed the above instructions, then you can configure, build and test 93 | the project respectively with the following commands from the project root on 94 | any operating system with any build system: 95 | 96 | ```sh 97 | cmake --preset=dev 98 | cmake --build --preset=dev 99 | ctest --preset=dev 100 | ``` 101 | 102 | If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you 103 | will also be able to select the above created user presets for automatic 104 | integration. 105 | 106 | Please note that both the build and test commands accept a `-j` flag to specify 107 | the number of jobs to use, which should ideally be specified to the number of 108 | threads your CPU has. You may also want to add that to your preset using the 109 | `jobs` property, see the [presets documentation][1] for more details. 110 | 111 | ### Developer mode targets 112 | 113 | These are targets you may invoke using the build command from above, with an 114 | additional `-t ` flag: 115 | 116 | #### `coverage` 117 | 118 | Available if `ENABLE_COVERAGE` is enabled. This target processes the output of 119 | the previously run tests when built with coverage configuration. The commands 120 | this target runs can be found in the `COVERAGE_TRACE_COMMAND` and 121 | `COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info 122 | file by default, which can be submitted to services with CI integration. The 123 | HTML command uses the trace command's output to generate an HTML document to 124 | `/coverage_html` by default. 125 | 126 | #### `docs` 127 | 128 | Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using 129 | Doxygen and m.css. The output will go to `/docs` by default 130 | (customizable using `DOXYGEN_OUTPUT_DIRECTORY`). 131 | 132 | #### `format-check` and `format-fix` 133 | 134 | These targets run the clang-format tool on the codebase to check errors and to 135 | fix them respectively. Customization available using the `FORMAT_PATTERNS` and 136 | `FORMAT_COMMAND` cache variables. 137 | 138 | #### `spell-check` and `spell-fix` 139 | 140 | These targets run the codespell tool on the codebase to check errors and to fix 141 | them respectively. Customization available using the `SPELL_COMMAND` cache 142 | variable. 143 | 144 | ## Running tests on Windows with `BUILD_SHARED_LIBS=ON` 145 | 146 | If you are building a shared library on Windows, you must add the path to the 147 | DLL file to `PATH` when you want to run tests. One way you could do that is by 148 | using PowerShell and writing a script for it, e.g. `env.ps1` at the project 149 | root: 150 | 151 | ```powershell 152 | $oldPrompt = (Get-Command prompt).ScriptBlock 153 | 154 | function prompt() { "(Debug) $(& $oldPrompt)" } 155 | 156 | $VsInstallPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -Property InstallationPath 157 | $Env:Path += ";$VsInstallPath\Common7\IDE;$Pwd\build\dev\Debug" 158 | ``` 159 | 160 | Then you can source this script by running `. env.ps1`. This particular 161 | example will only work for Debug builds. 162 | 163 | ### Passing `PATH` to editors 164 | 165 | Make sure you launch your editor of choice from the console with the above 166 | script sourced. Look for `(Debug)` in the prompt to confirm, then run e.g. 167 | `code .` for VScode or `devenv .` for Visual Studio. 168 | 169 | [1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html 170 | [2]: https://cmake.org/download/ 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is an example output of 2 | [cmake-init](https://github.com/friendlyanon/cmake-init) version 0.41.0 3 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | # ---- Variables ---- 2 | 3 | # We use variables separate from what CTest uses, because those have 4 | # customization issues 5 | set( 6 | COVERAGE_TRACE_COMMAND 7 | lcov -c -q 8 | -o "${PROJECT_BINARY_DIR}/coverage.info" 9 | -d "${PROJECT_BINARY_DIR}" 10 | --include "${PROJECT_SOURCE_DIR}/*" 11 | CACHE STRING 12 | "; separated command to generate a trace for the 'coverage' target" 13 | ) 14 | 15 | set( 16 | COVERAGE_HTML_COMMAND 17 | genhtml --legend -f -q 18 | "${PROJECT_BINARY_DIR}/coverage.info" 19 | -p "${PROJECT_SOURCE_DIR}" 20 | -o "${PROJECT_BINARY_DIR}/coverage_html" 21 | CACHE STRING 22 | "; separated command to generate an HTML report for the 'coverage' target" 23 | ) 24 | 25 | # ---- Coverage target ---- 26 | 27 | add_custom_target( 28 | coverage 29 | COMMAND ${COVERAGE_TRACE_COMMAND} 30 | COMMAND ${COVERAGE_HTML_COMMAND} 31 | COMMENT "Generating coverage report" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/dev-mode.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/folders.cmake) 2 | 3 | include(CTest) 4 | if(BUILD_TESTING) 5 | add_subdirectory(test) 6 | endif() 7 | 8 | option(BUILD_MCSS_DOCS "Build documentation using Doxygen and m.css" OFF) 9 | if(BUILD_MCSS_DOCS) 10 | include(cmake/docs.cmake) 11 | endif() 12 | 13 | option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) 14 | if(ENABLE_COVERAGE) 15 | include(cmake/coverage.cmake) 16 | endif() 17 | 18 | include(cmake/lint-targets.cmake) 19 | include(cmake/spell-targets.cmake) 20 | 21 | add_folders(Project) 22 | -------------------------------------------------------------------------------- /cmake/docs-ci.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | foreach(var IN ITEMS PROJECT_BINARY_DIR PROJECT_SOURCE_DIR) 4 | if(NOT DEFINED "${var}") 5 | message(FATAL_ERROR "${var} must be defined") 6 | endif() 7 | endforeach() 8 | set(bin "${PROJECT_BINARY_DIR}") 9 | set(src "${PROJECT_SOURCE_DIR}") 10 | 11 | # ---- Dependencies ---- 12 | 13 | set(mcss_SOURCE_DIR "${bin}/docs/.ci") 14 | if(NOT IS_DIRECTORY "${mcss_SOURCE_DIR}") 15 | file(MAKE_DIRECTORY "${mcss_SOURCE_DIR}_") 16 | file( 17 | DOWNLOAD 18 | https://github.com/mosra/m.css/archive/4a1324c22ebaf81d68e8745610b0127288358b8c.zip 19 | "${bin}/mcss.zip" 20 | STATUS status 21 | EXPECTED_MD5 e1b1d45b861b718299eeb91f8badfc6f 22 | ) 23 | if(NOT status MATCHES "^0;") 24 | message(FATAL_ERROR "Download failed with ${status}") 25 | endif() 26 | execute_process( 27 | COMMAND "${CMAKE_COMMAND}" -E tar xf "${bin}/mcss.zip" 28 | WORKING_DIRECTORY "${mcss_SOURCE_DIR}_" 29 | RESULT_VARIABLE result 30 | ) 31 | if(NOT result EQUAL "0") 32 | message(FATAL_ERROR "Extraction failed with ${result}") 33 | endif() 34 | file(REMOVE "${bin}/mcss.zip") 35 | file(GLOB dir LIST_DIRECTORIES ON "${mcss_SOURCE_DIR}_/*") 36 | file(RENAME "${dir}" "${mcss_SOURCE_DIR}") 37 | file(REMOVE "${mcss_SOURCE_DIR}_") 38 | endif() 39 | 40 | find_program(Python3_EXECUTABLE NAMES python3 python) 41 | if(NOT Python3_EXECUTABLE) 42 | message(FATAL_ERROR "Python executable was not found") 43 | endif() 44 | 45 | # ---- Process project() call in CMakeLists.txt ---- 46 | 47 | file(READ "${src}/CMakeLists.txt" content) 48 | 49 | string(FIND "${content}" "project(" index) 50 | if(index EQUAL "-1") 51 | message(FATAL_ERROR "Could not find \"project(\"") 52 | endif() 53 | string(SUBSTRING "${content}" "${index}" -1 content) 54 | 55 | string(FIND "${content}" "\n)\n" index) 56 | if(index EQUAL "-1") 57 | message(FATAL_ERROR "Could not find \"\\n)\\n\"") 58 | endif() 59 | string(SUBSTRING "${content}" 0 "${index}" content) 60 | 61 | file(WRITE "${bin}/docs-ci.project.cmake" "docs_${content}\n)\n") 62 | 63 | macro(list_pop_front list out) 64 | list(GET "${list}" 0 "${out}") 65 | list(REMOVE_AT "${list}" 0) 66 | endmacro() 67 | 68 | function(docs_project name) 69 | cmake_parse_arguments(PARSE_ARGV 1 "" "" "VERSION;DESCRIPTION;HOMEPAGE_URL" LANGUAGES) 70 | set(PROJECT_NAME "${name}" PARENT_SCOPE) 71 | if(DEFINED _VERSION) 72 | set(PROJECT_VERSION "${_VERSION}" PARENT_SCOPE) 73 | string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" versions "${_VERSION}") 74 | string(REPLACE . ";" versions "${versions}") 75 | set(suffixes MAJOR MINOR PATCH TWEAK) 76 | while(NOT versions STREQUAL "" AND NOT suffixes STREQUAL "") 77 | list_pop_front(versions version) 78 | list_pop_front(suffixes suffix) 79 | set("PROJECT_VERSION_${suffix}" "${version}" PARENT_SCOPE) 80 | endwhile() 81 | endif() 82 | if(DEFINED _DESCRIPTION) 83 | set(PROJECT_DESCRIPTION "${_DESCRIPTION}" PARENT_SCOPE) 84 | endif() 85 | if(DEFINED _HOMEPAGE_URL) 86 | set(PROJECT_HOMEPAGE_URL "${_HOMEPAGE_URL}" PARENT_SCOPE) 87 | endif() 88 | endfunction() 89 | 90 | include("${bin}/docs-ci.project.cmake") 91 | 92 | # ---- Generate docs ---- 93 | 94 | if(NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY) 95 | set(DOXYGEN_OUTPUT_DIRECTORY "${bin}/docs") 96 | endif() 97 | set(out "${DOXYGEN_OUTPUT_DIRECTORY}") 98 | 99 | foreach(file IN ITEMS Doxyfile conf.py) 100 | configure_file("${src}/docs/${file}.in" "${bin}/docs/${file}" @ONLY) 101 | endforeach() 102 | 103 | set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py") 104 | set(config "${bin}/docs/conf.py") 105 | 106 | file(REMOVE_RECURSE "${out}/html" "${out}/xml") 107 | 108 | execute_process( 109 | COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}" 110 | WORKING_DIRECTORY "${bin}/docs" 111 | RESULT_VARIABLE result 112 | ) 113 | if(NOT result EQUAL "0") 114 | message(FATAL_ERROR "m.css returned with ${result}") 115 | endif() 116 | -------------------------------------------------------------------------------- /cmake/docs.cmake: -------------------------------------------------------------------------------- 1 | # ---- Dependencies ---- 2 | 3 | set(extract_timestamps "") 4 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") 5 | set(extract_timestamps DOWNLOAD_EXTRACT_TIMESTAMP YES) 6 | endif() 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | mcss URL 11 | https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip 12 | URL_MD5 00cd2757ebafb9bcba7f5d399b3bec7f 13 | SOURCE_DIR "${PROJECT_BINARY_DIR}/mcss" 14 | UPDATE_DISCONNECTED YES 15 | ${extract_timestamps} 16 | ) 17 | FetchContent_MakeAvailable(mcss) 18 | 19 | find_package(Python3 3.6 REQUIRED) 20 | 21 | # ---- Declare documentation target ---- 22 | 23 | set( 24 | DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs" 25 | CACHE PATH "Path for the generated Doxygen documentation" 26 | ) 27 | 28 | set(working_dir "${PROJECT_BINARY_DIR}/docs") 29 | 30 | foreach(file IN ITEMS Doxyfile conf.py) 31 | configure_file("docs/${file}.in" "${working_dir}/${file}" @ONLY) 32 | endforeach() 33 | 34 | set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py") 35 | set(config "${working_dir}/conf.py") 36 | 37 | add_custom_target( 38 | docs 39 | COMMAND "${CMAKE_COMMAND}" -E remove_directory 40 | "${DOXYGEN_OUTPUT_DIRECTORY}/html" 41 | "${DOXYGEN_OUTPUT_DIRECTORY}/xml" 42 | COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}" 43 | COMMENT "Building documentation using Doxygen and m.css" 44 | WORKING_DIRECTORY "${working_dir}" 45 | VERBATIM 46 | ) 47 | -------------------------------------------------------------------------------- /cmake/folders.cmake: -------------------------------------------------------------------------------- 1 | set_property(GLOBAL PROPERTY USE_FOLDERS YES) 2 | 3 | # Call this function at the end of a directory scope to assign a folder to 4 | # targets created in that directory. Utility targets will be assigned to the 5 | # UtilityTargets folder, otherwise to the ${name}Targets folder. If a target 6 | # already has a folder assigned, then that target will be skipped. 7 | function(add_folders name) 8 | get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) 9 | foreach(target IN LISTS targets) 10 | get_property(folder TARGET "${target}" PROPERTY FOLDER) 11 | if(DEFINED folder) 12 | continue() 13 | endif() 14 | set(folder Utility) 15 | get_property(type TARGET "${target}" PROPERTY TYPE) 16 | if(NOT type STREQUAL "UTILITY") 17 | set(folder "${name}") 18 | endif() 19 | set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets") 20 | endforeach() 21 | endfunction() 22 | -------------------------------------------------------------------------------- /cmake/install-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/sharedTargets.cmake") 2 | -------------------------------------------------------------------------------- /cmake/install-rules.cmake: -------------------------------------------------------------------------------- 1 | if(PROJECT_IS_TOP_LEVEL) 2 | set( 3 | CMAKE_INSTALL_INCLUDEDIR "include/shared-${PROJECT_VERSION}" 4 | CACHE STRING "" 5 | ) 6 | set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH) 7 | endif() 8 | 9 | include(CMakePackageConfigHelpers) 10 | include(GNUInstallDirs) 11 | 12 | # find_package() call for consumers to find this project 13 | set(package shared) 14 | 15 | install( 16 | DIRECTORY 17 | include/ 18 | "${PROJECT_BINARY_DIR}/export/" 19 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 20 | COMPONENT shared_Development 21 | ) 22 | 23 | install( 24 | TARGETS shared_shared 25 | EXPORT sharedTargets 26 | RUNTIME # 27 | COMPONENT shared_Runtime 28 | LIBRARY # 29 | COMPONENT shared_Runtime 30 | NAMELINK_COMPONENT shared_Development 31 | ARCHIVE # 32 | COMPONENT shared_Development 33 | INCLUDES # 34 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 35 | ) 36 | 37 | write_basic_package_version_file( 38 | "${package}ConfigVersion.cmake" 39 | COMPATIBILITY SameMajorVersion 40 | ) 41 | 42 | # Allow package maintainers to freely override the path for the configs 43 | set( 44 | shared_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package}" 45 | CACHE STRING "CMake package config location relative to the install prefix" 46 | ) 47 | set_property(CACHE shared_INSTALL_CMAKEDIR PROPERTY TYPE PATH) 48 | mark_as_advanced(shared_INSTALL_CMAKEDIR) 49 | 50 | install( 51 | FILES cmake/install-config.cmake 52 | DESTINATION "${shared_INSTALL_CMAKEDIR}" 53 | RENAME "${package}Config.cmake" 54 | COMPONENT shared_Development 55 | ) 56 | 57 | install( 58 | FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" 59 | DESTINATION "${shared_INSTALL_CMAKEDIR}" 60 | COMPONENT shared_Development 61 | ) 62 | 63 | install( 64 | EXPORT sharedTargets 65 | NAMESPACE shared:: 66 | DESTINATION "${shared_INSTALL_CMAKEDIR}" 67 | COMPONENT shared_Development 68 | ) 69 | 70 | if(PROJECT_IS_TOP_LEVEL) 71 | include(CPack) 72 | endif() 73 | -------------------------------------------------------------------------------- /cmake/lint-targets.cmake: -------------------------------------------------------------------------------- 1 | set( 2 | FORMAT_PATTERNS 3 | source/*.cpp source/*.hpp 4 | include/*.hpp 5 | test/*.cpp test/*.hpp 6 | CACHE STRING 7 | "; separated patterns relative to the project source dir to format" 8 | ) 9 | 10 | set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use") 11 | 12 | add_custom_target( 13 | format-check 14 | COMMAND "${CMAKE_COMMAND}" 15 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 16 | -D "PATTERNS=${FORMAT_PATTERNS}" 17 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 18 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 19 | COMMENT "Linting the code" 20 | VERBATIM 21 | ) 22 | 23 | add_custom_target( 24 | format-fix 25 | COMMAND "${CMAKE_COMMAND}" 26 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 27 | -D "PATTERNS=${FORMAT_PATTERNS}" 28 | -D FIX=YES 29 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 30 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 31 | COMMENT "Fixing the code" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/lint.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | macro(default name) 4 | if(NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif() 7 | endmacro() 8 | 9 | default(FORMAT_COMMAND clang-format) 10 | default( 11 | PATTERNS 12 | source/*.cpp source/*.hpp 13 | include/*.hpp 14 | test/*.cpp test/*.hpp 15 | ) 16 | default(FIX NO) 17 | 18 | set(flag --output-replacements-xml) 19 | set(args OUTPUT_VARIABLE output) 20 | if(FIX) 21 | set(flag -i) 22 | set(args "") 23 | endif() 24 | 25 | file(GLOB_RECURSE files ${PATTERNS}) 26 | set(badly_formatted "") 27 | set(output "") 28 | string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length) 29 | 30 | foreach(file IN LISTS files) 31 | execute_process( 32 | COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}" 33 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 34 | RESULT_VARIABLE result 35 | ${args} 36 | ) 37 | if(NOT result EQUAL "0") 38 | message(FATAL_ERROR "'${file}': formatter returned with ${result}") 39 | endif() 40 | if(NOT FIX AND output MATCHES "\nDoxygen, making use of some useful 6 | * special commands. 7 | */ 8 | -------------------------------------------------------------------------------- /include/shared/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "shared/shared_export.hpp" 6 | 7 | /** 8 | * A note about the MSVC warning C4251: 9 | * This warning should be suppressed for private data members of the project's 10 | * exported classes, because there are too many ways to work around it and all 11 | * involve some kind of trade-off (increased code complexity requiring more 12 | * developer time, writing boilerplate code, longer compile times), but those 13 | * solutions are very situational and solve things in slightly different ways, 14 | * depending on the requirements of the project. 15 | * That is to say, there is no general solution. 16 | * 17 | * What can be done instead is understand where issues could arise where this 18 | * warning is spotting a legitimate bug. I will give the general description of 19 | * this warning's cause and break it down to make it trivial to understand. 20 | * 21 | * C4251 is emitted when an exported class has a non-static data member of a 22 | * non-exported class type. 23 | * 24 | * The exported class in our case is the class below (exported_class), which 25 | * has a non-static data member (m_name) of a non-exported class type 26 | * (std::string). 27 | * 28 | * The rationale here is that the user of the exported class could attempt to 29 | * access (directly, or via an inline member function) a static data member or 30 | * a non-inline member function of the data member, resulting in a linker 31 | * error. 32 | * Inline member function above means member functions that are defined (not 33 | * declared) in the class definition. 34 | * 35 | * Since this exported class never makes these non-exported types available to 36 | * the user, we can safely ignore this warning. It's fine if there are 37 | * non-exported class types as private member variables, because they are only 38 | * accessed by the members of the exported class itself. 39 | * 40 | * The name() method below returns a pointer to the stored null-terminated 41 | * string as a fundamental type (char const), so this is safe to use anywhere. 42 | * The only downside is that you can have dangling pointers if the pointer 43 | * outlives the class instance which stored the string. 44 | * 45 | * Shared libraries are not easy, they need some discipline to get right, but 46 | * they also solve some other problems that make them worth the time invested. 47 | */ 48 | 49 | /** 50 | * @brief Reports the name of the library 51 | * 52 | * Please see the note above for considerations when creating shared libraries. 53 | */ 54 | class SHARED_EXPORT exported_class 55 | { 56 | public: 57 | /** 58 | * @brief Initializes the name field to the name of the project 59 | */ 60 | exported_class(); 61 | 62 | /** 63 | * @brief Returns a non-owning pointer to the string stored in this class 64 | */ 65 | auto name() const -> char const*; 66 | 67 | private: 68 | SHARED_SUPPRESS_C4251 69 | std::string m_name; 70 | }; 71 | -------------------------------------------------------------------------------- /source/shared.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "shared/shared.hpp" 4 | 5 | exported_class::exported_class() 6 | : m_name {"shared"} 7 | { 8 | } 9 | 10 | auto exported_class::name() const -> char const* 11 | { 12 | return m_name.c_str(); 13 | } 14 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(sharedTests LANGUAGES CXX) 4 | 5 | include(../cmake/project-is-top-level.cmake) 6 | include(../cmake/folders.cmake) 7 | 8 | # ---- Dependencies ---- 9 | 10 | if(PROJECT_IS_TOP_LEVEL) 11 | find_package(shared REQUIRED) 12 | enable_testing() 13 | endif() 14 | 15 | # ---- Tests ---- 16 | 17 | add_executable(shared_test source/shared_test.cpp) 18 | target_link_libraries(shared_test PRIVATE shared::shared) 19 | target_compile_features(shared_test PRIVATE cxx_std_17) 20 | 21 | add_test(NAME shared_test COMMAND shared_test) 22 | 23 | # ---- End-of-file commands ---- 24 | 25 | add_folders(Test) 26 | -------------------------------------------------------------------------------- /test/source/shared_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "shared/shared.hpp" 4 | 5 | auto main() -> int 6 | { 7 | auto const exported = exported_class {}; 8 | 9 | return std::string("shared") == exported.name() ? 0 : 1; 10 | } 11 | --------------------------------------------------------------------------------