Releases

Learn how to release a new SDK or new versions of an SDK.

This guide goes over the minimal setup needed to publish something via publish and craft. This example will use a PyPI package.

This guide assumes this thing has never been released before and has no existing tags. If that's not the case feel free to skip this section!

A tag must exist for the "beginning" of the repository due to a limitation -- it is recommended to tag the first commit of the repository before any meaningful history such that the first changelog includes useful context.

Copied
git tag 0.0.0 "$(git log -1 --reverse --format=%h)"
git push origin --tags

First we need to ensure your repository responds to release/** branches and produces an artifact with the right name.

For our example repository that's done using push: branches and an artifact instruction.

We're just interested in making a GitHub release and a PyPI release so our craft configuration looks like:

Copied
minVersion: 0.28.1
targets:
  - name: pypi
  - name: github

This script will be invoked by craft by default when bumping the version.

Since we're specifying version in setup.cfg this is pretty easy to do with sed (we're ignoring $1 which contains the old version):

Copied
#!/usr/bin/env bash
set -euxo pipefail

sed -i "s/^version =.*/version = $2/" setup.cfg

Try the script out (but don't commit the changes):

Copied
$ ./scripts/bump-version.sh 0.0.0 1.0.0
+ sed -i 's/^version =.*/version = 1.0.0/' setup.cfg
$ git diff | grep '^[-+]'
--- a/setup.cfg
+++ b/setup.cfg
-version = 0.0.0
+version = 1.0.0
$ git checkout -- .

Nice!

This file is used to trigger the release from the GitHub UI.

You'll notice it uses vars.SENTRY_RELEASE_BOT_CLIENT_ID and secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY -- these should be available to your repository automatically!

Copied
name: Release

on:
  workflow_dispatch:
    inputs:
      version:
        description: Version to release
        required: true
      force:
        description: Force a release even when there are release-blockers (optional)
        required: false

jobs:
  release:
    runs-on: ubuntu-latest
    name: "Release a new version"
    steps:
      - name: Get auth token
        id: token
        uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
        with:
          app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
          private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}
      - uses: actions/checkout@v3
        with:
          token: ${{ steps.token.outputs.token }}
          fetch-depth: 0
      - name: Prepare release
        uses: getsentry/action-prepare-release@v1
        env:
          GITHUB_TOKEN: ${{ steps.token.outputs.token }}
        with:
          version: ${{ github.event.inputs.version }}
          force: ${{ github.event.inputs.force }}

Commit those three files and make a pull request.

Here's an example PR and the follow-up to fix fetch-depth.

Give the following teams access to your repository:

  • engineering -> write

You can do this self-service via the settings page of your repository: https://github.com/getsentry/REPONAME_HERE/settings/access

Download and save the default ruleset template as a JSON file.

Visit the ruleset setting page of your repository: https://github.com/getsentry/REPONAME_HERE/settings/rules, click on the green New ruleset button, choose Import a ruleset, and select the JSON file you just downloaded. You can tweak the ruleset settings, but please don't remove the App in the Bypass List.

Navigate to the actions tab of your repository, locate the release workflow, and create the first release! I used 1.0.0 as the first version.

This will create an issue in publish which you'll need an approver to add a label to.

To keep versioning consistent across SDKs, we generally follow semantic versioning (semver), with language- or platform-specific conventions around semver (if applicable). Craft has some precautionary checks to ensure we only publish valid, semver-like versions. Specifically, craft expects versions following this format:

Copied
<major>.<minor>.<patch>(-<prerelease>)?(-<build>)?

While the major, minor and patch version numbers are required, pre-release and build properties are optional but can also be combined.

If you want to create a preview or pre-release of your SDK, you can append a pre-release identifier, as well as an optional incremental pre-release number to your version. This will ensure that various craft publishing targets will treat the release as a pre-release, meaning for example that it will not get assigned a latest tag in package repositories. Likewise, pre-releases will not be inserted into our internal release-registry, which is used by the Sentry product as well as our docs and other tools to query the latest or specific releases of our packages.

Importantly, we have strict rules around which identifiers we accept as a pre-release, meaning the <prerelease> part of your version has to be one of the following: preview|pre|rc|dev|alpha|beta|unstable|a|b. Therefore, we do not accept arbitrary strings as pre-release identifiers. Using any other identifiers will result in a release being considered stable instead. This means, it will get assigned a latest tag in package repositories and inserted into our release-registry.

Examples:

Copied
// valid
1.0.0-preview
1.0.0-alpha.0
1.0.0-beta.1
1.0.0-rc.20
1.0.0-a

// invalid or incorrectly treated
1.0.0-foo
1.0.0-canary.0

Python has the concept of post releases, which craft handles implicitly. A post release is indicated by a -\d+ suffix to the semver version, for example: 1.0.0-1. Given that we only consider certain identifiers as valid pre-release identifiers, post releases are considered stable releases.

In some cases, you can append a build identifier to your version, for example if you release the same package version for different platforms or architectures. You can also combine build and pre-release identifiers but in this case, the pre-release identifier has to come first.

Examples:

Copied
// valid
1.0.0+x86_64
1.0.0-rc.1+x86_64

// invalid or incorrectly treated
1.0.0+rc.1+x86_64
1.0.0+x86_64-beta.0

In some cases, we need to maintain multiple or diverging publishing configs in a repository. For example, when we have to maintain multiple major versions of a package we release (e.g. Sentry JavaScript SDK v7 and v8), where we added or removed artifacts or publishing targets.

By default, our publishing process is configured to take the craft.yml config from the repo's default branch as the single source of truth for publishing.

You can opt your repo into using the config from a specific merge target branch (can be configured when triggering the Prepare Release Action) in two steps:

Add craft_config_from_merge_target: true when calling getsentry/action-prepare-release in your repo's release workflow:

Copied
# ...
jobs:
  release:
    runs-on: ubuntu-latest
    name: "Release a new version"
    steps:
      # ...
      - name: Prepare release
        uses: getsentry/action-prepare-release@v1
        with:
          # ...
          craft_config_from_merge_target: true

Add the branch(es) you want to take the config from to the publish.yml workflow in getsentry/publish:

Copied
# ...
- name: Set target repo checkout branch
  if: |
    fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'v7'

Note: The branch(es) registered in this step MUST BE protected in the target repository. Disable direct pushing to the branch and require approvals for PRs before they can be merged.

Good job! Now you can let your craft configs diverge across the different merge target branches and the publishing process will pick up the correct config based on the branch you're setting as a merge target.

Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").