|
| 1 | +name: Metadata, docs and deps |
| 2 | +permissions: |
| 3 | + contents: read |
| 4 | + pull-requests: read |
| 5 | +on: |
| 6 | + pull_request: |
| 7 | + branches-ignore: |
| 8 | + - "v[0-9]+.[0-9]+.[0-9]+.[0-9]+" |
| 9 | + - release |
| 10 | + |
| 11 | +env: |
| 12 | + FAIL_IF_CARGO_DENY: false # Set to true to make cargo-deny errors fail the job |
| 13 | + FAIL_IF_MISSING_DOCS: false # Set to true to make missing docs errors fail the job |
| 14 | + |
| 15 | +jobs: |
| 16 | + # Get crates changed in the PR that are not flagged as publish = false |
| 17 | + changed-crates: |
| 18 | + runs-on: ubuntu-latest |
| 19 | + outputs: |
| 20 | + crates: ${{ steps.changed-crates.outputs.crates }} |
| 21 | + crates_count: ${{ steps.changed-crates.outputs.crates_count }} |
| 22 | + base_ref: ${{ steps.changed-crates.outputs.base_ref }} |
| 23 | + steps: |
| 24 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 |
| 25 | + with: |
| 26 | + fetch-depth: 0 # Need full history for git diff |
| 27 | + - id: changed-crates |
| 28 | + uses: ./.github/actions/changed-crates |
| 29 | + |
| 30 | + # Check cargo metadata for crates that are not flagged as publish = false |
| 31 | + cargo-metadata: |
| 32 | + needs: changed-crates |
| 33 | + if: needs.changed-crates.outputs.crates_count > 0 |
| 34 | + runs-on: ubuntu-latest |
| 35 | + steps: |
| 36 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 |
| 37 | + with: |
| 38 | + fetch-depth: 0 # Need full history for git diff |
| 39 | + - name: Check Cargo.toml version is not changed in the PR |
| 40 | + run: | |
| 41 | + # Get the base ref for comparison |
| 42 | + BASE_REF='${{ needs.changed-crates.outputs.base_ref }}' |
| 43 | + |
| 44 | + # Get the list of changed crates |
| 45 | + CRATES='${{ needs.changed-crates.outputs.crates }}' |
| 46 | + |
| 47 | + FAILED=0 |
| 48 | + for crate in $(echo "$CRATES" | jq -r '.[]'); do |
| 49 | + if [[ -f "$crate/Cargo.toml" ]]; then |
| 50 | + echo "Checking $crate version is not changed in the PR..." |
| 51 | + |
| 52 | + # Get current version from Cargo.toml |
| 53 | + CURRENT_VERSION=$(grep -E '^\s*version\s*=' "$crate/Cargo.toml" | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/') |
| 54 | + |
| 55 | + # Get base version from Cargo.toml at base ref |
| 56 | + BASE_VERSION=$(git show "$BASE_REF:$crate/Cargo.toml" 2>/dev/null | grep -E '^\s*version\s*=' | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/' || echo "") |
| 57 | + |
| 58 | + if [[ -z "$BASE_VERSION" ]]; then |
| 59 | + echo " ✓ $crate is a new crate (no base version found)" |
| 60 | + elif [[ "$CURRENT_VERSION" != "$BASE_VERSION" ]]; then |
| 61 | + echo " ✗ ERROR: $crate version changed from $BASE_VERSION to $CURRENT_VERSION" |
| 62 | + echo " Version changes should only happen through the release process." |
| 63 | + FAILED=1 |
| 64 | + else |
| 65 | + echo " ✓ $crate version unchanged ($CURRENT_VERSION)" |
| 66 | + fi |
| 67 | + fi |
| 68 | + done |
| 69 | + |
| 70 | + if [[ $FAILED -eq 1 ]]; then |
| 71 | + echo "" |
| 72 | + echo "ERROR: One or more crates have version changes in this PR." |
| 73 | + echo "Version bumps should be done through the release process, not in feature PRs." |
| 74 | + exit 1 |
| 75 | + fi |
| 76 | +
|
| 77 | + - name: Check cargo metadata for changed crates |
| 78 | + run: | |
| 79 | + # Get the list of changed crates |
| 80 | + CRATES='${{ needs.changed-crates.outputs.crates }}' |
| 81 | + |
| 82 | + # Convert JSON array to space-separated arguments |
| 83 | + CRATE_ARGS=$(echo "$CRATES" | jq -r '.[]' | tr '\n' ' ') |
| 84 | + |
| 85 | + if [[ -n "$CRATE_ARGS" ]]; then |
| 86 | + echo "Checking crates: $CRATE_ARGS" |
| 87 | + ./scripts/check_cargo_metadata.sh $CRATE_ARGS |
| 88 | + else |
| 89 | + echo "No crates to check" |
| 90 | + fi |
| 91 | +
|
| 92 | + # Check for missing documentation and post results as PR comment |
| 93 | + missing-docs: |
| 94 | + needs: changed-crates |
| 95 | + if: needs.changed-crates.outputs.crates_count > 0 |
| 96 | + runs-on: ubuntu-latest |
| 97 | + permissions: |
| 98 | + pull-requests: write |
| 99 | + steps: |
| 100 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 |
| 101 | + - uses: dtolnay/rust-toolchain@stable |
| 102 | + - name: Check missing docs |
| 103 | + id: missing-docs |
| 104 | + run: | |
| 105 | + # Get the list of changed crates |
| 106 | + CRATES='${{ needs.changed-crates.outputs.crates }}' |
| 107 | + |
| 108 | + # Capture doc warnings for each crate |
| 109 | + OUTPUT="" |
| 110 | + TOTAL_WARNINGS=0 |
| 111 | + |
| 112 | + for crate in $(echo "$CRATES" | jq -r '.[]'); do |
| 113 | + echo "Checking docs for $crate..." |
| 114 | + |
| 115 | + # Run cargo check with missing_docs warning and capture output |
| 116 | + WARNINGS=$(RUSTFLAGS="-W missing_docs" cargo check -p "$crate" --message-format=json | jq -r 'select(.reason == "compiler-message") | select(.message.code.code == "missing_docs") | .message.rendered' 2>&1 || true) |
| 117 | +
|
| 118 | + echo "WARNINGS: $WARNINGS" |
| 119 | +
|
| 120 | + WARNING_COUNT=$(echo "$WARNINGS" | grep -c "missing documentation" 2>/dev/null || true) |
| 121 | + WARNING_COUNT=${WARNING_COUNT:-0} |
| 122 | + |
| 123 | + if [[ "$WARNING_COUNT" -gt 0 ]]; then |
| 124 | + OUTPUT="${OUTPUT}\n### 📦 \`${crate}\` - ${WARNING_COUNT} warning(s)\n\n<details>\n<summary>Show warnings</summary>\n\n\`\`\`\n${WARNINGS}\n\`\`\`\n\n</details>\n" |
| 125 | + TOTAL_WARNINGS=$((TOTAL_WARNINGS + WARNING_COUNT)) |
| 126 | + else |
| 127 | + OUTPUT="${OUTPUT}\n### 📦 \`${crate}\` - ✅ No warnings\n" |
| 128 | + fi |
| 129 | + done |
| 130 | + |
| 131 | + # Create the comment body |
| 132 | + if [[ $TOTAL_WARNINGS -gt 0 ]]; then |
| 133 | + HEADER="## 📚 Documentation Check Results\n\n⚠️ **${TOTAL_WARNINGS} documentation warning(s) found**\n" |
| 134 | + else |
| 135 | + HEADER="## 📚 Documentation Check Results\n\n✅ **No documentation warnings found!**\n" |
| 136 | + fi |
| 137 | + |
| 138 | + COMMENT_BODY="${HEADER}${OUTPUT}\n\n---\n*Updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC') | Commit: ${{ github.sha }}*" |
| 139 | + |
| 140 | + # Write to file for the comment action (handle multi-line) |
| 141 | + echo -e "$COMMENT_BODY" > doc-check-results.md |
| 142 | + |
| 143 | + echo "total_warnings=$TOTAL_WARNINGS" >> "$GITHUB_OUTPUT" |
| 144 | + |
| 145 | + - name: Fail if warnings found and FAIL_IF_MISSING_DOCS is true |
| 146 | + if: env.FAIL_IF_MISSING_DOCS == 'true' && steps.missing-docs.outputs.total_warnings > 0 |
| 147 | + run: | |
| 148 | + echo "missing-docs found ${{ steps.missing-docs.outputs.total_warnings }} warning(s) and FAIL_IF_MISSING_DOCS is enabled" |
| 149 | + exit 1 |
| 150 | +
|
| 151 | + - name: Find existing comment |
| 152 | + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 |
| 153 | + id: find-comment |
| 154 | + with: |
| 155 | + issue-number: ${{ github.event.pull_request.number }} |
| 156 | + comment-author: 'github-actions[bot]' |
| 157 | + body-includes: '## 📚 Documentation Check Results' |
| 158 | + |
| 159 | + - name: Create or update PR comment |
| 160 | + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 |
| 161 | + with: |
| 162 | + comment-id: ${{ steps.find-comment.outputs.comment-id }} |
| 163 | + issue-number: ${{ github.event.pull_request.number }} |
| 164 | + body-path: doc-check-results.md |
| 165 | + edit-mode: replace |
| 166 | + |
| 167 | + dependency-check: |
| 168 | + needs: changed-crates |
| 169 | + if: needs.changed-crates.outputs.crates_count > 0 |
| 170 | + runs-on: ubuntu-latest |
| 171 | + permissions: |
| 172 | + pull-requests: write |
| 173 | + steps: |
| 174 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 |
| 175 | + - uses: dtolnay/rust-toolchain@stable |
| 176 | + - uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1 |
| 177 | + with: |
| 178 | + tool: cargo-deny |
| 179 | + - name: Run cargo-deny on changed crates |
| 180 | + id: cargo-deny |
| 181 | + run: | |
| 182 | + # Get the list of changed crates |
| 183 | + CRATES='${{ needs.changed-crates.outputs.crates }}' |
| 184 | + |
| 185 | + OUTPUT="" |
| 186 | + TOTAL_ISSUES=0 |
| 187 | + TOTAL_ERRORS=0 |
| 188 | + |
| 189 | + for crate in $(echo "$CRATES" | jq -r '.[]'); do |
| 190 | + echo "Running cargo-deny for $crate..." |
| 191 | + |
| 192 | + # Run cargo-deny and capture output (always continue, results go to PR comment) |
| 193 | + DENY_OUTPUT=$(cargo deny --manifest-path "$crate/Cargo.toml" --color never check advisories bans sources 2>&1 || true) |
| 194 | + |
| 195 | + echo "DENY_OUTPUT: $DENY_OUTPUT" |
| 196 | +
|
| 197 | + # Count errors and warnings |
| 198 | + ERROR_COUNT=$(echo "$DENY_OUTPUT" | grep -cE "^error" 2>/dev/null || true) |
| 199 | + ERROR_COUNT=${ERROR_COUNT:-0} |
| 200 | + WARNING_COUNT=$(echo "$DENY_OUTPUT" | grep -cE "^warning" 2>/dev/null || true) |
| 201 | + WARNING_COUNT=${WARNING_COUNT:-0} |
| 202 | + ISSUE_COUNT=$((ERROR_COUNT + WARNING_COUNT)) |
| 203 | + |
| 204 | + if [[ "$ISSUE_COUNT" -gt 0 ]]; then |
| 205 | + OUTPUT="${OUTPUT}\n### 📦 \`${crate}\` - ${ERROR_COUNT} error(s), ${WARNING_COUNT} warning(s)\n\n<details>\n<summary>Show output</summary>\n\n\`\`\`\n${DENY_OUTPUT}\n\`\`\`\n\n</details>\n" |
| 206 | + TOTAL_ISSUES=$((TOTAL_ISSUES + ISSUE_COUNT)) |
| 207 | + else |
| 208 | + OUTPUT="${OUTPUT}\n### 📦 \`${crate}\` - ✅ No issues\n" |
| 209 | + fi |
| 210 | + done |
| 211 | + |
| 212 | + # Create the comment body |
| 213 | + if [[ $TOTAL_ISSUES -gt 0 ]]; then |
| 214 | + HEADER="## 🔒 Cargo Deny Results\n\n⚠️ **${TOTAL_ISSUES} issue(s) found** (advisories, bans, sources)\n" |
| 215 | + else |
| 216 | + HEADER="## 🔒 Cargo Deny Results\n\n✅ **No issues found!**\n" |
| 217 | + fi |
| 218 | + |
| 219 | + COMMENT_BODY="${HEADER}${OUTPUT}\n\n---\n*Updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC') | Commit: ${{ github.sha }}*" |
| 220 | + |
| 221 | + # Write to file for the comment action |
| 222 | + echo -e "$COMMENT_BODY" > cargo-deny-results.md |
| 223 | + |
| 224 | + echo "total_issues=$TOTAL_ISSUES" >> "$GITHUB_OUTPUT" |
| 225 | + echo "total_errors=$TOTAL_ERRORS" >> "$GITHUB_OUTPUT" |
| 226 | + |
| 227 | + - name: Find existing comment |
| 228 | + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 |
| 229 | + id: find-comment |
| 230 | + with: |
| 231 | + issue-number: ${{ github.event.pull_request.number }} |
| 232 | + comment-author: 'github-actions[bot]' |
| 233 | + body-includes: '## 🔒 Cargo Deny Results' |
| 234 | + |
| 235 | + - name: Create or update PR comment |
| 236 | + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 |
| 237 | + with: |
| 238 | + comment-id: ${{ steps.find-comment.outputs.comment-id }} |
| 239 | + issue-number: ${{ github.event.pull_request.number }} |
| 240 | + body-path: cargo-deny-results.md |
| 241 | + edit-mode: replace |
| 242 | + |
| 243 | + - name: Fail if errors found and FAIL_IF_CARGO_DENY is true |
| 244 | + if: env.FAIL_IF_CARGO_DENY == 'true' && steps.cargo-deny.outputs.total_errors > 0 |
| 245 | + run: | |
| 246 | + echo "cargo-deny found ${{ steps.cargo-deny.outputs.total_errors }} error(s) and FAIL_IF_CARGO_DENY is enabled" |
| 247 | + exit 1 |
0 commit comments