From 33940dfdadbfb37f13e7ec316ff9cf7eb7f8d6a1 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 11:37:34 +0100 Subject: [PATCH 01/10] GH-245: Fix jelly-text serialization in AoT builds Add regression test --- .github/workflows/aot-test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index 5713bfe..c019187 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -70,6 +70,11 @@ jobs: # Test rdf validate target/graalvm-native-image/jelly-cli \ rdf validate out.jelly --compare-to-rdf-file in.nt + + # Test conversion to Jelly-text (requires ProtobufFeature) + target/graalvm-native-image/jelly-cli \ + rdf from-jelly --out-format=jelly-text out.jelly > out.jellytxt && \ + [ -s out.jellytxt ] - name: Upload binary uses: actions/upload-artifact@v4 From 0b902ccce4e158a8732ac1aa338cfce910bcf2e9 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 11:46:36 +0100 Subject: [PATCH 02/10] Try fix the issue --- .../eu/neverblink/jelly/cli/graal/ProtobufFeature.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala index 29011d1..5903aa4 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala @@ -14,9 +14,10 @@ class ProtobufFeature extends Feature: override def beforeAnalysis(access: BeforeAnalysisAccess): Unit = val reflections = Reflections("eu.neverblink.jelly.core.proto.google.v1", Scanners.SubTypes) - val classes = reflections.getSubTypesOf( - classOf[com.google.protobuf.GeneratedMessage], - ).asScala ++ + val classes = Seq( + classOf[com.google.protobuf.DescriptorProtos.FieldOptions], + ) ++ + reflections.getSubTypesOf(classOf[com.google.protobuf.GeneratedMessage]).asScala ++ reflections.getSubTypesOf(classOf[com.google.protobuf.GeneratedMessage.Builder[?]]).asScala ++ reflections.getSubTypesOf(classOf[com.google.protobuf.ProtocolMessageEnum]).asScala classes.foreach(clazz => { From db661b15147061d4de3646f77ad9338646d821e6 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 11:55:03 +0100 Subject: [PATCH 03/10] Second try --- .../jelly/cli/graal/ProtobufFeature.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala index 5903aa4..630baf2 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala @@ -14,12 +14,14 @@ class ProtobufFeature extends Feature: override def beforeAnalysis(access: BeforeAnalysisAccess): Unit = val reflections = Reflections("eu.neverblink.jelly.core.proto.google.v1", Scanners.SubTypes) - val classes = Seq( - classOf[com.google.protobuf.DescriptorProtos.FieldOptions], - ) ++ - reflections.getSubTypesOf(classOf[com.google.protobuf.GeneratedMessage]).asScala ++ - reflections.getSubTypesOf(classOf[com.google.protobuf.GeneratedMessage.Builder[?]]).asScala ++ - reflections.getSubTypesOf(classOf[com.google.protobuf.ProtocolMessageEnum]).asScala + val classesForSubtyping: Seq[Class[?]] = Seq( + classOf[com.google.protobuf.DescriptorProtos.FieldOptionsOrBuilder], + classOf[com.google.protobuf.GeneratedMessage], + classOf[com.google.protobuf.GeneratedMessage.Builder[?]], + classOf[com.google.protobuf.ProtocolMessageEnum], + ) + val classes: Seq[Class[?]] = + classesForSubtyping.flatMap(reflections.getSubTypesOf(_).asScala.toSeq) classes.foreach(clazz => { RuntimeReflection.register(clazz) RuntimeReflection.register(clazz.getDeclaredMethods*) From 8de8098b26be42bbba5837ae7ca1d224e7a66427 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 12:55:42 +0100 Subject: [PATCH 04/10] Why wasn't this caught in the test above? --- .github/workflows/aot-test.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index c019187..5713bfe 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -70,11 +70,6 @@ jobs: # Test rdf validate target/graalvm-native-image/jelly-cli \ rdf validate out.jelly --compare-to-rdf-file in.nt - - # Test conversion to Jelly-text (requires ProtobufFeature) - target/graalvm-native-image/jelly-cli \ - rdf from-jelly --out-format=jelly-text out.jelly > out.jellytxt && \ - [ -s out.jellytxt ] - name: Upload binary uses: actions/upload-artifact@v4 From 4078727f01193e391337c4cf60228e07a7f5859a Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 12:57:17 +0100 Subject: [PATCH 05/10] try again --- .github/workflows/aot-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index 5713bfe..f255ff1 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -32,6 +32,7 @@ jobs: - name: Test the binary run: | + set -o pipefail set -eux # See if it runs at all From 19e50ce6333fb221c2cd9c49a1a40a746f24a32d Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 13:19:05 +0100 Subject: [PATCH 06/10] I'm confused... --- .github/workflows/aot-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index f255ff1..6a97adc 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -32,8 +32,7 @@ jobs: - name: Test the binary run: | - set -o pipefail - set -eux + set -euo pipefail # See if it runs at all target/graalvm-native-image/jelly-cli version From 7d86fef9a45342488c7c18862c11e3d8fb4d9b57 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 13:34:42 +0100 Subject: [PATCH 07/10] This way? --- .github/workflows/aot-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index 6a97adc..4af9d98 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -31,6 +31,7 @@ jobs: run: sbt GraalVMNativeImage/packageBin - name: Test the binary + shell: bash run: | set -euo pipefail From ecc839ac9a6d96a1133f0e142be11484baa16bc9 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 15:36:33 +0100 Subject: [PATCH 08/10] This way??? See: http://mywiki.wooledge.org/BashFAQ/105 --- .github/workflows/aot-test.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index 4af9d98..1128834 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -36,41 +36,43 @@ jobs: set -euo pipefail # See if it runs at all - target/graalvm-native-image/jelly-cli version + target/graalvm-native-image/jelly-cli version || exit 1 # Make sure reflection is supported - target/graalvm-native-image/jelly-cli version | grep "JVM reflection: supported" + target/graalvm-native-image/jelly-cli version | grep "JVM reflection: supported" \ + || exit 1 # Make sure large RDF/XML file parsing is supported - target/graalvm-native-image/jelly-cli version | grep "Large RDF/XML file parsing: supported" + target/graalvm-native-image/jelly-cli version | grep "Large RDF/XML file parsing: supported" \ + || exit 1 # Test RDF conversions echo '_:b _:b .' > in.nt target/graalvm-native-image/jelly-cli \ rdf to-jelly --in-format=nt in.nt > out.jelly && \ - [ -s out.jelly ] + [ -s out.jelly ] || exit 1 target/graalvm-native-image/jelly-cli \ rdf from-jelly --out-format=jelly-text out.jelly > out.txt && \ - [ -s out.txt ] + [ -s out.txt ] || exit 1 target/graalvm-native-image/jelly-cli \ rdf from-jelly --out-format=jsonld out.jelly > out.json && \ - [ -s out.json ] + [ -s out.json ] || exit 1 echo '{"@graph":[{"@id":"http://e.org/r","http://e.org/p":{"@value":"v"}}]}' | \ target/graalvm-native-image/jelly-cli rdf to-jelly --in-format "jsonld" > jsonld.jelly && \ - [ -s jsonld.jelly ] + [ -s jsonld.jelly ] || exit 1 echo '' | \ target/graalvm-native-image/jelly-cli rdf to-jelly --in-format "rdfxml" > rdfxml.jelly && \ - [ -s rdfxml.jelly ] + [ -s rdfxml.jelly ] || exit 1 # Invalid RDF/XMl input test # Regression test for: https://github.com/Jelly-RDF/cli/issues/217 echo 'invalidxml' | \ ( ! target/graalvm-native-image/jelly-cli rdf to-jelly --in-format "rdfxml" &> error.txt ) && \ - grep 'Content is not allowed in prolog' error.txt + grep 'Content is not allowed in prolog' error.txt || exit 1 # Test rdf validate target/graalvm-native-image/jelly-cli \ - rdf validate out.jelly --compare-to-rdf-file in.nt + rdf validate out.jelly --compare-to-rdf-file in.nt || exit 1 - name: Upload binary uses: actions/upload-artifact@v4 From 393ff6144e2a5b4d3c31bdc9b60fe75f35424580 Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 16:39:15 +0100 Subject: [PATCH 09/10] Now it works --- .github/workflows/aot-test.yml | 4 +++ build.sbt | 1 + .../jelly/cli/graal/GraalSubstitutes.java | 25 ++++++++++++++++--- .../jelly/cli/graal/ProtobufFeature.scala | 1 - 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index 1128834..1a870f9 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -54,6 +54,10 @@ jobs: target/graalvm-native-image/jelly-cli \ rdf from-jelly --out-format=jelly-text out.jelly > out.txt && \ [ -s out.txt ] || exit 1 + # From jelly-text + target/graalvm-native-image/jelly-cli \ + rdf to-jelly --in-format=jelly-text out.txt > out2.jelly && \ + [ -s out2.jelly ] || exit 1 target/graalvm-native-image/jelly-cli \ rdf from-jelly --out-format=jsonld out.jelly > out.json && \ [ -s out.json ] || exit 1 diff --git a/build.sbt b/build.sbt index 0064977..72fd6e7 100644 --- a/build.sbt +++ b/build.sbt @@ -103,4 +103,5 @@ lazy val root = (project in file(".")) // Do a fast build if it's a dev build // For the release build, optimize for speed and make a build report graalVMNativeImageOptions := graalOptions, + graalVMNativeImageCommand := "/opt/graalvm_2025_09/bin/native-image", ) 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 130ef12..f4eaa7e 100644 --- a/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java +++ b/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java @@ -3,9 +3,9 @@ import com.apicatalog.jsonld.JsonLdError; import com.apicatalog.jsonld.http.HttpClient; import com.apicatalog.jsonld.http.HttpResponse; -import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; +import com.google.protobuf.Descriptors; +import com.google.protobuf.TextFormat; +import com.oracle.svm.core.annotate.*; import java.net.URI; import java.nio.charset.Charset; @@ -93,3 +93,22 @@ final class UnicodeDetectingInputStreamSubstitute { @Delete private static Charset UTF_32LE; } + +/** + * Alias for TextFormat.TextGenerator to be used in substitutions (TextGenerator is private). + */ +@TargetClass(className = "com.google.protobuf.TextFormat", innerClass = "TextGenerator") +final class TextFormatTextGeneratorAlias { +} + +/** + * Disable redaction support in protobuf TextFormat printer, which we don't need. + * This a lot of reflection-heavy code that we can do without. + */ +@TargetClass(className = "com.google.protobuf.TextFormat", innerClass = "Printer") +final class TextFormatPrinterSubstitute { + @Substitute + private boolean shouldRedact(final Descriptors.FieldDescriptor field, TextFormatTextGeneratorAlias generator) { + return false; + } +} diff --git a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala index 630baf2..ab0ce91 100644 --- a/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala +++ b/src/main/scala/eu/neverblink/jelly/cli/graal/ProtobufFeature.scala @@ -15,7 +15,6 @@ class ProtobufFeature extends Feature: override def beforeAnalysis(access: BeforeAnalysisAccess): Unit = val reflections = Reflections("eu.neverblink.jelly.core.proto.google.v1", Scanners.SubTypes) val classesForSubtyping: Seq[Class[?]] = Seq( - classOf[com.google.protobuf.DescriptorProtos.FieldOptionsOrBuilder], classOf[com.google.protobuf.GeneratedMessage], classOf[com.google.protobuf.GeneratedMessage.Builder[?]], classOf[com.google.protobuf.ProtocolMessageEnum], From 14d5d781669194e0966ae69e4395efeb122f09cd Mon Sep 17 00:00:00 2001 From: Ostrzyciel Date: Fri, 9 Jan 2026 16:41:09 +0100 Subject: [PATCH 10/10] I always forget about this... --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index 72fd6e7..0064977 100644 --- a/build.sbt +++ b/build.sbt @@ -103,5 +103,4 @@ lazy val root = (project in file(".")) // Do a fast build if it's a dev build // For the release build, optimize for speed and make a build report graalVMNativeImageOptions := graalOptions, - graalVMNativeImageCommand := "/opt/graalvm_2025_09/bin/native-image", )