diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ed3c76c --- /dev/null +++ b/.clang-format @@ -0,0 +1,173 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +--- +Language: Json +BasedOnStyle: llvm +--- +Language: JavaScript +BasedOnStyle: Google +ColumnLimit: 100 +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..a7e001a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,89 @@ +--- +Checks: > + -*, + bugprone-*, + google-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -google-readability-braces-around-statements, + -google-readability-todo, + -google-readability-namespace-comments, + -google-runtime-references, + -misc-non-private-member-variables-in-classes, + -misc-unused-parameters, + -misc-no-recursion, + -misc-confusable-identifiers, + -modernize-return-braced-init-list, + -modernize-use-trailing-return-type, + -modernize-avoid-c-arrays, + -modernize-use-auto, + -modernize-use-nodiscard, + -performance-move-const-arg, + -readability-braces-around-statements, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-magic-numbers, + -readability-named-parameter, + -readability-redundant-declaration, + -readability-function-cognitive-complexity, + -readability-convert-member-functions-to-static, + -readability-make-member-function-const, + -bugprone-narrowing-conversions, + -bugprone-easily-swappable-parameters, + -bugprone-exception-escape, + -bugprone-implicit-widening-of-multiplication-result +WarningsAsErrors: '*' +HeaderFilterRegex: '' +FormatStyle: google +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassMemberCase + value: lower_case + - key: readability-identifier-naming.ConstexprVariableCase + value: CamelCase + - key: readability-identifier-naming.ConstexprVariablePrefix + value: k + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.GlobalConstantCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantPrefix + value: k + - key: readability-identifier-naming.StaticConstantCase + value: CamelCase + - key: readability-identifier-naming.StaticConstantPrefix + value: k + - key: readability-identifier-naming.StaticVariableCase + value: lower_case + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionIgnoredRegexp + value: '^[A-Z]+(_[A-Z]+)*_$' + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.PrivateMemberPrefix + value: _ + - key: readability-identifier-naming.ProtectedMemberPrefix + value: _ + - key: readability-identifier-naming.PublicMemberPrefix + value: '' + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.TypeAliasCase + value: CamelCase + - key: readability-identifier-naming.TypedefCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: 1 diff --git a/.clang-tidy-ignore b/.clang-tidy-ignore new file mode 100644 index 0000000..4511584 --- /dev/null +++ b/.clang-tidy-ignore @@ -0,0 +1 @@ +*/_deps/* diff --git a/.cppcheck-suppress b/.cppcheck-suppress new file mode 100644 index 0000000..870742a --- /dev/null +++ b/.cppcheck-suppress @@ -0,0 +1,7 @@ +unmatchedSuppression +missingIncludeSystem +unusedFunction +useStlAlgorithm +noExplicitConstructor +unusedStructMember +*:*_deps/* diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml new file mode 100644 index 0000000..d95e5d9 --- /dev/null +++ b/.github/workflows/clang-tidy.yml @@ -0,0 +1,27 @@ +name: clang-tidy +on: + push: + branches: + - 'main' + - 'release/*' + pull_request: + branches: + - '**' + +jobs: + clang_tidy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + + - name: Setup python + uses: actions/setup-python@v4 + + - name: Run clang-tidy + run: | + mkdir build + cmake -B build + make -C build clang-tidy diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml new file mode 100644 index 0000000..bbc4c57 --- /dev/null +++ b/.github/workflows/default.yml @@ -0,0 +1,51 @@ +name: CI +on: + push: + branches: + - 'main' + - 'release/*' + pull_request: + branches: + - '**' + +jobs: + build_and_lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + + - name: Setup python + uses: actions/setup-python@v4 + + - name: Install deps + run: sudo apt install cppcheck ccache + + - uses: actions/cache@v3.3.1 + id: ccache-persistence + with: + path: ~/.ccache + key: ccache-ccpp + restore-keys: | + ccache-ccpp + - name: setup ccache + run: mkdir -p ~/.ccache && echo "max_size = 300M" > ~/.ccache/ccache.conf && ccache -z && ccache -s + + - name: Build + run: | + mkdir build + cmake -B build + make -C build -j2 + + - name: Run pre-commit + uses: pre-commit/action@v3.0.0 + + - name: Run tests + run: | + make -C build run-unit-tests + + - name: ccache post-run + run: ccache -s && ccache -z + diff --git a/.gitignore b/.gitignore index e69de29..5f359f8 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,381 @@ +# Created by https://www.toptal.com/developers/gitignore/api/c++,meson,sonar,python,sonarqube,visualstudiocode,clion +# Edit at https://www.toptal.com/developers/gitignore?templates=c++,meson,sonar,python,sonarqube,visualstudiocode,clion + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### CLion ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Meson ### +# subproject directories +/subprojects/* +!/subprojects/*.wrap + +# Meson Directories +meson-logs +meson-private + +# Meson Files +meson_benchmark_setup.dat +meson_test_setup.dat +sanitycheckcpp.cc # C++ specific +sanitycheckcpp.exe # C++ specific + +# Ninja +build.ninja +.ninja_deps +.ninja_logs + +# Misc +compile_commands.json + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Sonar ### +#Sonar generated dir +/.sonar/ + +### SonarQube ### +# SonarQube ignore files. +# +# https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner +# Sonar Scanner working directories +.sonar/ +.sonarqube/ +.scannerwork/ + +# http://www.sonarlint.org/commandline/ +# SonarLint working directories, configuration files (including credentials) +.sonarlint/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/c++,meson,sonar,python,sonarqube,visualstudiocode,clion diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..689e7d9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,49 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# See https://github.com/pre-commit/pre-commit + +repos: + # Standard hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-ast + - id: check-case-conflict + - id: check-docstring-first + - id: check-merge-conflict + - id: check-symlinks + - id: debug-statements + - id: mixed-line-ending + - id: trailing-whitespace + exclude_types: [rst] + - id: fix-byte-order-marker + + + # Python hooks + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + + # CPP hooks + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: 'v14.0.6' + hooks: + - id: clang-format + - repo: https://github.com/pocc/pre-commit-hooks + rev: 'v1.3.5' + hooks: + - id: cppcheck + args: [ "--suppressions-list=.cppcheck-suppress", "--inline-suppr" ] + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eed2e3b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +project(ulog_cpp LANGUAGES CXX) + +# Determine if ulog_cpp is built as a subproject (using add_subdirectory) or if it is the main project. +set(MAIN_PROJECT OFF) +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(MAIN_PROJECT ON) +endif() + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_subdirectory(ulog_cpp) + +if(MAIN_PROJECT) + add_compile_options( + # always enable debug symbols (for stack trace) + -g + + # Warnings + -Wall + -Wextra + -Werror + + # disabled warnings + -Wno-missing-field-initializers + -Wno-unused-parameter + -Wno-float-equal # TODO: this is required for cli11 2.3.2 + ) + + # compiler specific flags + if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang")) + add_compile_options( + -fcolor-diagnostics # force color for clang (needed for clang + ccache) + -fdiagnostics-absolute-paths # force absolute paths + + -Qunused-arguments + + -Wno-unknown-warning-option + -Wno-unused-const-variable + ) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(-fdiagnostics-color=always) + endif() + + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + include(clang_format) + + add_subdirectory(examples) + add_subdirectory(test) + +endif() + +target_include_directories(${PROJECT_NAME} INTERFACE $) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4273d4e --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023, PX4 Development Team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/clang_format.cmake b/cmake/clang_format.cmake new file mode 100644 index 0000000..ca0fa8c --- /dev/null +++ b/cmake/clang_format.cmake @@ -0,0 +1,28 @@ + +file(GLOB_RECURSE ALL_SOURCE_FILES + ${CMAKE_SOURCE_DIR}/ulog_cpp/*.cpp + ${CMAKE_SOURCE_DIR}/test/*.cpp +) +file(GLOB_RECURSE ALL_HEADER_FILES + ${CMAKE_SOURCE_DIR}/ulog_cpp/*.hpp + ${CMAKE_SOURCE_DIR}/test/*.hpp +) + +add_custom_target( + format + COMMAND clang-format + -style=file -i + ${ALL_SOURCE_FILES} + ${ALL_HEADER_FILES} + USES_TERMINAL +) + +add_custom_target( + clang-tidy + COMMAND ${PROJECT_SOURCE_DIR}/tools/run-clang-tidy.py + -p . -use-color -header-filter='.*' -quiet + -extra-arg=-std=c++17 + -ignore ${PROJECT_SOURCE_DIR}/.clang-tidy-ignore + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + USES_TERMINAL +) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..6f76837 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,16 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_executable(ulog_info ulog_info.cpp) +target_link_libraries(ulog_info PUBLIC + ulog_cpp::ulog_cpp + ) + +add_executable(ulog_data ulog_data.cpp) +target_link_libraries(ulog_data PUBLIC + ulog_cpp::ulog_cpp + ) + +add_executable(ulog_writer ulog_writer.cpp) +target_link_libraries(ulog_writer PUBLIC + ulog_cpp::ulog_cpp + ) diff --git a/examples/ulog_data.cpp b/examples/ulog_data.cpp index 51ebbdc..fd7fab7 100644 --- a/examples/ulog_data.cpp +++ b/examples/ulog_data.cpp @@ -6,11 +6,10 @@ #include #include #include +#include +#include #include -#include "data_container.hpp" -#include "reader.hpp" - int main(int argc, char** argv) { if (argc < 2) { diff --git a/examples/ulog_info.cpp b/examples/ulog_info.cpp index 1748c56..8227e7f 100644 --- a/examples/ulog_info.cpp +++ b/examples/ulog_info.cpp @@ -8,11 +8,10 @@ #include #include #include +#include +#include #include -#include "data_container.hpp" -#include "reader.hpp" - int main(int argc, char** argv) { if (argc < 2) { diff --git a/examples/ulog_writer.cpp b/examples/ulog_writer.cpp index e93f29f..59dd211 100644 --- a/examples/ulog_writer.cpp +++ b/examples/ulog_writer.cpp @@ -7,8 +7,7 @@ #include #include #include - -#include "simple_writer.hpp" +#include using namespace std::chrono_literals; diff --git a/test/ulog_parsing_test.cpp b/test/ulog_parsing_test.cpp index 21e9831..f29f971 100644 --- a/test/ulog_parsing_test.cpp +++ b/test/ulog_parsing_test.cpp @@ -5,12 +5,11 @@ #include #include -#include - #include #include #include #include +#include class TestWriter : public ulog_cpp::Writer { public: diff --git a/tools/run-clang-tidy.py b/tools/run-clang-tidy.py new file mode 100755 index 0000000..0dfab5a --- /dev/null +++ b/tools/run-clang-tidy.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 +# +#===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===-----------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + +# flake8: noqa + + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +from __future__ import print_function + +import argparse +import fnmatch +import glob +import json +import multiprocessing +import os +import queue +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + +try: + import yaml +except ImportError: + yaml = None + + +def strtobool(val): + """Convert a string representation of truth to a bool following LLVM's CLI argument parsing.""" + + val = val.lower() + if val in ['', 'true', '1']: + return True + elif val in ['false', '0']: + return False + + # Return ArgumentTypeError so that argparse does not substitute its own error message + raise argparse.ArgumentTypeError( + "'{}' is invalid value for boolean argument! Try 0 or 1.".format(val) + ) + +def filter_files(ignore_config, files): + """Filter out all files specified via globs in DEFAULT_CLANG_TIDY_IGNORE. + """ + globs = list() + with open(ignore_config, 'r') as tidy_ignore: + for l in tidy_ignore: + line = l.strip() + # Tolerate comments and empty lines + if not line or line.startswith('#'): + continue + globs.append(line) + + exclude = set() + for g in globs: + for entry in files: + if fnmatch.fnmatch(entry, g): + exclude.add(entry) + + return list(set(files) - exclude), exclude + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = os.path.realpath('./') + while not os.path.isfile(os.path.join(result, path)): + parent = os.path.dirname(result) + if result == parent: + print('Error: could not find compilation database.') + sys.exit(1) + result = parent + return result + + +def make_absolute(f, directory): + if os.path.isabs(f): + return f + return os.path.normpath(os.path.join(directory, f)) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter, allow_enabling_alpha_checkers, + extra_arg, extra_arg_before, quiet, config_file_path, + config, line_filter, use_color, plugins): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if allow_enabling_alpha_checkers: + start.append('-allow-enabling-analyzer-alpha-checkers') + if header_filter is not None: + start.append('-header-filter=' + header_filter) + if line_filter is not None: + start.append('-line-filter=' + line_filter) + if use_color is not None: + if use_color: + start.append('--use-color') + else: + start.append('--use-color=false') + if checks: + start.append('-checks=' + checks) + if tmpdir is not None: + start.append('-export-fixes') + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append('--extra-arg=%s' % arg) + for arg in extra_arg_before: + start.append('--extra-arg-before=%s' % arg) + start.append('-p=' + build_path) + if quiet: + start.append('-quiet') + if config_file_path: + start.append('--config-file=' + config_file_path) + elif config: + start.append('-config=' + config) + for plugin in plugins: + start.append('-load=' + plugin) + start.append(f) + return start + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged=[] + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): + content = yaml.safe_load(open(replacefile, 'r')) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {'MainSourceFile': '', mergekey: merged} + with open(mergefile, 'w') as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, 'w').close() + + +def find_binary(arg, name, build_path): + """Get the path for a binary or exit""" + if arg: + if shutil.which(arg): + return arg + else: + raise SystemExit( + "error: passed binary '{}' was not found or is not executable" + .format(arg)) + + built_path = os.path.join(build_path, "bin", name) + binary = shutil.which(name) or shutil.which(built_path) + if binary: + return binary + else: + raise SystemExit( + "error: failed to find {} in $PATH or at {}" + .format(name, built_path)) + + +def apply_fixes(args, clang_apply_replacements_binary, tmpdir): + """Calls clang-apply-fixes on a given directory.""" + invocation = [clang_apply_replacements_binary] + invocation.append('-ignore-insert-conflict') + if args.format: + invocation.append('-format') + if args.style: + invocation.append('-style=' + args.style) + invocation.append(tmpdir) + subprocess.call(invocation) + + +def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, + failed_files): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, args.extra_arg_before, + args.quiet, args.config_file, args.config, + args.line_filter, args.use_color, + args.plugins) + + proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = proc.communicate() + if proc.returncode != 0: + if proc.returncode < 0: + msg = "%s: terminated by signal %d\n" % (name, -proc.returncode) + err += msg.encode('utf-8') + failed_files.append(name) + with lock: + sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8')) + if len(err) > 0: + sys.stdout.flush() + sys.stderr.write(err.decode('utf-8')) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH or in your build directory.') + parser.add_argument('-allow-enabling-alpha-checkers', + action='store_true', help='allow alpha checkers from ' + 'clang-analyzer.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + config_group = parser.add_mutually_exclusive_group() + config_group.add_argument('-config', default=None, + help='Specifies a configuration in YAML/JSON format: ' + ' -config="{Checks: \'*\', ' + ' CheckOptions: {x: y}}" ' + 'When the value is empty, clang-tidy will ' + 'attempt to find a file named .clang-tidy for ' + 'each source file in its parent directories.') + config_group.add_argument('-config-file', default=None, + help='Specify the path of .clang-tidy or custom config ' + 'file: e.g. -config-file=/some/path/myTidyConfigFile. ' + 'This option internally works exactly the same way as ' + '-config option after reading specified config file. ' + 'Use either -config-file or -config, not both.') + parser.add_argument('-header-filter', default=None, + help='regular expression matching the names of the ' + 'headers to output diagnostics from. Diagnostics from ' + 'the main file of each translation unit are always ' + 'displayed.') + parser.add_argument('-line-filter', default=None, + help='List of files with line ranges to filter the' + 'warnings.') + if yaml: + parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes', + help='Create a yaml file to store suggested fixes in, ' + 'which can be applied with clang-apply-replacements.') + parser.add_argument('-j', type=int, default=0, + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=['.*'], + help='files to be processed (regex on path)') + parser.add_argument('-fix', action='store_true', help='apply fix-its') + parser.add_argument('-format', action='store_true', help='Reformat code ' + 'after applying fixes') + parser.add_argument('-style', default='file', help='The style of reformat ' + 'code after applying fixes') + parser.add_argument('-use-color', type=strtobool, nargs='?', const=True, + help='Use colors in diagnostics, overriding clang-tidy\'s' + ' default behavior. This option overrides the \'UseColor' + '\' option in .clang-tidy file, if any.') + parser.add_argument('-p', dest='build_path', + help='Path used to read a compile command database.') + parser.add_argument('-extra-arg', dest='extra_arg', + action='append', default=[], + help='Additional argument to append to the compiler ' + 'command line.') + parser.add_argument('-extra-arg-before', dest='extra_arg_before', + action='append', default=[], + help='Additional argument to prepend to the compiler ' + 'command line.') + parser.add_argument('-ignore', default='', + help='File path to clang-tidy-ignore') + parser.add_argument('-quiet', action='store_true', + help='Run clang-tidy in quiet mode') + parser.add_argument('-load', dest='plugins', + action='append', default=[], + help='Load the specified plugin in clang-tidy.') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", + build_path) + + tmpdir = None + if args.fix: + clang_apply_replacements_binary = find_binary( + args.clang_apply_replacements_binary, "clang-apply-replacements", + build_path) + tmpdir = tempfile.mkdtemp() + + try: + invocation = get_tidy_invocation("", clang_tidy_binary, args.checks, + None, build_path, args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, args.extra_arg_before, + args.quiet, args.config_file, args.config, + args.line_filter, args.use_color, + args.plugins) + invocation.append('-list-checks') + invocation.append('-') + if args.quiet: + # Even with -quiet we still want to check if we can call clang-tidy. + with open(os.devnull, 'w') as dev_null: + subprocess.check_call(invocation, stdout=dev_null) + else: + subprocess.check_call(invocation) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = set([make_absolute(entry['file'], entry['directory']) + for entry in database]) + if args.ignore: + files, excluded = filter_files(args.ignore, files) + if excluded: + print("Excluding the following files:\n" + "\n".join(excluded) + "\n") + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('|'.join(args.files)) + + return_code = 0 + try: + # Spin up a bunch of tidy-launching threads. + task_queue = queue.Queue(max_task) + # List of files with a non-zero return code. + failed_files = [] + lock = threading.Lock() + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, clang_tidy_binary, tmpdir, build_path, + task_queue, lock, failed_files)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + if len(failed_files): + return_code = 1 + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print('\nCtrl-C detected, goodbye.') + if tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if yaml and args.export_fixes: + print('Writing fixes to ' + args.export_fixes + ' ...') + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + print('Error exporting fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if args.fix: + print('Applying fixes ...') + try: + apply_fixes(args, clang_apply_replacements_binary, tmpdir) + except: + print('Error applying fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code = 1 + + if tmpdir: + shutil.rmtree(tmpdir) + sys.exit(return_code) + + +if __name__ == '__main__': + main() diff --git a/ulog_cpp/CMakeLists.txt b/ulog_cpp/CMakeLists.txt index 6aa50e7..95787b3 100644 --- a/ulog_cpp/CMakeLists.txt +++ b/ulog_cpp/CMakeLists.txt @@ -1,23 +1,10 @@ -add_library(ulog_cpp +add_library(${PROJECT_NAME} data_container.cpp messages.cpp reader.cpp writer.cpp simple_writer.cpp ) +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_executable(ulog_info ulog_info.cpp) -target_link_libraries(ulog_info PUBLIC - ulog_cpp - ) - -add_executable(ulog_data ulog_data.cpp) -target_link_libraries(ulog_data PUBLIC - ulog_cpp - ) - -add_executable(ulog_writer ulog_writer.cpp) -target_link_libraries(ulog_writer PUBLIC - ulog_cpp - )