Skip to content

Build

Build #34

Workflow file for this run

name: Build
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# ZETTLR MAIN BUILD GITHUB ACTIONS FILE #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# This file contains the logic necessary to build a full release from source. #
# #
# WHEN IT RUNS: #
# #
# * Whenever someone successfully pushes develop --> master. #
# * Every Monday at noon (UTC) #
# * Manually #
# #
# The last two events are being used to create nightly releases. The first #
# event type indicates that the pipeline should build a regular release. #
# #
# HOW IT RUNS: #
# #
# 1. Three virtual machines are spun up in parallel: A Windows VM, a Ubuntu #
# VM, and a macOS VM. All three are tasked with building all releases that #
# we offer for the given platform. The Ubuntu VM builds the most since we #
# have many more Linux releases. Windows and macOS only build one x64 and #
# one ARM release. However, they also perform code signing (and #
# notarization in case of the macOS VM), which the Ubuntu VM doesn't do. #
# 2. Each VM uploads all resulting artifacts into their respective artifact #
# space, named "linux", "darwin", and "win32" respectively (analogous to #
# node.js's process.platform-variable). #
# 3. Iff (if and only if) all three runners have shut down successfully, #
# indicating successful builds, another Ubuntu VM will be started. That #
# one is tasked with downloading all releases from the three artifact #
# spaces, calculate the SHA-256 checksums for every file and afterwards #
# upload all files: In case of a nightly to the Zettlr server, where they #
# can be downloaded from nightly.zettlr.com, or, in case of a regular #
# release, to a newly created release draft here on GitHub. #
# #
# THINGS TO NOTE: #
# #
# * Every runner will retrieve the package.json's "version" field by running #
# a somewhat weird-looking command. This is necessary to make finding the #
# releases easier which are being produced by electron-forge and electron- #
# builder. #
# * Especially the macOS runner can often produce problems, because there #
# have been rumors that apparently even the macOS server operating systems #
# tend to open modal windows -- even though they're running headless. This #
# means that in case the macOS runners seem to hang it's likely we again #
# need to update the add-osx-cert.sh script. #
# * Everything is being run on Bash. Although PowerShell can do a lot, this #
# way all steps are within one language, making it easier to maintain. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
on:
push:
branches:
- master
workflow_dispatch: {} # Used to manually trigger a nightly build.
schedule: # Every Monday at noon (UTC) we run a scheduled nightly build
- cron: '0 12 * * 1'
# Defaults for every job and step in this workflow
defaults:
run:
shell: bash # Run everything using bash
# Global environment variables
env:
NODE_VERSION: '18'
# Ensure only a single build workflow runs at any one time
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
# This workflow file contains four jobs, three to build the corresponding
# releases on all three supported platforms, and a last one, which will
# create the release draft.
jobs:
# Trigger a refresh of the translations before every *proper* build.
i18n:
if: github.ref_name == 'master'
name: 'Preflight: Update translations'
uses: ./.github/workflows/i18n.yaml
# First, ensure that nothing breaks before attempting to build
preflight:
name: 'Preflight: Lint/Unit tests'
uses: ./.github/workflows/check.yml
# If we're building a full release, we have to make sure that nobody (that is:
# Hendrik) forgot to increment the tag version. If we already have a tag with
# this version, we should not proceed, because that will then lead to an
# overwriting of the files in the previous release. That doesn't wreak any
# havoc because we don't push unstable code to master, BUT it looks bad.
verify_version:
name: 'Preflight: Verify version tag'
runs-on: ubuntu-20.04
steps:
- name: Clone repository (master branch)
uses: actions/checkout@v3
with:
ref: 'master'
fetch-depth: 0 # Required for the tag verification step.
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
- name: Verify the corresponding tag does not yet exist
if: github.ref_name == 'master'
# NOTE: the ! will return 0 if the command fails, and 1 otherwise
run: '! git rev-parse "v${{env.version}}"'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# WINDOWS BUILDS #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
build_win_x64:
name: Windows (x86_64)
env:
npm_config_arch: x64
needs: [preflight, verify_version, i18n]
runs-on: windows-latest
steps:
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
- name: Package & Release
run: yarn package:win-x64 && yarn release:win-x64
env:
CSC_LINK: ${{ secrets.WIN_CERT_2025_03_15 }}
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_PASS_2025_03_15 }}
- name: Cache installer
uses: actions/upload-artifact@v3
with:
name: win32_x64
path: |
./release/Zettlr-${{env.version}}-x64.exe
# build_win_arm64:
# name: Windows (arm64)
# env:
# npm_config_arch: arm64
# TARGET_ARCH: arm64 # Seen at https://github.com/hydraulic-software/github-desktop/blob/conveyorize/.github/workflows/ci.yml
# needs: [preflight, verify_version, i18n]
# runs-on: windows-latest
# steps:
# - name: Checkout branch ${{ github.ref_name }}
# uses: actions/checkout@v3
# with:
# ref: ${{ github.ref_name }}
# - name: Setup NodeJS ${{ env.NODE_VERSION }}
# uses: actions/setup-node@v3
# with:
# node-version: ${{ env.NODE_VERSION }}
# cache: 'yarn'
# # NOTE: While the GitHub Actions hosters have already all required
# # software installed for building native ARM64 modules, we have to do one
# # step more of preparation, unfortunately. Specifically, we have to
# # manually download the correct ARM64 headers as Node.js does not (yet)
# # ship those officially. This involves two steps.
# # (see https://www.electronjs.org/docs/latest/tutorial/windows-arm/, but
# # the tutorial is not completely correct. Shoutout to
# # https://github.com/hydraulic-software/github-desktop for the hopefully
# # last piece of info we need.)
# - name: Retrieve path to AppData/Local
# run: |
# "appdata=$($env:LOCALAPPDATA)" >> $env:GITHUB_ENV
# shell: pwsh # NOTE: Runs explicitly on PowerShell
# # Now, we can download the correct headers. NOTE that unlike described in
# # the docs, we need to version the cache with the used NODE version, not
# # Electron version! Also NOTE that we have to specify node.exe, just node
# # WILL NOT WORK, because Windows.
# - name: Download Windows arm64 headers
# run: |
# NODEVER=$(node.exe --version)
# mkdir -p "${{ env.appdata }}\\node-gyp\\Cache\\${NODEVER#v}\\arm64"
# curl -sL \
# "https://unofficial-builds.nodejs.org/download/release/$NODEVER/win-arm64/node.lib" \
# -o "${{ env.appdata }}\\node-gyp\\Cache\\${NODEVER#v}\\arm64\\node.lib"
# # At this point, the Windows ARM64 build should succeed.
# - name: Set up build environment
# run: yarn install --immutable
# env:
# npm_config_ensure: "true"
# npm_config_dist_url: https://unofficial-builds.nodejs.org/download/release
# - name: Set tag version to nightly
# if: github.ref_name == 'develop'
# run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
# - name: Retrieve tag version
# id: ref
# run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
# - name: Package & Release
# run: yarn package:win-arm && yarn release:win-arm
# env:
# CSC_LINK: ${{ secrets.WIN_CERT_2025_03_15 }}
# CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_PASS_2025_03_15 }}
# # The ARM64 artifact is being output simply as .exe w/o arch, since we
# # have to include --ia32 in the build flags for ARM builds (thus,
# # strictly speaking it has two architectures).
# - name: Rename artifact
# run: mv ./release/Zettlr-${{env.version}}.exe ./release/Zettlr-${{env.version}}-arm64.exe
# - name: Cache installer
# uses: actions/upload-artifact@v3
# with:
# name: win32_arm64
# path: |
# ./release/Zettlr-${{env.version}}-arm64.exe
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# MACOS BUILDS #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
build_macos_x64:
name: macOS (x86_64)
env:
npm_config_arch: x64
needs: [preflight, verify_version, i18n]
runs-on: macos-latest
steps:
# Check out master for a regular release, or develop branch for a nightly
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
# Save the macOS certificate on this runner for forge to access it in the
# next step below.
- name: Retrieve code signing certificate
run: ./scripts/add-osx-cert.sh
env:
MACOS_CERT: ${{ secrets.MACOS_CERT }}
MACOS_CERT_PASS: ${{ secrets.MACOS_CERT_PASS }}
- name: Package
run: yarn package:mac-x64
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }}
- name: Release
run: yarn release:mac-x64
- name: Cache image file
uses: actions/upload-artifact@v3
with:
name: darwin_x64
path: |
./release/Zettlr-${{env.version}}-x64.dmg
build_macos_arm64:
name: macOS (arm64)
env:
npm_config_arch: arm64
needs: [preflight, verify_version, i18n]
runs-on: macos-latest
steps:
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
# Save the macOS certificate on this runner for forge to access it in the
# next step below.
- name: Retrieve code signing certificate
run: ./scripts/add-osx-cert.sh
env:
MACOS_CERT: ${{ secrets.MACOS_CERT }}
MACOS_CERT_PASS: ${{ secrets.MACOS_CERT_PASS }}
- name: Package
run: yarn package:mac-arm
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }}
- name: Release
run: yarn release:mac-arm
- name: Cache image file
uses: actions/upload-artifact@v3
with:
name: darwin_arm64
path: |
./release/Zettlr-${{env.version}}-arm64.dmg
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# LINUX BUILDS #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
build_linux_x64:
name: Linux (x86_64)
env:
npm_config_arch: x64
needs: [preflight, verify_version, i18n]
runs-on: ubuntu-20.04
steps:
# Check out master for a regular release, or develop branch for a nightly
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
# If this a scheduled workflow, we know that we should build a nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
- name: Package & Release
run: |
yarn package:linux-x64
yarn release:linux-x64
- name: Cache installers
uses: actions/upload-artifact@v3
with:
name: linux_x64
path: |
./release/Zettlr-${{env.version}}-amd64.deb
./release/Zettlr-${{env.version}}-x86_64.rpm
./release/Zettlr-${{env.version}}-x86_64.AppImage
build_linux_arm64:
name: Linux (arm64)
env:
npm_config_arch: arm64
needs: [preflight, verify_version, i18n]
runs-on: ubuntu-20.04
steps:
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
with:
ref: ${{ github.ref_name }}
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
- name: Package & Release
run: |
yarn package:linux-arm
yarn release:linux-arm
- name: Cache installers
uses: actions/upload-artifact@v3
with:
name: linux_arm64
path: |
./release/Zettlr-${{env.version}}-arm64.deb
./release/Zettlr-${{env.version}}-aarch64.rpm
./release/Zettlr-${{env.version}}-arm64.AppImage
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# PREPARE RELEASE DRAFT #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# After the three builds, this job downloads all assets, creates and verifies
# SHA256 checksums, and finally creates a release draft and uploads all
# assets to it. NOTE: If the workflow detects a nightly is being built, this
# step rather uploads the binaries to the Zettlr server instead of creating
# a release draft.
prepare_release:
name: Prepare release draft
# Make sure (and wait until) the builds have succeeded
needs:
- build_win_x64
# - build_win_arm64 # DEBUG: Disable win arm for now
- build_macos_x64
- build_macos_arm64
- build_linux_x64
- build_linux_arm64
runs-on: ubuntu-20.04
steps:
- name: Checkout branch ${{ github.ref_name }}
uses: actions/checkout@v3
- name: Setup NodeJS ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Set up build environment
run: yarn install --immutable
- name: Set tag version to nightly
if: github.ref_name == 'develop'
run: echo "$(cat package.json | jq '.version = .version + "-nightly"')" > package.json
- name: Retrieve tag version
id: ref
run: echo "version=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
- name: Make release directory
run: mkdir ./release
# Download all resulting assets from the previous steps.
- name: Retrieve installers (Windows x86_64)
uses: actions/download-artifact@v3
with:
name: win32_x64
path: ./release
# - name: Retrieve installers (Windows arm64)
# uses: actions/download-artifact@v3
# with:
# name: win32_arm64
# path: ./release # DEBUG: Disable win arm for now
- name: Retrieve installers (macOS x86_64)
uses: actions/download-artifact@v3
with:
name: darwin_x64
path: ./release
- name: Retrieve installers (macOS arm64)
uses: actions/download-artifact@v3
with:
name: darwin_arm64
path: ./release
- name: Retrieve installers (Linux x86_64)
uses: actions/download-artifact@v3
with:
name: linux_x64
path: ./release
- name: Retrieve installers (Linux arm64)
uses: actions/download-artifact@v3
with:
name: linux_arm64
path: ./release
# Generate the checksums
- name: Generate SHA256 checksums
# sha256sum "Zettlr-${{env.version}}-arm64.exe" >> "SHA256SUMS.txt" # DEBUG: Disable win arm for now
run: |
cd ./release
sha256sum "Zettlr-${{env.version}}-x64.exe" > "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-x64.dmg" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-arm64.dmg" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-x86_64.AppImage" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-arm64.AppImage" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-amd64.deb" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-arm64.deb" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-x86_64.rpm" >> "SHA256SUMS.txt"
sha256sum "Zettlr-${{env.version}}-aarch64.rpm" >> "SHA256SUMS.txt"
cd ..
- name: Verify checksums
run: |
cd ./release
sha256sum -c SHA256SUMS.txt
cd ..
# IF WE BUILD A NIGHTLY, AT THIS POINT JUST UPLOAD TO THE SERVER.
# We must make sure to copy the three additional files
# to the release folder because of the --delete flag in rsync below.
- name: Copy Nightly Static Files
if: github.ref_name == 'develop'
run: |
cp ./scripts/assets/nightly-index.php ./release/index.php
cp ./scripts/assets/nightly-sm_preview.png ./release/sm_preview.png
cp ./resources/icons/png/512x512.png ./release/logo.png
- name: Upload nightlies to the server
if: github.ref_name == 'develop'
uses: easingthemes/ssh-deploy@v3.0.1
env:
SSH_PRIVATE_KEY: ${{ secrets.NIGHTLY_SSH_PRIVATE_KEY }}
# --delete so that no old releases remain on the server. Each iteration is approx. 1GB in size.
ARGS: "-vzhr --delete" # verbose, compress, human-readble, recursive, delete
SOURCE: "release/"
REMOTE_HOST: ${{ secrets.NIGHTLY_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.NIGHTLY_REMOTE_USER }}
TARGET: ${{ secrets.NIGHTLY_TARGET }}
# OTHERWISE: Create a new release draft
- name: Create release draft
if: github.ref_name == 'master'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Populate the inputs of the release we already know
tag_name: v${{env.version}}
name: Release v${{env.version}}
body: If you can read this, we have forgotten to fill in the changelog. Sorry!
draft: true # Always create as draft, so that we can populate the remaining values easily
# Gosh, is that convenient as opposed to earlier!
files: |
./release/Zettlr-${{env.version}}-x64.exe
./release/Zettlr-${{env.version}}-arm64.exe
./release/Zettlr-${{env.version}}-x64.dmg
./release/Zettlr-${{env.version}}-arm64.dmg
./release/Zettlr-${{env.version}}-amd64.deb
./release/Zettlr-${{env.version}}-arm64.deb
./release/Zettlr-${{env.version}}-x86_64.rpm
./release/Zettlr-${{env.version}}-aarch64.rpm
./release/Zettlr-${{env.version}}-x86_64.AppImage
./release/Zettlr-${{env.version}}-arm64.AppImage
./release/SHA256SUMS.txt