Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/build-natives.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
151 changes: 151 additions & 0 deletions .github/workflows/release-graalvm.yaml
Original file line number Diff line number Diff line change
@@ -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"
72 changes: 72 additions & 0 deletions .github/workflows/test-graalvm.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 21 additions & 16 deletions pdfium/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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")
}
}
}
Expand Down Expand Up @@ -472,15 +470,19 @@ val embedPdfiumDylibForXcode = tasks.register<EmbedPdfiumDylibTask>("embedPdfium

val buildJniLinux = tasks.register<Exec>("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<Exec>("buildJniMacOs") {
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -564,7 +569,7 @@ tasks.register<JavaExec>("smokeTest") {
// ---------- Maven Central publication ----------

mavenPublishing {
coordinates("dev.nucleusframework.pdf", "pdfium", publishVersion)
coordinates("dev.nucleusframework", "pdfium", publishVersion)

pom {
name.set("Nucleus PDF — PDFium")
Expand Down
51 changes: 28 additions & 23 deletions pdfium/src/jvmMain/native/build-windows.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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%"
Expand Down
Loading