esp-emac 0.3.0

ESP32 EMAC bare-metal Ethernet MAC driver with DMA, RMII, and MDIO
Documentation
name: Release

# Publishes esp-emac to crates.io when a GitHub Release is published with a
# tag of the form `v<semver>`. Refuses to publish if Cargo.toml and the tag
# disagree, and refuses to re-publish a version already on crates.io
# (idempotent reruns).
#
# Pre-flight: the trait crate `eth-mdio-phy` (in the sibling eth-phy-rs
# repository) MUST already be on crates.io at a compatible version, because
# `cargo publish` emits a registry-only `version = "..."` dep once the local
# `path = "..."` is stripped, and verification will pull that registry copy.
#
# Pipeline (mirrors ci.yml — no green → no publish):
#   1. verify         — host fmt + clippy + test + rustdoc on the tagged ref.
#   2. verify-xtensa  — re-run the xtensa example build on the tagged ref.
#   3. verify-msrv    — re-run the MSRV 1.88 host build on the tagged ref.
#   4. verify-version — confirm tag matches Cargo.toml.
#   5. publish        — sparse-index skip-check, dry-run, then cargo publish.
#
# The draft Release itself is kept up-to-date by release-drafter.yml: every
# merged PR refreshes the draft. A maintainer reviews, edits, and publishes
# it manually — publishing creates the git tag, which fires this workflow.

on:
  release:
    types: [published]
  # Manual trigger for re-running the pipeline against an already-
  # published release (e.g. after a workflow-only fix lands in `main`
  # post-publish). The Release-event's `release.tag_name` is replaced
  # by `inputs.tag` when this trigger fires.
  workflow_dispatch:
    inputs:
      tag:
        description: 'Release tag to re-publish (e.g. v0.2.0)'
        required: true
        type: string

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  # Tag we operate on. `release.tag_name` exists when the workflow is
  # fired by a Release publish; for `workflow_dispatch` re-runs we fall
  # back to the user-supplied input.
  TAG_NAME: ${{ github.event.release.tag_name || inputs.tag }}
  # See ci.yml — workflow-scope `RUSTFLAGS` would override `.cargo/config.toml`
  # rustflags and break the xtensa link. `-D warnings` is enforced through
  # clippy's `-- -D warnings` and per-job `RUSTDOCFLAGS`.

jobs:
  verify:
    name: Verify (re-run CI before publishing)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ env.TAG_NAME }}
          path: esp-emac
      - uses: actions/checkout@v6
        with:
          repository: jethub-iot/eth-phy-rs
          path: eth-phy
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
          targets: riscv32imc-unknown-none-elf
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: esp-emac
      - working-directory: esp-emac
        run: cargo fmt --all -- --check
      - working-directory: esp-emac
        run: cargo clippy --lib --features 'mdio-phy embassy-net async defmt' -- -D warnings
      - working-directory: esp-emac
        run: cargo test --features 'mdio-phy embassy-net async defmt'
      - working-directory: esp-emac
        env:
          RUSTDOCFLAGS: --cfg docsrs
        run: cargo doc --no-deps --target riscv32imc-unknown-none-elf --features 'mdio-phy embassy-net async defmt'

  verify-xtensa:
    name: Verify (xtensa example builds)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ env.TAG_NAME }}
          path: esp-emac
      - uses: actions/checkout@v6
        with:
          repository: jethub-iot/eth-phy-rs
          path: eth-phy
      - uses: esp-rs/xtensa-toolchain@v1.7
        with:
          default: true
          buildtargets: esp32
          ldproxy: false
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: esp-emac
      - working-directory: esp-emac
        run: cargo build --release --example embassy_net_lan8720a --target xtensa-esp32-none-elf --features 'esp-hal mdio-phy embassy-net'

  verify-msrv:
    name: Verify MSRV 1.88 still builds (host)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ env.TAG_NAME }}
          path: esp-emac
      - uses: actions/checkout@v6
        with:
          repository: jethub-iot/eth-phy-rs
          path: eth-phy
      - uses: dtolnay/rust-toolchain@1.88.0
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: esp-emac
      - working-directory: esp-emac
        run: cargo build --lib
      - working-directory: esp-emac
        run: cargo build --lib --features 'mdio-phy embassy-net async defmt'

  verify-version:
    name: Verify tag matches Cargo.toml
    runs-on: ubuntu-latest
    needs: [verify, verify-xtensa, verify-msrv]
    outputs:
      version: ${{ steps.tag.outputs.version }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ env.TAG_NAME }}
      - name: Resolve version from tag
        id: tag
        run: |
          set -euo pipefail
          TAG='${{ env.TAG_NAME }}'
          # cargo / crates.io grammar: MAJOR.MINOR.PATCH with optional
          # `-prerelease`. SemVer `+build` metadata is intentionally NOT
          # accepted — cargo strips it on publish, so a `+build` tag
          # would never match Cargo.toml or the registry, only opening
          # a duplicate-publish footgun for no real benefit.
          if [[ ! "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)$ ]]; then
            echo "::error::Tag '$TAG' does not match 'v<MAJOR>.<MINOR>.<PATCH>[-<prerelease>]' (build metadata '+...' is not accepted, see release.yml comment)"
            exit 1
          fi
          echo "version=${BASH_REMATCH[1]}" >>"$GITHUB_OUTPUT"
      - name: Confirm Cargo.toml agrees
        run: |
          set -euo pipefail
          ACTUAL=$(cargo metadata --no-deps --format-version 1 \
                   | jq -r '.packages[] | select(.name == "esp-emac") | .version')
          if [ "$ACTUAL" != '${{ steps.tag.outputs.version }}' ]; then
            echo "::error::Cargo.toml says $ACTUAL, tag says ${{ steps.tag.outputs.version }}"
            exit 1
          fi

  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: [verify, verify-xtensa, verify-msrv, verify-version]
    steps:
      # Side-checkout the sibling jethub-iot/eth-phy-rs alongside this
      # crate. Even though `eth-mdio-phy` is `optional = true` and not
      # in the default feature set, cargo parses the *entire* manifest
      # (including optional path-deps) before it can strip paths and
      # publish. A missing `../eth-phy/...` directory makes
      # `cargo publish` fail with "No such file or directory" at the
      # manifest-load stage.
      - uses: actions/checkout@v6
        with:
          ref: ${{ env.TAG_NAME }}
          path: esp-emac
      - uses: actions/checkout@v6
        with:
          repository: jethub-iot/eth-phy-rs
          path: eth-phy
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: esp-emac

      - name: Skip if already on crates.io
        id: check
        run: |
          set -euo pipefail
          VERSION='${{ needs.verify-version.outputs.version }}'
          if curl -fsSL "https://index.crates.io/es/p-/esp-emac" 2>/dev/null \
               | jq -e --arg v "$VERSION" 'select(.vers == $v)' >/dev/null; then
            echo "esp-emac@$VERSION already published — skipping"
            echo "skip=true" >>"$GITHUB_OUTPUT"
          else
            echo "skip=false" >>"$GITHUB_OUTPUT"
          fi

      # `cargo publish --dry-run` packages and verifies against the
      # registry's read API but never authenticates, so we don't pass
      # `CARGO_REGISTRY_TOKEN` here. Keeping the token off this step
      # narrows its blast radius.
      #
      # The default `--verify` step compiles the packaged crate against
      # the **default** feature set. esp-emac's default is empty
      # (no `esp-hal`, no `eth-mdio-phy`), but the manifest-load itself
      # still needs the sibling eth-phy-rs checkout above.
      - name: Dry-run publish
        if: steps.check.outputs.skip == 'false'
        working-directory: esp-emac
        run: cargo publish --dry-run

      - name: Publish to crates.io
        if: steps.check.outputs.skip == 'false'
        working-directory: esp-emac
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.JETHUB_CRATES_TOKEN }}
        run: cargo publish