diff --git a/.github/workflows/build-natives.yaml b/.github/workflows/build-natives.yaml index e0c651d..b3bc544 100644 --- a/.github/workflows/build-natives.yaml +++ b/.github/workflows/build-natives.yaml @@ -27,6 +27,16 @@ jobs: - name: Install clang++ run: sudo apt-get update && sudo apt-get install -y clang + # ubuntu-24.04-arm has no Android SDK preinstalled; com.android.library + # needs one at configuration time. ubuntu-latest x86-64 already ships with it. + # Default 'tools' package fails on ARM64 (legacy, no ARM64 build) — install + # only what AGP actually needs at configuration time: the compile platform. + - name: Setup Android SDK + if: matrix.arch == 'aarch64' + uses: android-actions/setup-android@v3 + with: + packages: 'platforms;android-36' + - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/.github/workflows/release-graalvm.yaml b/.github/workflows/release-graalvm.yaml new file mode 100644 index 0000000..b024b4b --- /dev/null +++ b/.github/workflows/release-graalvm.yaml @@ -0,0 +1,151 @@ +name: Release GraalVM Native Image (Example Desktop App) + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: release-graalvm-${{ github.ref }} + cancel-in-progress: false + +jobs: + build-natives: + uses: ./.github/workflows/build-natives.yaml + + build: + needs: build-natives + name: GraalVM - ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - name: Linux x64 + os: ubuntu-latest + arch: amd64 + - name: Linux ARM64 + os: ubuntu-24.04-arm + arch: arm64 + - name: macOS ARM64 + os: macos-latest + arch: arm64 + - name: macOS Intel + os: macos-15-intel + arch: amd64 + - name: Windows x64 + os: windows-latest + arch: amd64 + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download PDFium JNI natives + uses: actions/download-artifact@v4 + with: + path: pdfium/src/jvmMain/resources/pdfium/native/ + pattern: 'pdfium-jni-*' + merge-multiple: true + + - name: Select Xcode 26 (macOS) + if: runner.os == 'macOS' + run: sudo xcode-select -s /Applications/Xcode_26.0.app/Contents/Developer + + - name: Setup Liberica NIK (GraalVM) + uses: graalvm/setup-graalvm@v1 + with: + distribution: 'liberica' + java-version: '25' + + - name: Setup MSVC (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Setup Android SDK (Linux ARM64) + if: matrix.os == 'ubuntu-24.04-arm' + uses: android-actions/setup-android@v3 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Build GraalVM native package + shell: bash + run: | + set -euo pipefail + case "$RUNNER_OS" in + Linux) TASK=packageGraalvmDeb ;; + macOS) TASK=packageGraalvmDmg ;; + Windows) TASK=packageGraalvmNsis ;; + esac + ./gradlew ":example:$TASK" -PnativeMarch=compatibility --no-daemon --stacktrace + + - name: Upload GraalVM artifact + uses: actions/upload-artifact@v4 + with: + name: graalvm-${{ runner.os }}-${{ matrix.arch }} + path: | + example/build/compose/binaries/**/graalvm-*/** + if-no-files-found: error + + publish: + name: Publish GitHub Release + needs: build + if: ${{ !cancelled() && needs.build.result == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Download all GraalVM artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: graalvm-* + + - name: Determine version + shell: bash + run: | + set -euo pipefail + TAG="${GITHUB_REF_NAME}" + VERSION="${TAG#v}" + echo "TAG=$TAG" >> "$GITHUB_ENV" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + if [[ "$VERSION" == *"-alpha"* ]] || [[ "$VERSION" == *"-beta"* ]]; then + echo "PRERELEASE=true" >> "$GITHUB_ENV" + else + echo "PRERELEASE=false" >> "$GITHUB_ENV" + fi + + - name: Collect release assets + shell: bash + run: | + set -euo pipefail + mkdir -p release-assets + find artifacts -type f \( -name '*.deb' -o -name '*.dmg' -o -name '*.exe' \) -exec cp {} release-assets/ \; + ls -lh release-assets/ + + - name: Upload to GitHub Release + shell: bash + run: | + set -euo pipefail + REPO="${{ github.repository }}" + if [ -z "$(ls -A release-assets/)" ]; then + echo "::error::No release assets found" + exit 1 + fi + if ! gh release view "$TAG" -R "$REPO" > /dev/null 2>&1; then + ARGS=(release create "$TAG" --title "$TAG" --generate-notes -R "$REPO") + if [ "$PRERELEASE" = "true" ]; then + ARGS+=(--prerelease) + fi + gh "${ARGS[@]}" + fi + gh release upload "$TAG" release-assets/* --clobber -R "$REPO" diff --git a/.github/workflows/test-graalvm.yaml b/.github/workflows/test-graalvm.yaml new file mode 100644 index 0000000..68bb2da --- /dev/null +++ b/.github/workflows/test-graalvm.yaml @@ -0,0 +1,72 @@ +name: Test GraalVM Native Image + +on: + pull_request: + branches: + - '*' + workflow_dispatch: + +concurrency: + group: test-graalvm-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build-natives: + uses: ./.github/workflows/build-natives.yaml + + graalvm: + needs: build-natives + name: GraalVM - ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - name: Linux x64 + os: ubuntu-latest + - name: macOS ARM64 + os: macos-latest + - name: Windows x64 + os: windows-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Download PDFium JNI natives + uses: actions/download-artifact@v4 + with: + path: pdfium/src/jvmMain/resources/pdfium/native/ + pattern: 'pdfium-jni-*' + merge-multiple: true + + - name: Select Xcode 26 (macOS) + if: runner.os == 'macOS' + run: sudo xcode-select -s /Applications/Xcode_26.0.app/Contents/Developer + + - name: Setup Liberica NIK (GraalVM) + uses: graalvm/setup-graalvm@v1 + with: + distribution: 'liberica' + java-version: '25' + + - name: Setup MSVC (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Build GraalVM native image + shell: bash + run: ./gradlew :example:packageGraalvmNative -PnativeMarch=compatibility --no-daemon --stacktrace + + - name: Upload GraalVM artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: graalvm-${{ runner.os }} + path: | + example/build/compose/tmp/**/graalvm/output/** + if-no-files-found: warn diff --git a/README.md b/README.md index 582741e..2bf3bd7 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Published to Maven Central. Requires Gradle 8.10+ and Kotlin 2.3.20+. The kotlin { sourceSets { commonMain.dependencies { - implementation("dev.nucleusframework.pdf:pdfium:0.1.0") + implementation("dev.nucleusframework:pdfium:149.0.7802.0") } } } diff --git a/example/build.gradle.kts b/example/build.gradle.kts index bc47099..0d8932a 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -209,7 +209,7 @@ nucleus.application { targetFormats(TargetFormat.Dmg, TargetFormat.Nsis, TargetFormat.Deb, TargetFormat.AppImage) packageName = "NucleusPdf" packageVersion = "1.0.0" - homepage = "https://github.com/nucleusframework/nucleus-pdf" + homepage = "https://github.com/kdroidFilter/ComposePdf" // jdk.security.auth: UnixSystem — required by FileKit's XDG/DBus picker on Linux. // java.management: DBus transport dependencies. // jdk.unsupported: used by various native-interop helpers. diff --git a/pdfium/build.gradle.kts b/pdfium/build.gradle.kts index 336e647..64818b9 100644 --- a/pdfium/build.gradle.kts +++ b/pdfium/build.gradle.kts @@ -32,7 +32,7 @@ val publishVersion: String = ?.removePrefix("refs/tags/v") ?: "0.1.0" -group = "dev.nucleusframework.pdf" +group = "dev.nucleusframework" version = publishVersion val pdfiumVersion = libs.versions.pdfium.bblanchon.get() @@ -114,17 +114,15 @@ kotlin { } } - // iOS targets are always declared so downstream KMP modules can resolve them on any host. - // On non-Mac hosts the actual compilation is disabled via kotlin.native.ignoreDisabledTargets. + // iOS targets are cross-compilable from any host (Kotlin/Native ships the + // necessary toolchain). The cinterop only needs the staged PDFium headers — + // libraryPaths/linkerOpts are consumed at final link time on macOS only. val iosTargets = listOf(iosArm64(), iosSimulatorArm64()) - - if (Os.isFamily(Os.FAMILY_MAC)) { - iosTargets.forEach { target -> - target.compilations.getByName("main") { - cinterops.create("pdfium") { - defFile(project.file("src/nativeInterop/cinterop/pdfium.def")) - packageName("dev.nucleusframework.pdfium.native") - } + iosTargets.forEach { target -> + target.compilations.getByName("main") { + cinterops.create("pdfium") { + defFile(project.file("src/nativeInterop/cinterop/pdfium.def")) + packageName("dev.nucleusframework.pdfium.native") } } } @@ -472,15 +470,19 @@ val embedPdfiumDylibForXcode = tasks.register("embedPdfium val buildJniLinux = tasks.register("buildJniLinux") { group = "pdfium" - description = "Compile the JNI glue for Linux x86_64." + description = "Compile the JNI glue for Linux (host architecture — x86_64 or aarch64)." onlyIf { Os.isFamily(Os.FAMILY_UNIX) && !Os.isFamily(Os.FAMILY_MAC) } dependsOn(installPdfiumJvmResources, installPdfiumHeaders) val scriptDir = layout.projectDirectory.dir("src/jvmMain/native") workingDir(scriptDir) commandLine("bash", "build-linux.sh") + val hostTriplet = when (System.getProperty("os.arch")) { + "aarch64", "arm64" -> "linux-aarch64" + else -> "linux-x86-64" + } environment("PDFIUM_INCLUDE", stagedHeadersDir.get().asFile.absolutePath) - environment("PDFIUM_LIB", nativeJniResourceDir.dir("linux-x86-64").asFile.absolutePath) - environment("OUT_DIR", nativeJniResourceDir.dir("linux-x86-64").asFile.absolutePath) + environment("PDFIUM_LIB", nativeJniResourceDir.dir(hostTriplet).asFile.absolutePath) + environment("OUT_DIR", nativeJniResourceDir.dir(hostTriplet).asFile.absolutePath) } val buildJniMacOs = tasks.register("buildJniMacOs") { @@ -530,7 +532,10 @@ tasks.named("jvmProcessResources") { dependsOn(installPdfiumJvmResources, buildJniLinux, buildJniMacOs, buildJniWindows, buildJniWindowsArm) } -tasks.named("wasmJsProcessResources") { +// Both wasmJs and js source sets read staged pdfium.wasm + runtime glue from +// webMain resources; wire the install/generate tasks as explicit deps of every +// processResources task that copies from that directory. +tasks.matching { it.name == "wasmJsProcessResources" || it.name == "jsProcessResources" }.configureEach { dependsOn(installPdfiumWasm, generatePdfiumWasmRuntime) } @@ -564,7 +569,7 @@ tasks.register("smokeTest") { // ---------- Maven Central publication ---------- mavenPublishing { - coordinates("dev.nucleusframework.pdf", "pdfium", publishVersion) + coordinates("dev.nucleusframework", "pdfium", publishVersion) pom { name.set("Nucleus PDF — PDFium") diff --git a/pdfium/src/jvmMain/native/build-windows.bat b/pdfium/src/jvmMain/native/build-windows.bat index 00ab40b..f9fd1cc 100644 --- a/pdfium/src/jvmMain/native/build-windows.bat +++ b/pdfium/src/jvmMain/native/build-windows.bat @@ -11,30 +11,35 @@ if "%PDFIUM_LIB%"=="" (echo PDFIUM_LIB unset & exit /b 1) if "%OUT_DIR%"=="" (echo OUT_DIR unset & exit /b 1) if "%JAVA_HOME%"=="" (echo JAVA_HOME unset & exit /b 1) -REM Bring MSVC into the environment if cl.exe is not already on PATH. -where cl.exe >nul 2>&1 +REM Always call vcvarsall.bat with the target arch. Even when cl.exe is already on +REM PATH (e.g. GitHub runners with ilammy/msvc-dev-cmd default x64), we still need +REM to reconfigure the env for the requested %ARCH% so cross-compiles like arm64 +REM link against the right libraries. +set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" +if not exist "!VSWHERE!" set "VSWHERE=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" +if not exist "!VSWHERE!" ( + echo ERROR: vswhere.exe not found. Install Visual Studio Build Tools. + exit /b 1 +) +for /f "usebackq tokens=*" %%i in (`"!VSWHERE!" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i" +if "!VSINSTALL!"=="" ( + echo ERROR: No Visual Studio installation with C++ tools detected by vswhere. + exit /b 1 +) +set "VCVARS=!VSINSTALL!\VC\Auxiliary\Build\vcvarsall.bat" +if not exist "!VCVARS!" ( + echo ERROR: vcvarsall.bat not found at !VCVARS! + exit /b 1 +) +REM For arm64 target on an x64 host, vcvarsall needs the cross-compile variant. +set "VCVARS_ARCH=%ARCH%" +if /I "%ARCH%"=="arm64" ( + if /I not "%PROCESSOR_ARCHITECTURE%"=="ARM64" set "VCVARS_ARCH=amd64_arm64" +) +call "!VCVARS!" %VCVARS_ARCH% if errorlevel 1 ( - set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" - if not exist "!VSWHERE!" set "VSWHERE=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" - if not exist "!VSWHERE!" ( - echo ERROR: cl.exe not on PATH and vswhere.exe not found. Install Visual Studio Build Tools or run from a Native Tools prompt. - exit /b 1 - ) - for /f "usebackq tokens=*" %%i in (`"!VSWHERE!" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i" - if "!VSINSTALL!"=="" ( - echo ERROR: No Visual Studio installation with C++ tools detected by vswhere. - exit /b 1 - ) - set "VCVARS=!VSINSTALL!\VC\Auxiliary\Build\vcvarsall.bat" - if not exist "!VCVARS!" ( - echo ERROR: vcvarsall.bat not found at !VCVARS! - exit /b 1 - ) - call "!VCVARS!" %ARCH% - if errorlevel 1 ( - echo ERROR: vcvarsall.bat %ARCH% failed. - exit /b 1 - ) + echo ERROR: vcvarsall.bat %VCVARS_ARCH% failed. + exit /b 1 ) if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"