Building and running tauri with jlink locally

This commit is contained in:
Connor Yoh 2025-06-13 11:55:09 +01:00
parent aed4ccd07b
commit 6a75ecc6ae
8 changed files with 756 additions and 112 deletions

228
JLINK-README.md Normal file
View File

@ -0,0 +1,228 @@
# JLink Runtime Bundling for Stirling-PDF
This guide explains how to use JLink to bundle a custom Java runtime with your Tauri application, eliminating the need for users to have Java installed.
## Overview
Instead of requiring users to install a JRE separately, JLink creates a minimal, custom Java runtime that includes only the modules your application needs. This approach:
- **Eliminates JRE dependency**: Users don't need Java installed
- **Reduces size**: Only includes necessary Java modules
- **Improves security**: Minimal attack surface with fewer modules
- **Ensures consistency**: Same Java version across all deployments
## Prerequisites
- **JDK 17 or higher** (not just JRE - you need `jlink` command)
- **Node.js and npm** for the frontend
- **Rust and Tauri CLI** for building the desktop app
## Quick Start
### 1. Build with JLink
Run the appropriate build script for your platform:
**Linux/macOS:**
```bash
./build-tauri-jlink.sh
```
**Windows:**
```cmd
build-tauri-jlink.bat
```
### 2. Build Tauri Application
```bash
cd frontend
npm run tauri build
```
The resulting application will include the bundled JRE and won't require Java to be installed on the target system.
## What the Build Script Does
1. **Builds the Stirling-PDF JAR** using Gradle
2. **Analyzes dependencies** using `jdeps` to determine required Java modules
3. **Creates custom JRE** using `jlink` with only necessary modules
4. **Copies files** to the correct Tauri directories:
- JAR file → `frontend/src-tauri/libs/`
- Custom JRE → `frontend/src-tauri/runtime/jre/`
5. **Creates test launchers** for standalone testing
## Directory Structure
After running the build script:
```
frontend/src-tauri/
├── libs/
│ └── Stirling-PDF-X.X.X.jar
├── runtime/
│ ├── jre/ # Custom JLink runtime
│ │ ├── bin/
│ │ │ ├── java(.exe)
│ │ │ └── ...
│ │ ├── lib/
│ │ └── ...
│ ├── launch-stirling.sh # Test launcher (Linux/macOS)
│ └── launch-stirling.bat # Test launcher (Windows)
└── tauri.conf.json # Already configured to bundle runtime
```
## Testing the Bundled Runtime
Before building the full Tauri app, you can test the bundled runtime:
**Linux/macOS:**
```bash
./frontend/src-tauri/runtime/launch-stirling.sh
```
**Windows:**
```cmd
frontend\src-tauri\runtime\launch-stirling.bat
```
This will start Stirling-PDF using the bundled JRE, accessible at http://localhost:8080
## Configuration Details
### Tauri Configuration (`tauri.conf.json`)
The bundle resources are configured to include both the JAR and runtime:
```json
{
"bundle": {
"resources": [
"libs/*.jar",
"runtime/jre/**/*"
]
}
}
```
### Gradle Configuration (`build.gradle`)
JLink options are configured in the jpackage section:
```gradle
jLinkOptions = [
"--strip-debug",
"--compress=2",
"--no-header-files",
"--no-man-pages"
]
addModules = [
"java.base",
"java.desktop",
"java.logging",
"java.management",
// ... other required modules
]
```
### Rust Code (`lib.rs`)
The application automatically detects and uses the bundled JRE instead of system Java.
## Modules Included
The custom runtime includes these Java modules:
- `java.base` - Core Java functionality
- `java.desktop` - AWT/Swing (for UI components)
- `java.instrument` - Java instrumentation (required by Jetty)
- `java.logging` - Logging framework
- `java.management` - JMX and monitoring
- `java.naming` - JNDI services
- `java.net.http` - HTTP client
- `java.security.jgss` - Security services
- `java.sql` - Database connectivity
- `java.xml` - XML processing
- `java.xml.crypto` - XML security
- `jdk.crypto.ec` - Elliptic curve cryptography
- `jdk.crypto.cryptoki` - PKCS#11 support
- `jdk.unsupported` - Internal APIs (used by some libraries)
## Troubleshooting
### JLink Not Found
```
❌ jlink is not available
```
**Solution**: Install a full JDK (not just JRE). JLink is included with JDK 9+.
### Module Not Found During Runtime
If the application fails with module-related errors, you may need to add additional modules to the `addModules` list in `build.gradle`.
### Large Runtime Size
The bundled runtime should be 50-80MB. If it's much larger:
- Ensure `--strip-debug` and `--compress=2` options are used
- Review the module list - remove unnecessary modules
- Consider using `--no-header-files` and `--no-man-pages`
## Benefits Over Traditional JAR Approach
| Aspect | Traditional JAR | JLink Bundle |
|--------|----------------|--------------|
| User Setup | Requires JRE installation | No Java installation needed |
| Distribution Size | Smaller JAR, but requires ~200MB JRE | Larger bundle (~80MB), but self-contained |
| Java Version | Depends on user's installed version | Consistent, controlled version |
| Security Updates | User manages JRE updates | Developer controls runtime version |
| Startup Time | May be faster (shared JRE) | Slightly slower (isolated runtime) |
## Advanced Usage
### Custom Module Analysis
To analyze your specific JAR's module requirements:
```bash
jdeps --print-module-deps --ignore-missing-deps build/libs/Stirling-PDF-*.jar
```
### Manual JLink Command
If you want to create the runtime manually:
```bash
jlink \
--add-modules java.base,java.desktop,java.logging,java.management,java.naming,java.net.http,java.security.jgss,java.sql,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported \
--strip-debug \
--compress=2 \
--no-header-files \
--no-man-pages \
--output frontend/src-tauri/runtime/jre
```
## Migration Guide
### From JAR-based Tauri App
1. Update your Tauri configuration to include the runtime resources
2. Update your Rust code to use the bundled JRE path
3. Run the JLink build script
4. Test the bundled runtime
5. Build and distribute the new self-contained app
### Deployment
The final Tauri application will be completely self-contained. Users can:
- Install the app normally (no Java installation required)
- Run the app immediately after installation
- Not worry about Java version compatibility issues
## Support
If you encounter issues with the JLink bundling:
1. Ensure you have a JDK (not JRE) installed
2. Check that the Java version is 17 or higher
3. Verify that the build script completed successfully
4. Test the bundled runtime using the provided launcher scripts
5. Check the Tauri build logs for any missing resources

128
build-tauri-jlink.bat Normal file
View File

@ -0,0 +1,128 @@
@echo off
REM Build script for Tauri with JLink runtime bundling
REM This script creates a self-contained Java runtime for Stirling-PDF
echo 🔧 Building Stirling-PDF with JLink runtime for Tauri...
echo ▶ Checking Java environment...
java -version >nul 2>&1
if errorlevel 1 (
echo ❌ Java is not installed or not in PATH
exit /b 1
)
jlink --version >nul 2>&1
if errorlevel 1 (
echo ❌ jlink is not available. Please ensure you have a JDK ^(not just JRE^) installed.
exit /b 1
)
echo ✅ Java and jlink detected
echo ▶ Building Stirling-PDF JAR...
call gradlew.bat clean bootJar --no-daemon
if errorlevel 1 (
echo ❌ Failed to build Stirling-PDF JAR
exit /b 1
)
REM Find the built JAR
for %%f in (build\libs\Stirling-PDF-*.jar) do set STIRLING_JAR=%%f
if not exist "%STIRLING_JAR%" (
echo ❌ No Stirling-PDF JAR found in build/libs/
exit /b 1
)
echo ✅ Built JAR: %STIRLING_JAR%
echo ▶ Creating Tauri directories...
if not exist "frontend\src-tauri\libs" mkdir "frontend\src-tauri\libs"
if not exist "frontend\src-tauri\runtime" mkdir "frontend\src-tauri\runtime"
echo ▶ Copying JAR to Tauri libs directory...
copy "%STIRLING_JAR%" "frontend\src-tauri\libs\"
echo ✅ JAR copied to frontend\src-tauri\libs\
echo ▶ Creating custom JRE with jlink...
if exist "frontend\src-tauri\runtime\jre" rmdir /s /q "frontend\src-tauri\runtime\jre"
REM Use predefined module list for Windows (jdeps may not be available)
set 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
echo ▶ Creating JLink runtime with modules: %MODULES%
jlink ^
--add-modules %MODULES% ^
--strip-debug ^
--compress=2 ^
--no-header-files ^
--no-man-pages ^
--output "frontend\src-tauri\runtime\jre"
if not exist "frontend\src-tauri\runtime\jre" (
echo ❌ Failed to create JLink runtime
exit /b 1
)
echo ✅ JLink runtime created at frontend\src-tauri\runtime\jre
echo ▶ Creating launcher scripts for testing...
REM Create Windows launcher script
echo @echo off > "frontend\src-tauri\runtime\launch-stirling.bat"
echo REM Launcher script for Stirling-PDF with bundled JRE >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo. >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo set SCRIPT_DIR=%%~dp0 >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo set JRE_DIR=%%SCRIPT_DIR%%jre >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo set LIBS_DIR=%%SCRIPT_DIR%%..\libs >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo. >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo REM Find the Stirling-PDF JAR >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo for %%%%f in ("%%LIBS_DIR%%\Stirling-PDF-*.jar") do set STIRLING_JAR=%%%%f >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo. >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo if not exist "%%STIRLING_JAR%%" ^( >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo echo ❌ Stirling-PDF JAR not found in %%LIBS_DIR%% >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo exit /b 1 >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo ^) >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo. >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo REM Launch with bundled JRE >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo "%%JRE_DIR%%\bin\java.exe" ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo -Xmx2g ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo -DBROWSER_OPEN=true ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo -DSTIRLING_PDF_DESKTOP_UI=false ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo -jar "%%STIRLING_JAR%%" ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo %%* >> "frontend\src-tauri\runtime\launch-stirling.bat"
echo ✅ Created launcher scripts for testing
echo ▶ Testing bundled JRE...
"frontend\src-tauri\runtime\jre\bin\java.exe" --version >nul 2>&1
if errorlevel 1 (
echo ❌ Bundled JRE test failed
exit /b 1
) else (
echo ✅ Bundled JRE works correctly
)
echo.
echo ✅ 🎉 JLink build setup completed successfully!
echo.
echo 📊 Summary:
echo • JAR: %STIRLING_JAR%
echo • Runtime: frontend\src-tauri\runtime\jre
echo • Modules: %MODULES%
echo.
echo 📋 Next steps:
echo 1. cd frontend
echo 2. npm run tauri build
echo.
echo 💡 Testing:
echo • Test bundled runtime: frontend\src-tauri\runtime\launch-stirling.bat
echo • Tauri configuration already updated to include bundled JRE
echo.
echo 💡 Benefits:
echo • No external JRE dependency
echo • Smaller distribution size with custom runtime
echo • Better security with minimal required modules
echo • Consistent Java version across all deployments
echo.
echo ✅ The application will now run without requiring users to install Java!

230
build-tauri-jlink.sh Normal file
View File

@ -0,0 +1,230 @@
#!/bin/bash
# Build script for Tauri with JLink runtime bundling
# This script creates a self-contained Java runtime for Stirling-PDF
set -e
echo "🔧 Building Stirling-PDF with JLink runtime for Tauri..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_step() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Check if Java is installed and version
print_step "Checking Java environment..."
if ! command -v java &> /dev/null; then
print_error "Java is not installed or not in PATH"
exit 1
fi
if ! command -v jlink &> /dev/null; then
print_error "jlink is not available. Please ensure you have a JDK (not just JRE) installed."
exit 1
fi
JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1)
if [ "$JAVA_VERSION" -lt 17 ]; then
print_error "Java 17 or higher is required. Found Java $JAVA_VERSION"
exit 1
fi
print_success "Java $JAVA_VERSION detected with jlink support"
# Check if jpackage is available (Java 14+)
if command -v jpackage &> /dev/null; then
print_success "jpackage is available for native packaging"
else
print_warning "jpackage is not available - using jlink only"
fi
# Clean and build the Stirling-PDF JAR
print_step "Building Stirling-PDF JAR..."
./gradlew clean bootJar --no-daemon
if [ ! -f "build/libs/Stirling-PDF-"*.jar ]; then
print_error "Failed to build Stirling-PDF JAR"
exit 1
fi
# Find the built JAR
STIRLING_JAR=$(ls build/libs/Stirling-PDF-*.jar | head -n 1)
print_success "Built JAR: $STIRLING_JAR"
# Create directories for Tauri
TAURI_SRC_DIR="frontend/src-tauri"
TAURI_LIBS_DIR="$TAURI_SRC_DIR/libs"
TAURI_RUNTIME_DIR="$TAURI_SRC_DIR/runtime"
print_step "Creating Tauri directories..."
mkdir -p "$TAURI_LIBS_DIR"
mkdir -p "$TAURI_RUNTIME_DIR"
# Copy the JAR to Tauri libs directory
print_step "Copying JAR to Tauri libs directory..."
cp "$STIRLING_JAR" "$TAURI_LIBS_DIR/"
print_success "JAR copied to $TAURI_LIBS_DIR/"
# Create a custom JRE using jlink
print_step "Creating custom JRE with jlink..."
# Determine modules needed by analyzing the JAR
print_step "Analyzing JAR dependencies..."
# Use jdeps to analyze module dependencies if available
if command -v jdeps &> /dev/null; then
print_step "Running jdeps analysis..."
REQUIRED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "")
if [ -n "$REQUIRED_MODULES" ]; then
print_success "jdeps detected modules: $REQUIRED_MODULES"
# Add additional modules we know Stirling-PDF needs
MODULES="$REQUIRED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported"
else
print_warning "jdeps analysis failed, using predefined module list"
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"
fi
else
print_warning "jdeps not available, using predefined module list"
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"
fi
print_step "Creating JLink runtime with modules: $MODULES"
# Remove existing runtime if present
rm -rf "$TAURI_RUNTIME_DIR/jre"
# Create the custom JRE
jlink \
--add-modules "$MODULES" \
--strip-debug \
--compress=2 \
--no-header-files \
--no-man-pages \
--output "$TAURI_RUNTIME_DIR/jre"
if [ ! -d "$TAURI_RUNTIME_DIR/jre" ]; then
print_error "Failed to create JLink runtime"
exit 1
fi
print_success "JLink runtime created at $TAURI_RUNTIME_DIR/jre"
# Calculate runtime size
RUNTIME_SIZE=$(du -sh "$TAURI_RUNTIME_DIR/jre" | cut -f1)
print_success "Runtime size: $RUNTIME_SIZE"
# Create launcher scripts for testing
print_step "Creating launcher scripts for testing..."
LAUNCHER_SCRIPT="$TAURI_RUNTIME_DIR/launch-stirling.sh"
cat > "$LAUNCHER_SCRIPT" << 'EOF'
#!/bin/bash
# Launcher script for Stirling-PDF with bundled JRE
# Get the directory of this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JRE_DIR="$SCRIPT_DIR/jre"
LIBS_DIR="$(dirname "$SCRIPT_DIR")/libs"
# Find the Stirling-PDF JAR
STIRLING_JAR=$(ls "$LIBS_DIR"/Stirling-PDF-*.jar | head -n 1)
if [ ! -f "$STIRLING_JAR" ]; then
echo "❌ Stirling-PDF JAR not found in $LIBS_DIR"
exit 1
fi
# Launch with bundled JRE
"$JRE_DIR/bin/java" \
-Xmx2g \
-DBROWSER_OPEN=true \
-DSTIRLING_PDF_DESKTOP_UI=false \
-jar "$STIRLING_JAR" \
"$@"
EOF
chmod +x "$LAUNCHER_SCRIPT"
# Create Windows launcher
LAUNCHER_BAT="$TAURI_RUNTIME_DIR/launch-stirling.bat"
cat > "$LAUNCHER_BAT" << 'EOF'
@echo off
REM Launcher script for Stirling-PDF with bundled JRE
set SCRIPT_DIR=%~dp0
set JRE_DIR=%SCRIPT_DIR%jre
set LIBS_DIR=%SCRIPT_DIR%..\libs
REM Find the Stirling-PDF JAR
for %%f in ("%LIBS_DIR%\Stirling-PDF-*.jar") do set STIRLING_JAR=%%f
if not exist "%STIRLING_JAR%" (
echo ❌ Stirling-PDF JAR not found in %LIBS_DIR%
exit /b 1
)
REM Launch with bundled JRE
"%JRE_DIR%\bin\java.exe" ^
-Xmx2g ^
-DBROWSER_OPEN=true ^
-DSTIRLING_PDF_DESKTOP_UI=false ^
-jar "%STIRLING_JAR%" ^
%*
EOF
print_success "Created launcher scripts for testing"
# Test the bundled runtime
print_step "Testing bundled JRE..."
if [ -f "$TAURI_RUNTIME_DIR/jre/bin/java" ]; then
JAVA_VERSION_OUTPUT=$("$TAURI_RUNTIME_DIR/jre/bin/java" --version 2>&1 | head -n 1)
print_success "Bundled JRE works: $JAVA_VERSION_OUTPUT"
else
print_error "Bundled JRE executable not found"
exit 1
fi
# Display summary
echo ""
print_success "🎉 JLink build setup completed successfully!"
echo ""
echo -e "${BLUE}📊 Summary:${NC}"
echo " • JAR: $STIRLING_JAR"
echo " • Runtime: $TAURI_RUNTIME_DIR/jre ($RUNTIME_SIZE)"
echo " • Modules: $MODULES"
echo ""
echo -e "${BLUE}📋 Next steps:${NC}"
echo " 1. cd frontend"
echo " 2. npm run tauri build"
echo ""
echo -e "${BLUE}💡 Testing:${NC}"
echo " • Test bundled runtime: $LAUNCHER_SCRIPT"
echo " • Tauri configuration already updated to include bundled JRE"
echo ""
echo -e "${BLUE}💡 Benefits:${NC}"
echo " • No external JRE dependency"
echo " • Smaller distribution size with custom runtime"
echo " • Better security with minimal required modules"
echo " • Consistent Java version across all deployments"
echo ""
print_success "The application will now run without requiring users to install Java!"

View File

@ -2,3 +2,4 @@
# will have compiled files and executables
/target/
/gen/schemas
/runtime/

View File

@ -0,0 +1,22 @@
@echo off
REM Launcher script for Stirling-PDF with bundled JRE
set SCRIPT_DIR=%~dp0
set JRE_DIR=%SCRIPT_DIR%jre
set LIBS_DIR=%SCRIPT_DIR%..\libs
REM Find the Stirling-PDF JAR
for %%f in ("%LIBS_DIR%\Stirling-PDF-*.jar") do set STIRLING_JAR=%%f
if not exist "%STIRLING_JAR%" (
echo ❌ Stirling-PDF JAR not found in %LIBS_DIR%
exit /b 1
)
REM Launch with bundled JRE
"%JRE_DIR%\bin\java.exe" ^
-Xmx2g ^
-DBROWSER_OPEN=true ^
-DSTIRLING_PDF_DESKTOP_UI=false ^
-jar "%STIRLING_JAR%" ^
%*

View File

@ -27,10 +27,10 @@ async fn get_backend_logs() -> Result<Vec<String>, String> {
Ok(logs.iter().cloned().collect())
}
// Command to start the backend sidecar
// Command to start the backend with bundled JRE
#[tauri::command]
async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
add_log("🚀 Attempting to start backend sidecar...".to_string());
add_log("🚀 start_backend() called - Attempting to start backend with bundled JRE...".to_string());
// Check if backend is already running
{
@ -41,25 +41,36 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
}
}
add_log("📋 Creating Java command to run JAR directly".to_string());
// Use Tauri's resource API to find the JAR file
// Use Tauri's resource API to find the bundled JRE and JAR
let resource_dir = app.path().resource_dir().map_err(|e| {
let error_msg = format!("❌ Failed to get resource directory: {}", e);
add_log(error_msg.clone());
error_msg
})?;
add_log(format!("🔍 Looking for JAR in Tauri resource directory: {:?}", resource_dir));
add_log(format!("🔍 Resource directory: {:?}", resource_dir));
// In dev mode, resources are in target/debug/libs, in production they're bundled
// Find the bundled JRE
let jre_dir = resource_dir.join("runtime").join("jre");
let java_executable = if cfg!(windows) {
jre_dir.join("bin").join("java.exe")
} else {
jre_dir.join("bin").join("java")
};
if !java_executable.exists() {
let error_msg = format!("❌ Bundled JRE not found at: {:?}", java_executable);
add_log(error_msg.clone());
return Err(error_msg);
}
add_log(format!("✅ Found bundled JRE: {:?}", java_executable));
// Find the Stirling-PDF JAR
let libs_dir = resource_dir.join("libs");
add_log(format!("🔍 Checking libs directory: {:?}", libs_dir));
// Find all Stirling-PDF JAR files and pick the latest version
let mut jar_files: Vec<_> = std::fs::read_dir(&libs_dir)
.map_err(|e| {
let error_msg = format!("Failed to read libs directory: {}. Make sure the JAR is copied to frontend/src-tauri/libs/", e);
let error_msg = format!("Failed to read libs directory: {}. Make sure the JAR is copied to libs/", e);
add_log(error_msg.clone());
error_msg
})?
@ -72,12 +83,12 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
.collect();
if jar_files.is_empty() {
let error_msg = "No Stirling-PDF JAR found in Tauri resources/libs directory. Please run the build script to generate and copy the JAR.".to_string();
let error_msg = "No Stirling-PDF JAR found in libs directory.".to_string();
add_log(error_msg.clone());
return Err(error_msg);
}
// Sort by filename to get the latest version (assumes semantic versioning in filename)
// Sort by filename to get the latest version
jar_files.sort_by(|a, b| {
let name_a = a.file_name().to_string_lossy().to_string();
let name_b = b.file_name().to_string_lossy().to_string();
@ -85,9 +96,20 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
});
let jar_path = jar_files[0].path();
add_log(format!("📋 Selected latest JAR from {} available: {:?}", jar_files.len(), jar_path.file_name().unwrap()));
add_log(format!("📋 Selected JAR: {:?}", jar_path.file_name().unwrap()));
// Normalize the paths to remove Windows UNC prefix \\?\
let normalized_java_path = if cfg!(windows) {
let path_str = java_executable.to_string_lossy();
if path_str.starts_with(r"\\?\") {
std::path::PathBuf::from(&path_str[4..]) // Remove \\?\ prefix
} else {
java_executable.clone()
}
} else {
java_executable.clone()
};
// Normalize the path to remove Windows UNC prefix \\?\
let normalized_jar_path = if cfg!(windows) {
let path_str = jar_path.to_string_lossy();
if path_str.starts_with(r"\\?\") {
@ -99,28 +121,38 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
jar_path.clone()
};
add_log(format!("📦 Found JAR file in resources: {:?}", jar_path));
add_log(format!("📦 Found JAR file: {:?}", jar_path));
add_log(format!("📦 Normalized JAR path: {:?}", normalized_jar_path));
add_log(format!("📦 Normalized Java path: {:?}", normalized_java_path));
// Log the equivalent command for external testing
let java_command = format!(
"java -Xmx2g -DBROWSER_OPEN=false -DSTIRLING_PDF_DESKTOP_UI=true -jar \"{}\"",
"\"{}\" -Xmx2g -DBROWSER_OPEN=false -DSTIRLING_PDF_DESKTOP_UI=false -jar \"{}\"",
normalized_java_path.display(),
normalized_jar_path.display()
);
add_log(format!("🔧 Equivalent command to run externally: {}", java_command));
add_log(format!("🔧 Equivalent command: {}", java_command));
// Create Java command with bundled JRE using normalized paths
// Configure logging to write outside src-tauri to prevent dev server restarts
let temp_dir = std::env::temp_dir();
let log_dir = temp_dir.join("stirling-pdf-logs");
std::fs::create_dir_all(&log_dir).ok(); // Create log directory if it doesn't exist
// Create Java command directly
let sidecar_command = app
.shell()
.command("java")
.command(normalized_java_path.to_str().unwrap())
.args([
"-Xmx2g",
"-DBROWSER_OPEN=false",
"-DSTIRLING_PDF_DESKTOP_UI=false",
&format!("-Dlogging.file.path={}", log_dir.display()),
"-Dlogging.file.name=stirling-pdf.log",
"-jar",
normalized_jar_path.to_str().unwrap()
]);
add_log("⚙️ Sidecar command created, attempting to spawn...".to_string());
add_log("⚙️ Starting backend with bundled JRE...".to_string());
let (mut rx, child) = sidecar_command
.spawn()
@ -136,7 +168,7 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
*process_guard = Some(Arc::new(child));
}
add_log("Sidecar spawned successfully, monitoring output...".to_string());
add_log("Backend started with bundled JRE, monitoring output...".to_string());
// Listen to sidecar output for debugging
tokio::spawn(async move {
@ -221,7 +253,7 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
println!("⏳ Waiting for backend startup...");
tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
Ok("Backend startup initiated successfully".to_string())
Ok("Backend startup initiated successfully with bundled JRE".to_string())
}
// Command to check if backend is healthy
@ -269,7 +301,7 @@ async fn get_backend_status() -> Result<String, String> {
// Try to check if process is still alive
let pid = child.pid();
println!("🔍 Checking backend process status, PID: {}", pid);
Ok(format!("Backend process is running (PID: {})", pid))
Ok(format!("Backend process is running with bundled JRE (PID: {})", pid))
},
None => Ok("Backend process is not running".to_string()),
}
@ -296,77 +328,61 @@ async fn check_backend_port() -> Result<bool, String> {
}
}
// Command to check if JAR file exists
// Command to check bundled runtime and JAR
#[tauri::command]
async fn check_jar_exists(app: tauri::AppHandle) -> Result<String, String> {
println!("🔍 Checking for JAR files in Tauri resources...");
println!("🔍 Checking for bundled JRE and JAR files...");
// Check in the Tauri resource directory (bundled)
if let Ok(resource_dir) = app.path().resource_dir() {
let jar_path = resource_dir;
println!("Checking bundled resources: {:?}", jar_path);
let mut status_parts = Vec::new();
if jar_path.exists() {
match std::fs::read_dir(&jar_path) {
// Check bundled JRE
let jre_dir = resource_dir.join("runtime").join("jre");
let java_executable = if cfg!(windows) {
jre_dir.join("bin").join("java.exe")
} else {
jre_dir.join("bin").join("java")
};
if java_executable.exists() {
status_parts.push("✅ Bundled JRE found".to_string());
} else {
status_parts.push("❌ Bundled JRE not found".to_string());
}
// Check JAR files
let libs_dir = resource_dir.join("libs");
if libs_dir.exists() {
match std::fs::read_dir(&libs_dir) {
Ok(entries) => {
let mut jar_files = Vec::new();
for entry in entries {
if let Ok(entry) = entry {
let jar_files: Vec<String> = entries
.filter_map(|entry| entry.ok())
.filter(|entry| {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("jar")
&& path.file_name()
.unwrap()
.to_string_lossy()
.contains("Stirling-PDF") {
jar_files.push(path.file_name().unwrap().to_string_lossy().to_string());
}
}
}
path.extension().and_then(|s| s.to_str()) == Some("jar")
&& path.file_name().unwrap().to_string_lossy().contains("Stirling-PDF")
})
.map(|entry| entry.file_name().to_string_lossy().to_string())
.collect();
if !jar_files.is_empty() {
println!("✅ Found JAR files in bundled resources: {:?}", jar_files);
return Ok(format!("Found JAR files: {:?}", jar_files));
status_parts.push(format!("✅ Found JAR files: {:?}", jar_files));
} else {
status_parts.push("❌ No Stirling-PDF JAR files found".to_string());
}
}
Err(e) => {
println!("❌ Failed to read resource directory: {}", e);
status_parts.push(format!("❌ Failed to read libs directory: {}", e));
}
}
} else {
status_parts.push("❌ Libs directory not found".to_string());
}
Ok(status_parts.join("\n"))
} else {
Ok("❌ Could not access bundled resources".to_string())
}
// Check in development mode location (libs directory)
let dev_jar_path = std::path::PathBuf::from("libs");
println!("Checking development libs directory: {:?}", dev_jar_path);
if dev_jar_path.exists() {
match std::fs::read_dir(&dev_jar_path) {
Ok(entries) => {
let mut jar_files = Vec::new();
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("jar")
&& path.file_name()
.unwrap()
.to_string_lossy()
.contains("Stirling-PDF") {
jar_files.push(path.file_name().unwrap().to_string_lossy().to_string());
}
}
}
if !jar_files.is_empty() {
println!("✅ Found JAR files in development libs: {:?}", jar_files);
return Ok(format!("Found JAR files: {:?}", jar_files));
}
}
Err(e) => {
println!("❌ Failed to read libs directory: {}", e);
}
}
}
println!("❌ No Stirling-PDF JAR files found");
Ok("No Stirling-PDF JAR files found. Please run './build-tauri.sh' or 'build-tauri.bat' to build and copy the JAR.".to_string())
}
// Command to test sidecar binary directly
@ -387,33 +403,48 @@ async fn test_sidecar_binary(app: tauri::AppHandle) -> Result<String, String> {
}
}
// Command to check Java environment
// Command to check Java environment (bundled version)
#[tauri::command]
async fn check_java_environment() -> Result<String, String> {
println!("🔍 Checking Java environment...");
async fn check_java_environment(app: tauri::AppHandle) -> Result<String, String> {
println!("🔍 Checking bundled Java environment...");
let output = std::process::Command::new("java")
.arg("--version")
.output();
match output {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let version_info = if !stdout.is_empty() { stdout } else { stderr };
println!("✅ Java found: {}", version_info);
Ok(format!("Java available: {}", version_info.trim()))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
println!("❌ Java command failed: {}", stderr);
Ok(format!("Java command failed: {}", stderr))
if let Ok(resource_dir) = app.path().resource_dir() {
let jre_dir = resource_dir.join("runtime").join("jre");
let java_executable = if cfg!(windows) {
jre_dir.join("bin").join("java.exe")
} else {
jre_dir.join("bin").join("java")
};
if java_executable.exists() {
let output = std::process::Command::new(&java_executable)
.arg("--version")
.output();
match output {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let version_info = if !stdout.is_empty() { stdout } else { stderr };
println!("✅ Bundled Java found: {}", version_info);
Ok(format!("Bundled Java available: {}", version_info.trim()))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
println!("❌ Bundled Java command failed: {}", stderr);
Ok(format!("Bundled Java command failed: {}", stderr))
}
}
Err(e) => {
println!("❌ Failed to execute bundled Java: {}", e);
Ok(format!("Failed to execute bundled Java: {}", e))
}
}
} else {
Ok("❌ Bundled JRE not found".to_string())
}
Err(e) => {
println!("❌ Java not found: {}", e);
Ok(format!("Java not found or not in PATH: {}", e))
}
} else {
Ok("❌ Could not access bundled resources".to_string())
}
}
@ -422,18 +453,21 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
// Disable file logging in debug mode to prevent dev server restart loops
// if cfg!(debug_assertions) {
// app.handle().plugin(
// tauri_plugin_log::Builder::default()
// .level(log::LevelFilter::Info)
// .build(),
// )?;
// }
// Automatically start the backend when Tauri starts
let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(1000)).await; // Small delay to ensure app is ready
add_log("🔄 Tauri app ready, starting backend...".to_string());
match start_backend(app_handle).await {
Ok(result) => {
add_log(format!("🚀 Auto-started backend on Tauri startup: {}", result));

View File

@ -32,7 +32,8 @@
"icons/icon.ico"
],
"resources": [
"libs/*.jar"
"libs/*.jar",
"runtime/jre/**/*"
]
},
"plugins": {

View File

@ -4,7 +4,7 @@ import HomePage from './pages/HomePage';
import { SidecarTest } from './components/SidecarTest';
export default function App() {
const [showTests, setShowTests] = useState(true); // Start with tests visible
const [showTests, setShowTests] = useState(false); // Start with app visible
return (
<div className="min-h-screen bg-gray-100">