From 6e4972ae1f4b72a99139b1622bc06e33732b9498 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 19 Jul 2026 14:10:35 +0200 Subject: [PATCH] Fix native Linux build and add a native Linux CI job cmd/build-linux.ts had drifted from premake and from recent source changes: - sync test_util sources (DocProperties, PdfDarkMode Oklab/ImageRules and their _ut tests, all called unconditionally by test_util) - add the a-gumbo dependency lib (GumboHtmlParser) and the PdfCadDetect/PdfCadEnhanceDevice/PdfDarkModeNoOp sources premake lists for test_engines - fix renamed a-zlib/a-openjpeg archive names in the link list - link -lcrypto (Crypto_posix.cpp uses OpenSSL MD5) - wrap test_engines archives in --start-group: harfbuzz's custom allocator symbols live in mupdf's mupdf_load_system_font.c, behind it in link order a-openjpeg: include the x86 intrinsic headers before the malloc poison pragma. clang, unlike gcc, enforces poison inside system headers, and mm_malloc.h (pulled in by SSE headers) calls malloc; never seen on macOS because arm64 has no xmmintrin.h. Extend the Linux workflow with a native job that builds all dependency libs with clang, runs test_util's unit-test suite natively on Linux, and links test_engines. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012mBFgiNUBqbB5x2dg996Ft prompt: get SumatraPDF fully working on Linux with a minimal patch and working CI; second PR, stacked on the Wine one: the native Linux build fixes plus its CI job --- .github/workflows/linux.yml | 29 +++++++++++++++++++++++++++++ cmd/build-linux.ts | 19 +++++++++++++++++-- ext/a-openjpeg/opj_malloc.h | 6 ++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d60a1351b6a..a0a1f9b1243 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -47,3 +47,32 @@ jobs: with: name: SumatraPDF-linux-wine-debug path: out/dbg64-wine/SumatraPDF.exe + + native: + name: Native build and unit tests + runs-on: ubuntu-latest + container: debian:trixie + steps: + - name: Check out source code + uses: actions/checkout@v7 + + - name: Install clang, OpenSSL headers and bun + run: | + apt-get update + apt-get install -y --no-install-recommends \ + build-essential clang lld libssl-dev \ + ca-certificates curl unzip git + curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash + + # builds all dependency libs, runs test_util's unit-test suite + # natively, and links test_engines + - name: Build dependencies and run native unit tests + run: bun cmd/build-linux.ts -debug + + - name: Upload test tools + uses: actions/upload-artifact@v7 + with: + name: sumatra-linux-native-tools + path: | + out/linux-dbg64/test_util + out/linux-dbg64/test_engines diff --git a/cmd/build-linux.ts b/cmd/build-linux.ts index 337f90b7a07..c3a0cd66e5b 100644 --- a/cmd/build-linux.ts +++ b/cmd/build-linux.ts @@ -30,6 +30,7 @@ import { spawnCmd, } from "./build-deps-common"; import { + aGumbo, zlib, unrar, libwebp, @@ -594,6 +595,7 @@ const DEP_LIBS_BASE = [ }, ], }, + aGumbo, zlib, makeUnrar, makeLibdjvu, @@ -623,7 +625,12 @@ const TEST_UTIL_SOURCES = [ "src/Commands.cpp", "src/CrashHandlerNoOp.cpp", "src/DisplayMode.cpp", + "src/DocProperties.cpp", "src/Flags.cpp", + "src/PdfDarkModeImageClassifier_ut.cpp", + "src/PdfDarkModeImageRules.cpp", + "src/PdfDarkModeOklab.cpp", + "src/PdfDarkModeOklab_ut.cpp", "src/RefHoverDetect.cpp", "src/RefHoverTextDetect.cpp", "src/SimpleLog_ut.cpp", @@ -663,6 +670,9 @@ const TEST_ENGINES_SOURCES = [ "src/GumboHelpers.cpp", "src/MobiDoc.cpp", "src/PalmDbReader.cpp", + "src/PdfCadDetect.cpp", + "src/PdfCadEnhanceDevice.cpp", + "src/PdfDarkModeNoOp.cpp", "src/TreeModel.cpp", "src/tools/test_engines.cpp", ]; @@ -880,6 +890,7 @@ async function buildTestUtil( ...commonFlags, ...units.map((u) => u.obj), join(outDir, "lib", "libbase.a"), + "-lcrypto", ]; const res = await spawnCmd(linkArgs); if (!res.ok) { @@ -957,6 +968,7 @@ async function buildTestEngines( exePath, ...commonFlags, ...units.map((u) => u.obj), + "-Wl,--start-group", join(outDir, "lib", "libbase.a"), join(outDir, "lib", "libmupdf.a"), join(outDir, "lib", "libcmark-gfm.a"), @@ -966,12 +978,15 @@ async function buildTestEngines( join(outDir, "lib", "libfreetype.a"), join(outDir, "lib", "libbrotli.a"), join(outDir, "lib", "liblcms2.a"), - join(outDir, "lib", "libopenjpeg.a"), + join(outDir, "lib", "liba-openjpeg.a"), join(outDir, "lib", "libjbig2dec.a"), join(outDir, "lib", "liblibjpeg-turbo.a"), join(outDir, "lib", "libdjvudec.a"), join(outDir, "lib", "liblibarchive.a"), - join(outDir, "lib", "libzlib.a"), + join(outDir, "lib", "liba-gumbo.a"), + join(outDir, "lib", "liba-zlib.a"), + "-Wl,--end-group", + "-lcrypto", ]; const res = await spawnCmd(linkArgs); if (!res.ok) { diff --git a/ext/a-openjpeg/opj_malloc.h b/ext/a-openjpeg/opj_malloc.h index 70b944afc7a..08bd6269714 100644 --- a/ext/a-openjpeg/opj_malloc.h +++ b/ext/a-openjpeg/opj_malloc.h @@ -19,6 +19,12 @@ void * opj_realloc(void * m, size_t s); void opj_free(void * m); #if defined(__GNUC__) && !defined(OPJ_SKIP_POISON) +/* clang (unlike gcc) enforces poison inside system headers, and mm_malloc.h + (pulled in by the SSE intrinsic headers) calls malloc/free. Include the + intrinsics before poisoning so later includes are no-ops via include guards. */ +#if defined(__x86_64__) || defined(__i386__) +#include +#endif #pragma GCC poison malloc calloc realloc free #endif