diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml
index b43e6b3..f5ec8dd 100644
--- a/.github/workflows/aot-test.yml
+++ b/.github/workflows/aot-test.yml
@@ -41,8 +41,9 @@ jobs:
target/graalvm-native-image/jelly-cli version | grep "JVM reflection: supported"
# Test RDF conversions
- echo '_:b _:b .' | target/graalvm-native-image/jelly-cli \
- rdf to-jelly --in-format=nt > out.jelly && \
+ echo '_:b _:b .' > in.nt
+ target/graalvm-native-image/jelly-cli \
+ rdf to-jelly --in-format=nt in.nt > out.jelly && \
[ -s out.jelly ]
target/graalvm-native-image/jelly-cli \
rdf from-jelly --out-format=jelly-text out.jelly > out.txt && \
@@ -56,6 +57,10 @@ jobs:
echo '' | \
target/graalvm-native-image/jelly-cli rdf to-jelly --in-format "rdfxml" > rdfxml.jelly && \
[ -s rdfxml.jelly ]
+
+ # Test rdf validate
+ target/graalvm-native-image/jelly-cli \
+ rdf validate out.jelly --compare-to-rdf-file in.nt
- name: Upload binary
uses: actions/upload-artifact@v4
diff --git a/build.sbt b/build.sbt
index f2ef2ca..1fed135 100644
--- a/build.sbt
+++ b/build.sbt
@@ -34,6 +34,8 @@ lazy val graalOptions = Seq(
"--initialize-at-build-time=org.glassfish.json.UnicodeDetectingInputStream",
"-H:+TrackPrimitiveValues", // SkipFlow optimization -- will be default in GraalVM 25
"-H:+UsePredicates", // SkipFlow optimization -- will be default in GraalVM 25
+ // Make sure we don't include stuff that should be unreachable in the native image
+ "-H:AbortOnMethodReachable=*UUID.randomUUID*",
)
lazy val TestSerial = config("test-serial") extend Test
diff --git a/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java b/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java
index 4b68ba8..130ef12 100644
--- a/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java
+++ b/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java
@@ -9,8 +9,8 @@
import java.net.URI;
import java.nio.charset.Charset;
-import java.security.Provider;
-import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
// Substitutions of classes and methods for GraalVM native image builds.
// These try to remove things from the static analysis (and, in effect, from the final binary)
@@ -53,14 +53,33 @@ final class HttpLibSubstitute { }
final class HttpEnvSubstitute { }
/**
- * UUID generation is used by Jena for blank node IDs, but we can fall back to the default implementation.
+ * Use pseudo-random UUIDs instead of secure random ones for seeding the blank node allocator.
+ * The secure random number generation pulls in a lot of stuff we don't need.
+ *
+ * For conversion commands, this is not used at all, because we preserve blank node IDs.
*/
-@Substitute
-@TargetClass(className = "sun.security.jca.ProviderList")
-final class ProviderListSubstitute {
+@TargetClass(org.apache.jena.riot.lang.BlankNodeAllocatorHash.class)
+final class BlankNodeAllocatorHashSubstitute {
+ @Substitute
+ UUID freshSeed() {
+ ThreadLocalRandom r = ThreadLocalRandom.current();
+ return new UUID(r.nextLong(), r.nextLong());
+ }
+}
+
+/**
+ * Jena uses secure random number generation to create blank node IDs in its Model API.
+ *
+ * Replaced with pseudo-random UUIDs to avoid including secure random number generation in the native image.
+ * This should be good enough for our purposes, because this is only used in init code for vocabularies
+ * and not for user data.
+ */
+@TargetClass(org.apache.jena.graph.BlankNodeId.class)
+final class BlankNodeIdSubstitute {
@Substitute
- public List providers() {
- return List.of();
+ public static String createFreshId() {
+ ThreadLocalRandom r = ThreadLocalRandom.current();
+ return new UUID(r.nextLong(), r.nextLong()).toString();
}
}