diff --git a/CHANGELOG.md b/CHANGELOG.md index d858f3c..9219a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **rust/package-crate** - Package Rust crates for publishing to crates.io + - Workspace support with `--package` flag for workspace crates + - Handles workspace version inheritance (`version.workspace = true`) + - Configurable allow-dirty flag + - Additional cargo arguments support + - Verification output showing packaged `.crate` files +- **rust/publish-crate** - Publish Rust crates to crates.io + - Workspace support with `--package` flag for workspace crates + - Handles workspace version inheritance (`version.workspace = true`) + - Secure token handling (hidden in logs) + - Dry-run mode for testing + - Configurable allow-dirty flag + - Additional cargo arguments support - **rust/build-library** - Build Rust libraries with flexible profile and feature control - **rust/lint** - Run cargo fmt and cargo clippy for code quality checks - **rust/verify-toolchain** - Reusable action to verify Rust toolchain and components are installed diff --git a/rust/package-crate/README.md b/rust/package-crate/README.md new file mode 100644 index 0000000..7d5c26f --- /dev/null +++ b/rust/package-crate/README.md @@ -0,0 +1,134 @@ +# Package Rust Crate + +Composite action to package a Rust crate for publishing to crates.io. Supports both workspace and standalone crates. + +## Features + +- ✅ **Workspace Support**: Handles workspace crates with `version.workspace = true` +- ✅ **Standalone Support**: Works with single-crate projects +- ✅ **Allow Dirty**: Optional flag to package with uncommitted changes +- ✅ **Flexible Arguments**: Pass additional cargo arguments +- ✅ **Verification Output**: Lists packaged `.crate` files after successful packaging + +## Usage + +### Workspace Crate + +For crates that use workspace version inheritance (`version.workspace = true`): + +```yaml +- name: Package workspace crate + uses: firestoned/github-actions/rust/package-crate@v1.2.4 + with: + package: my-crate-name + workspace: true + allow-dirty: true +``` + +### Standalone Crate + +For standalone crates (single Cargo.toml): + +```yaml +- name: Package crate + uses: firestoned/github-actions/rust/package-crate@v1.2.4 + with: + allow-dirty: false +``` + +### With Additional Arguments + +```yaml +- name: Package crate with features + uses: firestoned/github-actions/rust/package-crate@v1.2.4 + with: + package: my-crate + workspace: true + cargo-args: '--no-verify' +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `package` | Package name to package (for workspace crates) | No | `''` | +| `workspace` | Whether this is a workspace crate (uses --package flag) | No | `false` | +| `allow-dirty` | Allow packaging with uncommitted changes | No | `true` | +| `cargo-args` | Additional arguments to pass to cargo package | No | `''` | + +## Outputs + +This action produces a `.crate` file in `target/package/` directory. + +## Prerequisites + +- Rust toolchain must be installed (use `firestoned/github-actions/rust/setup-rust-build`) +- For workspace crates, must be run from workspace root + +## Example: Complete Release Workflow + +```yaml +jobs: + package-crates: + runs-on: ubuntu-latest + strategy: + matrix: + crate: + - name: my-derive-crate + - name: my-runtime-crate + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: firestoned/github-actions/rust/setup-rust-build@v1.2.4 + with: + target: x86_64-unknown-linux-gnu + + - name: Update workspace version + run: | + sed -i 's/^version = ".*"/version = "1.0.0"/' Cargo.toml + + - name: Package crate + uses: firestoned/github-actions/rust/package-crate@v1.2.4 + with: + package: ${{ matrix.crate.name }} + workspace: true + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.crate.name }}-package + path: target/package/${{ matrix.crate.name }}-*.crate +``` + +## Why Use This Action? + +### Problem: Workspace Version Management + +When using workspace version inheritance: + +```toml +# Cargo.toml (workspace root) +[workspace.package] +version = "1.0.0" + +# my-crate/Cargo.toml +[package] +version.workspace = true +``` + +Running `cd my-crate && cargo package` fails because the workspace version is not accessible from the crate directory. + +### Solution: Package from Workspace Root + +This action runs `cargo package --package ` from the workspace root, allowing cargo to properly resolve workspace-inherited fields. + +## Notes + +- The action uses `--allow-dirty` by default since CI often modifies `Cargo.toml` to update versions +- For workspace crates, `workspace: true` and `package: ` must both be specified +- The packaged `.crate` file is placed in `target/package/` + +## License + +MIT diff --git a/rust/package-crate/action.yaml b/rust/package-crate/action.yaml new file mode 100644 index 0000000..c7c0561 --- /dev/null +++ b/rust/package-crate/action.yaml @@ -0,0 +1,74 @@ +# Copyright (c) 2025 Erick Bourgeois, firestoned +# SPDX-License-Identifier: MIT + +name: 'Package Rust Crate' +description: 'Package a Rust crate for publishing to crates.io, supporting both workspace and standalone crates' +author: 'Erick Bourgeois' + +inputs: + package: + description: 'Package name to package (for workspace crates, use --package flag)' + required: false + default: '' + workspace: + description: 'Whether this is a workspace crate (uses --package flag)' + required: false + default: 'false' + allow-dirty: + description: 'Allow packaging with uncommitted changes' + required: false + default: 'true' + cargo-args: + description: 'Additional arguments to pass to cargo package' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Verify Rust toolchain + uses: firestoned/github-actions/rust/verify-toolchain@main + with: + require-cargo: true + + - name: Package crate + shell: bash + run: | + set -e + + # Build cargo command + CMD="cargo package --verbose" + + # Add package flag for workspace crates + if [ "${{ inputs.workspace }}" = "true" ] && [ -n "${{ inputs.package }}" ]; then + CMD="$CMD --package ${{ inputs.package }}" + echo "Packaging workspace crate: ${{ inputs.package }}" + elif [ -n "${{ inputs.package }}" ]; then + echo "WARNING: package specified but workspace=false. The package flag requires workspace=true." + echo "Packaging current crate in directory" + else + echo "Packaging current crate" + fi + + # Add allow-dirty flag + if [ "${{ inputs.allow-dirty }}" = "true" ]; then + CMD="$CMD --allow-dirty" + fi + + # Add any additional cargo arguments + if [ -n "${{ inputs.cargo-args }}" ]; then + CMD="$CMD ${{ inputs.cargo-args }}" + fi + + echo "Running: $CMD" + $CMD + + echo "" + echo "Package created successfully" + + # List the packaged crate + if [ -d "target/package" ]; then + echo "" + echo "Packaged crate(s):" + ls -lh target/package/*.crate 2>/dev/null || echo "No .crate files found in target/package" + fi diff --git a/rust/publish-crate/README.md b/rust/publish-crate/README.md new file mode 100644 index 0000000..c70bba0 --- /dev/null +++ b/rust/publish-crate/README.md @@ -0,0 +1,171 @@ +# Publish Rust Crate + +Composite action to publish a Rust crate to crates.io. Supports both workspace and standalone crates. + +## Features + +- ✅ **Workspace Support**: Handles workspace crates with `version.workspace = true` +- ✅ **Standalone Support**: Works with single-crate projects +- ✅ **Secure Token Handling**: Token hidden in logs +- ✅ **Allow Dirty**: Optional flag to publish with uncommitted changes +- ✅ **Dry Run**: Test publishing without actually uploading +- ✅ **Flexible Arguments**: Pass additional cargo arguments + +## Usage + +### Workspace Crate + +For crates that use workspace version inheritance (`version.workspace = true`): + +```yaml +- name: Publish workspace crate + uses: firestoned/github-actions/rust/publish-crate@v1.2.4 + with: + package: my-crate-name + workspace: true + token: ${{ secrets.CARGO_REGISTRY_TOKEN }} +``` + +### Standalone Crate + +For standalone crates (single Cargo.toml): + +```yaml +- name: Publish crate + uses: firestoned/github-actions/rust/publish-crate@v1.2.4 + with: + token: ${{ secrets.CARGO_REGISTRY_TOKEN }} + allow-dirty: false +``` + +### Dry Run + +Test publishing without actually uploading to crates.io: + +```yaml +- name: Test publish (dry run) + uses: firestoned/github-actions/rust/publish-crate@v1.2.4 + with: + package: my-crate + workspace: true + token: ${{ secrets.CARGO_REGISTRY_TOKEN }} + dry-run: true +``` + +### With Additional Arguments + +```yaml +- name: Publish with custom registry + uses: firestoned/github-actions/rust/publish-crate@v1.2.4 + with: + package: my-crate + workspace: true + token: ${{ secrets.CARGO_REGISTRY_TOKEN }} + cargo-args: '--registry my-registry' +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `package` | Package name to publish (for workspace crates) | No | `''` | +| `workspace` | Whether this is a workspace crate (uses --package flag) | No | `false` | +| `token` | crates.io API token (from secrets.CARGO_REGISTRY_TOKEN) | **Yes** | - | +| `allow-dirty` | Allow publishing with uncommitted changes | No | `true` | +| `dry-run` | Perform a dry run without actually publishing | No | `false` | +| `cargo-args` | Additional arguments to pass to cargo publish | No | `''` | + +## Outputs + +None. The crate is published directly to crates.io. + +## Prerequisites + +- Rust toolchain must be installed (use `firestoned/github-actions/rust/setup-rust-build`) +- For workspace crates, must be run from workspace root +- Valid crates.io API token stored in repository secrets + +## Example: Complete Release Workflow + +```yaml +jobs: + publish-crates: + runs-on: ubuntu-latest + needs: sign-artifacts + strategy: + max-parallel: 1 # Publish sequentially for dependencies + matrix: + crate: + - name: my-derive-crate # Publish derive crate first + - name: my-runtime-crate # Then publish crate that depends on it + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: firestoned/github-actions/rust/setup-rust-build@v1.2.4 + with: + target: x86_64-unknown-linux-gnu + + - name: Update workspace version + run: | + sed -i 's/^version = ".*"/version = "1.0.0"/' Cargo.toml + + - name: Publish to crates.io + uses: firestoned/github-actions/rust/publish-crate@v1.2.4 + with: + package: ${{ matrix.crate.name }} + workspace: true + token: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + - name: Wait for crate propagation + if: matrix.crate.name == 'my-derive-crate' + run: | + echo "Waiting 60 seconds for crate to be indexed..." + sleep 60 +``` + +## Why Use This Action? + +### Problem: Workspace Version Management + +When using workspace version inheritance: + +```toml +# Cargo.toml (workspace root) +[workspace.package] +version = "1.0.0" + +# my-crate/Cargo.toml +[package] +version.workspace = true +``` + +Running `cd my-crate && cargo publish` fails because the workspace version is not accessible from the crate directory. + +### Solution: Publish from Workspace Root + +This action runs `cargo publish --package ` from the workspace root, allowing cargo to properly resolve workspace-inherited fields. + +## Security + +- The `token` input is masked in GitHub Actions logs +- The action displays `[token hidden]` in the log output instead of the actual token +- Store your crates.io token in repository secrets as `CARGO_REGISTRY_TOKEN` + +## Publishing Order + +For workspace crates with dependencies: +1. Use `max-parallel: 1` in your matrix strategy +2. Publish dependencies first (e.g., derive macros) +3. Add a wait step after dependency crates to allow crates.io indexing +4. Then publish dependent crates + +## Notes + +- The action uses `--allow-dirty` by default since CI often modifies `Cargo.toml` to update versions +- For workspace crates, `workspace: true` and `package: ` must both be specified +- Use `dry-run: true` to test the publishing process without uploading + +## License + +MIT diff --git a/rust/publish-crate/action.yaml b/rust/publish-crate/action.yaml new file mode 100644 index 0000000..a37e198 --- /dev/null +++ b/rust/publish-crate/action.yaml @@ -0,0 +1,85 @@ +# Copyright (c) 2025 Erick Bourgeois, firestoned +# SPDX-License-Identifier: MIT + +name: 'Publish Rust Crate' +description: 'Publish a Rust crate to crates.io, supporting both workspace and standalone crates' +author: 'Erick Bourgeois' + +inputs: + package: + description: 'Package name to publish (for workspace crates, use --package flag)' + required: false + default: '' + workspace: + description: 'Whether this is a workspace crate (uses --package flag)' + required: false + default: 'false' + token: + description: 'crates.io API token (usually from secrets.CARGO_REGISTRY_TOKEN)' + required: true + allow-dirty: + description: 'Allow publishing with uncommitted changes' + required: false + default: 'true' + dry-run: + description: 'Perform a dry run without actually publishing' + required: false + default: 'false' + cargo-args: + description: 'Additional arguments to pass to cargo publish' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Verify Rust toolchain + uses: firestoned/github-actions/rust/verify-toolchain@main + with: + require-cargo: true + + - name: Publish crate + shell: bash + run: | + set -e + + # Build cargo command + CMD="cargo publish --token ${{ inputs.token }}" + + # Add package flag for workspace crates + if [ "${{ inputs.workspace }}" = "true" ] && [ -n "${{ inputs.package }}" ]; then + CMD="$CMD --package ${{ inputs.package }}" + echo "Publishing workspace crate: ${{ inputs.package }}" + elif [ -n "${{ inputs.package }}" ]; then + echo "WARNING: package specified but workspace=false. The package flag requires workspace=true." + echo "Publishing current crate in directory" + else + echo "Publishing current crate" + fi + + # Add allow-dirty flag + if [ "${{ inputs.allow-dirty }}" = "true" ]; then + CMD="$CMD --allow-dirty" + fi + + # Add dry-run flag + if [ "${{ inputs.dry-run }}" = "true" ]; then + CMD="$CMD --dry-run" + echo "DRY RUN MODE: Not actually publishing to crates.io" + fi + + # Add any additional cargo arguments + if [ -n "${{ inputs.cargo-args }}" ]; then + CMD="$CMD ${{ inputs.cargo-args }}" + fi + + echo "Running: cargo publish [token hidden] ..." + $CMD + + if [ "${{ inputs.dry-run }}" = "true" ]; then + echo "" + echo "✓ Dry run completed successfully" + else + echo "" + echo "✓ Published to crates.io successfully" + fi