Skip to content

Redline maven plugin - #131

Merged
andreaTP merged 20 commits into
bytecodealliance:mainfrom
andreaTP:redline-maven-plugin
Aug 25, 2026
Merged

Redline maven plugin#131
andreaTP merged 20 commits into
bytecodealliance:mainfrom
andreaTP:redline-maven-plugin

Conversation

@andreaTP

Copy link
Copy Markdown
Contributor

No description provided.

@andreaTP
andreaTP force-pushed the redline-maven-plugin branch 4 times, most recently from b659f62 to 7b5388f Compare July 29, 2026 20:20
@andreaTP

Copy link
Copy Markdown
Contributor Author

on hold, not having jffi requires too much Java versions contorsionism.

@andreaTP
andreaTP force-pushed the redline-maven-plugin branch 2 times, most recently from 401e2a6 to f58545f Compare August 10, 2026 10:18

@andreaTP andreaTP left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few comments

}

@Test
public void nativeCodeIsAvailable() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is machine dependent

"var host = RedlineTarget.detectHost().orElse(null);"));

initBody.addStatement(
StaticJavaParser.parseStatement(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no parseStatement + string interpolation, we should use plain JavaParser to build the desired code, is more maintainable in the longer run

<dependencies>
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javaparser as a dependency of the build time compiler looks a little strange, but maybe worth if it keeps complexity low.

</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>runtime</artifactId>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't runtime a transitive dependency of redline?

<dependencies>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-api-experimental</artifactId>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to explicitly add the api module? isn't it transitive from runner?

<configuration>
<name>endive.test.AddModule</name>
<wasmFile>src/test/resources/add.wat.wasm</wasmFile>
<interpreterFallback>WARN</interpreterFallback>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

un-necessary

}

@Test
public void nativeCodeIsAvailable() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is machine dependent

<version>0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comments as above for the transitive deps

@andreaTP
andreaTP marked this pull request as ready for review August 24, 2026 16:50

@andreaTP andreaTP left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is finally ready, there might be a few rough edges still, but I want to stop growing it more.

Let's merge and fix on top if something is off.

@andreaTP
andreaTP force-pushed the redline-maven-plugin branch from 04eee9f to 79db10f Compare August 25, 2026 12:21
Architecture:
- Bridge module uses exec:java with Generator.main() instead of the
  Maven plugin, breaking the circular dependency (plugin → redline →
  bridge → plugin)
- New redline/build-time-compiler module with RedlineGenerator that
  handles native code generation and extends generated sources with
  builder()/safeBuilder()/loadNativeCode() methods
- Maven plugin calls RedlineGenerator when <redlineTargets> is configured

Config extended with:
- redlineTargets: list of target triples for cross-compilation
- targetResourceFolder: where .native files are written

Generated module class gains (when redlineTargets configured):
- loadNativeCode(): loads platform-specific native code from resources
- builder(): automatic backend selection (native if platform + Java 25
  supported, bytecode otherwise)
- safeBuilder(): always uses bytecode compiler

NativeMachineFactory.Builder gains toInstanceBuilder() to return a
configured Instance.Builder for the generated builder() method.
RedlineTarget gains fromTriple() for target triple lookup.
- Add <redline>true</redline> shortcut to compile for all supported
  platforms (instead of listing 6 target triples)
- Add redline/it module with Maven Invoker integration test:
  - Compiles add.wat.wasm with native code for all platforms
  - Tests builder() (native), safeBuilder() (bytecode), loadNativeCode()
  - Verifies native and bytecode produce identical results
- Add logging when redline native compilation runs
- Add Rust toolchain + cranelift_bridge.wasm build to ci.yaml
- Remove -Predline from release.yaml and redline.yaml
- Plugin depends directly on redline-build-time-compiler (no reflection)
- Bridge uses exec:java with GeneratorMain to break the cycle
- Generated code uses NativeMachineFactoryProvider SPI (no Panama import)
- Remove redundant targetResourceFolder from Config
- Validate target triples before compilation in RedlineGenerator
- E2E tests: JFFI (all JDKs) + Panama (JDK 25+ only)
- Restore javadoc comments on Mojo fields
Replace Rust toolchain + make all in CI with inlay-maven-plugin
fetching the pre-built wasm from ghcr.io/bytecodealliance/endive-cranelift-bridge.
No Rust needed in any CI workflow except the new wasm-publish.yaml.

- Bridge POM: inlay:fetch before exec:java GeneratorMain
- Root POM: add inlay-maven-plugin to pluginManagement
- CI/redline/release workflows: remove Rust toolchain steps
- New wasm-publish.yaml: build + publish wasm to GHCR on wasm-build changes
- wkg.lock: pins wasm digest for reproducible builds
The it module is unconditional (builds on all JDKs) but
redline-runner-experimental requires JDK 25+. The Panama E2E
test project declares its own dependency via @project.version@.
The it module is unconditional but redline-runner-experimental
requires JDK 25+. Move it to a java25 profile so it only resolves
on JDK 25+ where the Panama E2E test actually runs.
Matches the redlineExperimental flag naming — both are user-facing
plugin parameters for the experimental redline feature.
Replace parseStatement + string concatenation with direct AST node
construction, matching the idiom already used by build-time-compiler's
Generator (which has zero parse calls in its codegen).

Each generator method now carries a comment showing the code it emits.
Generated output is unchanged.
- Drop redline-api and runtime from the E2E POMs; both come
  transitively from the runner, leaving a single consumer dependency
- Drop interpreterFallback=WARN; the trivial add module never needs it
  and FAIL (the default) is the stricter check
- Guard nativeCodeIsAvailable with an assumption on detectHost(), so it
  skips rather than fails on platforms Redline does not target
Two bugs that would have made the publish automation a no-op or
produced an unpullable artifact:

- It pushed :latest while the bridge POM consumes :999.0.0-SNAPSHOT, so
  republishing would never update what the build actually reads. The tag
  now lives in one env var, noted as needing to match the POM.
- It pushed from the repo root, so the image title annotation became
  "redline/cranelift_bridge.wasm" and the OCI client tried to write into
  a directory that does not exist on pull. Push from redline/ instead.

Also emit the wkg.lock refresh command in the job summary, since a
publish invalidates the pinned digest until the lock file is updated.
Removing " -Predline" also consumed the following newline in three
places, concatenating commands: "versions:set ... git add ." would run
git as a Maven lifecycle phase, and "clean deploy" stopped being its own
command. One -Predline also survived, referencing a profile this PR
deletes. The release workflow could not have succeeded.
detectHost() used isAarch64 as its only discriminator, so every other
architecture fell through to the x86_64 variant. On Linux/riscv64 (or
ppc64le, s390x, 32-bit ARM) it returned LINUX_X86_64, the
.x86_64-linux.native resource loaded, and the runner handed x86-64
machine code to the CPU — a JVM crash rather than a fallback to the
build-time compiler.

Recognise x86_64/amd64/x64 explicitly and return empty otherwise, which
routes those platforms to the compiled-bytecode path. Adds the first
tests for RedlineTarget, using the endive.redline.os.* overrides.
Correctness:
- discover() caught ServiceConfigurationError around the loop body, but
  ServiceLoader raises it from the iterator, so a for-each let it escape.
  Wrap next() instead, and also catch LinkageError: the Panama runner is
  compiled for 25 and fails to link on older JDKs, which is exactly the
  case the catch was meant to tolerate.
- NativeCodeSerializer allocated byte[count][] straight from the file, so
  a corrupt count became OutOfMemoryError before truncation could be
  detected. Reject negative counts and lengths and collect incrementally.
- A corrupt blob threw UncheckedIOException from a static initializer,
  which bricks the class for the life of the JVM. Load into a local and
  leave CODE null instead, so builder() degrades to compiled bytecode.

DX:
- Generate nativeProvider(), non-empty exactly when builder() takes the
  native path, and have builder() use it as the single source of truth.
  This makes the choice observable, and gives modules with imported
  memories the provider they must create those imports with.
- Assert in both E2Es that the native path actually engages. Every other
  assertion there passes when it silently does not, because the fallback
  produces identical results.

Cleanup: drop the unused Cli.execute overload, put expected before
actual in the E2E assertEquals calls, and rename the generator's
interpreterBuilder helper to compiledBuilder, which is what it emits.
Since redline builds unconditionally and no longer needs -Predline or a
Rust toolchain, ci.yaml runs the identical command on a superset of the
platforms: it covers ubuntu-latest/25 and macos-latest/25 (exactly the
redline matrix), pins the same testsuite ref and sets the same
MAVEN_OPTS.

Note this leaves redline covered only by jobs with continue-on-error,
so redline failures no longer turn the build red on their own.
None of this was written down: that a Rust toolchain is not needed
because inlay fetches a digest-pinned wasm from GHCR, how to build the
bridge locally, that a stale local wasm silently shadows the pinned one,
and that publishing does not refresh wkg.lock so the lock has to be
updated in a follow-up commit.
The imageRef was a hardcoded 999.0.0-SNAPSHOT, which versions:set does
not rewrite, so a release would have shipped jars built from a mutable
OCI tag: once that tag moved, the release could no longer be rebuilt
from its git tag.

The tag is now the cranelift-bridge.version property, and the publish
workflow takes a version as a workflow_dispatch input, so an immutable
release tag can be published and adopted before cutting a release. The
workflow validates the input is semver up front, since an unparseable
tag otherwise only fails later when a consumer writes wkg.lock.

${project.version} cannot be reused here: the Maven version 999-SNAPSHOT
is not semver, which is what wkg.lock requires.
The adopt step was manual, so forgetting it would silently ship jars
built against the mutable development snapshot, unreproducible as soon
as that tag moved.

The release now retags the digest already pinned in wkg.lock under the
release version, points cranelift-bridge.version at it and re-pins the
lock, all before the version-bump commit so both land in it. Retagging
rather than rebuilding means the released wasm is byte-identical to the
artifact CI tested and no Rust toolchain is needed in this pipeline.

A guard before deploy refuses to publish if the property still resolves
to a SNAPSHOT tag, and the snapshot value is restored afterwards so main
is not left pinned to the release tag.
Without org.opencontainers.image.source the GHCR package is not linked
to this repository, so GITHUB_TOKEN has no write access to it from
Actions and the package does not appear under the repo. The package was
first pushed by hand and carries no such annotation, so the link has to
be granted once in the package settings; this keeps it in place for
every subsequent publish.
@andreaTP
andreaTP force-pushed the redline-maven-plugin branch from 79db10f to 8b95968 Compare August 25, 2026 12:21
it, runner-jffi-tests and runner-tests publish nothing, but all three
were being built during a release: the first two sat in the unconditional
module list and runner-tests rode in via the java25 profile. Building
them there only adds time and risk to the release.

They now sit behind !release activation, mirroring how the root pom keeps
its own test modules out of releases.
@andreaTP
andreaTP merged commit a777ea9 into bytecodealliance:main Aug 25, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant