From 20d16f0762d00a42da8a3ecd7a0c231125b87436 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 20:10:23 +0000 Subject: [PATCH 1/2] NF-1014: Incorporate ScanCode into CI for OSS compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added ScanCode installation to Dockerfile • Created run_scancode.sh script for license compliance analysis • Added VSCode task for running license compliance check • Added GitHub Actions workflow job for license compliance check Co-Authored-By: zfields@blues.com --- .devcontainer/ci/Dockerfile | 1 + .github/workflows/ci.yml | 25 +++++++-- .gitignore | 1 + .vscode/tasks.json | 14 +++++ scripts/run_scancode.sh | 103 ++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 3 deletions(-) create mode 100755 scripts/run_scancode.sh diff --git a/.devcontainer/ci/Dockerfile b/.devcontainer/ci/Dockerfile index 334df54f..b6a9c7d8 100644 --- a/.devcontainer/ci/Dockerfile +++ b/.devcontainer/ci/Dockerfile @@ -70,6 +70,7 @@ RUN ["dash", "-c", "\ && pip install --break-system-packages \ breathe \ sphinx-rtd-theme \ + scancode-toolkit \ && apt-get clean \ && apt-get purge \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe8d3628..8e35b16b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # TODO: This is a 3rd party GitHub action from some dude. Ideally, we'd - # use something more "official". + # TODO: This is a 3rd party GitHub action from "some dude". + # Ideally, we'd use something more official. - name: Check if Dockerfile changed uses: dorny/paths-filter@v2 id: filter @@ -188,10 +188,29 @@ jobs: run: | docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_cppcheck.sh ghcr.io/blues/note_c_ci:latest + run_scancode: + runs-on: ubuntu-latest + if: ${{ always() }} + needs: [build_ci_docker_image] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Load CI Docker image + # Only load the Docker image artifact if build_ci_docker_image actually + # ran (e.g. it wasn't skipped and was successful). + if: ${{ needs.build_ci_docker_image.result == 'success' }} + uses: ./.github/actions/load-ci-image + + - name: Run license compliance check + run: | + docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_scancode.sh ghcr.io/blues/note_c_ci:latest + publish_ci_image: runs-on: ubuntu-latest # Make sure unit tests unit tests passed before publishing. - needs: [build_ci_docker_image, run_unit_tests] + needs: [build_ci_docker_image, run_unit_tests, run_scancode] # Only publish the image if this is a push event and the Docker image was rebuilt if: ${{ github.event_name == 'push' && needs.build_ci_docker_image.result == 'success' }} diff --git a/.gitignore b/.gitignore index 1cd36c56..0c31550d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ settings.json CMakeFiles/ CMakeCache.txt cppcheck_output.txt +scancode_console_output.txt diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b00c48ce..d7a95d46 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -120,5 +120,19 @@ ], "group": "test" } + , + { + "label": "Note-C: Run License Compliance Check", + "type": "shell", + "command": "${workspaceFolder}/scripts/run_scancode.sh", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "LC_ALL": "C" + } + }, + "problemMatcher": [], + "group": "test" + } ] } diff --git a/scripts/run_scancode.sh b/scripts/run_scancode.sh new file mode 100755 index 00000000..007fefcd --- /dev/null +++ b/scripts/run_scancode.sh @@ -0,0 +1,103 @@ +#!/bin/bash +set -eo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +SRC_DIR="$SCRIPT_DIR/.." + +echo "Running License Compliance Analysis..." +echo + +# Create a function to generate the summary +generate_summary() { + { + # Initialize flag + has_critical_issues=false + + echo + + # Always generate and display summary regardless of exit code + echo "=== License Compliance Summary ===" + echo + + # Display license information + echo "License Information:" + echo "-------------------" + jq -r '.summary.declared_license_expression' scancode_output.json || echo "None found" + echo + + # Display license clarity score + echo "License Clarity Score:" + echo "---------------------" + jq -r '.summary.license_clarity_score.score' scancode_output.json || echo "Not available" + echo + + # Display other licenses found + echo "Other Licenses Found:" + echo "--------------------" + jq -r '.summary.other_license_expressions[] | select(.value != null) | "\(.value): \(.count) occurrences"' scancode_output.json || echo "None found" + echo + + # Display copyright holders + echo "Copyright Holders:" + echo "-----------------" + jq -r '.tallies.holders[] | select(.value != null) | "\(.value): \(.count) occurrences"' scancode_output.json || echo "None found" + echo + + # Check for non-compliant licenses + echo "License Compliance Issues:" + echo "-------------------------" + # List of non-compliant or problematic licenses + problematic_licenses=("gpl-2.0" "gpl-3.0" "agpl-3.0" "cc-by-nc" "proprietary") + + found_problematic=false + for license in "${problematic_licenses[@]}"; do + count=$(jq -r ".summary.other_license_expressions[] | select(.value == \"$license\") | .count" scancode_output.json 2>/dev/null || echo "0") + if [ "$count" != "0" ] && [ "$count" != "" ]; then + echo "WARNING: Found $count occurrences of $license license, which may have compliance implications." + found_problematic=true + has_critical_issues=true + fi + done + + if ! $found_problematic; then + echo "No problematic licenses found." + fi + echo + + # Display status and details + if $has_critical_issues; then + echo "Status: FAILED - License compliance issues found" + echo + echo "Review and fix license compliance issues before proceeding" + else + echo "Status: PASSED - No license compliance issues found" + echo + echo "Note: Review license information for potential improvements" + fi + } + + # Return 1 if critical issues found, 0 otherwise + if $has_critical_issues; then + return 1 + else + return 0 + fi +} + +# Run scancode and capture output and exit code +scancode \ + --license \ + --copyright \ + --classify \ + --summary \ + --license-clarity-score \ + --tallies \ + --json-pp scancode_output.json \ + --timeout 120 \ + --processes 2 \ + . 2>&1 | tee scancode_console_output.txt + +generate_summary + +# Exit with scancode's status code +exit $? From 3050662fd8fad9f2715c5b388fcd91c70079603c Mon Sep 17 00:00:00 2001 From: "Zachary J. Fields" Date: Tue, 25 Mar 2025 13:47:09 -0500 Subject: [PATCH 2/2] chore: cleanup --- .devcontainer/ci/Dockerfile | 2 +- .vscode/tasks.json | 45 ++++++++++++++++++------------------- scripts/run_scancode.sh | 8 ++++--- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/.devcontainer/ci/Dockerfile b/.devcontainer/ci/Dockerfile index b6a9c7d8..d2e9850e 100644 --- a/.devcontainer/ci/Dockerfile +++ b/.devcontainer/ci/Dockerfile @@ -69,8 +69,8 @@ RUN ["dash", "-c", "\ valgrind \ && pip install --break-system-packages \ breathe \ - sphinx-rtd-theme \ scancode-toolkit \ + sphinx-rtd-theme \ && apt-get clean \ && apt-get purge \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d7a95d46..ae7d69a1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -55,12 +55,12 @@ } }, { - "label": "Note-C: Run `astyle` Formatter", + "label": "Note-C: Generate Coverage HTML", "type": "shell", - "command": "./scripts/run_astyle.sh", + "command": "genhtml lcov.info -o tmp", "args": [], "options": { - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/build/test/coverage", "env": { "LC_ALL": "C" } @@ -68,15 +68,18 @@ "problemMatcher": [], "group": { "kind": "none" - } + }, + "dependsOn": [ + "Note-C: Compile and Run ALL Tests (with coverage)" + ] }, { - "label": "Note-C: Generate Coverage HTML", + "label": "Note-C: Generate Documentation", "type": "shell", - "command": "genhtml lcov.info -o tmp", + "command": "./scripts/build_docs.sh", "args": [], "options": { - "cwd": "${workspaceFolder}/build/test/coverage", + "cwd": "${workspaceFolder}", "env": { "LC_ALL": "C" } @@ -84,15 +87,12 @@ "problemMatcher": [], "group": { "kind": "none" - }, - "dependsOn": [ - "Note-C: Compile and Run ALL Tests (with coverage)" - ] + } }, { - "label": "Note-C: Generate Documentation", + "label": "Note-C: Run `astyle` Formatter", "type": "shell", - "command": "./scripts/build_docs.sh", + "command": "./scripts/run_astyle.sh", "args": [], "options": { "cwd": "${workspaceFolder}", @@ -106,32 +106,31 @@ } }, { - "label": "Note-C: Run Static Analysis", + "label": "Note-C: Run License Compliance Check", "type": "shell", - "command": "${workspaceFolder}/scripts/run_cppcheck.sh", + "command": "${workspaceFolder}/scripts/run_scancode.sh", "options": { "cwd": "${workspaceFolder}", "env": { "LC_ALL": "C" } }, - "problemMatcher": [ - "$gcc" - ], + "problemMatcher": [], "group": "test" - } - , + }, { - "label": "Note-C: Run License Compliance Check", + "label": "Note-C: Run Static Analysis", "type": "shell", - "command": "${workspaceFolder}/scripts/run_scancode.sh", + "command": "${workspaceFolder}/scripts/run_cppcheck.sh", "options": { "cwd": "${workspaceFolder}", "env": { "LC_ALL": "C" } }, - "problemMatcher": [], + "problemMatcher": [ + "$gcc" + ], "group": "test" } ] diff --git a/scripts/run_scancode.sh b/scripts/run_scancode.sh index 007fefcd..b8210aaa 100755 --- a/scripts/run_scancode.sh +++ b/scripts/run_scancode.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -eo pipefail +set -exo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" SRC_DIR="$SCRIPT_DIR/.." @@ -94,8 +94,10 @@ scancode \ --tallies \ --json-pp scancode_output.json \ --timeout 120 \ - --processes 2 \ - . 2>&1 | tee scancode_console_output.txt + --processes 4 \ + --ignore "build/*" \ + --verbose \ + $SRC_DIR 2>&1 | tee scancode_console_output.txt generate_summary