CI: Refactor C++ checks linting setup.
This commit is contained in:
437
.github/workflows/sub_lint.yml
vendored
437
.github/workflows/sub_lint.yml
vendored
@@ -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 "<details><summary>:information_source: Found $qtconnectionSyntax QT string-based connections :arrow_right: consider using QT functor-Based Connections</summary>" >> ${{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 "</details>" >> ${{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 "<details><summary>:warning: CppLint found $cpplintErrors errors / warnings</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: No cpplint errors found </summary> " >> ${{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 "</details>" >> ${{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 "<details><summary>:pencil2: Clang-format would reformat $clangFormatErrors files</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: Clang-format would reformat no file</summary> " >> ${{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 "</details>" >> ${{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 "<details><summary>:pencil2: Codespell found $misspellings misspellings</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: Codespell found no misspellings</summary>" >> ${{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 "</details>" >> ${{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 "<details><summary>:fire: Clang-Tidy found :fire: $clangTidyErrors errors, :warning: $clangTidyWarnings warnings and :pencil2: $clangTidyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ $clangTidyWarnings -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:warning: Clang-Tidy found :warning: $clangTidyWarnings warnings and :pencil2: $clangTidyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ $clangTidyNotes -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:pencil2: Clang-Tidy found :pencil2: $clangTidyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: Clang-Tidy found no errors, warnings or notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
fi
|
||||
echo "" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
# List enabled checks in the report
|
||||
echo "<details><summary>:information_source: Enabled checks</summary>" >> ${{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 "</details>" >> ${{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 "</details>" >> ${{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 "<details><summary>:fire: Clazy found :fire: $clazyErrors errors, :warning: $clazyWarnings warnings and :pencil2: $clazyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ "$clazyWarnings" -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:warning: Clazy found :warning: $clazyWarnings warnings and :pencil2: $clazyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ "$clazyNotes" -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:pencil2: Clazy found :pencil2: $clazyNotes notes</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes</summary>" >> ${{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 "</details>" >> ${{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 "<details><summary>:fire: Clazy found :fire: $clazyQT6Errors errors, :warning: $clazyQT6Warnings warnings and :pencil2: $clazyQT6Notes notes for porting to QT6</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ "$clazyQT6Warnings" -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:warning: Clazy found :warning: $clazyQT6Warnings warnings and :pencil2: $clazyQT6Notes notes for porting to QT6</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
elif [ "$clazyNotes" -gt 0 ]
|
||||
then
|
||||
echo "<details><summary>:pencil2: Clazy found :pencil2: $clazyQT6Notes notes for porting to QT6</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
|
||||
else
|
||||
echo "<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6</summary>" >> ${{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 "</details>" >> ${{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
|
||||
|
||||
83
tools/lint/clang_format.py
Normal file
83
tools/lint/clang_format.py
Normal file
@@ -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"<details><summary>:pencil2: Clang-format would reformat"
|
||||
f" {clang_format_errors} file(s)</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-format would reformat no file</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
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()
|
||||
131
tools/lint/clang_tidy.py
Normal file
131
tools/lint/clang_tidy.py
Normal file
@@ -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"<details><summary>:fire: Clang-Tidy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clang-Tidy found :warning: {warnings_count} "
|
||||
f"warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clang-Tidy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-Tidy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(clang_tidy_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"<details><summary>:information_source: Enabled checks</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(enabled_checks_content)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
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()
|
||||
119
tools/lint/clazy.py
Normal file
119
tools/lint/clazy.py
Normal file
@@ -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"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings "
|
||||
f"and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
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("</details>")
|
||||
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()
|
||||
120
tools/lint/clazy_qt6.py
Normal file
120
tools/lint/clazy_qt6.py
Normal file
@@ -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"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings and "
|
||||
f":pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
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()
|
||||
111
tools/lint/codespell.py
Normal file
111
tools/lint/codespell.py
Normal file
@@ -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"<details><summary>:pencil2: Codespell found {misspellings} misspellings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Codespell found no misspellings</summary>"
|
||||
)
|
||||
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("</details>")
|
||||
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()
|
||||
65
tools/lint/cpplint.cfg
Normal file
65
tools/lint/cpplint.cfg
Normal file
@@ -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
|
||||
90
tools/lint/cpplint.py
vendored
Normal file
90
tools/lint/cpplint.py
vendored
Normal file
@@ -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"<details><summary>:warning: CppLint found {errors_count} errors / warnings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: No cpplint errors found </summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(aggregated_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
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()
|
||||
75
tools/lint/qt_connections.py
Normal file
75
tools/lint/qt_connections.py
Normal file
@@ -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"<details><summary>:information_source: Found {count} QT string-based connections → consider using QT functor-based connections</summary>\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</details>\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()
|
||||
Reference in New Issue
Block a user