From cc708d28d01674704f0a6f9d87639ec419663a92 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Wed, 5 Mar 2025 22:41:42 +0000 Subject: [PATCH] CI: Refactor C++ checks linting setup. --- .github/workflows/sub_lint.yml | 437 ++++----------------------------- tools/lint/clang_format.py | 83 +++++++ tools/lint/clang_tidy.py | 131 ++++++++++ tools/lint/clazy.py | 119 +++++++++ tools/lint/clazy_qt6.py | 120 +++++++++ tools/lint/codespell.py | 111 +++++++++ tools/lint/cpplint.cfg | 65 +++++ tools/lint/cpplint.py | 90 +++++++ tools/lint/qt_connections.py | 75 ++++++ 9 files changed, 844 insertions(+), 387 deletions(-) create mode 100644 tools/lint/clang_format.py create mode 100644 tools/lint/clang_tidy.py create mode 100644 tools/lint/clazy.py create mode 100644 tools/lint/clazy_qt6.py create mode 100644 tools/lint/codespell.py create mode 100644 tools/lint/cpplint.cfg create mode 100644 tools/lint/cpplint.py create mode 100644 tools/lint/qt_connections.py diff --git a/.github/workflows/sub_lint.yml b/.github/workflows/sub_lint.yml index 3e0ecb64a2..61cdbcbde8 100644 --- a/.github/workflows/sub_lint.yml +++ b/.github/workflows/sub_lint.yml @@ -76,81 +76,6 @@ on: default: true type: boolean required: false - cpplintFilters: - default: >- - ' - +build/deprecated, - -build/endif_comment, - +build/explicit_make_pair, - -build/forward_decl, - -build/header_guard, - +build/include, - -build/include_subdir, - -build/include_alpha, - -build/include_order, - +build/include_what_you_use, - +build/namespaces_headers, - +build/namespaces_literals, - -build/namespaces, - +build/printf_format, - +build/storage_class, - -legal/copyright, - +readability/alt_tokens, - -readability/braces, - +readability/casting, - +readability/check, - +readability/constructors, - +readability/fn_size, - +readability/inheritance, - +readability/multiline_comment, - +readability/multiline_string, - +readability/namespace, - -readability/nolint, - +readability/nul, - +readability/strings, - -readability/todo, - +readability/utf8, - +runtime/arrays, - +runtime/casting, - +runtime/explicit, - +runtime/int, - +runtime/init, - +runtime/invalid_increment, - +runtime/member_string_references, - +runtime/memset, - +runtime/operator, - +runtime/printf, - +runtime/printf_format, - +runtime/references, - +runtime/string, - +runtime/threadsafe_fn, - +runtime/vlog, - -whitespace/blank_line, - -whitespace/braces, - -whitespace/comma, - -whitespace/comments, - -whitespace/empty_conditional_body, - -whitespace/empty_if_body, - -whitespace/empty_loop_body, - -whitespace/end_of_line, - -whitespace/ending_newline, - -whitespace/forcolon, - -whitespace/indent, - -whitespace/indent_namespace, - -whitespace/line_length, - -whitespace/newline, - -whitespace/operators, - -whitespace/parens, - -whitespace/semicolon, - -whitespace/tab, - -whitespace/todo - ' - type: string - required: false - cpplintLineLength: - default: 120 - type: string - required: false cpplintFailSilent: default: true type: boolean @@ -325,352 +250,89 @@ jobs: --files "${{ inputs.changedPythonFiles }}" \ --log-dir "${{ env.logdir }}" \ --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + + # C++ linting steps + - name: Install FreeCAD dependencies if: inputs.changedCppFiles != '' && always() - run: | - sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends \ - doxygen \ - graphviz \ - imagemagick \ - libboost-date-time-dev \ - libboost-dev \ - libboost-filesystem-dev \ - libboost-graph-dev \ - libboost-iostreams-dev \ - libboost-program-options-dev \ - libboost-python-dev \ - libboost-regex-dev \ - libboost-serialization-dev \ - libboost-thread-dev \ - libcoin-dev \ - libeigen3-dev \ - libkdtree++-dev \ - libmedc-dev \ - libocct-data-exchange-dev \ - libocct-ocaf-dev \ - libocct-visualization-dev \ - libopencv-dev \ - libproj-dev \ - libpyside2-dev \ - libqt5opengl5-dev \ - libqt5svg5-dev \ - libqt5x11extras5-dev \ - libshiboken2-dev \ - libspnav-dev \ - libvtk9-dev \ - libx11-dev \ - libxerces-c-dev \ - libyaml-cpp-dev \ - libzipios++-dev \ - netgen \ - netgen-headers \ - occt-draw \ - pyside2-tools \ - python3-dev \ - python3-git \ - python3-markdown \ - python3-matplotlib \ - python3-packaging \ - python3-pivy \ - python3-ply \ - python3-pyside2.qtcore \ - python3-pyside2.qtgui \ - python3-pyside2.qtnetwork \ - python3-pyside2.qtsvg \ - python3-pyside2.qtwidgets \ - qtbase5-dev \ - qttools5-dev \ - shiboken2 \ - swig \ - ccache \ - xvfb + run: ./package/ubuntu/install-apt-packages.sh + - name: Run CMake # This is needed for Clang tools to work correctly if: inputs.changedCppFiles != '' && always() run: | mkdir build && cmake -S ./ -B ./build/ -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE + - name: Check old Qt string-based connections (https://wiki.qt.io/New_Signal_Slot_Syntax) if: inputs.checkQtConnections && inputs.changedCppFiles != '' && always() continue-on-error: ${{ inputs.qtConnectionsFailSilent }} run: | - qtconnectionSyntax=0 - exclude="*[.md,.log,.ts,.git]" - # Check all files for QT string-based connections - for file in ${{ inputs.changedFiles }} #TODO does this makes sense in Python files ? - do - grep -nIHE --exclude="$exclude" $' SIGNAL| SLOT' $file | sed -e "s/$/ <--Consider using Functor-Based Connections/" >> ${{ env.logdir }}qtConnections.log || true #TODO seems to trigger false positives - done - # Write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}qtConnections.log ]; then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/grepMatcherWarning.json" - cat ${{ env.logdir }}qtConnections.log - echo "::remove-matcher owner=grepMatcher-warning::" - qtconnectionSyntax=$(wc -l < ${{ env.logdir }}qtConnections.log) - fi - echo "Found $qtconnectionSyntax QT string-based connections" - # Write the report - if [ $qtconnectionSyntax -gt 0 ]; then - echo "
:information_source: Found $qtconnectionSyntax QT string-based connections :arrow_right: consider using QT functor-Based Connections" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # documentation link - echo "For more information see: https://wiki.qt.io/New_Signal_Slot_Syntax or https://github.com/FreeCAD/FreeCAD/issues/6166" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}qtConnections.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo ":heavy_check_mark: No string-based connections found " >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $qtconnectionSyntax -eq 0 ] + python3 tools/lint/qt_connections.py \ + --files "${{ inputs.changedFiles }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Cpplint if: inputs.checkCpplint && inputs.changedCppFiles != '' && always() continue-on-error: ${{ inputs.cpplintFailSilent }} run: | - cpplintErrors=0 - pip install --break-system-packages cpplint - # Run cpplint - for file in ${{ inputs.changedCppFiles }} - do - cpplint --filter=${{ inputs.cpplintFilters }} --linelength=${{ inputs.cpplintLineLength }} $file &>> ${{ env.logdir }}cpplint.log || true - done - # If cpplint has run successfully, write the Log to the console with the Problem Matchers - if [ ${{ env.logdir }}cpplint.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/cpplint.json" - cat ${{ env.logdir }}cpplint.log - echo "::remove-matcher owner=cpplint::" - cpplintErrors=$(grep -nIHE "\[[0-9]\]$" ${{ env.logdir }}cpplint.log | wc -l ) || true - fi - echo "Found $cpplintErrors cpplint errors" - # Write the report - if [ $cpplintErrors -gt 0 ] - then - echo "
:warning: CppLint found $cpplintErrors errors / warnings" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: No cpplint errors found " >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}cpplint.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $cpplintErrors -eq 0 ] + python3 tools/lint/cpplint.py \ + --files "${{ inputs.changedCppFiles }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Clang-format if: inputs.checkClangFormat && inputs.changedCppFiles != '' && always() continue-on-error: ${{ inputs.clangFormatFailSilent }} run: | - clangFormatErrors=0 - # Run clang-format on all cpp files - clang-format --dry-run --ferror-limit=1 --verbose --style=${{ inputs.clangStyle }} ${{ inputs.changedCppFiles }} &>> ${{ env.logdir }}clang-format.log || true - # If clang-format has run successfully, write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}clang-format.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/clang.json" - cat ${{ env.logdir }}clang-format.log - echo "::remove-matcher owner=clang::" - clangFormatErrors=$(grep -nIHE "\[-Wclang-format-violations]$" ${{ env.logdir }}clang-format.log | wc -l ) || true - fi - echo "Found $clangFormatErrors clang-format errors" - # Write the report - if [ $clangFormatErrors -gt 0 ] - then - echo "
:pencil2: Clang-format would reformat $clangFormatErrors files" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: Clang-format would reformat no file " >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}clang-format.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $clangFormatErrors -eq 0 ] + python3 tools/lint/clang_format.py \ + --files "${{ inputs.changedCppFiles }}" \ + --clang-style "${{ inputs.clangStyle }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Codespell if: inputs.checkSpelling && always() continue-on-error: ${{ inputs.codespellFailSilent }} run: | - pip install --break-system-packages codespell - wget https://raw.githubusercontent.com/codespell-project/codespell/master/codespell_lib/data/dictionary.txt - #wget https://raw.githubusercontent.com/codespell-project/codespell/master/codespell_lib/data/dictionary_rare.txt - set +e - misspellings=$( { codespell --quiet-level 3 --summary --count --ignore-words ${{ inputs.listIgnoredMisspelling }} --skip ${{ inputs.spellingIgnore }} -D dictionary.txt ${{ inputs.changedFiles }} > ${{ env.logdir }}codespell.log ; } 2>&1 ) - set -e - # If codespell has run successfully, write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}codespell.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/codespell.json" - cat ${{ env.logdir }}codespell.log - echo "::remove-matcher owner=codespell::" - fi - echo "Found $misspellings misspellings" - # Write the report - if [ $misspellings -gt 0 ] - then - echo "
:pencil2: Codespell found $misspellings misspellings" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: Codespell found no misspellings" >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "To ignore false positives, append the word to the [.github/codespellignore](https://github.com/FreeCAD/FreeCAD/blob/master/.github/codespellignore) file (lowercase)" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}codespell.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $misspellings -eq 0 ] + python3 tools/lint/codespell.py \ + --files "${{ inputs.changedFiles }}" \ + --ignore-words "${{ inputs.listIgnoredMisspelling }}" \ + --skip "${{ inputs.spellingIgnore }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Clang-tidy if: inputs.checkClangTidy && inputs.changedCppFiles != '' && always() continue-on-error: ${{ inputs.clangTidyFailSilent }} run: | - clangTidyErrors=0 - clangTidyWarnings=0 - clangTidyNotes=0 - sudo apt-get install -y --no-install-recommends clang-tidy - #TODO: check where this "clang-tidy.yaml" goes ; shall this be put in the fixes ? - clang-tidy --quiet --format-style=${{ inputs.clangStyle }} --export-fixes=clang-tidy.yaml -p build/ --explain-config &>> ${{ env.logdir }}clang-tidy-enabled-checks.log - # Run clang-tidy on all cpp files - set +e - clang-tidy --quiet --format-style=${{ inputs.clangStyle }} --export-fixes=clang-tidy.yaml -p build/ ${{ inputs.changedCppFiles }} &>> ${{ env.logdir }}clang-tidy.log - exitCode=$? - set -e - # If clang-tidy has run successfully, write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}clang-tidy.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/clang.json" - cat ${{ env.logdir }}clang-tidy.log - echo "::remove-matcher owner=clang::" - clangTidyErrors=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): error: .+$" ${{ env.logdir }}clang-tidy.log | wc -l ) || true - clangTidyWarnings=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): warning: .+$" ${{ env.logdir }}clang-tidy.log | wc -l ) || true - clangTidyNotes=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): note: .+$" ${{ env.logdir }}clang-tidy.log | wc -l ) || true - fi - echo "Found $clangTidyErrors errors, $clangTidyWarnings warnings, $clangTidyNotes notes" - # Write the report - if [ $clangTidyErrors -gt 0 ] - then - echo "
:fire: Clang-Tidy found :fire: $clangTidyErrors errors, :warning: $clangTidyWarnings warnings and :pencil2: $clangTidyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ $clangTidyWarnings -gt 0 ] - then - echo "
:warning: Clang-Tidy found :warning: $clangTidyWarnings warnings and :pencil2: $clangTidyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ $clangTidyNotes -gt 0 ] - then - echo "
:pencil2: Clang-Tidy found :pencil2: $clangTidyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: Clang-Tidy found no errors, warnings or notes" >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # List enabled checks in the report - echo "
:information_source: Enabled checks" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}clang-tidy-enabled-checks.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}clang-tidy.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $clangTidyErrors -eq 0 ] + python3 tools/lint/clang_tidy.py \ + --files "${{ inputs.changedCppFiles }}" \ + --clang-style "${{ inputs.clangStyle }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Clazy if: inputs.checkClazy && inputs.changedCppFiles != '' && always() continue-on-error: ${{ inputs.clazyFailSilent }} - env: - CLAZY_HEADER_FILTER: "^$" run: | - clazyErrors=0 - clazyWarnings=0 - clazyNotes=0 - sudo apt-get install -y --no-install-recommends clazy - #TODO: check where this "clazy.yaml" goes ; shall this be put in the fixes ? - # Run clazy on all changed cpp files, ignoring any included headers - for file in ${{ inputs.changedCppFiles }} - do - clazy-standalone --export-fixes=clazy.yaml -checks=${{ inputs.clazyChecks }} -p build/compile_commands.json $file &>> ${{ env.logdir }}clazy.log || true - done - # If clazy has run successfully, write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}clazy.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/clang.json" - cat ${{ env.logdir }}clazy.log - echo "::remove-matcher owner=clang::" - clazyErrors=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): error: .+$" ${{ env.logdir }}clazy.log | wc -l ) || true - clazyWarnings=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): warning: .+$" ${{ env.logdir }}clazy.log | wc -l ) || true - clazyNotes=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): note: .+$" ${{ env.logdir }}clazy.log | wc -l ) || true - fi - echo "Found $clazyErrors errors, $clazyWarnings warnings, $clazyNotes notes" - # Write the report - if [ "$clazyErrors" -gt 0 ] - then - echo "
:fire: Clazy found :fire: $clazyErrors errors, :warning: $clazyWarnings warnings and :pencil2: $clazyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ "$clazyWarnings" -gt 0 ] - then - echo "
:warning: Clazy found :warning: $clazyWarnings warnings and :pencil2: $clazyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ "$clazyNotes" -gt 0 ] - then - echo "
:pencil2: Clazy found :pencil2: $clazyNotes notes" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: Clazy found no errors, warnings or notes" >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "[List of checks](https://github.com/KDE/clazy#list-of-checks), [This explains some of the clazy warnings](https://www.kdab.com/uncovering-32-qt-best-practices-compile-time-clazy/) " >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}clazy.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $clazyErrors -eq 0 ] + python3 tools/lint/clazy.py \ + --files "${{ inputs.changedCppFiles }}" \ + --clazy-checks "${{ inputs.clazyChecks }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + - name: Clazy-QT6 if: inputs.checkClazyQT6 && inputs.changedCppFiles != '' && github.ref == inputs.QT6Branch && always() continue-on-error: ${{ inputs.clazyQT6FailSilent }} run: | - clazyQT6Errors=0 - clazyQT6Warnings=0 - clazyQT6Notes=0 - sudo apt-get install -y --no-install-recommends clazy - #TODO: check where this "clazyQT6.yaml" goes ; shall this be put in the fixes ? - # Run clazy checks for Qt6 on all cpp files - clazy-standalone --export-fixes=clazyQT6.yaml -checks=${{ inputs.clazyQT6Checks }} -p build/ ${{ inputs.changedCppFiles }} &>> ${{ env.logdir }}clazyQT6.log || true - # If clazy has run successfully, write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}clazyQT6.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/clang.json" - cat ${{ env.logdir }}clazyQT6.log - echo "::remove-matcher owner=clang::" - clazyQT6Errors=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): error: .+$" ${{ env.logdir }}clazyQT6.log | wc -l ) || true - clazyQT6Warnings=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): warning: .+$" ${{ env.logdir }}clazyQT6.log | wc -l ) || true - clazyQT6Notes=$(grep -nIHE "^(.+):([0-9]+):([0-9]+): note: .+$" ${{ env.logdir }}clazyQT6.log | wc -l ) || true - fi - echo "Found $clazyQT6Errors errors, $clazyQT6Warnings warnings, $clazyQT6Notes notes" - # Write the report - if [ "$clazyQT6Errors" -gt 0 ] - then - echo "
:fire: Clazy found :fire: $clazyQT6Errors errors, :warning: $clazyQT6Warnings warnings and :pencil2: $clazyQT6Notes notes for porting to QT6" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ "$clazyQT6Warnings" -gt 0 ] - then - echo "
:warning: Clazy found :warning: $clazyQT6Warnings warnings and :pencil2: $clazyQT6Notes notes for porting to QT6" >> ${{env.reportdir}}${{ env.reportfilename }} - elif [ "$clazyNotes" -gt 0 ] - then - echo "
:pencil2: Clazy found :pencil2: $clazyQT6Notes notes for porting to QT6" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo "
:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6" >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}clazyQT6.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $clazyQT6Errors -eq 0 ] + python3 tools/lint/clazy_qt6.py \ + --files "${{ inputs.changedCppFiles }}" \ + --clazy-qt6-checks "${{ inputs.clazyQT6Checks }}" \ + --log-dir "${{ env.logdir }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" + + # Upload steps + - name: Upload logs and fixes if: always() uses: actions/upload-artifact@v4 @@ -679,6 +341,7 @@ jobs: path: | ${{ env.logdir }} ${{ env.fixesdir }} + - name: Upload report if: always() uses: actions/upload-artifact@v4 diff --git a/tools/lint/clang_format.py b/tools/lint/clang_format.py new file mode 100644 index 0000000000..3c13d0fecb --- /dev/null +++ b/tools/lint/clang_format.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import re +import logging +from utils import ( + run_command, + init_environment, + append_file, + emit_problem_matchers, + write_file, + add_common_arguments, +) + + +def count_clang_format_errors(log_text: str) -> int: + """ + Count lines that indicate clang-format violations. + The pattern looks for lines ending with "[-Wclang-format-violations]". + """ + pattern = r"\[-Wclang-format-violations\]$" + matches = re.findall(pattern, log_text, re.MULTILINE) + logging.debug("Matches found: %s", matches) + return len(matches) + + +def generate_markdown_report(clang_format_errors: int, output: str) -> str: + """Generate a Markdown report section for the clang-format output.""" + report_lines = [] + if clang_format_errors > 0: + report_lines.append( + f"
:pencil2: Clang-format would reformat" + f" {clang_format_errors} file(s)" + ) + else: + report_lines.append( + "
:heavy_check_mark: Clang-format would reformat no file" + ) + report_lines.append("") + report_lines.append("````") + report_lines.append(output) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Run clang-format in dry-run mode on C++ files and append a Markdown report." + ) + add_common_arguments(parser) + parser.add_argument( + "--clang-style", + required=True, + help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).", + ) + args = parser.parse_args() + init_environment(args) + + cmd = ( + f"clang-format --dry-run --ferror-limit=1 --verbose " + f"--style={args.clang_style} {args.files}" + ) + stdout, stderr, _ = run_command(cmd) + output = stdout + "\n" + stderr + + log_file = os.path.join(args.log_dir, "clang-format.log") + write_file(log_file, output) + emit_problem_matchers(log_file, "clang.json", "clang") + + clang_format_errors = count_clang_format_errors(output) + logging.info("Found %s clang-format errors", clang_format_errors) + + report = generate_markdown_report(clang_format_errors, output) + append_file(args.report_file, report) + + sys.exit(0 if clang_format_errors == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/clang_tidy.py b/tools/lint/clang_tidy.py new file mode 100644 index 0000000000..ea48be086c --- /dev/null +++ b/tools/lint/clang_tidy.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import re +import logging +from utils import ( + run_command, + init_environment, + add_common_arguments, + emit_problem_matchers, + write_file, + append_file, +) + + +def generate_markdown_report( + errors_count: int, + warnings_count: int, + notes_count: int, + clang_tidy_output: str, + enabled_checks_content: str, +) -> str: + """Generate a Markdown report section for clang-tidy results and enabled checks.""" + report_lines = [] + if errors_count > 0: + report_lines.append( + f"
:fire: Clang-Tidy found :fire: {errors_count} errors, " + f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes" + ) + elif warnings_count > 0: + report_lines.append( + f"
:warning: Clang-Tidy found :warning: {warnings_count} " + f"warnings and :pencil2: {notes_count} notes" + ) + elif notes_count > 0: + report_lines.append( + f"
:pencil2: Clang-Tidy found :pencil2: {notes_count} notes" + ) + else: + report_lines.append( + "
:heavy_check_mark: Clang-Tidy found no errors, warnings or notes" + ) + report_lines.append("") + report_lines.append("````") + report_lines.append(clang_tidy_output) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + report_lines.append( + "
:information_source: Enabled checks" + ) + report_lines.append("") + report_lines.append("````") + report_lines.append(enabled_checks_content) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + + +def count_occurrences(pattern: str, text: str) -> int: + """Count all occurrences of a regex pattern in text using MULTILINE mode.""" + matches = re.findall(pattern, text, re.MULTILINE) + logging.info("Pattern '%s' found %d matches", pattern, len(matches)) + return len(matches) + + +def main(): + parser = argparse.ArgumentParser( + description="Run clang-tidy on provided C++ files and append a Markdown report." + ) + add_common_arguments(parser) + parser.add_argument( + "--clang-style", + required=True, + help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).", + ) + args = parser.parse_args() + init_environment(args) + + build_dir = "build" + + clang_tidy_base_cmd = [ + "clang-tidy", + "--quiet", + f"--format-style={args.clang_style}", + "--export-fixes=clang-tidy.yaml", + "-p", + build_dir, + ] + + enabled_cmd = clang_tidy_base_cmd + ["--explain-config"] + enabled_stdout, enabled_stderr, _ = run_command(enabled_cmd) + enabled_output = enabled_stdout + enabled_stderr + enabled_checks_log = os.path.join(args.log_dir, "clang-tidy-enabled-checks.log") + write_file(enabled_checks_log, enabled_output) + + clang_cmd = clang_tidy_base_cmd + args.files.split() + clang_stdout, clang_stderr, _ = run_command(clang_cmd) + clang_tidy_output = clang_stdout + clang_stderr + + clang_tidy_log = os.path.join(args.log_dir, "clang-tidy.log") + write_file(clang_tidy_log, clang_tidy_output) + emit_problem_matchers(clang_tidy_log, "clang.json", "clang") + + error_pattern = r"^.+:\d+:\d+: error: .+$" + warning_pattern = r"^.+:\d+:\d+: warning: .+$" + note_pattern = r"^.+:\d+:\d+: note: .+$" + + errors_count = count_occurrences(error_pattern, clang_tidy_output) + warnings_count = count_occurrences(warning_pattern, clang_tidy_output) + notes_count = count_occurrences(note_pattern, clang_tidy_output) + + logging.info( + "Found %d errors, %d warnings, %d notes", + errors_count, + warnings_count, + notes_count, + ) + + report = generate_markdown_report( + errors_count, warnings_count, notes_count, clang_tidy_output, enabled_output + ) + append_file(args.report_file, report) + + sys.exit(0 if errors_count == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/clazy.py b/tools/lint/clazy.py new file mode 100644 index 0000000000..e11b3fb3cf --- /dev/null +++ b/tools/lint/clazy.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import re +import logging + +from utils import ( + run_command, + init_environment, + write_file, + append_file, + emit_problem_matchers, +) + + +def generate_markdown_report( + aggregated_output: str, errors_count: int, warnings_count: int, notes_count: int +) -> str: + """Generate a Markdown report based on clazy output and issue counts.""" + report_lines = [] + if errors_count > 0: + report_lines.append( + f"
:fire: Clazy found :fire: {errors_count} errors, " + f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes" + ) + elif warnings_count > 0: + report_lines.append( + f"
:warning: Clazy found :warning: {warnings_count} warnings " + f"and :pencil2: {notes_count} notes" + ) + elif notes_count > 0: + report_lines.append( + f"
:pencil2: Clazy found :pencil2: {notes_count} notes" + ) + else: + report_lines.append( + "
:heavy_check_mark: Clazy found no errors, warnings or notes" + ) + report_lines.append("") + report_lines.append( + "[List of checks](https://github.com/KDE/clazy#list-of-checks), " + "[This explains some of the clazy warnings](https://www.kdab.com/uncovering-32-qt-best-practices-compile-time-clazy/)" + ) + report_lines.append("````") + report_lines.append(aggregated_output) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Run Clazy on provided C++ files and append a Markdown report." + ) + parser.add_argument( + "--files", + required=True, + help="A space-separated list (or glob-expanded string) of C++ files to check.", + ) + parser.add_argument( + "--clazy-checks", + required=True, + help="Comma-separated list of clazy checks to run.", + ) + parser.add_argument( + "--log-dir", required=True, help="Directory where the log file will be written." + ) + parser.add_argument( + "--report-file", + required=True, + help="Path to the Markdown report file to append results.", + ) + parser.add_argument("--verbose", action="store_true", help="Enable verbose output.") + args = parser.parse_args() + init_environment(args) + + build_compile_commands = "build/compile_commands.json" + fix_file = "clazy.yaml" + + output = "" + file_list = args.files.split() + + for file in file_list: + cmd = [ + "clazy-standalone", + f"--export-fixes={fix_file}", + f"-checks={args.clazy_checks}", + "-p", + build_compile_commands, + file, + ] + stdout, stderr, _ = run_command(cmd) + output += stdout + "\n" + stderr + "\n" + + log_file = os.path.join(args.log_dir, "clazy.log") + write_file(log_file, output) + emit_problem_matchers(log_file, "clang.json", "clang") + + error_pattern = r"^.+:\d+:\d+: error: .+$" + warning_pattern = r"^.+:\d+:\d+: warning: .+$" + note_pattern = r"^.+:\d+:\d+: note: .+$" + errors_count = len(re.findall(error_pattern, output, re.MULTILINE)) + warnings_count = len(re.findall(warning_pattern, output, re.MULTILINE)) + notes_count = len(re.findall(note_pattern, output, re.MULTILINE)) + + logging.info( + f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes" + ) + + report = generate_markdown_report(output, errors_count, warnings_count, notes_count) + append_file(args.report_file, report + "\n") + + sys.exit(0 if errors_count == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/clazy_qt6.py b/tools/lint/clazy_qt6.py new file mode 100644 index 0000000000..6d6a12b67a --- /dev/null +++ b/tools/lint/clazy_qt6.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import re +import logging + +from utils import ( + run_command, + init_environment, + write_file, + append_file, + emit_problem_matchers, +) + + +def count_occurrences(pattern, text): + matches = re.findall(pattern, text, re.MULTILINE) + logging.debug(f"Found {len(matches)} matches for pattern: {pattern}") + return len(matches) + + +def generate_markdown_report( + output: str, errors_count: int, warnings_count: int, notes_count: int +) -> str: + """Generate a Markdown report based on Clazy QT6 output and issue counts.""" + report_lines = [] + if errors_count > 0: + report_lines.append( + f"
:fire: Clazy found :fire: {errors_count} errors, " + f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes for porting to QT6" + ) + elif warnings_count > 0: + report_lines.append( + f"
:warning: Clazy found :warning: {warnings_count} warnings and " + f":pencil2: {notes_count} notes for porting to QT6" + ) + elif notes_count > 0: + report_lines.append( + f"
:pencil2: Clazy found :pencil2: {notes_count} notes for porting to QT6" + ) + else: + report_lines.append( + "
:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6" + ) + report_lines.append("") + report_lines.append("````") + report_lines.append(output) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Run Clazy checks for QT6 on provided C++ files and append a Markdown report." + ) + parser.add_argument( + "--files", + required=True, + help="A space-separated list (or glob-expanded string) of C++ files to check.", + ) + parser.add_argument( + "--clazy-qt6-checks", + required=True, + help="Comma-separated list of clazy QT6 checks to run.", + ) + parser.add_argument( + "--log-dir", required=True, help="Directory where the log file will be written." + ) + parser.add_argument( + "--report-file", + required=True, + help="Path to the Markdown report file to append results.", + ) + parser.add_argument("--verbose", action="store_true", help="Enable verbose output.") + args = parser.parse_args() + + # Initialize logging and required directories using a shared utility. + init_environment(args) + + build_dir = "build" + fix_file = "clazyQT6.yaml" + + # Build the clazy command as a list. + cmd = [ + "clazy-standalone", + f"--export-fixes={fix_file}", + f"-checks={args.clazy_qt6_checks}", + "-p", + build_dir, + ] + args.files.split() + + stdout, stderr, _ = run_command(cmd) + output = stdout + stderr + + log_file = os.path.join(args.log_dir, "clazyQT6.log") + write_file(log_file, output) + emit_problem_matchers(log_file, "clang.json", "clang") + + error_pattern = r"^.+:\d+:\d+: error: .+$" + warning_pattern = r"^.+:\d+:\d+: warning: .+$" + note_pattern = r"^.+:\d+:\d+: note: .+$" + errors_count = count_occurrences(error_pattern, output) + warnings_count = count_occurrences(warning_pattern, output) + notes_count = count_occurrences(note_pattern, output) + + logging.info( + f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes" + ) + + report = generate_markdown_report(output, errors_count, warnings_count, notes_count) + append_file(args.report_file, report + "\n") + + sys.exit(0 if errors_count == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/codespell.py b/tools/lint/codespell.py new file mode 100644 index 0000000000..7c5c9eb9b6 --- /dev/null +++ b/tools/lint/codespell.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import urllib.request +import logging + +from utils import ( + run_command, + init_environment, + write_file, + append_file, + emit_problem_matchers, +) + + +def download_dictionary(url, dest): + logging.info(f"Downloading dictionary from {url} to {dest}") + try: + urllib.request.urlretrieve(url, dest) + except Exception as e: + logging.error(f"Error downloading dictionary: {e}", file=sys.stderr) + sys.exit(1) + + +def generate_markdown_report(misspellings: int, log_file: str) -> str: + """ + Generate a Markdown report section based on the codespell results and log file. + """ + report_lines = [] + if misspellings > 0: + report_lines.append( + f"
:pencil2: Codespell found {misspellings} misspellings" + ) + else: + report_lines.append( + "
:heavy_check_mark: Codespell found no misspellings" + ) + report_lines.append("") + report_lines.append( + "To ignore false positives, append the word to the [.github/codespellignore]" + "(https://github.com/FreeCAD/FreeCAD/blob/master/.github/codespellignore) file (lowercase)" + ) + report_lines.append("````") + with open(log_file, "r", encoding="utf-8") as lf: + report_lines.append(lf.read()) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Run codespell on files and append a Markdown report." + ) + parser.add_argument( + "--ignore-words", + required=True, + help="Comma-separated list of words to ignore (from codespellignore).", + ) + parser.add_argument( + "--skip", required=True, help="Comma-separated list of file patterns to skip." + ) + args = parser.parse_args() + init_environment(args) + + dictionary_file = "dictionary.txt" + if not os.path.exists(dictionary_file): + dictionary_url = "https://raw.githubusercontent.com/codespell-project/codespell/master/codespell_lib/data/dictionary.txt" + logging.info("Dictionary not found; downloading...") + download_dictionary(dictionary_url, dictionary_file) + + run_command(["pip", "install", "-q", "codespell"], check=True) + + cmd = [ + "codespell", + "--quiet-level", + "3", + "--summary", + "--count", + "--ignore-words", + args.ignore_words, + "--skip", + args.skip, + "-D", + dictionary_file, + ] + args.files.split() + stdout, stderr, _ = run_command(cmd) + output = stdout + "\n" + stderr + "\n" + + log_file = os.path.join(args.log_dir, "codespell.log") + write_file(log_file, output) + emit_problem_matchers(log_file, "codespell.json", "codespell") + + try: + misspellings = int(stdout.strip()) + except ValueError: + logging.info(f"Could not parse misspellings count from output:\n{stdout}") + misspellings = 0 + + logging.info(f"Found {misspellings} misspellings") + + report = generate_markdown_report(misspellings, log_file) + append_file(args.report_file, report + "\n") + + sys.exit(0 if misspellings == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/cpplint.cfg b/tools/lint/cpplint.cfg new file mode 100644 index 0000000000..b71efec8a0 --- /dev/null +++ b/tools/lint/cpplint.cfg @@ -0,0 +1,65 @@ ++build/deprecated, +-build/endif_comment, ++build/explicit_make_pair, +-build/forward_decl, +-build/header_guard, ++build/include, +-build/include_subdir, +-build/include_alpha, +-build/include_order, ++build/include_what_you_use, ++build/namespaces_headers, ++build/namespaces_literals, +-build/namespaces, ++build/printf_format, ++build/storage_class, +-legal/copyright, ++readability/alt_tokens, +-readability/braces, ++readability/casting, ++readability/check, ++readability/constructors, ++readability/fn_size, ++readability/inheritance, ++readability/multiline_comment, ++readability/multiline_string, ++readability/namespace, +-readability/nolint, ++readability/nul, ++readability/strings, +-readability/todo, ++readability/utf8, ++runtime/arrays, ++runtime/casting, ++runtime/explicit, ++runtime/int, ++runtime/init, ++runtime/invalid_increment, ++runtime/member_string_references, ++runtime/memset, ++runtime/operator, ++runtime/printf, ++runtime/printf_format, ++runtime/references, ++runtime/string, ++runtime/threadsafe_fn, ++runtime/vlog, +-whitespace/blank_line, +-whitespace/braces, +-whitespace/comma, +-whitespace/comments, +-whitespace/empty_conditional_body, +-whitespace/empty_if_body, +-whitespace/empty_loop_body, +-whitespace/end_of_line, +-whitespace/ending_newline, +-whitespace/forcolon, +-whitespace/indent, +-whitespace/indent_namespace, +-whitespace/line_length, +-whitespace/newline, +-whitespace/operators, +-whitespace/parens, +-whitespace/semicolon, +-whitespace/tab, +-whitespace/todo \ No newline at end of file diff --git a/tools/lint/cpplint.py b/tools/lint/cpplint.py new file mode 100644 index 0000000000..5b3005f8fd --- /dev/null +++ b/tools/lint/cpplint.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os +import re +import logging +from utils import ( + run_command, + init_environment, + write_file, + append_file, + emit_problem_matchers, + add_common_arguments, +) + +DEFAULT_LINE_LENGTH_LIMIT = 100 + +def load_cpplint_filters(conf_file="cpplint.cfg"): + """Load cpplint filters from a configuration file.""" + try: + with open(conf_file, "r", encoding="utf-8") as f: + filters = f.read().strip() + return filters + except Exception as e: + logging.warning(f"Could not load {conf_file}: {e}") + return "" + +def count_cpplint_errors(log_text: str) -> int: + """ + Count the number of cpplint error/warning lines. + """ + matches = re.findall(r"\[[0-9]+\]$", log_text, re.MULTILINE) + return len(matches) + +def generate_markdown_report(aggregated_output: str, errors_count: int) -> str: + """Generate a Markdown report section based on cpplint output and error count.""" + report_lines = [] + if errors_count > 0: + report_lines.append( + f"
:warning: CppLint found {errors_count} errors / warnings" + ) + else: + report_lines.append( + "
:heavy_check_mark: No cpplint errors found " + ) + report_lines.append("") + report_lines.append("````") + report_lines.append(aggregated_output) + report_lines.append("````") + report_lines.append("
") + report_lines.append("") + return "\n".join(report_lines) + +def main(): + parser = argparse.ArgumentParser( + description="Run cpplint on provided C++ files and append a Markdown report." + ) + add_common_arguments(parser) + args = parser.parse_args() + init_environment(args) + + run_command(["pip", "install", "-q", "cpplint"], check=True) + + cpplint_filters = load_cpplint_filters("cpplint.cfg") + + aggregated_output = "" + for file in args.files.split(): + cmd = [ + "cpplint", + f"--filters={cpplint_filters}", + f"--linelength={DEFAULT_LINE_LENGTH_LIMIT}", + file, + ] + stdout, stderr, _ = run_command(cmd) + aggregated_output += stdout + stderr + "\n" + + cpplint_log = os.path.join(args.log_dir, "cpplint.log") + write_file(cpplint_log, aggregated_output) + emit_problem_matchers(cpplint_log, "cpplint.json", "cpplint") + + errors_count = count_cpplint_errors(aggregated_output) + logging.info(f"Found {errors_count} cpplint errors") + + report = generate_markdown_report(aggregated_output, errors_count) + append_file(args.report_file, report) + + sys.exit(0 if errors_count == 0 else 1) + +if __name__ == "__main__": + main() diff --git a/tools/lint/qt_connections.py b/tools/lint/qt_connections.py new file mode 100644 index 0000000000..3e1f890328 --- /dev/null +++ b/tools/lint/qt_connections.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import argparse +import os +import re +import sys +from utils import ( + add_common_arguments, + init_environment, + write_file, + append_file, + emit_problem_matchers, +) + + +def check_qt_connections(file_path): + """ + Scan the file for lines containing 'SIGNAL' or 'SLOT' and return a list of matching lines + with line numbers and a suggestion. + """ + matches = [] + try: + with open(file_path, "r", encoding="utf-8", errors="replace") as f: + for lineno, line in enumerate(f, start=1): + if re.search(r"\b(SIGNAL|SLOT)\b", line): + matches.append( + f"{file_path}:{lineno}: {line.rstrip()} <-- Consider using functor-based connections" + ) + except Exception as e: + logging.debug(f"Error reading {file_path}: {e}") + return matches + + +def generate_markdown_report(all_matches): + """Generate a Markdown report based on the list of QT connection matches.""" + count = len(all_matches) + if count > 0: + md_report = ( + f"
:information_source: Found {count} QT string-based connections → consider using QT functor-based connections\n" + "\nFor more information see: https://wiki.qt.io/New_Signal_Slot_Syntax or https://github.com/FreeCAD/FreeCAD/issues/6166\n" + "```\n" + "\n".join(all_matches) + "\n```\n
\n\n" + ) + else: + md_report = ":heavy_check_mark: No string-based connections found\n\n" + return md_report + + +def main(): + parser = argparse.ArgumentParser( + description="Check for old Qt string-based connections in files and append a Markdown report." + ) + add_common_arguments(parser) + args = parser.parse_args() + init_environment(args) + + all_matches = [] + for file_path in args.files.split(): + logging.debug(f"Checking file: {file_path}") + matches = check_qt_connections(file_path) + all_matches.extend(matches) + + log_file = os.path.join(args.log_dir, "qtConnections.log") + write_file(log_file, "\n".join(all_matches)) + emit_problem_matchers(log_file, "grepMatcherWarning.json", "grepMatcher-warning") + + count = len(all_matches) + logging.info(f"Found {count} QT string-based connections") + + md_report = generate_markdown_report(all_matches) + append_file(args.report_file, md_report) + + sys.exit(0 if count == 0 else 1) + + +if __name__ == "__main__": + main()