From 238289fff7fd13870ccf3d0dd949eebd57357308 Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 15 Oct 2026 15:53:43 +0900 Subject: [PATCH] Build existing Haskell targets through one pinned Nix checkout --- .github/workflows/nix.yml | 94 +++++++++++++++++++ .gitignore | 1 + Makefile | 37 ++++++++ scripts/android/build-android.sh | 12 +-- scripts/desktop/build-lib-linux.sh | 2 +- scripts/desktop/build-lib-mac.sh | 2 +- scripts/nix/README.md | 72 +++++++++++++- scripts/nix/build.sh | 85 +++++++++++++++++ .../simplex-chat-reproduce-builds-android.sh | 5 +- 9 files changed, 295 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/nix.yml create mode 100644 Makefile create mode 100755 scripts/nix/build.sh diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000000..a259a7f3bf --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,94 @@ +name: Nix Haskell builds + +on: + workflow_dispatch: + push: + branches: [master, stable, build/stable-ghc-9.14] + paths: + - 'flake.*' + - '*.cabal' + - 'cabal.project*' + - 'src/**' + - 'apps/simplex-*/**' + - 'bots/**' + - 'scripts/nix/**' + - 'Makefile' + - '.github/workflows/nix.yml' + pull_request: + paths: + - 'flake.*' + - '*.cabal' + - 'cabal.project*' + - 'src/**' + - 'apps/simplex-*/**' + - 'bots/**' + - 'scripts/nix/**' + - 'Makefile' + - '.github/workflows/nix.yml' + +permissions: + contents: read + +concurrency: + group: nix-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: ${{ matrix.system }} / pinned Haskell compiler + runs-on: ${{ matrix.runner }} + timeout-minutes: 360 + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + runner: ubuntu-24.04 + - system: aarch64-linux + runner: ubuntu-24.04-arm + - system: aarch64-darwin + runner: macos-15 + - system: x86_64-darwin + runner: macos-15-intel + env: + NIX_SYSTEM: ${{ matrix.system }} + NIX_BUILD_TIMEOUT: 7200 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + extra_nix_config: | + extra-substituters = https://cache.iog.io https://cache.zw3rk.com + extra-trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk= + + - name: Show outputs from this checkout + run: make nix-list + + # Use the compiler fork in flake.lock, including both Android ABIs. + # Stock GHC installed by ghcup is not interchangeable with this compiler. + - name: Build existing Haskell outputs + run: make nix-build + + - name: Collect library ZIPs and CLIs + shell: bash + run: | + mkdir -p artifacts + for output in result-nix/"$NIX_SYSTEM"/*; do + [ -d "$output" ] || continue + destination="artifacts/$(basename "$output")" + mkdir -p "$destination" + find -L "$output" -maxdepth 1 -type f -name '*.zip' -exec cp {} "$destination/" \; + if [ -f "$output/bin/simplex-chat" ]; then + cp "$output/bin/simplex-chat" "$destination/" + fi + done + + - uses: actions/upload-artifact@v4 + with: + name: nix-${{ matrix.system }}-${{ github.sha }} + path: artifacts/ + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 4ceea4d1a1..530876e045 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ website/package/generated* website/.nyc_output/ website/coverage/ result +result-nix/ # Ignore API documentation website/api-docs/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..8b5668ae44 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.DEFAULT_GOAL := help + +NIX_TARGET ?= all +NIX_ARGS ?= +export NIX_SYSTEM +export NIX_OUT_DIR +export NIX_BUILD_TIMEOUT + +.PHONY: help nix-list nix-build nix-native nix-android nix-windows nix-linux-static nix-ios nix-darwin-libs + +help: ## Show build commands. + @awk 'BEGIN { FS = ":.*## " } /^[a-zA-Z_-]+:.*## / { printf " %-20s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +# The flake lock selects the compiler; these targets never install a stock GHC. +nix-list: ## List selected Hydra outputs without building (NIX_TARGET=all). + @./scripts/nix/build.sh --list $(NIX_TARGET) + +nix-build: ## Build the host's existing Hydra outputs (NIX_TARGET=all, NIX_ARGS=...). + ./scripts/nix/build.sh $(NIX_TARGET) $(NIX_ARGS) + +nix-native: ## Build the native Haskell library and CLI. + ./scripts/nix/build.sh native $(NIX_ARGS) + +nix-android: ## Build both Android ABIs and their support libraries on x86_64-linux. + ./scripts/nix/build.sh android $(NIX_ARGS) + +nix-windows: ## Cross-build the Windows library and CLI on x86_64-linux. + ./scripts/nix/build.sh windows $(NIX_ARGS) + +nix-linux-static: ## Cross-build the x86_64, i686, and aarch64 Linux static CLIs. + ./scripts/nix/build.sh linux-static $(NIX_ARGS) + +nix-ios: ## Build the iOS Haskell library ZIP on the selected Darwin host. + ./scripts/nix/build.sh ios $(NIX_ARGS) + +nix-darwin-libs: ## Build Swift JSON and tagged JSON library ZIPs on Darwin. + ./scripts/nix/build.sh darwin-libs $(NIX_ARGS) diff --git a/scripts/android/build-android.sh b/scripts/android/build-android.sh index 267db9f243..da8af78c93 100755 --- a/scripts/android/build-android.sh +++ b/scripts/android/build-android.sh @@ -5,7 +5,7 @@ set -eu repo="https://github.com/simplex-chat/simplex-chat" u="$USER" -tmp="$(mktemp -d -t)" +tmp="$(mktemp -d)" folder="$tmp/simplex-chat" nix_ver="nix-2.22.0" @@ -105,13 +105,9 @@ build() { sed -i.bak "s/android.version_code=.*/android.version_code=${vercode}/" "$folder/apps/multiplatform/gradle.properties" for arch in $arches; do - if [ "$arch" = "armv7a" ]; then - android_simplex_lib="${folder}#hydraJobs.${arch}-android:lib:simplex-chat.x86_64-linux" - android_support_lib="${folder}#hydraJobs.${arch}-android:lib:support.x86_64-linux" - else - android_simplex_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:simplex-chat" - android_support_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:support" - fi + # Both ABIs use this checkout and the same compiler selected by flake.lock. + android_simplex_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:simplex-chat" + android_support_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:support" android_simplex_lib_output="${PWD}/result/pkg-${arch}-android-libsimplex.zip" android_support_lib_output="${PWD}/result/pkg-${arch}-android-libsupport.zip" diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index 6ee419c5a8..a254004a32 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -9,7 +9,7 @@ function readlink() { OS=linux ARCH="$(uname -m)" DATABASE_BACKEND="${1:-sqlite}" -GHC_VERSION=9.6.3 +GHC_VERSION=$(ghc --numeric-version) if [ "$ARCH" == "aarch64" ]; then COMPOSE_ARCH=arm64 diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 5563086c29..45c961eeef 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -5,7 +5,7 @@ set -e OS=mac ARCH="${1:-$(uname -m)}" COMPOSE_ARCH=$ARCH -GHC_VERSION=9.6.3 +GHC_VERSION=$(ghc --numeric-version) DATABASE_BACKEND="${2:-sqlite}" if [ "$ARCH" == "arm64" ]; then diff --git a/scripts/nix/README.md b/scripts/nix/README.md index 5117032710..bb35ff3072 100644 --- a/scripts/nix/README.md +++ b/scripts/nix/README.md @@ -1,4 +1,74 @@ -# Updating nix package config +# Haskell builds with Nix + +The compiler and all cross-compilers come from the haskell.nix input pinned in +`flake.lock`. The `ghc914-sh` compiler is the stable-haskell fork, not the stock +GHC 9.14 release from GHCup. Use the same source checkout and lock file for every +platform; Android ARMv7 does not require a separate branch or tag. + +Run these commands from the repository root: + +```sh +make help +make nix-list # list this host's existing Hydra outputs +make nix-native # native library and CLI +make nix-android # ARM64 + ARMv7 libraries, on x86_64-linux +make nix-windows # Windows library and CLI, on x86_64-linux +make nix-linux-static # x86_64, i686, and ARM64 static Linux CLIs +make nix-build # all existing outputs for this build host +``` + +Each command builds this checkout, without cloning another branch. Output links +are placed under `result-nix//`. ZIP filenames inside the +outputs retain the existing Hydra names. For example, the ARMv7 client library is +`result-nix/x86_64-linux/armv7a-android:lib:simplex-chat/pkg-armv7a-android-libsimplex.zip`. +Nix includes tracked working-tree changes; add new source files to Git before +building so they are included. + +For a remote builder, select its build system explicitly. Nix must already have +that builder configured: + +```sh +make nix-ios NIX_SYSTEM=aarch64-darwin +make nix-darwin-libs NIX_SYSTEM=x86_64-darwin +make nix-build NIX_SYSTEM=aarch64-linux +``` + +`nix-ios` produces the Haskell static-library ZIP. It does not run Xcode, sign an +iOS application, or upload to TestFlight. ARM64 device libraries and Intel +simulator libraries retain their separate outputs. The existing iOS preparation +scripts retag ARM64 archives with `mac2ios -s` when an Apple Silicon simulator is +needed. + +`NIX_ARGS` passes additional options to `nix build`, and `NIX_OUT_DIR` changes the +output-link directory. Each derivation has a two-hour timeout by default; set +`NIX_BUILD_TIMEOUT` in seconds to change it. For a wall-clock limit on a local +batch, use `timeout` from the Nix development environment: + +```sh +nix develop -c timeout 6h make nix-build +make nix-native NIX_ARGS=--dry-run +``` + +`.github/workflows/nix.yml` runs these same outputs on Linux ARM64/x86_64 and +Darwin ARM64/x86_64, with a six-hour job limit. It uploads the existing library ZIPs +and CLIs under an artifact named for the source SHA. Hydra continues to consume +the same `hydraJobs` attributes. The older application release workflow still +uses its existing GHCup/Docker compiler settings; migrating that packaging and +release path is separate from this Nix build lane. + +To build both Android APK ABIs from the current checkout, preserving the same +compiler and source, use the existing builder's source/skip-checkout flags: + +```sh +scripts/android/build-android.sh -sg VERSION_CODE +``` + +This command also needs the Android SDK, Gradle, and its other existing tools. +The APK reproduction script now checks out the supplied tag for both ABIs. +Historical releases that used a separate `-armv7a` tag must be reproduced with +their historical build scripts. + +## Updating package hashes 1. Install `nix`, `gawk` and `jq`. diff --git a/scripts/nix/build.sh b/scripts/nix/build.sh new file mode 100755 index 0000000000..b846f62635 --- /dev/null +++ b/scripts/nix/build.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env sh +set -eu + +# Use this checkout and its lock file for every target, including cross builds. +repo_dir=$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd) + +usage() { + cat <<'EOF' +Usage: scripts/nix/build.sh [--list] TARGET [nix build options...] + +Targets: all, native, android, windows, linux-static, ios, darwin-libs +NIX_SYSTEM selects the build host (defaults to this machine's Nix system). +NIX_OUT_DIR selects the output links (defaults to result-nix in this checkout). +NIX_BUILD_TIMEOUT bounds each derivation in seconds (defaults to 7200). +--list prints the selected Hydra attributes without evaluating or building them. +EOF +} + +list_only=false +if [ "${1:-}" = "--list" ]; then + list_only=true + shift +fi +case "${1:-}" in + ''|-h|--help) usage; exit 0 ;; +esac +target=$1 +shift + +system=${NIX_SYSTEM:-} +if [ -z "$system" ]; then + case "$(uname -s):$(uname -m)" in + Linux:x86_64) system=x86_64-linux ;; + Linux:aarch64|Linux:arm64) system=aarch64-linux ;; + Darwin:x86_64) system=x86_64-darwin ;; + Darwin:arm64|Darwin:aarch64) system=aarch64-darwin ;; + *) echo 'Unsupported build host; set NIX_SYSTEM explicitly.' >&2; exit 1 ;; + esac +fi +case "$system" in + x86_64-linux|aarch64-linux|x86_64-darwin|aarch64-darwin) ;; + *) printf 'Unsupported NIX_SYSTEM: %s\n' "$system" >&2; exit 1 ;; +esac + +packages= +add_native() { + packages="$packages lib:simplex-chat exe:simplex-chat" +} +add_android() { + packages="$packages aarch64-android:lib:simplex-chat aarch64-android:lib:support armv7a-android:lib:simplex-chat armv7a-android:lib:support" +} +add_windows() { + packages="$packages x86_64-windows:exe:simplex-chat x86_64-windows:lib:simplex-chat" +} +add_linux_static() { + packages="$packages x86_64-linux-static:exe:simplex-chat i686-linux-static:exe:simplex-chat aarch64-linux-static:exe:simplex-chat" +} +add_darwin_libs() { + packages="$packages $system-ios:lib:simplex-chat $system:lib:simplex-chat" +} + +case "$target:$system" in + all:x86_64-linux) add_native; add_android; add_windows; add_linux_static ;; + all:aarch64-linux|native:*) add_native ;; + all:*-darwin) add_native; add_darwin_libs ;; + android:x86_64-linux) add_android ;; + windows:x86_64-linux) add_windows ;; + linux-static:x86_64-linux) add_linux_static ;; + ios:*-darwin) packages="$system-ios:lib:simplex-chat" ;; + darwin-libs:*-darwin) add_darwin_libs ;; + *) printf 'Target %s is not available on build host %s.\n' "$target" "$system" >&2; exit 1 ;; +esac + +out_dir=${NIX_OUT_DIR:-$repo_dir/result-nix} +for package in $packages; do + attribute="hydraJobs.$system.$package" + if "$list_only"; then + printf '%s\n' "$attribute" + else + mkdir -p "$out_dir/$system" + nix build --print-build-logs --timeout "${NIX_BUILD_TIMEOUT:-7200}" \ + --out-link "$out_dir/$system/$package" \ + "$repo_dir#$attribute" "$@" + fi +done diff --git a/scripts/simplex-chat-reproduce-builds-android.sh b/scripts/simplex-chat-reproduce-builds-android.sh index 4bd7262d17..e223522342 100755 --- a/scripts/simplex-chat-reproduce-builds-android.sh +++ b/scripts/simplex-chat-reproduce-builds-android.sh @@ -245,10 +245,7 @@ Continue?' # Build phase for arch in $ARCHES; do case "$arch" in - armv7a) - build_tag="${tag}-armv7a" - ;; - aarch64) + aarch64|armv7a) build_tag="${tag}" ;; *)