fix(native): unbreak the native image build and smoke test (four regressions since 2026-07-27) - #5811
Conversation
…image build native-maven-plugin 1.1.6 fails every native-image goal invocation with A required class was missing while executing org.graalvm.buildtools:native-maven-plugin:1.1.6:compile-no-fork: org/apache/maven/shared/utils/logging/MessageUtils on all four platform legs. 1.1.6 added AbstractNativeImageMojo.isColorEnabled(), which calls MessageUtils.isColorEnabled() from maven-shared-utils, but the plugin's published pom (and its META-INF/maven/plugin.xml) does not declare that artifact: the plugin's Gradle build leaves it compileOnly, so it is stripped. Maven core does not export org.apache.maven.shared.utils from the maven.api realm either - the jar in Maven's lib/ stays confined to plexus.core - so the class is absent from the plugin realm at execution time. 1.1.7 removes the MessageUtils usage entirely. Verified with a minimal reproducer project: 1.1.6 fails identically to CI (with and without <extensions>true</extensions>), 1.1.7 gets past the color probe and invokes native-image normally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Tick the box to add this pull request to the merge queue (same as
|
ReviewReviewed the diff against Strengths
Suggestions / observations
Nits
No bugs, performance, or security concerns - this is a build-configuration change that only affects the native-image packaging step. LGTM pending a successful native-image workflow dispatch. |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
🟢 Coverage ∅ diff coverage
Metric Results Coverage variation Report missing for d3b2bda1 Diff coverage ✅ ∅ diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (d3b2bda) Report Missing Report Missing Report Missing Head commit (ea63dd8) 183939 128390 69.80% Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#5811) 0 0 ∅ (not applicable) Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%1 Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Two further regressions were hiding behind the native-maven-plugin failure. The Native Image workflow was last green on 2026-07-27 (run 30282412460, 9f6f610); everything below landed after it, all with [skip ci], and the workflow has no push/pull_request trigger, so nothing caught them. 1. native.graalvm.version 25.0.2 -> 25.2.4 (3fecba4) left native-image.yml's java-version at 25.0.2. The Truffle artifacts on the image classpath then no longer match the SVM Truffle runtime inside the builder, and TruffleBaseFeature.afterRegistration aborts with NoSuchMethodError: OptimizedTruffleRuntime.getLoopNodeFactory() which is exactly the failure the property's comment already warned about. 25.2.4 is not a drop-in for the builder: it is a GraalVM intermediate release, published under the graal-25.2.4 tag with assets named graalvm-community-jdk-25i2-25.0.4_*, which setup-graalvm cannot select via a plain java-version. jdk-25.0.2 is the newest mainline JDK 25 Community build, so the pin goes back to 25.0.2. 2. The image analysis aborted on io.grpc.netty.shaded.io.netty.internal.tcnative classes (AsyncSSLPrivateKeyMethod, CertificateCompressionAlgo, ReferenceCountedOpenSslEngine, ...): a reflection registration marks them reachable, SVM then initializes them at build time, and their static initializers hit UnsatisfiedLinkError calling into libnetty_tcnative, which the builder cannot load. The pom already defers io.netty to run time for this reason, but grpc-netty-shaded relocates Netty under io.grpc.netty.shaded.io.netty, which that prefix does not match, so the relocated copy now gets the same directive. dependabot.yml gains a note that GraalVM PRs must be hand-reviewed and merged only alongside a matching java-version bump. Deliberately not an ignore rule: org.graalvm.* also appears in engine/pom.xml, which should keep tracking latest, and ignore rules match coordinates rather than modules - the same trap already documented there for ANTLR. Verified on macOS/arm64 with GraalVM CE 25.0.2, the exact builder CI pins: 'mvn -Pnative -pl native -am -DskipTests package' now succeeds, generating the image in 2m 41s. The binary boots, serves HTTP, and answers SQL, Cypher and embedded GraalJS ('var x = 40 + 2; x' -> 42) correctly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dis smoke check
The linux legs got past the image build and failed in the smoke test instead,
for two separate reasons.
1. Deferring the whole io.grpc.netty.shaded.io.netty package to run time was
too broad. It also moved PlatformDependent0 off build-time initialization,
and its initializer reflectively reads java.nio.Bits.UNALIGNED,
java.nio.Buffer.address and sun.misc.Unsafe.theUnsafe. The GraalVM
reachability metadata repository does register all of those, but every entry
is conditioned on typeReached of the UNSHADED class (java.nio.Bits.UNALIGNED
hangs off io.netty.util.internal.PlatformDependent0$6), a condition the
relocated copy can never satisfy. The image built cleanly and then died at
startup the moment the gRPC plugin touched NettyServerBuilder:
MissingReflectionRegistrationError: Cannot reflectively read or write
field 'private static boolean java.nio.Bits.UNALIGNED'
Mirroring ~40 metadata entries under the shaded package names would trade a
build-time failure for a class of startup failures only a smoke test can
catch. Instead the deferral now names just the five classes the builder
actually rejects - the io.grpc.netty.shaded.io.netty.internal.tcnative
package plus handler.ssl.ReferenceCountedOpenSslEngine - so everything else
stays build-time initialized exactly as it was in the last green build.
2. exercise.sh's Redis PING has been failing since "Require authentication on
the Redis wire protocol" (3e76b3b) reached main with the 26.8.1 security
merge: every command other than AUTH and HELLO-with-AUTH now answers
"-NOAUTH Authentication required.". The check warn-skipped by default but is
a hard failure under WIRE_STRICT=1, which both Linux legs set, so it would
have failed the build for a reason unrelated to the binary. It now sends
RESP AUTH before PING. The Postgres check already authenticates via
PGPASSWORD/-U and needed no change.
smoke.sh's HOST/HTTP/PG become environment-overridable. They were hardcoded, so
on a machine that already has something on 2480 the script polls THAT server
and passes without ever testing the binary under test. CI sets none of them and
keeps the current defaults.
Verified on macOS/arm64 with GraalVM CE 25.0.2 by running the CI smoke script
against the native binary with the full Linux wire-protocol plugin list, which
is what exercises the gRPC path that crashed:
[exercise] Studio index / create DB / SQL / Cypher / JS round-trips
[exercise] Redis PING -> PONG
[exercise] Bolt negotiated version bytes: 00000405
[exercise] Mongo hello -> reply received
[exercise] gRPC services: com.arcadedb.grpc.ArcadeDbAdminService
com.arcadedb.grpc.ArcadeDbService grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
[smoke] PASS (exit 0)
The Postgres-wire assertion warn-skips locally (no psql installed); CI installs
postgresql-client and asserts it on both Linux legs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review: fix(native): unbreak the native image buildSolid, careful fix. I verified each of the three regressions and the two test-script changes against the tree; everything is internally consistent and the root-cause analysis in the description holds up on inspection. What I checked and confirmed
Notes / nits (non-blocking)
Nice work isolating three stacked regressions and documenting the reasoning at each step. |
📜 License Compliance Check✅ License check passed. See artifacts for full report. License Summary (first 50 lines) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5811 +/- ##
==========================================
- Coverage 67.90% 67.85% -0.05%
==========================================
Files 1792 1792
Lines 151893 151893
Branches 32196 32196
==========================================
- Hits 103136 103061 -75
- Misses 35398 35483 +85
+ Partials 13359 13349 -10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Problem
Every leg of the Native Image workflow fails. It was last green on 2026-07-27 (run 30282412460,
9f6f610a8). Four independent regressions landed after that, each hidden behind the previous one. Every one arrived with[skip ci]or on a side branch, andnative-image.ymlhas nopush/pull_requesttrigger, so nothing caught any of them - the 26.8.1 release run on 2026-08-03 was the first casualty.1.
native-maven-plugin0.10.6 -> 1.1.6 (68bcf720b)1.1.6 added
AbstractNativeImageMojo.isColorEnabled(), an unconditionalinvokestaticonMessageUtils.isColorEnabled(). The plugin's published pom does not declaremaven-shared-utils- its Gradle build leaves the dependencycompileOnly, so it is stripped from both the pom andMETA-INF/maven/plugin.xml, which list onlyutils,openjson,graalvm-reachability-metadata,cyclonedx-maven-plugin,mojo-executorand transitives: exactly theurls[0..25]set in the failure log. Maven core does not exportorg.apache.maven.shared.utilsfrom themaven.apirealm either (not amongmaven-core's 76 exported packages), so the jar in Maven'slib/stays confined toplexus.core.<extensions>true</extensions>is not a factor - the same failure occurs in a plainplugin>realm.Fix: 1.1.7, which drops the
MessageUtilsusage entirely.2.
native.graalvm.version25.0.2 -> 25.2.4 (3fecba4bf)The bump left
native-image.yml'sjava-version: "25.0.2"untouched, so the Truffle artifacts on the image classpath no longer match the SVM Truffle runtime inside the builder andTruffleBaseFeature.afterRegistrationaborts. The property's own comment already warned about this failure, naming the method.Fix: pin back to 25.0.2. 25.2.4 is not a drop-in for the builder: it is a GraalVM intermediate release, published under tag
graal-25.2.4with assets namedgraalvm-community-jdk-25i2-25.0.4_*, whichsetup-graalvmcannot select through a plainjava-version.jdk-25.0.2is the newest mainline JDK 25 Community build.3. Shaded Netty reaching tcnative at build time
A reflection registration marks the tcnative classes reachable, SVM initializes them at build time, and their static initializers call into
libnetty_tcnative, which the image builder cannot load. The pom already defersio.nettyto run time for this reason, butgrpc-netty-shadedrelocates Netty underio.grpc.netty.shaded.io.netty, which that prefix does not match.Fix: defer exactly the five classes the builder rejects - the
...shaded.io.netty.internal.tcnativepackage plushandler.ssl.ReferenceCountedOpenSslEngine.Deferring the whole relocated package is wrong, and this PR tried it first. It also moves
PlatformDependent0off build-time init, and its initializer reflectively readsjava.nio.Bits.UNALIGNED,java.nio.Buffer.addressandsun.misc.Unsafe.theUnsafe. The reachability metadata repository does register those, but every entry is conditioned ontypeReachedof the unshaded class (UNALIGNEDhangs offio.netty.util.internal.PlatformDependent0$6) - a condition the relocated copy can never satisfy. The image then builds cleanly and dies at startup as soon as the gRPC plugin touchesNettyServerBuilder:Mirroring ~40 metadata entries under the shaded names would trade a build-time failure for a class of startup failures only a smoke test can catch. Keeping everything else build-time initialized reproduces the last green build's behavior. One consequence worth knowing:
OpenSsl's initializer probes for tcnative inside a try/catch, so it stays build-time initialized with "unavailable" frozen in - gRPC without TLS is unaffected, and TLS falls back to the JDK SSL provider.4. Redis smoke check never authenticates
Not an image problem. "Require authentication on the Redis wire protocol" (
3e76b3b82) reached main with the 26.8.1 security merge, after the last green run: every command other thanAUTHandHELLO-with-AUTHnow answers-NOAUTH.exercise.shstill sent a barePING. It warn-skips by default but is a hard failure underWIRE_STRICT=1, which both Linux legs set - so the build would have gone red for a reason unrelated to the binary.Fix: send RESP
AUTHbeforePING. The Postgres check already authenticates viaPGPASSWORD/-Uand needed no change.Verification
Built and smoke-tested on macOS/arm64 with GraalVM CE 25.0.2 - the exact builder CI pins:
Then the actual CI smoke script, with the full Linux wire-protocol plugin list (this is what exercises the gRPC path that crashed):
The gRPC reflection listing is the meaningful line: it can only be produced by a
GrpcServerPluginthat got pastNettyServerBuilder, which is precisely where the Linux legs died. The JS round-trip proves Truffle is genuinely functional in the image, not merely past the version check from #2.For #1 specifically, a minimal standalone project (hello-world + the plugin) isolated it from ArcadeDB entirely: 1.1.6 with
extensions-> byte-identical error and realm listing to CI; 1.1.6 without -> same error,plugin>realm; 1.1.6 + an explicitmaven-shared-utilsplugin dependency -> passes; 1.1.7 unchanged -> passes.Not verified: the Postgres-wire assertion warn-skips locally (no
psqlhere; CI installspostgresql-clientand asserts it). The three Linux/Windows legs are unverified, in particular the two static-link modes (-Dnative.static=truemusl,-Dnative.mostlystatic=true), which cannot be exercised on macOS. Dispatch the workflow against this branch before merging.Also in this PR
smoke.sh-HOST/HTTP/PGbecome environment-overridable. Hardcoded ports mean that on a machine already running something on 2480 the script polls that server and passes without ever touching the binary under test, which is a false green. CI sets none of them and keeps the current defaults..github/dependabot.yml- a note that GraalVM PRs must be hand-reviewed and merged only alongside a matchingjava-versionbump. Deliberately not an ignore rule:org.graalvm.*also appears inengine/pom.xml, which should keep tracking latest, and ignore rules match coordinates rather than modules - the same trap already documented there for ANTLR.Follow-up worth considering (not in this PR)
native-image.ymltriggers only onworkflow_dispatchandrelease: published, andnative/pom.xmlsits outside the default reactor (-Pnative). That is why four separate breakages accumulated undetected and surfaced during a release. Apull_requesttrigger scoped tonative/**plus the plugin/GraalVM properties (build-only, no release upload) would have caught all four. Note that #4 came from a change toredisw, so a path filter narrow enough to only watchnative/**would still have missed it.🤖 Generated with Claude Code