Browse Source

build: Add GitHub actions build for Windows (#8627)

This is sort of a proof of concept, but since our current Windows
builder is down this might solve the problem. It includes a change for
easier code signing (taking the certificate in a secret/env var rather
than existing already on disk), but otherwise mirrors precisely what we
already do in the build server.
Jakob Borg 3 years ago
parent
commit
452daad14b
2 changed files with 146 additions and 1 deletions
  1. 117 0
      .github/workflows/build-syncthing.yaml
  2. 29 1
      build.go

+ 117 - 0
.github/workflows/build-syncthing.yaml

@@ -0,0 +1,117 @@
+name: Build Syncthing
+
+on:
+  pull_request:
+  push:
+
+env:
+  # The go version to use for builds.
+  GO_VERSION: "1.19.3"
+
+  # Optimize compatibility on the slow archictures.
+  GO386: softfloat
+  GOARM: "5"
+  GOMIPS: softfloat
+
+  # Avoid hilarious amounts of obscuring log output when running tests.
+  LOGGER_DISCARD: "1"
+
+# A note on actions and third party code... The actions under actions/ (like
+# `uses: actions/checkout`) are maintained by GitHub, and we need to trust
+# GitHub to maintain their code and infrastructure or we're in deep shit in
+# general. The same doesn't necessarily apply to other actions authors, so
+# some care needs to be taken when adding steps, especially in the paths
+# that lead up to code being packaged and signed.
+
+jobs:
+
+  #
+  # Windows, quick build and test, runs always
+  #
+
+  build-windows:
+    name: Build and test on Windows
+    runs-on: windows-latest
+    steps:
+      - name: Set git to use LF
+        # Without this, the checkout will happen with CRLF line endings,
+        # which is fine for the source code but messes up tests that depend
+        # on data on disk being as expected. Ideally, those tests should be
+        # fixed, but not today.
+        run: |
+          git config --global core.autocrlf false
+          git config --global core.eol lf
+
+      - uses: actions/checkout@v3
+
+      - uses: actions/setup-go@v3
+        # `cache: true` gives us automatic caching of modules and build
+        # cache, speeding up builds. The cache key is dependent on the Go
+        # version and our go.sum contents.
+        with:
+          go-version: ${{ env.GO_VERSION }}
+          cache: true
+
+      - name: Build and test
+        run: |
+          go run build.go
+          go run build.go test
+
+  #
+  # Windows, build signed packages
+  #
+
+  package-windows:
+    name: Create packages for Windows
+    runs-on: windows-latest
+    # We only run this job for release pushes.
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release')
+    # This is also enforced by the environment which contains the secrets.
+    environment: signing
+    needs:
+      - build-windows
+    steps:
+      - name: Set git to use LF
+        run: |
+          git config --global core.autocrlf false
+          git config --global core.eol lf
+
+      - uses: actions/checkout@v3
+        # `fetch-depth: 0` because we want to check out the entire repo
+        # including tags and branches, not just the latest commit which
+        # lacks version info.
+        with:
+          fetch-depth: 0
+
+      - uses: actions/setup-go@v3
+        with:
+          go-version: ${{ env.GO_VERSION }}
+
+      - uses: actions/cache@v3
+        with:
+          path: |
+            ~\AppData\Local\go-build
+            ~\go\pkg\mod
+          key: ${{ runner.os }}-go-${{ env.GOVERSION }}-package-${{ hashFiles('**/go.sum') }}
+
+      - name: Install dependencies
+        run: |
+          go install github.com/josephspurrier/goversioninfo/cmd/[email protected]
+
+      - name: Create packages
+        run: |
+          go run build.go -goarch amd64 zip
+          go run build.go -goarch arm zip
+          go run build.go -goarch arm64 zip
+          go run build.go -goarch 386 zip
+        env:
+          CODESIGN_SIGNTOOL: ${{ secrets.CODESIGN_SIGNTOOL }}
+          CODESIGN_CERTIFICATE_BASE64: ${{ secrets.CODESIGN_CERTIFICATE_BASE64 }}
+          CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODESIGN_CERTIFICATE_PASSWORD }}
+          CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }}
+
+      - name: Archive artifacts
+        uses: actions/upload-artifact@v3
+        with:
+          name: packages
+          path: syncthing-windows-*.zip

+ 29 - 1
build.go

@@ -15,6 +15,7 @@ import (
 	"bytes"
 	"compress/flate"
 	"compress/gzip"
+	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"flag"
@@ -1383,6 +1384,33 @@ func windowsCodesign(file string) {
 		args := []string{"sign", "/fd", algo}
 		if f := os.Getenv("CODESIGN_CERTIFICATE_FILE"); f != "" {
 			args = append(args, "/f", f)
+		} else if b := os.Getenv("CODESIGN_CERTIFICATE_BASE64"); b != "" {
+			// Decode the PFX certificate from base64.
+			bs, err := base64.RawStdEncoding.DecodeString(b)
+			if err != nil {
+				log.Println("Codesign: signing failed: decoding base64:", err)
+				return
+			}
+
+			// Write it to a temporary file
+			f, err := os.CreateTemp("", "codesign-*.pfx")
+			if err != nil {
+				log.Println("Codesign: signing failed: creating temp file:", err)
+				return
+			}
+			_ = f.Chmod(0600) // best effort remove other users' access
+			defer os.Remove(f.Name())
+			if _, err := f.Write(bs); err != nil {
+				log.Println("Codesign: signing failed: writing temp file:", err)
+				return
+			}
+			if err := f.Close(); err != nil {
+				log.Println("Codesign: signing failed: closing temp file:", err)
+				return
+			}
+
+			// Use that when signing
+			args = append(args, "/f", f.Name())
 		}
 		if p := os.Getenv("CODESIGN_CERTIFICATE_PASSWORD"); p != "" {
 			args = append(args, "/p", p)
@@ -1402,7 +1430,7 @@ func windowsCodesign(file string) {
 
 		bs, err := runError(st, args...)
 		if err != nil {
-			log.Println("Codesign: signing failed:", string(bs))
+			log.Printf("Codesign: signing failed: %v: %s", err, string(bs))
 			return
 		}
 		log.Println("Codesign: successfully signed", file, "using", algo)