crate-ci / cargo-release

Cargo subcommand `release`: everything about releasing a rust crate.
Apache License 2.0
1.33k stars 112 forks source link

What would an auto-release workflow look like? #117

Open epage opened 5 years ago

epage commented 5 years ago

Something I've been considering is the idea of auto-releasing after each PR but the question is "how?"

epage commented 5 years ago

One approach I just came across via awesome-rust is semantic which uses clog to generate the changelog and determine the bump level based on the commit messages.

sunng87 commented 5 years ago

Personally I prefer an isolation between commit log, which is dev-facing, and release notes, which is user facing. So I didn't included auto changelog generation from very beginning.

However, I agree it's possible to determine the release level from commit log or public api changes. I wish semantic could offer a binary of library just for determining bump level so we can add optional support for that.

epage commented 5 years ago

Personally I prefer an isolation between commit log, which is dev-facing, and release notes, which is user facing. So I didn't included auto changelog generation from very beginning.

Ideally, yes but I maintain too many crates to always worry about the changelog. Maybe switching to cargo-release will help. However, I do tend to make my commits more user focused (if they are feat/fix rather than refactor/chore).

And too often I see projects, major projects, without them and I'm at a loss digging through commits when they make breaking changes. Something is better than nothing.

However, I agree it's possible to determine the release level from commit log or public api changes. I wish semantic could offer a binary of library just for determining bump level so we can add optional support for that.

At my quick glance, it looked like semantic was just calling into clog-cli's crate to see if anything was considered breaking from conventional-changelog's perspective, so that should be relatively easy to pull out.

epage commented 5 years ago

The downside to semantic's approach is you have to hope you did things right for it to correctly guess what version field to bump. If you mess up, you might end up needing to yank a release. I have also found that while it is easy to get people to do checklist tasks in the source code (like update changelog), it is harder to be programmatically precise tasks like with commit messages.

Another approach is to instead have the source always represent what the next version will be. If a commit makes a breaking change, then that commit is responsible for updating the version number without doing a release. The CI then releases that version. To support this, we might want a --no-release flag that is a shortcut for --no-push, --no-tag, etc that a developer will call in prep for their PR.

The use cases we'd want to consider supporting

... I have more thoughts and there is some problem areas but I wanted to post this without a long delay as I write it all up

TomPridham commented 2 years ago

we recently did this at my work and i figured it would be helpful to share since it was not straight forward to figure out. the workflow we have relies on two main actions, one to ensure that pr titles(and thus the merge commit) have a valid bump level and one that reads that commit and creates the tag and bumps everything.

a MAJOR caveat with this is that adding those secrets makes them available for use by anyone opening a pr on your repo. if you google "push to protected branch github action", you will get a bunch of results like this: https://github.community/t/allowing-github-actions-bot-to-push-to-protected-branch/16536 . we are using this on a private repo, so we're not really concerned with someone doing some fishy. i think there must be ways to mitigate that since rust itself has something like this, but i haven't looked into it at all

name: Check PR Title

on:
  pull_request:
    types: [opened, edited, synchronize, reopened]

jobs:
  check-pr-title:
    runs-on: ubuntu-latest
    steps:
      - uses: deepakputhraya/action-pr-title@master
        with:
          allowed_prefixes: "#major,#minor,#patch,#none" # title should start with the given prefix
          prefix_case_sensitive: false # title prefix are case insensitive
          github_token: ${{ github.token }} # Default: ${{ github.token }}
name: Bump version, create new tag and release point
on:
  push:
    branches:
      - "main"

jobs:
  bump_version:
    name: Bump version, create tag/release point
    runs-on: ubuntu-latest
    # prevent recursively starting actions. it will only start one extra and then fail on the
    # `bump_version` step if it is removed, but it's good to have for other github actions that
    # might trigger on pushes to main
    if: "!startsWith(github.event.head_commit.message, '[RELEASE]')"
    steps:
      # cache cargo release so we don't have to install it every time
      - name: Cache cargo release and rust install steps
        id: cache-release
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            ~/target/
          key: ${{ runner.os }}-cargo-release

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          repository: pdq/rover
          # if you have branch protection on, you will need to a secret PAT for a repo admin or add
          # an exception for user who generated the PAT
          # this action caches the authentication it uses and makes that the default for future
          # steps. adding the token here means you don't have to add it manually in a later step
          token: ${{ secrets.PAT_GIT_BOT }}
          fetch-depth: "0"

      - name: Import GPG key
        uses: crazy-max/ghaction-import-gpg@v4
        with:
          # if you are requiring signed commits, you need to add a secret private key that will be
          # used to sign the commits/tags
          gpg_private_key: ${{ secrets.GPG_RSA_PRIVATE_KEY }}
          git_user_signingkey: true
          git_commit_gpgsign: true

        # this is an easy way to get the bump level from the pr title once it has been merged. it
        # makes the bump level available to use in the cargo release step
      - name: Get version from PR
        id: bump_version
        uses: anothrNick/github-tag-action@1.36.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WITH_V: false
          DEFAULT_BUMP: none
          DRY_RUN: true

      - name: install cargo-release
        uses: actions-rs/cargo@v1
        if: steps.cache-release.outputs.cache-hit != 'true'
        with:
          command: install
          args: cargo-release

      - name: Setup Git info
        run: |
          git config user.name "git-bot"
          git config user.email "git-bot@your-domain.com"

      - name: Create tag and update Cargo.toml
        uses: actions-rs/cargo@v1
        with:
          command: release
          args: ${{ steps.bump_version.outputs.part }} --execute --no-confirm
petersooley commented 2 years ago

For what it's worth, if you don't need signed commits or are worried about protected branches, you can get away with a much more out-of-the-box release process.

name: Release

env:
  CARGO_TERM_COLOR: always

defaults:
  run:
    shell: bash

on:
  workflow_dispatch:
    inputs:
      level:
        type: choice
        description: Bump level
        options:
          - patch
          - minor
          - major

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: cache cargo cache
        id: cache-release
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            ~/target/
          key: ${{ runner.os }}-cargo-release

      - name: checkout code
        uses: actions/checkout@v3

      - name: git config
        run: |
          git config user.name "Github Actions: Release"
          git config user.email "<>"

      # fail early if tests fail
      - name: run tests
        run: |
          make test

      - name: install cargo-release
        uses: actions-rs/cargo@v1
        if: steps.cache-release.outputs.cache-hit != 'true'
        with:
          command: install
          args: cargo-release

      - name: cargo release ${{ github.event.inputs.level }}
        uses: actions-rs/cargo@v1
        with:
          command: release
          args: ${{ github.event.inputs.level }} --execute --no-confirm --config .github/workflows/cargo-release.toml
epage commented 1 year ago

With 0.22 out, we now have cargo release --unpublished which would allow CI to automatically release crates if a commit is pushed/merged with the version fields changed.

While this isn't fully automatic, this gets as close as I think is safe at this point.

thomaseizinger commented 1 year ago

I've started building a tool that I called semverlog. The idea is to eventually have one-click style releases.

semverlog has a compute-bump-level command which will look for a .changes directory in the current working dir and print "major", "minor" or "patch" to stdout, based on the changes found.

I was envisioning the following integration with cargo release:

cargo release version --level-from-command="semverlog compute-bump-level"

cargo release already figures out which dependencies to release in which order in a workspace. To compute the level, it would execute the given command with the crate-to-be-released as the working directory.

What do you think of this idea?

epage commented 1 year ago

I can't fully articulate why but that handshake doesn't feel quite right to me.

thomaseizinger commented 1 year ago

I can't fully articulate why but that handshake doesn't feel quite right to me.

Do you see any possible integration that would compute the bump level dynamically?