fix: remove deprecated doc_auto_cfg feature (merged into doc_cfg in 1… #31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com> | |
| # | |
| # SPDX-License-Identifier: MIT | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| force_publish: | |
| description: "Force publish (ignores version check)" | |
| type: boolean | |
| default: false | |
| skip_tests: | |
| description: "Skip test stage (for emergency releases)" | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| attestations: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| RUST_BACKTRACE: short | |
| RUSTFLAGS: -D warnings | |
| jobs: | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # STAGE 1: CHECKS (parallel) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| check: | |
| name: Check (${{ matrix.rust }} / ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # MSRV | |
| - rust: "1.92" | |
| os: ubuntu-latest | |
| msrv: true | |
| # Stable (primary) | |
| - rust: stable | |
| os: ubuntu-latest | |
| # Stable on macOS | |
| - rust: stable | |
| os: macos-latest | |
| # Stable on Windows | |
| - rust: stable | |
| os: windows-latest | |
| # Nightly (for future compat) | |
| - rust: nightly | |
| os: ubuntu-latest | |
| allow_fail: true | |
| continue-on-error: ${{ matrix.allow_fail || false }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust ${{ matrix.rust }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.rust }} | |
| components: clippy | |
| - name: Cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ${{ matrix.rust }}-${{ matrix.os }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| - name: Check (all features) | |
| run: cargo check --all-features | |
| - name: Check (no default features) | |
| run: cargo check --no-default-features | |
| fmt: | |
| name: Format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: rustfmt | |
| - name: Check formatting | |
| run: cargo +nightly fmt --all -- --check | |
| docs: | |
| name: Documentation | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTDOCFLAGS: -D warnings | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Build docs | |
| run: cargo doc --all-features --no-deps | |
| security: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install cargo-deny | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-deny | |
| - name: Security audit | |
| run: cargo deny check advisories | |
| reuse: | |
| name: REUSE Compliance | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install reuse | |
| run: pip install --user reuse | |
| - name: Check REUSE compliance | |
| run: reuse lint | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # STAGE 2: TEST (after checks pass) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| test: | |
| name: Test Suite | |
| needs: [check, fmt, security, reuse] | |
| if: ${{ !inputs.skip_tests }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache | |
| uses: Swatinem/rust-cache@v2 | |
| continue-on-error: true | |
| with: | |
| shared-key: test | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Install nextest | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-nextest | |
| - name: Run tests | |
| run: cargo nextest run --all-features --profile ci --no-tests=pass | |
| - name: Run doctests | |
| run: cargo test --doc --all-features || true | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: test-results | |
| path: target/nextest/ci/junit.xml | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| - name: Upload test results to Codecov | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: target/nextest/ci/junit.xml | |
| report_type: test_results | |
| fail_ci_if_error: false | |
| coverage: | |
| name: Coverage | |
| needs: test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - name: Cache | |
| uses: Swatinem/rust-cache@v2 | |
| continue-on-error: true | |
| with: | |
| shared-key: coverage | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Install tools | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-llvm-cov,cargo-nextest | |
| - name: Generate coverage | |
| run: | | |
| cargo llvm-cov nextest --all-features --profile ci --lcov --output-path lcov.info --no-tests=pass | |
| cargo llvm-cov report --html | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: lcov.info | |
| report_type: coverage | |
| fail_ci_if_error: false | |
| verbose: true | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: coverage-report | |
| path: target/llvm-cov/html/ | |
| retention-days: 30 | |
| - name: Coverage summary | |
| run: | | |
| COVERAGE=$(cargo llvm-cov report 2>/dev/null | grep TOTAL | awk '{print $NF}' || echo "N/A") | |
| echo "## Coverage: $COVERAGE" >> $GITHUB_STEP_SUMMARY | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # STAGE 3: RELEASE (after tests pass, main branch only) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| changelog: | |
| name: Update Changelog | |
| needs: [check, fmt, security, reuse] | |
| runs-on: ubuntu-latest | |
| if: | | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| github.ref == 'refs/heads/main' && | |
| !contains(github.event.head_commit.message || '', '[skip ci]') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| - name: Install git-cliff | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: git-cliff | |
| - name: Generate changelog | |
| id: changelog | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if ! git cliff --config cliff.toml --github-token "$GITHUB_TOKEN" -o CHANGELOG.md 2>/dev/null; then | |
| echo "::warning::GitHub API unavailable, generating without contributors" | |
| git cliff --config cliff.toml -o CHANGELOG.md | |
| fi | |
| if git diff --quiet CHANGELOG.md 2>/dev/null; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Commit changelog | |
| if: steps.changelog.outputs.changed == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| for i in 1 2 3; do | |
| git add CHANGELOG.md | |
| git diff --cached --quiet && { echo "No changes to commit"; exit 0; } | |
| git commit -m "chore: update CHANGELOG.md [skip ci]" || true | |
| if git push origin main 2>&1; then | |
| echo "Push successful" | |
| exit 0 | |
| fi | |
| echo "Push failed, retrying..." | |
| git fetch origin main | |
| git reset --hard origin/main | |
| git cliff --config cliff.toml -o CHANGELOG.md | |
| sleep $((i * 2)) | |
| done | |
| echo "::warning::Failed to push changelog after 3 attempts" | |
| exit 0 | |
| release: | |
| name: Release | |
| needs: [check, fmt, docs, security, reuse, test, coverage, changelog] | |
| if: | | |
| always() && | |
| needs.check.result == 'success' && | |
| needs.fmt.result == 'success' && | |
| needs.docs.result == 'success' && | |
| needs.security.result == 'success' && | |
| needs.reuse.result == 'success' && | |
| (needs.test.result == 'success' || needs.test.result == 'skipped') && | |
| (needs.coverage.result == 'success' || needs.coverage.result == 'skipped') && | |
| needs.changelog.result == 'success' && | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| github.ref == 'refs/heads/main' && | |
| !contains(github.event.head_commit.message || '', '[skip ci]') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| published: ${{ steps.publish.outputs.published }} | |
| version: ${{ steps.publish.outputs.version }} | |
| tag: ${{ steps.publish.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Detect version | |
| id: version | |
| run: | | |
| VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[0].version') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Detected version: $VERSION" | |
| - name: Check if already published | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| # Check crates.io API directly for more reliable results | |
| if curl -s "https://crates.io/api/v1/crates/entity-derive/$VERSION" | jq -e '.version' >/dev/null 2>&1; then | |
| echo "already_published=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Version $VERSION already exists on crates.io" | |
| else | |
| echo "already_published=false" >> "$GITHUB_OUTPUT" | |
| echo "Version $VERSION not yet published" | |
| fi | |
| - name: Version already published | |
| if: steps.check.outputs.already_published == 'true' && !inputs.force_publish | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| echo "## ℹ️ Version $VERSION already published" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "This version is already available on [crates.io](https://crates.io/crates/entity-derive/$VERSION)." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### To publish a new version:" >> $GITHUB_STEP_SUMMARY | |
| echo "1. Update \`version\` in \`Cargo.toml\`" >> $GITHUB_STEP_SUMMARY | |
| echo "2. Follow [SemVer](https://semver.org/) rules:" >> $GITHUB_STEP_SUMMARY | |
| echo " - **MAJOR** (x.0.0): Breaking API changes" >> $GITHUB_STEP_SUMMARY | |
| echo " - **MINOR** (0.x.0): New features, backwards compatible" >> $GITHUB_STEP_SUMMARY | |
| echo " - **PATCH** (0.0.x): Bug fixes, backwards compatible" >> $GITHUB_STEP_SUMMARY | |
| echo "3. Commit and push to trigger a new release" >> $GITHUB_STEP_SUMMARY | |
| - name: Publish to crates.io | |
| id: publish | |
| if: steps.check.outputs.already_published == 'false' || inputs.force_publish | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| TAG="${{ steps.version.outputs.tag }}" | |
| echo "Publishing entity-derive $VERSION..." | |
| OUTPUT=$(cargo publish --no-verify 2>&1) || true | |
| if echo "$OUTPUT" | grep -q "already exists"; then | |
| echo "::notice::Version $VERSION already exists on crates.io" | |
| echo "published=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| elif echo "$OUTPUT" | grep -q "Uploading\|Successfully"; then | |
| echo "Successfully published entity-derive $VERSION" | |
| echo "published=true" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| else | |
| echo "::error::Failed to publish: $OUTPUT" | |
| exit 1 | |
| fi | |
| - name: Create git tag | |
| if: steps.publish.outputs.published == 'true' | |
| run: | | |
| TAG="${{ steps.version.outputs.tag }}" | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "Tag $TAG already exists" | |
| else | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$TAG" -m "Release $TAG" | |
| git push origin "$TAG" | |
| echo "Created tag $TAG" | |
| fi | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ steps.version.outputs.tag }}" | |
| VERSION="${{ steps.version.outputs.version }}" | |
| # Pull latest changelog (updated by changelog job) | |
| git pull origin main --rebase 2>/dev/null || true | |
| # Extract release notes from CHANGELOG.md | |
| NOTES="" | |
| if [ -f CHANGELOG.md ]; then | |
| # Extract section between current version header and next version header | |
| NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | sed '1d;$d') | |
| fi | |
| # Fallback | |
| if [ -z "$NOTES" ]; then | |
| NOTES="See [CHANGELOG.md](https://github.com/RAprogramm/entity-derive/blob/main/CHANGELOG.md) for details." | |
| fi | |
| # Create release with notes | |
| gh release create "$TAG" \ | |
| --title "🚀 Release $VERSION" \ | |
| --notes "$NOTES" \ | |
| --verify-tag || echo "Release already exists or creation failed" | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Release Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ steps.publish.outputs.published }}" == "true" ]; then | |
| echo "✅ Published **entity-derive ${{ steps.version.outputs.version }}**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- [crates.io](https://crates.io/crates/entity-derive/${{ steps.version.outputs.version }})" >> $GITHUB_STEP_SUMMARY | |
| echo "- [docs.rs](https://docs.rs/entity-derive/${{ steps.version.outputs.version }})" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ steps.check.outputs.already_published }}" == "true" ]; then | |
| echo "ℹ️ Version ${{ steps.version.outputs.version }} already published" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⏭️ No release triggered" >> $GITHUB_STEP_SUMMARY | |
| fi |