Skip to content

Commit 08af9c9

Browse files
committed
JRE bundling prototype - complete and working
- Docker-based JRE build with jlink (63MB) - 21 Java modules including jdk.zipfs for JPype - Wheel packaging with bundled JRE (162MB total) - Automatic JRE detection in jvm.py - All tests passing without system Java Status: Production-ready prototype Note: JRE binaries excluded from git via .gitignore
1 parent f66d6ba commit 08af9c9

8 files changed

Lines changed: 662 additions & 2 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Ignore build output
2+
output/
3+
*.log
4+
5+
# Ignore test package artifacts (JRE, JARs, wheels, venvs)
6+
test-package/
7+
dist/
8+
*.whl
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Dockerfile for JRE Bundling Prototype
2+
# Purpose: Analyze JAR dependencies and build minimal JRE for ArcadeDB
3+
4+
# ARG must be before any FROM to be available globally
5+
ARG ARCADEDB_TAG=24.11.1
6+
7+
# Copy ArcadeDB Docker image and extract JARs
8+
# We'll use the full ArcadeDB distribution as source
9+
FROM arcadedata/arcadedb:${ARCADEDB_TAG} AS arcadedb-source
10+
11+
# Back to JRE builder
12+
FROM eclipse-temurin:21-jdk-jammy AS jre-analyzer
13+
14+
WORKDIR /build
15+
16+
# Copy JARs from ArcadeDB
17+
COPY --from=arcadedb-source /home/arcadedb/lib/*.jar /build/jars/
18+
19+
# Define required Java modules for ArcadeDB
20+
# Based on analysis, these are the modules needed
21+
# We use a fixed list for reproducibility
22+
# Note: jdk.zipfs is required for JPype JAR filesystem support
23+
RUN mkdir -p /build/analysis && \
24+
echo "java.base,java.compiler,java.desktop,java.logging,java.management,java.naming,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.incubator.vector,jdk.internal.vm.ci,jdk.jfr,jdk.management,jdk.sctp,jdk.unsupported,jdk.zipfs" > /build/analysis/required-modules.txt && \
25+
echo "=== Required Java Platform Modules ===" && \
26+
cat /build/analysis/required-modules.txt
27+
28+
# Build minimal JRE with jlink
29+
# Note: We ignore split packages since they're in shaded JARs and not needed in JRE
30+
RUN MODULES=$(cat /build/analysis/required-modules.txt | tr -d '\n') && \
31+
echo "Building JRE with modules: $MODULES" && \
32+
jlink --module-path ${JAVA_HOME}/jmods \
33+
--add-modules ${MODULES} \
34+
--ignore-signing-information \
35+
--strip-debug \
36+
--no-man-pages \
37+
--no-header-files \
38+
--compress zip-9 \
39+
--output /build/minimal-jre && \
40+
echo "=== JRE Build Complete ===" && \
41+
du -sh /build/minimal-jre
42+
43+
# Test the minimal JRE
44+
RUN /build/minimal-jre/bin/java -version && \
45+
echo "=== JRE Version Test Passed ==="
46+
47+
# Final stage - minimal image with results
48+
FROM busybox:latest AS final
49+
COPY --from=jre-analyzer /build/analysis /analysis
50+
COPY --from=jre-analyzer /build/minimal-jre /minimal-jre
51+
COPY --from=jre-analyzer /build/jars /jars
52+
CMD ["/bin/sh"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# JRE Bundling Prototype
2+
3+
This directory contains the Docker-based prototype for JRE bundling validation.
4+
5+
## Purpose
6+
7+
Validate the JRE bundling approach locally before implementing full CI/CD:
8+
9+
- Analyze JAR dependencies with `jdeps`
10+
- Build minimal JRE with `jlink`
11+
- Test JRE functionality
12+
- Measure size impact
13+
14+
## Quick Start
15+
16+
```bash
17+
# Build the JRE prototype
18+
./build-jre-prototype.sh
19+
20+
# Output will be in ./output/
21+
# - analysis/required-modules.txt - Java modules needed
22+
# - minimal-jre/ - Minimal JRE build
23+
# - jars/ - ArcadeDB JAR files
24+
```
25+
26+
## Files
27+
28+
- `Dockerfile.jre-prototype` - Multi-stage Docker build for JRE analysis and creation
29+
- `build-jre-prototype.sh` - Build script that orchestrates Docker build and extraction
30+
- `output/` - Generated output directory (git-ignored)
31+
32+
## Expected Results
33+
34+
- **JRE Size:** ~40-70 MB (compressed)
35+
- **Required Modules:** Core Java modules for ArcadeDB operation
36+
- **Functionality:** JRE can run `java -version` and basic JVM operations
37+
38+
## Next Steps
39+
40+
After successful prototype:
41+
42+
1. Test JRE with Python/JPype
43+
2. Create prototype packages (base + JRE variants)
44+
3. Validate with actual ArcadeDB operations
45+
4. Document findings
46+
5. Update build system based on learnings
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# JRE Bundling Prototype Results
2+
3+
## Status: ✅ SUCCESS
4+
5+
This document tracks the results of the JRE bundling prototype.
6+
7+
## Objective
8+
9+
Create a minimal JRE bundled with the Python package to eliminate external Java dependency.
10+
11+
## Approach
12+
13+
Using Docker for reproducible builds:
14+
1. Analyze ArcadeDB JAR dependencies
15+
2. Build minimal JRE with jlink
16+
3. Package everything into a wheel
17+
18+
## Results
19+
20+
### ✅ Successful Implementation
21+
22+
**Build System:**
23+
- Docker multi-stage build with Eclipse Temurin JDK 21
24+
- Fully reproducible, no host dependencies
25+
- Automated extraction and packaging workflow
26+
27+
**JRE Specifications:**
28+
- Size: **63MB** (with zip-9 compression)
29+
- Java Version: OpenJDK 21.0.8 LTS (Temurin)
30+
- Modules: 21 Java platform modules
31+
- Core: `java.base`, `java.compiler`, `java.logging`, `java.management`
32+
- Database: `java.sql`, `java.transaction.xa`, `java.naming`
33+
- Security: `java.security.jgss`, `java.security.sasl`
34+
- Utilities: `java.xml`, `java.rmi`, `java.scripting`, `java.prefs`
35+
- UI: `java.desktop` (for AWT/font support)
36+
- JDK: `jdk.jfr`, `jdk.management`, `jdk.sctp`, `jdk.unsupported`, `jdk.incubator.vector`, `jdk.internal.vm.ci`
37+
- **Critical:** `jdk.zipfs` (required for JPype JAR filesystem access)
38+
39+
**Package Specifications:**
40+
- Wheel Size: **162MB** total
41+
- JRE: 63MB
42+
- JARs: 99MB (85 JARs, gRPC excluded)
43+
- Python code: <1MB
44+
- Package Name: `arcadedb_embedded_jre_prototype`
45+
- Version: 25.10.1-SNAPSHOT (auto-detected from pom.xml)
46+
47+
**Test Results:**
48+
```
49+
✅ Bundled JRE detected correctly (62.2 MB installed)
50+
✅ Database created successfully
51+
✅ Transaction committed
52+
✅ Query executed: bundled_jre_test = 42
53+
✅ Package works without system Java!
54+
```
55+
56+
### Technical Implementation
57+
58+
**Bundled JRE Detection (`jvm.py`):**
59+
```python
60+
def get_bundled_jre_path() -> str | None:
61+
"""Detect if a JRE is bundled with the package."""
62+
package_dir = Path(__file__).parent
63+
jre_dir = package_dir / "jre"
64+
java_executable = jre_dir / "bin" / "java"
65+
if java_executable.exists():
66+
return str(java_executable)
67+
return None
68+
```
69+
70+
**JPype Integration:**
71+
- Automatically detects bundled JRE at `arcadedb_embedded/jre/`
72+
- Uses `libjvm.so` from `jre/lib/server/` for JPype startup
73+
- Falls back to system Java if bundled JRE not found
74+
- No code changes required in user applications
75+
76+
**Build Process:**
77+
1. `build-jre-prototype.sh` - Builds JRE with Docker, extracts results
78+
2. `test-jre-wheel.sh` - Creates wheel, installs in venv, runs tests
79+
3. Fully automated, single command: `./build-jre-prototype.sh && ./test-jre-wheel.sh`
80+
81+
### Key Learnings
82+
83+
**Module Requirements:**
84+
- Initially missing `jdk.zipfs` caused JPype to fail with "Unable to find filesystem for jar"
85+
- JPype requires JAR filesystem support to access Java modules
86+
- All AWT/Desktop modules needed even for headless operation (ArcadeDB dependencies)
87+
88+
**Version Management:**
89+
- `_version.py` must be generated before wheel build
90+
- Using `write_version.py` from parent directory works correctly
91+
- Version auto-detected from Maven `pom.xml`
92+
93+
**JVM Startup:**
94+
- Must pass explicit `libjvm.so` path to JPype
95+
- Path: `jre/lib/server/libjvm.so` (relative to package root)
96+
- Cannot rely on JPype's default Java detection when bundling
97+
98+
### File Sizes Comparison
99+
100+
| Component | Size | Notes |
101+
|-----------|------|-------|
102+
| Minimal JRE (Docker) | 63MB | jlink with zip-9 compression |
103+
| JRE (installed) | 62.2MB | Extracted in site-packages |
104+
| All JARs | 99MB | 85 JARs excluding gRPC |
105+
| **Total Wheel** | **162MB** | Complete self-contained package |
106+
| Base Package (no JRE) | ~100MB | For comparison |
107+
108+
### Next Steps
109+
110+
1. **Integration into Main Build:**
111+
- Add JRE variant to `build-all.sh`
112+
- Create separate `pyproject.toml` for JRE variant
113+
- Update GitHub Actions workflow
114+
115+
2. **Package Variants:**
116+
- `arcadedb-embedded` - Base package (requires system Java)
117+
- `arcadedb-embedded-jre` - JRE bundled variant (162MB)
118+
119+
3. **Documentation:**
120+
- Update README with installation options
121+
- Document JRE bundle for users without Java
122+
- Add troubleshooting guide
123+
124+
4. **Optimization Opportunities:**
125+
- Profile which Java modules are actually used at runtime
126+
- Consider platform-specific wheels (currently linux-x86_64 only)
127+
- Evaluate GraalVM native-image for even smaller size
128+
129+
### Conclusion
130+
131+
**The JRE bundling prototype is fully functional and ready for integration.**
132+
133+
- Zero external dependencies (no Java installation required)
134+
- Reasonable size increase (62MB for complete Java independence)
135+
- Fully automated Docker-based build
136+
- Production-ready implementation
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash
2+
# Build JRE Prototype using Docker
3+
# This script builds a minimal JRE for ArcadeDB and extracts it for testing
4+
5+
set -e
6+
7+
echo "================================="
8+
echo "JRE Bundling Prototype Build"
9+
echo "================================="
10+
echo ""
11+
12+
# Configuration
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
OUTPUT_DIR="${SCRIPT_DIR}/output"
15+
16+
# Auto-detect Docker tag from pom.xml (same logic as build-all.sh)
17+
echo "🔍 Detecting version from pom.xml..."
18+
ARCADEDB_TAG=$(python3 "${SCRIPT_DIR}/../extract_version.py" --format=docker)
19+
echo "📌 ArcadeDB version detected: ${ARCADEDB_TAG}"
20+
echo ""
21+
22+
# Clean previous output
23+
rm -rf "${OUTPUT_DIR}"
24+
mkdir -p "${OUTPUT_DIR}"
25+
26+
echo "Step 1: Building JRE prototype with Docker..."
27+
echo ""
28+
29+
# Build the Docker image
30+
docker build \
31+
--build-arg ARCADEDB_TAG="${ARCADEDB_TAG}" \
32+
-f "${SCRIPT_DIR}/Dockerfile.jre-prototype" \
33+
-t arcadedb-jre-prototype:latest \
34+
"${SCRIPT_DIR}"
35+
36+
echo ""
37+
echo "Step 2: Extracting JRE and analysis results..."
38+
echo ""
39+
40+
# Create a temporary container to extract files
41+
CONTAINER_ID=$(docker create arcadedb-jre-prototype:latest)
42+
43+
# Extract analysis results
44+
docker cp "${CONTAINER_ID}:/analysis" "${OUTPUT_DIR}/"
45+
46+
# Extract minimal JRE
47+
docker cp "${CONTAINER_ID}:/minimal-jre" "${OUTPUT_DIR}/"
48+
49+
# Extract JARs for reference
50+
docker cp "${CONTAINER_ID}:/jars" "${OUTPUT_DIR}/"
51+
52+
# Clean up container
53+
docker rm "${CONTAINER_ID}"
54+
55+
echo ""
56+
echo "Step 3: Analyzing results..."
57+
echo ""
58+
59+
# Display module list
60+
echo "Required Java modules:"
61+
cat "${OUTPUT_DIR}/analysis/required-modules.txt"
62+
echo ""
63+
64+
# Display JRE size
65+
JRE_SIZE=$(du -sh "${OUTPUT_DIR}/minimal-jre" | cut -f1)
66+
echo "Minimal JRE size: ${JRE_SIZE}"
67+
echo ""
68+
69+
# Count JARs (excluding gRPC)
70+
JAR_COUNT=$(ls "${OUTPUT_DIR}/jars"/*.jar 2> /dev/null | wc -l)
71+
GRPC_COUNT=$(ls "${OUTPUT_DIR}/jars"/*grpcw*.jar 2> /dev/null | wc -l || echo 0)
72+
FILTERED_COUNT=$((JAR_COUNT - GRPC_COUNT))
73+
echo "Total JARs: ${JAR_COUNT}"
74+
echo "gRPC JARs (excluded): ${GRPC_COUNT}"
75+
echo "Filtered JARs: ${FILTERED_COUNT}"
76+
echo ""
77+
78+
# Test the JRE
79+
echo "Step 4: Testing minimal JRE..."
80+
"${OUTPUT_DIR}/minimal-jre/bin/java" -version
81+
echo ""
82+
83+
echo "================================="
84+
echo "✅ Prototype build complete!"
85+
echo "================================="
86+
echo ""
87+
echo "Output directory: ${OUTPUT_DIR}"
88+
echo ""
89+
echo "Next steps:"
90+
echo " 1. Review required modules in: ${OUTPUT_DIR}/analysis/required-modules.txt"
91+
echo " 2. Test JRE with Python/JPype"
92+
echo " 3. Create prototype packages (base + JRE variants)"
93+
echo ""

0 commit comments

Comments
 (0)