Browse Source

build: process for automatic release tags (#10133)

Make the release tagging consistent. Push to release branch to create a
stable release; push to release-rc to release a new candidate.
Jakob Borg 4 months ago
parent
commit
58c85fc9db

+ 30 - 6
.github/workflows/build-syncthing.yaml

@@ -173,7 +173,7 @@ jobs:
 
 
   codesign-windows:
   codesign-windows:
     name: Codesign for Windows
     name: Codesign for Windows
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
     runs-on: windows-latest
     runs-on: windows-latest
     needs:
     needs:
@@ -280,7 +280,7 @@ jobs:
 
 
   package-macos:
   package-macos:
     name: Package for macOS
     name: Package for macOS
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
     runs-on: macos-latest
     runs-on: macos-latest
     steps:
     steps:
@@ -380,7 +380,7 @@ jobs:
 
 
   notarize-macos:
   notarize-macos:
     name: Notarize for macOS
     name: Notarize for macOS
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
     needs:
     needs:
       - package-macos
       - package-macos
@@ -524,7 +524,7 @@ jobs:
 
 
   sign-for-upgrade:
   sign-for-upgrade:
     name: Sign for upgrade
     name: Sign for upgrade
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-')  || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
     needs:
     needs:
       - codesign-windows
       - codesign-windows
@@ -723,6 +723,8 @@ jobs:
     name: Publish release files
     name: Publish release files
     if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
     if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
+    permissions:
+      contents: write
     needs:
     needs:
       - sign-for-upgrade
       - sign-for-upgrade
       - package-debian
       - package-debian
@@ -782,13 +784,35 @@ jobs:
         with:
         with:
           args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
           args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest
 
 
+      - name: Create GitHub release and push binaries
+        run: |
+          maybePrerelease=""
+          if [[ $VERSION == *-* ]]; then
+            maybePrerelease="--prerelease"
+          fi
+          export GH_PROMPT_DISABLED=1
+          if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
+            gh release create \
+              "$VERSION" \
+              $maybePrerelease \
+              --title "$VERSION" \
+              --notes-from-tag
+          fi
+          gh release upload "$VERSION" \
+            packages/*.asc packages/*.json \
+            packages/syncthing-*.tar.gz \
+            packages/syncthing-*.zip \
+            packages/syncthing*.deb
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
   #
   #
   # Push Debian/APT archive
   # Push Debian/APT archive
   #
   #
 
 
   publish-apt:
   publish-apt:
     name: Publish APT
     name: Publish APT
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v'))
     environment: release
     environment: release
     needs:
     needs:
       - package-debian
       - package-debian
@@ -867,7 +891,7 @@ jobs:
   docker-syncthing:
   docker-syncthing:
     name: Build and push Docker images
     name: Build and push Docker images
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
-    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v'))
+    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v'))
     environment: docker
     environment: docker
     permissions:
     permissions:
       contents: read
       contents: read

+ 55 - 0
.github/workflows/release-syncthing.yaml

@@ -0,0 +1,55 @@
+name: Release Syncthing
+
+on:
+  push:
+    branches:
+      - release
+      - release-rc*
+
+permissions:
+  contents: write
+
+jobs:
+  create-release-tag:
+    name: Create release tag
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+          ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
+
+      - uses: actions/setup-go@v5
+        with:
+          go-version: stable
+
+      - name: Get svu
+        run: |
+          go install github.com/caarlos0/svu@latest
+
+      - name: Determine version to release
+        run: |
+          if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
+            next=$(svu next)
+          else
+            next=$(svu prerelease --pre-release rc)
+          fi
+          echo "NEXT=$next" >> $GITHUB_ENV
+          echo "Next version is $next"
+
+          prev=$(git describe --exclude "*-*" --abbrev=0)
+          echo "PREV=$prev" >> $GITHUB_ENV
+          echo "Previous version is $prev"
+
+      - name: Determine release notes
+        run: |
+          go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Create and push tag
+        run: |
+          git config --global user.name 'Syncthing Release Automation'
+          git config --global user.email '[email protected]'
+          git tag -a -F notes.md "$NEXT"
+          git push origin "$NEXT"

+ 18 - 0
relnotes/README.md

@@ -0,0 +1,18 @@
+# Release Notes
+
+Files in this directory constitute manual release notes for a given release.
+When relevant, they should be created prior to that release so that they can
+be included in the corresponding tag message, etc.
+
+To add release notes for a release 1.2.3, create a file named `v1.2.3.md`
+consisting of an initial H2-level header and further notes as desired. For
+example:
+
+```
+## Major changes in v1.2.3
+
+- Files are now synchronized twice as fast on Tuesdays
+```
+
+The release notes will also be included in candidate releases (e.g.
+v1.2.3-rc.1).

+ 109 - 0
script/relnotes.go

@@ -0,0 +1,109 @@
+// Copyright (C) 2025 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+//go:build ignore
+// +build ignore
+
+package main
+
+import (
+	"bytes"
+	"cmp"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+)
+
+var (
+	githubToken = os.Getenv("GITHUB_TOKEN")
+	githubRepo  = cmp.Or(os.Getenv("GITHUB_REPOSITORY"), "syncthing/syncthing")
+)
+
+func main() {
+	ver := flag.String("new-ver", "", "New version tag")
+	prevVer := flag.String("prev-ver", "", "Previous version tag")
+	branch := flag.String("branch", "HEAD", "Branch to release from")
+	flag.Parse()
+
+	log.SetOutput(os.Stderr)
+
+	if *ver == "" {
+		log.Fatalln("Must set --new-ver")
+	}
+	if githubToken == "" {
+		log.Fatalln("Must set $GITHUB_TOKEN")
+	}
+
+	addl, err := additionalNotes(*ver)
+	if err != nil {
+		log.Fatalln("Gathering additional notes:", err)
+	}
+	notes, err := generatedNotes(*ver, *branch, *prevVer)
+	if err != nil {
+		log.Fatalln("Gathering github notes:", err)
+	}
+
+	if addl != "" {
+		fmt.Println(addl)
+	}
+	fmt.Println(notes)
+}
+
+// Load potential additional release notes from within the repo
+func additionalNotes(newVer string) (string, error) {
+	ver, _, _ := strings.Cut(newVer, "-")
+	bs, err := os.ReadFile(fmt.Sprintf("relnotes/%s.md", ver))
+	if os.IsNotExist(err) {
+		return "", nil
+	}
+	return string(bs), err
+}
+
+// Load generated release notes (list of pull requests and contributors)
+// from GitHub.
+func generatedNotes(newVer, targetCommit, prevVer string) (string, error) {
+	fields := map[string]string{
+		"tag_name":          newVer,
+		"target_commitish":  targetCommit,
+		"previous_tag_name": prevVer,
+	}
+	bs, err := json.Marshal(fields)
+	if err != nil {
+		return "", err
+	}
+	req, err := http.NewRequest(http.MethodPost, "https://api.github.com/repos/"+githubRepo+"/releases/generate-notes", bytes.NewReader(bs)) //nolint:noctx
+	if err != nil {
+		return "", err
+	}
+
+	req.Header.Set("Accept", "application/vnd.github+json")
+	req.Header.Set("Authorization", "Bearer "+githubToken)
+	req.Header.Set("X-Github-Api-Version", "2022-11-28")
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return "", err
+	}
+	if res.StatusCode != http.StatusOK {
+		bs, _ := io.ReadAll(res.Body)
+		log.Print(string(bs))
+		return "", errors.New(res.Status) //nolint:err113
+	}
+	defer res.Body.Close()
+
+	var resJSON struct {
+		Body string
+	}
+	if err := json.NewDecoder(res.Body).Decode(&resJSON); err != nil {
+		return "", err
+	}
+	return resJSON.Body, nil
+}