From a2695e8c08ebdbec0c40fc429d98c588e1930da0 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 30 Aug 2024 10:13:40 -0700 Subject: [PATCH] Set up Windows build pipeline (#292) This adds a new job to the Build Helper pipeline for building for Windows. This includes code signing via DigiCert. Right now, we can only build for x64 on Windows as wavesrv fails to build for arm64 in the default runner and the Windows ARM runner images are missing a bunch of tooling. This also adds new separated arm64 and x64 for macOS for those who don't want to use the universal binary. This also improves the general code quality of the Taskfile.yml and the build-helper.yml files. --- .editorconfig | 2 +- .github/workflows/build-helper.yml | 69 ++++++++++++-- Taskfile.yml | 46 +++++---- electron-builder.config.cjs | 144 +++++++++++++++-------------- 4 files changed, 168 insertions(+), 93 deletions(-) diff --git a/.editorconfig b/.editorconfig index 229cce1a..984c2c6f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true end_of_line = lf insert_final_newline = true -[*.{js,jsx,ts,tsx,json,yml,css,less}] +[*.{js,jsx,ts,tsx,cjs,json,yml,yaml,css,less}] charset = utf-8 indent_style = space indent_size = 4 diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml index 5ac0b887..b684c111 100644 --- a/.github/workflows/build-helper.yml +++ b/.github/workflows/build-helper.yml @@ -1,3 +1,7 @@ +# Build Helper workflow - Builds, signs, and packages binaries for each supported platform, then uploads to a staging bucket in S3 for wider distribution. +# For more information on the macOS signing and notarization, see https://www.electron.build/code-signing and https://www.electron.build/configuration/mac +# For more information on the Windows Code Signing, see https://docs.digicert.com/en/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html and https://docs.digicert.com/en/digicert-keylocker/signing-tools/sign-authenticode-with-electron-builder-using-ksp-integration.html + name: "Build Helper" on: workflow_dispatch env: @@ -9,17 +13,15 @@ jobs: matrix: include: - platform: "darwin" - arch: "universal" runner: "macos-latest-xlarge" - platform: "linux" - arch: "amd64" runner: "ubuntu-latest" - platform: "linux" - arch: "arm64" runner: ubuntu-24.04-arm64-16core + - platform: "windows" + runner: "windows-latest" # - platform: "windows" - # arch: "amd64" - # runner: "windows-latest" + # runner: "windows-11-arm64-16core" runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 @@ -28,11 +30,21 @@ jobs: run: | sudo apt-get update sudo apt-get install --no-install-recommends -y libarchive-tools libopenjp2-tools rpm squashfs-tools - - name: Upgrade AWS CLI (Mac only) # The pre-installed version of the AWS CLI has a segfault problem so we'll install it via Homebrew instead. + + # The pre-installed version of the AWS CLI has a segfault problem so we'll install it via Homebrew instead. + - name: Upgrade AWS CLI (Mac only) if: matrix.platform == 'darwin' run: brew update && brew install awscli - - name: Install FPM # The version of FPM that comes bundled with electron-builder doesn't include a Linux ARM target. Installing Gems onto the runner is super quick so we'll just do this for all targets. + + # The version of FPM that comes bundled with electron-builder doesn't include a Linux ARM target. Installing Gems onto the runner is super quick so we'll just do this for all targets. + - name: Install FPM (not Windows) + if: matrix.platform != 'windows' run: sudo gem install fpm + - name: Install FPM (Windows only) + if: matrix.platform == 'windows' + run: gem install fpm + + # General build dependencies - uses: actions/setup-go@v5 with: go-version: ${{env.GO_VERSION}} @@ -53,7 +65,40 @@ jobs: - name: Set Version id: set-version run: echo "WAVETERM_VERSION=$(node "./version.cjs")" >> "$GITHUB_OUTPUT" - - name: Build ${{ matrix.platform }}/${{ matrix.arch }} + shell: bash + + # Windows Code Signing Setup + - name: Set up certificate (Windows only) + if: matrix.platform == 'windows' + run: | + echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + shell: bash + - name: Set signing variables (Windows only) + if: matrix.platform == 'windows' + id: variables + run: | + echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_OUTPUT" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV" + echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH + echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH + echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH + shell: bash + - name: Setup Keylocker KSP (Windows only) + if: matrix.platform == 'windows' + run: | + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o Keylockertools-windows-x64.msi + msiexec /i Keylockertools-windows-x64.msi /quiet /qn + C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user + smctl windows certsync + shell: cmd + + # Build and upload packages + - name: Build (not Windows) + if: matrix.platform != 'windows' run: task package env: USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one. @@ -62,6 +107,14 @@ jobs: APPLE_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_PWD }} APPLE_TEAM_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} + - name: Build (Windows only) + if: matrix.platform == 'windows' + run: task package + env: + USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one. + CSC_LINK: ${{ steps.variables.outputs.SM_CLIENT_CERT_FILE }} + CSC_KEY_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} + shell: powershell # electron-builder's Windows code signing package has some compatibility issues with pwsh, so we need to use Windows Powershell - name: Upload to S3 staging run: aws s3 cp make/ s3://waveterm-github-artifacts/staging-w2/${{ steps.set-version.outputs.WAVETERM_VERSION }}/ --recursive --exclude "*/*" --exclude "builder-*.yml" env: diff --git a/Taskfile.yml b/Taskfile.yml index e7cf9843..967aa34e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,7 +10,7 @@ vars: sh: node version.cjs RM: '{{if eq OS "windows"}}cmd --% /c del /S{{else}}rm {{end}}' RMRF: '{{if eq OS "windows"}}powershell Remove-Item -Force -Recurse{{else}}rm -rf{{end}}' - DATE: '{{if eq OS "windows"}}powershell date -UFormat{{else}}date{{end}}' + DATE: '{{if eq OS "windows"}}powershell Get-Date -UFormat{{else}}date{{end}}' tasks: electron:dev: @@ -52,11 +52,12 @@ tasks: desc: Build the wavesrv component. deps: - generate - - build:server:darwin - - build:server:other + - build:server:linux + - build:server:macos + - build:server:windows - build:server:darwin: - desc: Build the wavesrv component for Darwin platforms (generates artifacts for both arm64 and amd64). + build:server:macos: + desc: Build the wavesrv component for macOS (Darwin) platforms (generates artifacts for both arm64 and amd64). status: - exit {{if eq OS "darwin"}}1{{else}}0{{end}} cmds: @@ -64,35 +65,48 @@ tasks: ignore_error: true - task: build:server:internal vars: - GOARCH: arm64 - - task: build:server:internal - vars: - GOARCH: amd64 + ARCHS: arm64,amd64 - build:server:other: - desc: Build the wavesrv component for non-Darwin platforms (only generates artifacts for the current architecture). + build:server:windows: + desc: Build the wavesrv component for Windows platforms (only generates artifacts for the current architecture). status: - - exit {{if eq OS "darwin"}}0{{else}}1{{end}} + - exit {{if eq OS "windows"}}1{{else}}0{{end}} cmds: - cmd: "{{.RM}} dist/bin/wavesrv*" ignore_error: true - task: build:server:internal vars: - GOARCH: + ARCHS: + sh: echo {{if eq "arm" ARCH}}arm64{{else}}{{ARCH}}{{end}} + + build:server:linux: + desc: Build the wavesrv component for Linux platforms (only generates artifacts for the current architecture). + status: + - exit {{if eq OS "linux"}}1{{else}}0{{end}} + cmds: + - cmd: "{{.RM}} dist/bin/wavesrv*" + ignore_error: true + - task: build:server:internal + vars: + ARCHS: sh: echo {{if eq "arm" ARCH}}arm64{{else}}{{ARCH}}{{end}} GO_LDFLAGS: -linkmode 'external' -extldflags=-static build:server:internal: requires: vars: - - GOARCH + - ARCHS cmds: - - CGO_ENABLED=1 GOARCH={{.GOARCH}} go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{.GOARCH}}{{exeExt}} cmd/server/main-server.go + - cmd: CGO_ENABLED=1 GOARCH={{.ARCH}} go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{.ARCH}}{{exeExt}} cmd/server/main-server.go + for: + var: ARCHS + split: "," + as: ARCH sources: - "cmd/server/*.go" - "pkg/**/*.go" generates: - - dist/bin/wavesrv.{{.GOARCH}}{{exeExt}} + - dist/bin/wavesrv.*{{exeExt}} deps: - go:mod:tidy internal: true diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index e636b02a..4b4f6104 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -7,76 +7,84 @@ const path = require("path"); * @see https://www.electron.build/configuration/configuration */ const config = { - appId: pkg.build.appId, - productName: pkg.productName, - artifactName: "${productName}-${platform}-${arch}-${version}.${ext}", - npmRebuild: false, - nodeGypRebuild: false, - electronCompile: false, - files: [ - { - from: "./dist", - to: "./dist", - filter: ["**/*"], - }, - { - from: ".", - to: ".", - filter: ["package.json"], - }, - "!node_modules", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. - ], - directories: { - output: "make", - }, - asarUnpack: [ - "dist/bin/**/*", // wavesrv and wsh binaries - ], - mac: { - target: [ - { - target: "zip", - arch: "universal", - }, - { - target: "dmg", - arch: "universal", - }, + appId: pkg.build.appId, + productName: pkg.productName, + artifactName: "${productName}-${platform}-${arch}-${version}.${ext}", + npmRebuild: false, + nodeGypRebuild: false, + electronCompile: false, + files: [ + { + from: "./dist", + to: "./dist", + filter: ["**/*"], + }, + { + from: ".", + to: ".", + filter: ["package.json"], + }, + "!node_modules", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. ], - icon: "build/icons.icns", - category: "public.app-category.developer-tools", - minimumSystemVersion: "10.15.0", - notarize: process.env.APPLE_TEAM_ID - ? { - teamId: process.env.APPLE_TEAM_ID, - } - : false, - binaries: fs - .readdirSync("dist/bin", { recursive: true, withFileTypes: true }) - .filter((f) => f.isFile() && (f.name.startsWith("wavesrv") || f.name.includes("darwin"))) - .map((f) => path.resolve(f.path, f.name)), - }, - linux: { - executableName: pkg.productName, - category: "TerminalEmulator", - icon: "build/icons.icns", - target: ["zip", "deb", "rpm", "AppImage", "pacman"], - synopsis: pkg.description, - description: null, - desktop: { - Name: pkg.productName, - Comment: pkg.description, - Keywords: "developer;terminal;emulator;", - category: "Development;Utility;", + directories: { + output: "make", + }, + asarUnpack: [ + "dist/bin/**/*", // wavesrv and wsh binaries + ], + mac: { + target: [ + { + target: "zip", + arch: ["universal", "arm64", "x64"], + }, + { + target: "dmg", + arch: ["universal", "arm64", "x64"], + }, + ], + icon: "build/icons.icns", + category: "public.app-category.developer-tools", + minimumSystemVersion: "10.15.0", + notarize: process.env.APPLE_TEAM_ID + ? { + teamId: process.env.APPLE_TEAM_ID, + } + : false, + binaries: fs + .readdirSync("dist/bin", { recursive: true, withFileTypes: true }) + .filter((f) => f.isFile() && (f.name.startsWith("wavesrv") || f.name.includes("darwin"))) + .map((f) => path.resolve(f.parentPath, f.name)), + }, + linux: { + executableName: pkg.productName, + category: "TerminalEmulator", + icon: "build/icons.icns", + target: ["zip", "deb", "rpm", "AppImage", "pacman"], + synopsis: pkg.description, + description: null, + desktop: { + Name: pkg.productName, + Comment: pkg.description, + Keywords: "developer;terminal;emulator;", + category: "Development;Utility;", + }, + }, + win: { + icon: "build/icons.icns", + publisherName: "Command Line, Inc.", + target: ["nsis", "msi", "zip"], + certificateSubjectName: "Command Line Inc", + certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH, + signingHashAlgorithms: ["sha256"], + }, + appImage: { + license: "LICENSE", + }, + publish: { + provider: "generic", + url: "https://dl.waveterm.dev/releases-w2", }, - }, - appImage: { - license: "LICENSE", - }, - publish: { - provider: "generic", - url: "https://dl.waveterm.dev/releases-w2", - }, }; module.exports = config;