diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index 7dda8c9bd..468f425ae 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -46,6 +46,9 @@ jobs: queries: "" # Default query suite packs: github/ccr-${{ matrix.language }}-queries config: | + paths-ignore: + - third-party + - third-party-licenses.*.md default-setup: org: model-packs: [ ${{ github.event.inputs.code_scanning_codeql_packs }} ] diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index d9cb59fb7..a2638eb1f 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -1,9 +1,22 @@ -# Create a github action that runs the license check script and fails if it exits with a non-zero status +# Automatically fix license files on PRs that need updates +# Tries to auto-commit the fix, or comments with instructions if push fails name: License Check -on: [push, pull_request] +on: + pull_request: + branches: + - main # Only run when PR targets main + paths: + - "**.go" + - go.mod + - go.sum + - ".github/licenses.tmpl" + - "script/licenses*" + - "third-party-licenses.*.md" + - "third-party/**" permissions: - contents: read + contents: write + pull-requests: write jobs: license-check: @@ -12,10 +25,87 @@ jobs: steps: - name: Check out code uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: "go.mod" - - name: check licenses - run: ./script/licenses-check + + # actions/setup-go does not setup the installed toolchain to be preferred over the system install, + # which causes go-licenses to raise "Package ... does not have module info" errors. + # For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 + - name: Regenerate licenses + env: + CI: "true" + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + ./script/licenses + + - name: Check for changes + id: changes + continue-on-error: true + run: script/licenses-check + + - name: Commit and push fixes + if: steps.changes.outcome == 'failure' + continue-on-error: true + id: push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add third-party-licenses.*.md third-party/ + git commit -m "chore: regenerate license files + +Auto-generated by license-check workflow" + git push + + - name: Check if already commented + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' + id: check_comment + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const alreadyCommented = comments.some(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('## ⚠️ License files need updating') + ); + + core.setOutput('already_commented', alreadyCommented ? 'true' : 'false'); + + - name: Comment with instructions if cannot push + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' && steps.check_comment.outputs.already_commented == 'false' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## ⚠️ License files need updating + +The license files are out of date. I tried to fix them automatically but don't have permission to push to this branch. + +**Please run:** +\`\`\`bash +script/licenses +git add third-party-licenses.*.md third-party/ +git commit -m "chore: regenerate license files" +git push +\`\`\` + +Alternatively, enable "Allow edits by maintainers" in the PR settings so I can fix it automatically.` + }); + + - name: Fail check if changes needed + if: steps.changes.outcome == 'failure' + run: exit 1 + diff --git a/script/licenses b/script/licenses index 4200316b9..026af7f98 100755 --- a/script/licenses +++ b/script/licenses @@ -1,6 +1,39 @@ #!/bin/bash +# +# Generate license files for all platform/arch combinations. +# This script handles architecture-specific dependency differences by: +# 1. Generating separate license reports per GOOS/GOARCH combination +# 2. Grouping identical reports together (comma-separated arch names) +# 3. Creating an index at the top of each platform file +# 4. Copying all license files to third-party/ +# +# Note: third-party/ is a union of all license files across all architectures. +# This means that license files for dependencies present in only some architectures +# may still appear in third-party/. This is intentional and ensures compliance. +# +# Note: we ignore warnings because we want the command to succeed, however the output should be checked +# for any new warnings, and potentially we may need to add license information. +# +# Normally these warnings are packages containing non go code, which may or may not require explicit attribution, +# depending on the license. -go install github.com/google/go-licenses@latest +set -e + +# Pinned version for CI reproducibility, latest for local development +# See: https://github.com/cli/cli/pull/11161 +if [ "$CI" = "true" ]; then + go install github.com/google/go-licenses@5348b744d0983d85713295ea08a20cca1654a45e # v2.0.1 +else + go install github.com/google/go-licenses@latest +fi + +# actions/setup-go does not setup the installed toolchain to be preferred over the system install, +# which causes go-licenses to raise "Package ... does not have module info" errors in CI. +# For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 +if [ "$CI" = "true" ]; then + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH +fi rm -rf third-party mkdir -p third-party @@ -8,14 +41,129 @@ export TEMPDIR="$(mktemp -d)" trap "rm -fr ${TEMPDIR}" EXIT -for goos in linux darwin windows ; do - # Note: we ignore warnings because we want the command to succeed, however the output should be checked - # for any new warnings, and potentially we may need to add license information. - # - # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, - # depending on the license. - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses save ./... --save_path="${TEMPDIR}/${goos}" --force || echo "Ignore warnings" - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.md || echo "Ignore warnings" - cp -fR "${TEMPDIR}/${goos}"/* third-party/ +# Cross-platform hash function (works on both Linux and macOS) +compute_hash() { + if command -v md5sum >/dev/null 2>&1; then + md5sum | cut -d' ' -f1 + elif command -v md5 >/dev/null 2>&1; then + md5 -q + else + # Fallback to cksum if neither is available + cksum | cut -d' ' -f1 + fi +} + +# Function to get architectures for a given OS +get_archs() { + case "$1" in + linux) echo "386 amd64 arm64" ;; + darwin) echo "amd64 arm64" ;; + windows) echo "386 amd64 arm64" ;; + esac +} + +# Generate reports for each platform/arch combination +for goos in darwin linux windows; do + echo "Processing ${goos}..." + + archs=$(get_archs "$goos") + + for goarch in $archs; do + echo " Generating for ${goos}/${goarch}..." + + # Generate the license report for this arch + report_file="${TEMPDIR}/${goos}_${goarch}_report.md" + GOOS="${goos}" GOARCH="${goarch}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > "${report_file}" 2>/dev/null || echo " (warnings ignored for ${goos}/${goarch})" + + # Save licenses to temp directory + GOOS="${goos}" GOARCH="${goarch}" GOFLAGS=-mod=mod go-licenses save ./... --save_path="${TEMPDIR}/${goos}_${goarch}" --force 2>/dev/null || echo " (warnings ignored for ${goos}/${goarch})" + + # Copy to third-party (accumulate all - union of all architectures for compliance) + if [ -d "${TEMPDIR}/${goos}_${goarch}" ]; then + cp -fR "${TEMPDIR}/${goos}_${goarch}"/* third-party/ 2>/dev/null || true + fi + + # Extract just the package list (skip header), sort it, and hash it + # Use LC_ALL=C for consistent sorting across different systems + packages_file="${TEMPDIR}/${goos}_${goarch}_packages.txt" + if [ -s "${report_file}" ] && grep -qE '^ - \[' "${report_file}" 2>/dev/null; then + grep -E '^ - \[' "${report_file}" | LC_ALL=C sort > "${packages_file}" + hash=$(cat "${packages_file}" | compute_hash) + else + echo "(FAILED TO GENERATE LICENSE REPORT FOR ${goos}/${goarch})" > "${packages_file}" + hash="FAILED_${goos}_${goarch}" + fi + + # Store hash for grouping + echo "${hash}" > "${TEMPDIR}/${goos}_${goarch}_hash.txt" + done + + # Group architectures with identical reports (deterministic order) + # Create groups file: hash -> comma-separated archs + groups_file="${TEMPDIR}/${goos}_groups.txt" + rm -f "${groups_file}" + + # Process architectures in order to build groups + for goarch in $archs; do + hash=$(cat "${TEMPDIR}/${goos}_${goarch}_hash.txt") + # Check if we've seen this hash before + if grep -q "^${hash}:" "${groups_file}" 2>/dev/null; then + # Append to existing group + existing=$(grep "^${hash}:" "${groups_file}" | cut -d: -f2) + sed -i.bak "s/^${hash}:.*/${hash}:${existing}, ${goarch}/" "${groups_file}" + rm -f "${groups_file}.bak" + else + # New group + echo "${hash}:${goarch}" >> "${groups_file}" + fi + done + + # Generate the combined report for this platform + output_file="third-party-licenses.${goos}.md" + + cat > "${output_file}" << 'EOF' +# GitHub MCP Server dependencies + +The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. + +## Table of Contents + +EOF + + # Build table of contents (sorted for determinism) + # Use LC_ALL=C for consistent sorting across different systems + LC_ALL=C sort "${groups_file}" | while IFS=: read -r hash group_archs; do + # Create anchor-friendly name + anchor=$(echo "${group_archs}" | tr ', ' '-' | tr -s '-') + echo "- [${group_archs}](#${anchor})" >> "${output_file}" + done + + echo "" >> "${output_file}" + echo "---" >> "${output_file}" + echo "" >> "${output_file}" + + # Add each unique report section (sorted for determinism) + # Use LC_ALL=C for consistent sorting across different systems + LC_ALL=C sort "${groups_file}" | while IFS=: read -r hash group_archs; do + # Get the packages from the first arch in this group + first_arch=$(echo "${group_archs}" | cut -d',' -f1 | tr -d ' ') + packages=$(cat "${TEMPDIR}/${goos}_${first_arch}_packages.txt") + + cat >> "${output_file}" << EOF +## ${group_archs} + +The following packages are included for the ${group_archs} architectures. + +${packages} + +EOF + done + + # Add footer + echo "[github/github-mcp-server]: https://github.com/github/github-mcp-server" >> "${output_file}" + + echo "Generated ${output_file}" done +echo "Done! License files generated." + diff --git a/script/licenses-check b/script/licenses-check index 67b567d02..430c8170b 100755 --- a/script/licenses-check +++ b/script/licenses-check @@ -1,21 +1,34 @@ #!/bin/bash +# +# Check that license files are up to date. +# This script regenerates the license files and compares them with the committed versions. +# If there are differences, it exits with an error. -go install github.com/google/go-licenses@latest - -for goos in linux darwin windows ; do - # Note: we ignore warnings because we want the command to succeed, however the output should be checked - # for any new warnings, and potentially we may need to add license information. - # - # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, - # depending on the license. - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.copy.md || echo "Ignore warnings" - if ! diff -s third-party-licenses.${goos}.copy.md third-party-licenses.${goos}.md; then - printf "License check failed.\n\nPlease update the license file by running \`.script/licenses\` and committing the output." - rm -f third-party-licenses.${goos}.copy.md - exit 1 - fi - rm -f third-party-licenses.${goos}.copy.md +set -e + +# Store original files for comparison +TEMPDIR="$(mktemp -d)" +trap "rm -fr ${TEMPDIR}" EXIT + +# Save original license markdown files +for goos in darwin linux windows; do + cp "third-party-licenses.${goos}.md" "${TEMPDIR}/" done +# Save the state of third-party directory +cp -r third-party "${TEMPDIR}/third-party.orig" + +# Regenerate using the same script +./script/licenses + +# Check for any differences in workspace +if ! git diff --exit-code --quiet third-party-licenses.*.md third-party/; then + echo "License files are out of date:" + git diff third-party-licenses.*.md third-party/ + echo "" + printf "\nLicense check failed.\n\nPlease update the license files by running \`./script/licenses\` and committing the output.\n" + exit 1 +fi +echo "License check passed for all platforms." diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 32cdb5b6d..ef8816689 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -2,10 +2,15 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [amd64, arm64](#amd64-arm64) +--- + +## amd64, arm64 + +The following packages are included for the amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 32cdb5b6d..851a70594 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -2,10 +2,15 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [386, amd64, arm64](#386-amd64-arm64) +--- + +## 386, amd64, arm64 + +The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index c7e00fb13..f4f8ee42c 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -2,10 +2,15 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [386, amd64, arm64](#386-amd64-arm64) +--- + +## 386, amd64, arm64 + +The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE))