From 75cb52df6a3a7805f952bfd49fe2c27fe8b96dee Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Sat, 17 Oct 2026 00:51:06 +0900 Subject: [PATCH] Share the mobile export manifest with desktop build guards --- flake.nix | 29 +------------- scripts/desktop/build-lib-linux.sh | 4 +- scripts/desktop/build-lib-mac.sh | 4 +- scripts/desktop/build-lib-windows.sh | 4 +- scripts/desktop/check-exports.sh | 35 +++++++++++++++++ scripts/desktop/test-exports.sh | 59 ++++++++++++++++++++++++++++ scripts/nix/README.md | 4 ++ scripts/nix/chat-exports.nix | 3 ++ scripts/nix/chat-exports.txt | 26 ++++++++++++ 9 files changed, 131 insertions(+), 37 deletions(-) create mode 100755 scripts/desktop/check-exports.sh create mode 100755 scripts/desktop/test-exports.sh create mode 100644 scripts/nix/chat-exports.nix create mode 100644 scripts/nix/chat-exports.txt diff --git a/flake.nix b/flake.nix index 891ce55338..88d814178e 100644 --- a/flake.nix +++ b/flake.nix @@ -158,34 +158,7 @@ let drv = pkgs': drv' { extra-modules = []; inherit pkgs'; }; in let inherit (import ./scripts/nix/mobile-library.nix { inherit pkgs; }) withComponentPhases withMobileLibrary; in - let chatExports = [ - "chat_close_store" - "chat_decrypt_file" - "chat_decrypt_media" - "chat_encrypt_file" - "chat_encrypt_media" - "chat_migrate_init" - "chat_migrate_init_key" - "chat_migrate_init_queue" - "chat_parse_markdown" - "chat_parse_server" - "chat_parse_uri" - "chat_password_hash" - "chat_read_file" - "chat_recv_msg" - "chat_recv_msg_wait" - "chat_reopen_store" - "chat_send_cmd" - "chat_send_cmd_retry" - "chat_send_remote_cmd" - "chat_send_remote_cmd_retry" - "chat_valid_name" - "chat_parse_badge_code" - "chat_json_length" - "chat_badge_keygen" - "chat_badge_issue" - "chat_write_file" - ]; in + let chatExports = import ./scripts/nix/chat-exports.nix; in # This will package up all *.a in $out into a pkg.zip that can # be downloaded from hydra. let withHydraLibPkg = pkg: pkg.overrideAttrs (old: { diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index a254004a32..6ff4a7301a 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -21,9 +21,7 @@ root_dir="$(dirname "$(dirname "$(readlink "$0")")")" cd $root_dir BUILD_DIR=dist-newstyle/build/$ARCH-$OS/ghc-${GHC_VERSION}/simplex-chat-* -exports=( $(sed 's/foreign export ccall "chat_migrate_init_key"//' src/Simplex/Chat/Mobile.hs | sed 's/foreign export ccall "chat_reopen_store"//' |grep "foreign export ccall" | cut -d '"' -f2) ) -for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc -l); if [ $count -ne 1 ]; then echo Wrong exports in libsimplex.dll.def. Add \"$elem\" to that file; exit 1; fi ; done -for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done +scripts/desktop/check-exports.sh #rm -rf $BUILD_DIR if [[ "$DATABASE_BACKEND" == "postgres" ]]; then diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 45c961eeef..17a869c2b1 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -20,9 +20,7 @@ GHC_LIBS_DIR=$(ghc --print-libdir) BUILD_DIR=dist-newstyle/build/$ARCH-*/ghc-*/simplex-chat-* -exports=( $(sed 's/foreign export ccall "chat_migrate_init_key"//' src/Simplex/Chat/Mobile.hs | sed 's/foreign export ccall "chat_reopen_store"//' |grep "foreign export ccall" | cut -d '"' -f2) ) -for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc -l); if [ $count -ne 1 ]; then echo Wrong exports in libsimplex.dll.def. Add \"$elem\" to that file; exit 1; fi ; done -for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done +scripts/desktop/check-exports.sh rm -rf $BUILD_DIR diff --git a/scripts/desktop/build-lib-windows.sh b/scripts/desktop/build-lib-windows.sh index 15e021adf7..76db4ced88 100755 --- a/scripts/desktop/build-lib-windows.sh +++ b/scripts/desktop/build-lib-windows.sh @@ -17,9 +17,7 @@ fi BUILD_DIR=dist-newstyle/build/$ARCH-$OS/ghc-*/simplex-chat-* -exports=( $(sed 's/foreign export ccall "chat_migrate_init_key"//' src/Simplex/Chat/Mobile.hs | sed 's/foreign export ccall "chat_reopen_store"//' |grep "foreign export ccall" | cut -d '"' -f2) ) -for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc -l); if [ $count -ne 1 ]; then echo Wrong exports in libsimplex.dll.def. Add \"$elem\" to that file; exit 1; fi ; done -for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done +scripts/desktop/check-exports.sh # IMPORTANT: in order to get a working build you should use x86_64 MinGW with make, cmake, gcc. # 100% working MinGW is https://github.com/brechtsanders/winlibs_mingw/releases/download/13.1.0-16.0.5-11.0.0-ucrt-r5/winlibs-x86_64-posix-seh-gcc-13.1.0-mingw-w64ucrt-11.0.0-r5.zip diff --git a/scripts/desktop/check-exports.sh b/scripts/desktop/check-exports.sh new file mode 100755 index 0000000000..b5d404430e --- /dev/null +++ b/scripts/desktop/check-exports.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Run from the repository root, as the desktop build scripts do. +manifest=scripts/nix/chat-exports.txt +awk ' + { sub(/\r$/, "") } + $0 !~ /^[A-Za-z_][A-Za-z_0-9]*$/ || seen[$0]++ { + print "Invalid or duplicate export in " FILENAME ": " $0 > "/dev/stderr" + exit 1 + } + END { if (NR == 0) exit 1 } +' "$manifest" + +# Keep the historical exclusions shared by the desktop DLL/export guards. +exports=( $(sed 's/foreign export ccall "chat_migrate_init_key"//' src/Simplex/Chat/Mobile.hs | sed 's/foreign export ccall "chat_reopen_store"//' | grep "foreign export ccall" | cut -d '"' -f2) ) +for symbol in "${exports[@]}"; do + count=$(awk -v symbol="$symbol" ' + { sub(/\r$/, ""); sub(/^[[:space:]]+/, ""); if ($0 == symbol) count++ } + END { print count + 0 } + ' libsimplex.dll.def) + if [ "$count" -ne 1 ]; then + echo "Wrong exports in libsimplex.dll.def. Expected exactly one $symbol" >&2 + exit 1 + fi + count=$(awk -v symbol="$symbol" ' + { sub(/\r$/, ""); if ($0 == symbol) count++ } + END { print count + 0 } + ' "$manifest") + if [ "$count" -ne 1 ]; then + echo "Wrong exports in $manifest. Expected exactly one $symbol" >&2 + exit 1 + fi +done diff --git a/scripts/desktop/test-exports.sh b/scripts/desktop/test-exports.sh new file mode 100755 index 0000000000..25475d508a --- /dev/null +++ b/scripts/desktop/test-exports.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -eu + +repo=$(cd "$(dirname "$0")/../.." && pwd) +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT +mkdir -p "$fixture/bin" +cat > "$fixture/bin/ghc" <<'STUB' +#!/usr/bin/env bash +case "$1" in + --numeric-version) echo 9.6.3 ;; + --print-libdir) echo /unused-ghc-libdir ;; + *) exit 99 ;; +esac +STUB +cat > "$fixture/bin/cabal" <<'STUB' +#!/usr/bin/env bash +echo BUILD_REACHED +exit 77 +STUB +chmod +x "$fixture/bin/ghc" "$fixture/bin/cabal" + +for scenario in current missing duplicate similar-name crlf dll-missing dll-duplicate excluded; do + for platform in linux mac windows; do + work="$fixture/$scenario-$platform" + mkdir -p "$work/scripts/desktop" "$work/scripts/nix" "$work/src/Simplex/Chat" + cp "$repo/scripts/desktop/build-lib-$platform.sh" "$repo/scripts/desktop/check-exports.sh" "$work/scripts/desktop/" + cp "$repo/scripts/nix/chat-exports.txt" "$work/scripts/nix/" + cp "$repo/src/Simplex/Chat/Mobile.hs" "$work/src/Simplex/Chat/" + cp "$repo/libsimplex.dll.def" "$work/" + cp "$fixture/bin/cabal" "$work/scripts/desktop/prepare-openssl-windows.sh" + manifest="$work/scripts/nix/chat-exports.txt" + case "$scenario" in + missing) sed '/^chat_migrate_init$/d' "$manifest" > "$manifest.new"; mv "$manifest.new" "$manifest" ;; + duplicate) echo chat_migrate_init >> "$manifest" ;; + similar-name) sed 's/^chat_migrate_init$/chat_migrate_init_extra/' "$manifest" > "$manifest.new"; mv "$manifest.new" "$manifest" ;; + crlf) awk '{ printf "%s\r\n", $0 }' "$manifest" > "$manifest.new"; mv "$manifest.new" "$manifest" ;; + dll-missing) sed '/^ chat_migrate_init$/d' "$work/libsimplex.dll.def" > "$work/def.new"; mv "$work/def.new" "$work/libsimplex.dll.def" ;; + dll-duplicate) echo ' chat_migrate_init' >> "$work/libsimplex.dll.def" ;; + excluded) + for file in "$manifest" "$work/libsimplex.dll.def"; do + sed '/chat_migrate_init_key/d; /chat_reopen_store/d' "$file" > "$file.new"; mv "$file.new" "$file" + done ;; + esac + if output=$(cd "$work" && PATH="$fixture/bin:$PATH" bash "scripts/desktop/build-lib-$platform.sh" 2>&1); then + status=0 + else + status=$? + fi + case "$scenario" in + current|crlf|excluded) + [ "$status" -eq 77 ] && [[ "$output" == *BUILD_REACHED* ]] || { echo "$platform/$scenario did not reach the build stub: $output" >&2; exit 1; } ;; + *) + [ "$status" -eq 1 ] && [[ "$output" != *BUILD_REACHED* ]] || { echo "$platform/$scenario did not stop at the export guard: $output" >&2; exit 1; } ;; + esac + echo "PASS $platform/$scenario" + done +done diff --git a/scripts/nix/README.md b/scripts/nix/README.md index 8ac627cee3..70f88c21fd 100644 --- a/scripts/nix/README.md +++ b/scripts/nix/README.md @@ -76,6 +76,10 @@ that store is pruned. iOS ZIPs contain both the plain `libHSsimplex-chat-*.a` an the rolled `libHSsimplex-chat-*-ghc*.a` used by `update-pbxproj.sh`. Android and Windows clients link the threaded RTS into `libsimplex.so` or `libsimplex.dll`; Android's separate `libsupport.so` resolves RTS symbols from the client library. +Android client export flags use the ordered `chat-exports.txt` manifest. The +desktop library scripts check that manifest and `libsimplex.dll.def` against the +Haskell exports before building; update the manifest when adding an export. +Run `scripts/desktop/test-exports.sh` to check all three guards without compiling. ARM64 Android libraries retain the 16 KiB linker alignment. These steps run in the derivation's build/install phases because v2 component overrides do not run the older `postBuild` and `postInstall` hooks. diff --git a/scripts/nix/chat-exports.nix b/scripts/nix/chat-exports.nix new file mode 100644 index 0000000000..837cfe3013 --- /dev/null +++ b/scripts/nix/chat-exports.nix @@ -0,0 +1,3 @@ +# Preserve the manifest order for the final mobile library linker flags. +builtins.filter (line: builtins.isString line && line != "") + (builtins.split "\r?\n" (builtins.readFile ./chat-exports.txt)) diff --git a/scripts/nix/chat-exports.txt b/scripts/nix/chat-exports.txt new file mode 100644 index 0000000000..19c81d5d66 --- /dev/null +++ b/scripts/nix/chat-exports.txt @@ -0,0 +1,26 @@ +chat_close_store +chat_decrypt_file +chat_decrypt_media +chat_encrypt_file +chat_encrypt_media +chat_migrate_init +chat_migrate_init_key +chat_migrate_init_queue +chat_parse_markdown +chat_parse_server +chat_parse_uri +chat_password_hash +chat_read_file +chat_recv_msg +chat_recv_msg_wait +chat_reopen_store +chat_send_cmd +chat_send_cmd_retry +chat_send_remote_cmd +chat_send_remote_cmd_retry +chat_valid_name +chat_parse_badge_code +chat_json_length +chat_badge_keygen +chat_badge_issue +chat_write_file