gridoxide 0.1.0

AC power flow and state estimation solver with a sparse Newton-Raphson core
Documentation
name: Publish to crates.io

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      dry_run:
        description: 'Package and verify everything without uploading'
        type: boolean
        default: true

env:
  CARGO_TERM_COLOR: always

jobs:
  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    # Same tag trigger as pypi.yml, so one `v*` tag cuts both the wheels and
    # the crate. Nothing here depends on that workflow, though: a PyPI failure
    # shouldn't hold back the crate or vice versa.
    #
    # Holds the CARGO_REGISTRY_TOKEN secret, which is the point of the
    # environment — scoping the token to this one job, and letting a required
    # reviewer be added later without touching this file.
    environment: crate-release
    steps:
      # No `submodules:` — the two git submodules under tests/data/ hold only
      # test fixtures, and Cargo.toml's `exclude` keeps that whole directory
      # out of the packaged .crate anyway.
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-publish-

      # A version published to crates.io can never be replaced, so catch a
      # mistyped tag here rather than by burning the wrong version number.
      # Skipped on workflow_dispatch, which has no tag to compare against.
      - name: Check tag matches crate version
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          set -euo pipefail
          version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "gridoxide") | .version')
          tag="${GITHUB_REF_NAME#v}"
          if [ "$tag" != "$version" ]; then
            echo "::error::Tag $GITHUB_REF_NAME does not match Cargo.toml version $version"
            exit 1
          fi
          echo "Tag $GITHUB_REF_NAME matches Cargo.toml version $version"

      # Catches the two ways a .crate can be broken without any local build
      # noticing: a file the package needs that `exclude` dropped, and a
      # dependency that resolves locally but not from the registry.
      #
      # `--workspace` packages the other workspace crates too, and
      # verifies each crate against the others it just packaged rather than
      # against crates.io, where this version does not exist yet. Verifies on
      # default features only, matching build.yml — `klu`/`pardiso` need a C
      # toolchain, libclang, or a local oneMKL install. That's a limit on what
      # this job *compiles*, not on what ships: the crate is source-only, so
      # every feature's source is published either way.
      - name: Package
        run: cargo package --workspace

      # The `cgmes` feature is the one whose dependencies come from outside
      # this repo (cimoxide's `cimoxide-model`), so build it from the packaged
      # .crate rather than the working tree — that is what a crates.io
      # consumer actually gets. Pure Rust, so unlike `klu` it needs
      # nothing extra on the runner.
      - name: Check the packaged crate builds with --features cgmes
        run: |
          set -euo pipefail
          version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "gridoxide") | .version')
          # Every workspace crate's .crate, unpacked side by side.
          for crate in target/package/*-$version.crate; do
            tar xzf "$crate" -C target/package
          done
          # This version of the workspace's other crates is not on crates.io
          # until the step below, so point the extracted gridoxide at the
          # extracted crates — again, exactly what a consumer gets.
          patches=()
          for dir in target/package/gridoxide-*-$version/; do
            name=$(basename "$dir" "-$version")
            patches+=(--config "patch.crates-io.$name.path='$PWD/$dir'")
          done
          cargo build --features cgmes \
            --manifest-path "target/package/gridoxide-$version/Cargo.toml" "${patches[@]}"

      # Everything above is the dry run: a `workflow_dispatch` with dry_run
      # left on exercises the whole packaging path and stops here, without
      # spending a version number.
      #
      # `--no-verify` because the `cargo package` step above already built the
      # very same .crates; re-verifying would just compile faer twice.
      # `--workspace` publishes in dependency order, gridoxide-sparse first
      # and gridoxide last.
      - name: Publish
        if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish --workspace --no-verify