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()