diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index 504f60e910..6ae532137b 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -256,7 +256,6 @@ jobs: if: matrix.platform == 'macos-15' env: AARCH64_JAVA_HOME: ${{ env.JAVA_HOME }} - # Both darwin arches for the universal bundle. JPDFIUM_PLATFORMS: ${{ matrix.jpdfium_platforms }} run: task desktop:jlink:universal-mac @@ -266,7 +265,6 @@ jobs: MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} DISABLE_ADDITIONAL_FEATURES: true - # Bundle only this matrix entry's natives; saves ~45 MB per Tauri bundle. JPDFIUM_PLATFORMS: ${{ matrix.jpdfium_platforms }} run: task desktop:prepare diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index 2c3dc459ba..33d1172da4 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -47,13 +47,10 @@ jobs: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} PLATFORM: ${{ inputs.platform }} run: | - # jpdfium_platforms: subset of JPDFium natives to bundle into this matrix entry's bootJar. - # macOS gets both darwin arches because universal-apple-darwin is a fat bundle. WINDOWS='{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64","jpdfium_platforms":"windows-x64"}' MACOS='{"platform":"macos-15","args":"--target universal-apple-darwin","name":"macos-universal","jpdfium_platforms":"darwin-arm64,darwin-x64"}' LINUX='{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64","jpdfium_platforms":"linux-x64"}' - # Resolve requested platform (workflow_dispatch or workflow_call inputs; default "all"). case "$PLATFORM" in windows) ENTRIES=("$WINDOWS") ;; macos) ENTRIES=("$MACOS") ;; @@ -113,8 +110,6 @@ jobs: toolchain: stable targets: ${{ matrix.platform == 'macos-15' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - # Capture x86_64 JAVA_HOME before the next setup-java overwrites it; the universal - # JRE build needs jmods from both arches. - name: Set up x86_64 JDK 25 (macOS universal JRE) if: matrix.platform == 'macos-15' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 @@ -141,12 +136,10 @@ jobs: - name: Setup Task uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0 - # Build the universal JRE before desktop:prepare so jlink:runtime short-circuits. - name: Build universal macOS JRE if: matrix.platform == 'macos-15' env: AARCH64_JAVA_HOME: ${{ env.JAVA_HOME }} - # Both darwin arches for the universal bundle. JPDFIUM_PLATFORMS: ${{ matrix.jpdfium_platforms }} run: task desktop:jlink:universal-mac @@ -156,7 +149,6 @@ jobs: MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} DISABLE_ADDITIONAL_FEATURES: true - # Bundle only this matrix entry's natives; saves ~45 MB per Tauri bundle. JPDFIUM_PLATFORMS: ${{ matrix.jpdfium_platforms }} run: task desktop:prepare @@ -272,8 +264,6 @@ jobs: echo "Certificate imported successfully." - name: Sign JPDFium dylibs inside bootJar (macOS only) - # JPDFium ships unsigned dylibs; notarytool rejects them inside the .app. - # APPLE_SIGNING_IDENTITY is exported by the Verify Certificate step above. if: matrix.platform == 'macos-15' && env.APPLE_CERTIFICATE != '' run: bash frontend/scripts/sign-jpdfium-dylibs-in-bootjar.sh diff --git a/.taskfiles/desktop.yml b/.taskfiles/desktop.yml index 2e7b82b434..ca2bed1c60 100644 --- a/.taskfiles/desktop.yml +++ b/.taskfiles/desktop.yml @@ -3,9 +3,7 @@ version: '3' vars: JLINK_MODULES: "java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" - # JPDFium natives bundled into the Tauri bootJar. Defaults to current host - # (drops ~45 MB of foreign-OS natives). Override via JPDFIUM_PLATFORMS env - # (comma list of platform keys, or 'all'). + # Override via JPDFIUM_PLATFORMS env (csv of platform keys, or 'all'). JPDFIUM_PLATFORMS: sh: | if [ -n "${JPDFIUM_PLATFORMS:-}" ]; then diff --git a/app/common/build.gradle b/app/common/build.gradle index 3cdd652ab1..95f555759e 100644 --- a/app/common/build.gradle +++ b/app/common/build.gradle @@ -62,9 +62,7 @@ dependencies { api 'com.stirling:jpdfium:1.0.0' - // -PjpdfiumPlatforms accepts 'all' (default) or a comma list of platform keys - // (linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64). Per-OS Tauri - // bundles use this to ship only their target's natives jar (~11-12 MB each). + // -PjpdfiumPlatforms=all| def jpdfiumPlatformsProp = (project.findProperty('jpdfiumPlatforms') ?: 'all').toString().trim() def jpdfiumAllPlatforms = ['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64', 'windows-x64'] def jpdfiumPlatforms = jpdfiumPlatformsProp == 'all' diff --git a/app/common/src/test/java/stirling/software/common/jpdfium/JPDFiumSmokeTest.java b/app/common/src/test/java/stirling/software/common/jpdfium/JPDFiumSmokeTest.java index 83298987ba..e6b40951bc 100644 --- a/app/common/src/test/java/stirling/software/common/jpdfium/JPDFiumSmokeTest.java +++ b/app/common/src/test/java/stirling/software/common/jpdfium/JPDFiumSmokeTest.java @@ -13,7 +13,6 @@ import org.junit.jupiter.api.io.TempDir; import stirling.software.jpdfium.PdfDocument; -/** End-to-end smoke check: JPDFium resolves from Maven Central and opens a sample PDF. */ class JPDFiumSmokeTest { @Test diff --git a/app/core/build.gradle b/app/core/build.gradle index 8cdb62f0e2..64a1862c99 100644 --- a/app/core/build.gradle +++ b/app/core/build.gradle @@ -163,8 +163,6 @@ bootJar { attributes( 'Implementation-Title': 'Stirling-PDF', 'Implementation-Version': project.version, - // JEP 472: grants FFM native-access to the unnamed module so we - // don't need --enable-native-access on the launch command. 'Enable-Native-Access': 'ALL-UNNAMED' ) } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index bfe75979b6..9408aa821f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -81,7 +81,6 @@ public class MergeController { } } - // fileOrder is newline-delimited original filenames in the desired order. private static MultipartFile[] reorderFilesByProvidedOrder( MultipartFile[] files, String fileOrder) { String[] desired = @@ -226,7 +225,6 @@ public class MergeController { } } - // Fall back to XMP if Info dict has no dates. PDMetadata metadata = doc.getDocumentCatalog().getMetadata(); if (metadata != null) { try (InputStream is = metadata.createInputStream()) { @@ -288,7 +286,6 @@ public class MergeController { files = new MultipartFile[0]; } - // Front-end visible order, when provided, wins over the request's sortType. if (fileOrder != null && !fileOrder.isBlank()) { log.info("Reordering files based on fileOrder parameter"); files = reorderFilesByProvidedOrder(files, fileOrder); @@ -299,8 +296,6 @@ public class MergeController { try (TempFile mt = new TempFile(tempFileManager, ".pdf")) { - // Stage each upload to disk and pre-validate via JPDFium's header parse so - // a corrupted input gets flagged by index rather than as a generic merge fail. List inputPaths = new ArrayList<>(files.length); List invalidIndexes = new ArrayList<>(); for (int index = 0; index < files.length; index++) { @@ -310,16 +305,12 @@ public class MergeController { inputPaths.add(tempFile.toPath()); try (PdfDocument ignored = PdfDocument.open(tempFile.toPath())) { - // header parsed cleanly } catch (Exception e) { ExceptionUtils.logException("PDF pre-validate", e); invalidIndexes.add(index); } } - // PDFium runs off-heap, so peak Java heap stays roughly constant in input size. - // FPDF_ImportPagesByIndex only carries pages, so we capture+offset bookmarks - // ourselves and inject them via the streaming setBookmarks. int[] pageCounts; try { pageCounts = @@ -332,9 +323,6 @@ public class MergeController { throw e; } - // Signature flatten still uses PDFBox (JPDFium's flatten fuses ALL widgets, - // not just signature fields). Skip the PDFBox round-trip when there's nothing to - // flatten. boolean sigFlattenNeeded = false; if (removeCertSign) { try (PdfDocument check = PdfDocument.open(mt.getFile().toPath())) { @@ -376,8 +364,6 @@ public class MergeController { } } } else { - // Promote the merged temp file to a fresh TempFile so the response - // owns it independently of mt's try-with-resources. outputTempFile = new TempFile(tempFileManager, ".pdf"); try { Files.copy( @@ -413,17 +399,10 @@ public class MergeController { return WebResponseUtils.pdfFileToWebResponse(outputTempFile, mergedFileName); } - /** - * Merge {@code inputPaths} via JPDFium, preserving source bookmarks with page-offset - * translation and optionally prepending a TOC chapter header per source. - * - * @return page-count-per-input array, parallel to {@code inputPaths} - */ private int[] mergeWithJpdfium( List inputPaths, MultipartFile[] files, boolean generateToc, Path outputPath) throws IOException { if (inputPaths.isEmpty()) { - // Match PDFBox: produce an empty file so callers always get an output. try (PdfDocument empty = PdfDocument.open(new byte[0])) { empty.save(outputPath); } catch (Exception ignored) { @@ -444,7 +423,6 @@ public class MergeController { docs.add(doc); pageCounts[i] = doc.pageCount(); pageOffsets[i] = runningOffset; - // Bookmarks must be read while the source doc is still open. sourceBookmarks.add(doc.bookmarks()); runningOffset += pageCounts[i]; } @@ -456,8 +434,6 @@ public class MergeController { if (combinedTree.entries().isEmpty()) { merged.save(outputPath); } else { - // Streams the merged doc to disk then appends the outline as - // an incremental update; heap stays KB-scale. PdfBookmarkEditor.setBookmarks(merged, combinedTree, outputPath); } } @@ -468,18 +444,12 @@ public class MergeController { try { doc.close(); } catch (Exception ignored) { - // best-effort } } } return pageCounts; } - /** - * Flatten each source's bookmarks (children become siblings, parent/child structure is lost - * because {@link BookmarkTree.Builder} only takes top-level entries) with page-offset - * translation, optionally prepending a TOC chapter header per source. - */ private BookmarkTree buildCombinedBookmarkTree( MultipartFile[] files, int[] pageOffsets, @@ -508,11 +478,6 @@ public class MergeController { return builder.build(); } - /** - * Iterative DFS over {@code root}, appending each GoTo-page bookmark as a top-level entry with - * {@code offset} added. Iterative + visited-set + node cap guard against hostile inputs (deep - * nesting, cycles, huge outlines). - */ private void addBookmarkFlat(BookmarkTree.Builder builder, Bookmark root, int offset) { final int maxNodes = 100_000; java.util.Deque stack = new java.util.ArrayDeque<>(); @@ -531,7 +496,6 @@ public class MergeController { } if (bm.hasChildren()) { List children = bm.children(); - // Reverse-push keeps DFS left-to-right. for (int i = children.size() - 1; i >= 0; i--) { stack.push(children.get(i)); } diff --git a/app/core/src/test/java/stirling/software/SPDF/bench/MergeBenchmark.java b/app/core/src/test/java/stirling/software/SPDF/bench/MergeBenchmark.java index a9c453cfec..c3b4952079 100644 --- a/app/core/src/test/java/stirling/software/SPDF/bench/MergeBenchmark.java +++ b/app/core/src/test/java/stirling/software/SPDF/bench/MergeBenchmark.java @@ -45,28 +45,15 @@ import stirling.software.jpdfium.PdfMerge; import stirling.software.jpdfium.doc.PdfBookmarkEditor; import stirling.software.jpdfium.doc.PdfBookmarkEditor.BookmarkTree; -/** - * Heap + wall-clock benchmark: PDFBox PDFMergerUtility vs JPDFium PdfMerge. - * - *

Generates N synthetic image-heavy PDFs, merges them with each engine, samples heap every 25 ms - * during the merge, and reports peak heap (Java heap only - PDFium's off-heap arena is - * intentionally excluded, RSS is reported separately). - * - *

{@code
- * ./gradlew :stirling-pdf:test --tests '*MergeBenchmark*' -Dmerge.bench=true
- * }
- */ +/** Heap + wall-clock benchmark: PDFBox vs JPDFium merge. Run with -Dmerge.bench=true. */ public final class MergeBenchmark { private static final int PAGES_PER_DOC = Integer.getInteger("merge.bench.pages", 100); private static final int IMAGE_W = Integer.getInteger("merge.bench.imgW", 800); private static final int IMAGE_H = Integer.getInteger("merge.bench.imgH", 600); private static final int DOC_COUNT = Integer.getInteger("merge.bench.docs", 2); - // Sources will inject this many GoTo-page bookmarks each, so the merged doc - // should have DOC_COUNT*INTERNAL_BOOKMARKS source entries (+DOC_COUNT TOC headers when on). private static final int INTERNAL_BOOKMARKS = Integer.getInteger("merge.bench.internalBookmarks", 0); - // 0.0..1.0; bump toward 0.95 with larger images to drive inputs into the 100s of MB. private static final float JPEG_QUALITY = Float.parseFloat(System.getProperty("merge.bench.jpegQ", "0.6")); private static final boolean WITH_TOC = @@ -74,29 +61,22 @@ public final class MergeBenchmark { private static final boolean WITH_SIG_REMOVAL = Boolean.parseBoolean(System.getProperty("merge.bench.sigRemoval", "false")); - // When true, JPDFium TOC goes via streaming setBookmarks (KB-scale heap) instead of - // load+save through PDFBox. Lets the benchmark contrast the two strategies. private static boolean withJpdfiumToc = Boolean.parseBoolean(System.getProperty("merge.bench.jpdfiumToc", "false")); - // Median over N iterations to absorb GC + disk cache outliers on the first run. private static final int ITERATIONS = Math.max(1, Integer.getInteger("merge.bench.iterations", 1)); private static final long SAMPLE_PERIOD_MS = 25L; - // Mutable so compareAllMergeScenarios() can re-toggle scenarios without - // regenerating the inputs. private static boolean withToc = WITH_TOC; private static boolean withSigRemoval = WITH_SIG_REMOVAL; - /** Gated by -Dmerge.bench=true (generating the inputs alone takes ~60s). */ @Test @EnabledIfSystemProperty(named = "merge.bench", matches = "true") void compareMergeMemoryFootprint() throws Exception { main(new String[0]); } - /** Plain + TOC + sigRemoval scenarios on a single shared input set. */ @Test @EnabledIfSystemProperty(named = "merge.bench", matches = "true") void compareAllMergeScenarios() throws Exception { @@ -133,7 +113,6 @@ public final class MergeBenchmark { .sum() / 1024); - // One-doc warmup to JIT the hot paths. System.out.println("--- Warmup pass (1 input, results discarded) ---"); withToc = false; withSigRemoval = false; @@ -147,7 +126,6 @@ public final class MergeBenchmark { rows.add(runScenario("withToc", inputs, workDir, true, false)); rows.add(runScenario("withSigRemoval", inputs, workDir, false, true)); - // Final summary table. System.out.println(); System.out.println( "================================ Summary ================================"); @@ -180,11 +158,9 @@ public final class MergeBenchmark { } System.out.println(); } finally { - // inputs/outputs left on disk for inspection } } - /** One scenario on a shared set of inputs. */ private static ScenarioRow runScenario( String name, List inputs, Path workDir, boolean toc, boolean sig) throws Exception { @@ -299,8 +275,6 @@ public final class MergeBenchmark { long buildMs = (System.nanoTime() - t0) / 1_000_000; System.out.printf(" generation took %,d ms%n%n", buildMs); - // Warmup primes classloaders + native lib + JIT. For big inputs - // a single doc is enough to hit the hot paths. long perDocBytes = Files.size(inputs.getFirst()); List warmupInputs = perDocBytes > 50L * 1024 * 1024 ? List.of(inputs.getFirst()) : inputs; @@ -351,11 +325,9 @@ public final class MergeBenchmark { System.out.println(" " + outPdfbox); System.out.println(" " + outJpdfium); } finally { - // workDir is under the OS temp area; cleanup happens eventually } } - /** PDF with a unique procedurally-generated JPEG + caption per page. */ private static void generateTestPdf(Path out, int pages) throws IOException { try (PDDocument doc = new PDDocument()) { Random rng = new Random(42L); @@ -384,7 +356,6 @@ public final class MergeBenchmark { cs.endText(); } } - // Internal bookmarks evenly spaced - used to assert source bookmarks survive merge. if (INTERNAL_BOOKMARKS > 0 && pages > 0) { PDDocumentOutline outline = new PDDocumentOutline(); doc.getDocumentCatalog().setDocumentOutline(outline); @@ -401,7 +372,6 @@ public final class MergeBenchmark { } } - /** Total bookmark count, including nested entries. */ private static int countBookmarks(Path pdf) { try (PdfDocument doc = PdfDocument.open(pdf)) { int total = 0; @@ -425,10 +395,6 @@ public final class MergeBenchmark { return count; } - /** - * Per-page unique JPEG. Per-pixel random noise resists JPEG compression so output stays large, - * which is what we want for measuring real merge cost. - */ private static byte[] generateRandomJpeg(Random rng, int w, int h) throws IOException { BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); int[] pixels = new int[w * h]; @@ -451,8 +417,6 @@ public final class MergeBenchmark { } finally { g.dispose(); } - // Custom JPEG quality (default ImageIO.write ~0.75 is too aggressive when we want big - // inputs). ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); try { @@ -479,7 +443,6 @@ public final class MergeBenchmark { merger.setDestinationFileName(stage1.toAbsolutePath().toString()); merger.mergeDocuments(null); - // Post-process via load+save only when TOC or sig removal is on. if (withToc || withSigRemoval) { try (PDDocument doc = org.apache.pdfbox.Loader.loadPDF(stage1.toFile())) { if (withSigRemoval) { @@ -506,8 +469,6 @@ public final class MergeBenchmark { item.setDestination(doc.getPage(idx)); } outline.addLast(item); - // Every synthetic input has PAGES_PER_DOC pages, so we can skip a - // re-count. idx += PAGES_PER_DOC; } } @@ -523,7 +484,6 @@ public final class MergeBenchmark { } private static void runJpdfiumMerge(List inputs, Path output) throws IOException { - // Mirror MergeController: capture+offset source bookmarks, merge pages, inject outline. List docs = new ArrayList<>(); int[] pageCounts = new int[inputs.size()]; int[] pageOffsets = new int[inputs.size()]; @@ -567,7 +527,6 @@ public final class MergeBenchmark { } } - // Sig removal still uses PDFBox (JPDFium's flatten is page-wide). Skip when no sigs. boolean sigFlattenNeeded = false; if (withSigRemoval) { try (PdfDocument check = PdfDocument.open(output)) { @@ -598,7 +557,6 @@ public final class MergeBenchmark { java.nio.file.Files.move(post, output, java.nio.file.StandardCopyOption.REPLACE_EXISTING); } - /** Flatten and offset-translate one source's bookmark list onto the combined tree. */ private static void addBookmarkFlat( BookmarkTree.Builder builder, List bookmarks, @@ -613,10 +571,6 @@ public final class MergeBenchmark { } } - /** - * Run {@code task} with a memory-sampling thread polling heap usage in the background. Returns - * peak heap-used seen during the run. - */ private static BenchResult profile(ThrowingRunnable task) throws Exception { forceGcQuiescence(); @@ -657,11 +611,6 @@ public final class MergeBenchmark { return new BenchResult(peakHeap.get(), peakNonHeap.get(), wallMs); } - /** - * Aggressively quiesce the heap so the next sample reflects the steady state, not lingering - * temporary objects from the previous step. Two GCs back-to-back plus a short sleep usually - * does it. - */ private static void forceGcQuiescence() { for (int i = 0; i < 3; i++) { System.gc(); diff --git a/build.gradle b/build.gradle index 149f01e497..34642b02f9 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,6 @@ ext { googleJavaFormatVersion = "1.28.0" logback = "1.5.32" // junit-platform-launcher version managed by Spring Boot BOM - // modernJavaVersion drives javac --release and the restart-helper compile target. modernJavaVersion = 25 } @@ -265,8 +264,6 @@ subprojects { tasks.withType(Test).configureEach { useJUnitPlatform() finalizedBy(jacocoReport) - // Forward -Dmerge.bench.* props into the test JVM and give the bench - // enough heap that PDFBox finishes (we're measuring it, not OOMing). System.properties.findAll { k, v -> k.toString().startsWith("merge.bench") } .each { k, v -> systemProperty(k.toString(), v) } if (System.properties.any { k, v -> k.toString().startsWith("merge.bench") }) { @@ -453,8 +450,6 @@ subprojects { "-XX:+ExplicitGCInvokesConcurrent", "-XX:+UseStringDeduplication", "-XX:+UseCompactObjectHeaders", - // bootRun launches from classfiles, not the bootJar, so the - // Enable-Native-Access manifest attribute doesn't apply. "--enable-native-access=ALL-UNNAMED" ] } diff --git a/frontend/scripts/sign-jpdfium-dylibs-in-bootjar.sh b/frontend/scripts/sign-jpdfium-dylibs-in-bootjar.sh index 12a0bfe5de..33c6723783 100644 --- a/frontend/scripts/sign-jpdfium-dylibs-in-bootjar.sh +++ b/frontend/scripts/sign-jpdfium-dylibs-in-bootjar.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash -# Sign every .dylib inside the bootJar's JPDFium native jars so Apple's -# notarytool accepts the Tauri .app (Tauri's own codesign walk doesn't -# descend into .jar files, JPDFium's published natives are unsigned). -# -# Pre: gradle bootJar has produced the fat jar (e.g. via `task desktop:prepare`). -# Pre: APPLE_SIGNING_IDENTITY points at a Developer ID Application identity in the keychain. +# Sign every .dylib inside the bootJar's JPDFium native jars. +# Requires APPLE_SIGNING_IDENTITY set to a Developer ID identity in the keychain. # # Usage: sign-jpdfium-dylibs-in-bootjar.sh [path/to/stirling-pdf-*.jar] @@ -30,8 +26,6 @@ if ! command -v jar >/dev/null 2>&1; then exit 0 fi -# Sign both the Gradle output AND the Tauri staging copy -# (frontend/src-tauri/libs/), since Tauri bundles the staging copy. BOOTJARS=() if [ -n "${1:-}" ]; then BOOTJARS+=("$1") @@ -57,8 +51,6 @@ for BOOTJAR in "${BOOTJARS[@]}"; do # shellcheck disable=SC2064 trap "rm -rf '$WORK'" EXIT - # List then extract by exact path (`jar xf` has no glob support). - # Portable while-read loop because macOS ships bash 3.2 (no mapfile). NATIVE_JAR_PATHS=() while IFS= read -r line; do [ -n "$line" ] || continue @@ -72,7 +64,6 @@ for BOOTJAR in "${BOOTJARS[@]}"; do continue fi - # ${ARR[@]+...} guard: bash 3.2 treats an empty "${ARR[@]}" as unbound under set -u. ( cd "$WORK" && jar xf "$BOOTJAR" ${NATIVE_JAR_PATHS[@]+"${NATIVE_JAR_PATHS[@]}"} ) \ || { echo "jar xf failed to extract natives jars" >&2; exit 1; } @@ -82,12 +73,10 @@ for BOOTJAR in "${BOOTJARS[@]}"; do base=$(basename "$nat_jar") echo " Processing $base" - # Explode the natives jar. exp_dir="$WORK/${base%.jar}.expanded" mkdir -p "$exp_dir" ( cd "$exp_dir" && jar xf "$nat_jar" ) - # Sign every .dylib in the exploded native jar's tree. signed=0 while IFS= read -r dylib; do codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ @@ -101,8 +90,6 @@ for BOOTJAR in "${BOOTJARS[@]}"; do fi echo " signed $signed dylib(s)" - # -0 stores without deflate (dylibs don't compress, and Spring Boot's - # NestedJarFile prefers stored entries). rm -f "$nat_jar" ( cd "$exp_dir" && jar cfM0 "$nat_jar" . ) ANY_SIGNED=1 @@ -114,7 +101,6 @@ for BOOTJAR in "${BOOTJARS[@]}"; do continue fi - # Replace the natives jars inside the bootJar in place. ( cd "$WORK" && jar uf "$BOOTJAR" \ BOOT-INF/lib/jpdfium-natives-darwin-x64-*.jar \ BOOT-INF/lib/jpdfium-natives-darwin-arm64-*.jar ) \ diff --git a/frontend/src-tauri/src/commands/backend.rs b/frontend/src-tauri/src/commands/backend.rs index 92587c37b2..8d5a9764f1 100644 --- a/frontend/src-tauri/src/commands/backend.rs +++ b/frontend/src-tauri/src/commands/backend.rs @@ -205,7 +205,6 @@ fn run_stirling_pdf_jar(app: &tauri::AppHandle, java_path: &PathBuf, jar_path: & let java_options = vec![ "-Xmx2g", - // FFM access for JPDFium is granted via Enable-Native-Access in the bootJar manifest. "-DBROWSER_OPEN=false", "-DSTIRLING_PDF_TAURI_MODE=true", &log_path_option, diff --git a/testing/test.sh b/testing/test.sh index 026f7bd885..a9146c06ce 100644 --- a/testing/test.sh +++ b/testing/test.sh @@ -442,8 +442,7 @@ compare_file_lists() { echo "New files created during test:" cat "${diff_file}.added" | sed 's/^> //' - # JPDFium extracts native libs to /tmp/.../jpdfium-/ on first use - # and registers deleteOnExit; not a leak. + # Exclude JPDFium native cache (deleteOnExit-registered, not a leak). grep -i "tmp\|temp" "${diff_file}.added" \ | grep -v '/jpdfium-[0-9]\+/' \ > "${diff_file}.tmp" || true