Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
66c648b
Extend Maven plugin with redline native compilation support
andreaTP Jul 24, 2026
fb51e1e
Add <redline>true</redline> flag and E2E integration test
andreaTP Jul 24, 2026
d76f175
Remove -Predline profile, redline always builds
andreaTP Aug 10, 2026
5e73e8e
Use inlay to fetch cranelift_bridge.wasm from GHCR
andreaTP Aug 12, 2026
f9e1b7d
Remove Panama runner dep from it module
andreaTP Aug 14, 2026
4fdfa28
Gate Panama runner dep in it module behind java25 profile
andreaTP Aug 14, 2026
b7908cf
bump inlay
andreaTP Aug 21, 2026
e3c0652
Rename redlineTargets to redlineTargetsExperimental
andreaTP Aug 24, 2026
c52f066
Build generated code with JavaParser AST nodes, not parseStatement
andreaTP Aug 24, 2026
d842559
Address PR review: minimal E2E POMs, platform-independent assertion
andreaTP Aug 24, 2026
04bbc9c
Fix wasm-publish: correct tag and push directory
andreaTP Aug 24, 2026
5e726f3
Fix release.yaml corrupted by the -Predline removal
andreaTP Aug 24, 2026
0d3526c
Do not report unknown architectures as x86_64
andreaTP Aug 24, 2026
3e4ea5b
Harden native code loading and expose the selected backend
andreaTP Aug 24, 2026
8b17138
Drop redline.yaml, now redundant with the main CI workflow
andreaTP Aug 24, 2026
df15253
Document the Cranelift bridge wasm workflow in CONTRIBUTING
andreaTP Aug 25, 2026
36d1291
Make the bridge wasm tag a property, settable from the publish workflow
andreaTP Aug 25, 2026
cd105da
Publish an immutable bridge wasm as part of the release
andreaTP Aug 25, 2026
8b95968
Annotate the published wasm with its source repository
andreaTP Aug 25, 2026
2c1eef6
Keep redline test modules out of the release reactor
andreaTP Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions .github/workflows/redline.yaml

This file was deleted.

68 changes: 54 additions & 14 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,55 @@ jobs:
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1

- id: install-secret-key
name: Install gpg secret key
run: |
cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG

- name: Build cranelift_bridge.wasm
working-directory: redline/wasm-build
run: make all

- name: Compile
run: ./mvnw --batch-mode -Dquickly -Predline
run: ./mvnw --batch-mode -Dquickly

- name: Setup Git
run: |
git config user.name "Endive BOT"
git config user.email "endive@bytecodealliance.org"

- name: Install ORAS
uses: oras-project/setup-oras@v1

# Jars must be built against an immutable wasm tag: the development
# snapshot is mutable, so a release built against it stops being
# reproducible the moment that tag moves.
#
# This retags the digest already pinned in wkg.lock rather than rebuilding
# from Rust, so the released wasm is byte-identical to the one CI tested
# and no toolchain is needed here.
- name: Publish the bridge wasm under the release version
run: |
set -euo pipefail
DIGEST=$(grep -oE 'sha256:[0-9a-f]{64}' redline/wkg.lock | head -1 || true)
if [ -z "$DIGEST" ]; then
echo "::error::No digest found in redline/wkg.lock"
exit 1
fi
echo "Retagging $DIGEST as $VERSION"
echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
oras tag "ghcr.io/bytecodealliance/endive-cranelift-bridge@${DIGEST}" "$VERSION"
env:
VERSION: ${{ github.event.inputs.release-version }}

# Runs before "Set the version" so the property and lock changes are
# swept into the release commit by its "git add ." below.
- name: Pin the build to the released wasm
run: |
./mvnw versions:set-property -Dproperty=cranelift-bridge.version \
-DnewVersion=${{ github.event.inputs.release-version }} -DgenerateBackupPoms=false
./mvnw generate-sources -pl :redline-bridge-experimental -Dinlay.update

- name: Set the version
run: |
./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.release-version }} -Predline
./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.release-version }}
git add .
git commit -m "Release version update ${{ github.event.inputs.release-version }}"
git push
Expand All @@ -71,19 +94,36 @@ jobs:
env:
GITHUB_TOKEN: ${{secrets.GH_TOKEN}}

# Last line of defence: if the steps above were skipped or edited away, the
# jars would be built from a mutable tag and could not be rebuilt later.
- name: Verify the bridge wasm tag is immutable
run: |
set -euo pipefail
v=$(./mvnw help:evaluate -Dexpression=cranelift-bridge.version -q -DforceStdout)
echo "cranelift-bridge.version = $v"
case "$v" in
*SNAPSHOT*)
echo "::error::Refusing to release jars built against the mutable wasm tag '$v'"
exit 1
;;
esac

- name: Release to Maven Central
run: |
# -Dquickly is needed to locally publish wasm-corpus
./mvnw --batch-mode -Dquickly -Predline
./mvnw --batch-mode clean deploy -Drelease -Predline -DskipTests=true -X
./mvnw --batch-mode -Dquickly
./mvnw --batch-mode clean deploy -Drelease -DskipTests=true -X
env:
MAVEN_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

- name: Back to Snapshot
run: |
./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=999-SNAPSHOT -Predline
./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=999-SNAPSHOT
./mvnw versions:set-property -Dproperty=cranelift-bridge.version \
-DnewVersion=999.0.0-SNAPSHOT -DgenerateBackupPoms=false
./mvnw generate-sources -pl :redline-bridge-experimental -Dinlay.update
git add .
git commit -m "Snapshot version update"
git push
Expand Down
96 changes: 96 additions & 0 deletions .github/workflows/wasm-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Publish cranelift_bridge.wasm

on:
push:
branches: [main]
paths: ['redline/wasm-build/**']
workflow_dispatch:
inputs:
version:
description: >-
Semver tag to publish, e.g. 1.2.0. Use an immutable release tag when
preparing a release; leave the default to refresh the development
snapshot. Must be valid semver: wkg.lock rejects tags like "latest".
required: true
default: 999.0.0-SNAPSHOT

# Pushes to main refresh the development snapshot; a manual run can publish any
# semver tag. Must match cranelift-bridge.version in the root pom.xml for the
# build to actually consume what was published.
env:
WASM_VERSION: ${{ inputs.version || '999.0.0-SNAPSHOT' }}

jobs:
build-and-publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout sources
uses: actions/checkout@v7

# Catches "latest", "1.2" and similar before anything is pushed: an
# unparseable tag only fails later, when a consumer writes wkg.lock.
- name: Validate version is semver
run: |
if ! echo "$WASM_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'; then
echo "::error::'$WASM_VERSION' is not valid semver; wkg.lock would reject it"
exit 1
fi

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1

- name: Build cranelift_bridge.wasm
working-directory: redline/wasm-build
run: make all

- name: Install ORAS
uses: oras-project/setup-oras@v1

- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin

# Pushed from inside redline/ so the org.opencontainers.image.title
# annotation is a bare filename. A path like "redline/cranelift_bridge.wasm"
# makes the OCI client try to write into a directory that does not exist
# on pull.
#
# The source annotation links the package to this repository, which is what
# lets GITHUB_TOKEN write to it from Actions and makes it show up under the
# repo's packages.
- name: Push to GHCR
working-directory: redline
run: |
oras push \
--annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
ghcr.io/bytecodealliance/endive-cranelift-bridge:${WASM_VERSION} \
cranelift_bridge.wasm:application/wasm

- name: Next steps
run: |
{
echo "Published \`$WASM_VERSION\`."
echo
echo "Publishing does not update the lock file. Until it is refreshed the"
echo "build keeps resolving the previously pinned digest, and re-pushing an"
echo "already-locked tag makes every build fail with a digest mismatch."
echo
echo "To consume it:"
echo
echo '```bash'
echo "# 1. point the build at this tag"
echo "./mvnw versions:set-property -Dproperty=cranelift-bridge.version \\"
echo " -DnewVersion=$WASM_VERSION -DgenerateBackupPoms=false"
echo
echo "# 2. re-pin the digest"
echo "./mvnw generate-sources -pl :redline-bridge-experimental -Dinlay.update"
echo
echo "# 3. commit both"
echo "git commit -am 'Use cranelift_bridge.wasm $WASM_VERSION'"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,36 @@ Basic steps:

note: if you're working using a *corporate proxy* (or anything like this), you might need to pass the usual `-Dhttps.proxyHost=...` and `-Dhttps.proxyPort=...` in order to properly instruct Maven about this (this can be required for example for `test-gen-plugin` since it downloads the testsuite).

### Redline and the Cranelift bridge

The experimental redline native compiler needs `cranelift_bridge.wasm`, a Rust crate compiled to `wasm32-wasip1`. **You do not need a Rust toolchain to build Endive.** The [inlay](https://github.com/roastedroot/inlay) Maven plugin downloads a prebuilt copy from GHCR during `generate-sources`, pinned by digest in `redline/wkg.lock`, so a fresh clone builds with a plain `mvn clean install`.

To work on the Rust side you do need Rust with the `wasm32-wasip1` target:

* `make -C redline/wasm-build all` builds `redline/cranelift_bridge.wasm` (gitignored)
* inlay skips the download whenever that file already exists, so your local build picks it up

That skip has a sharp edge: a **stale** `redline/cranelift_bridge.wasm` left over from an earlier `make all` silently shadows the pinned artifact, and you end up testing against a different bridge than CI. Delete the file to go back to the published one.

Publishing is handled by `.github/workflows/wasm-publish.yaml`. Pushes to `main` touching `redline/wasm-build/**` refresh the development snapshot; a manual dispatch can publish any semver tag. The tag must be valid semver — the `wkg.lock` format rejects `latest` — and the workflow validates that before pushing anything.

Which tag the build consumes is the `cranelift-bridge.version` property in the root `pom.xml`. Publishing does **not** update it, and does **not** refresh the lock file: until both are updated the build keeps resolving the previously pinned digest, and re-pushing an already-locked tag makes every build fail with a digest mismatch rather than silently drifting.

To adopt a published wasm (the workflow prints these in its job summary):

```bash
# 1. point the build at the tag that was published
./mvnw versions:set-property -Dproperty=cranelift-bridge.version \
-DnewVersion=<version> -DgenerateBackupPoms=false

# 2. re-pin the digest
./mvnw generate-sources -pl :redline-bridge-experimental -Dinlay.update

# 3. commit pom.xml and redline/wkg.lock together
```

**Releases handle this automatically.** `release.yaml` retags the digest currently pinned in `wkg.lock` as the release version, points the property at it, re-pins the lock, and commits both alongside the version bump — so every release has a matching immutable wasm artifact, byte-identical to the one CI tested. It retags rather than rebuilding, so the release needs no Rust toolchain. Afterwards it restores the snapshot property, and a guard refuses to deploy if the property still resolves to a `SNAPSHOT` tag.

### Proposals implementation

Our priority is to focus on implementing [proposals](https://github.com/WebAssembly/proposals) that are in the most advanced stages of development. While we wholeheartedly encourage and support explorations, we’ll be dedicating less time to early-stage proposals until we have more comprehensive support for those that are stabilized.
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@
<artifactId>redline-bridge-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-build-time-compiler-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-compiler-experimental</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ public String[] getVersion() {
"The indexes of functions that should be interpreted, separated by commas")
Set<Integer> interpretedFunctions;

@CommandLine.Option(
order = 7,
names = "--module-interface",
description =
"Fully qualified class name for which to generate _ModuleExports and"
+ " _ModuleImports wrappers")
String moduleInterface;

@Override
public void run() {
var config =
Expand All @@ -82,6 +90,7 @@ public void run() {
.withTargetWasmFolder(targetWasmFolder)
.withInterpreterFallback(interpreterFallback)
.withInterpretedFunctions(interpretedFunctions)
.withModuleInterface(moduleInterface)
.build();

var generator = new Generator(config);
Expand All @@ -90,13 +99,15 @@ public void run() {
var interpretedFunctions = generator.generateResources();
generator.generateMetaWasm(interpretedFunctions);
generator.generateSources();
if (moduleInterface != null && !moduleInterface.isEmpty()) {
generator.generateModuleInterface(moduleInterface);
}
} catch (IOException e) {
throw new CommandLine.PicocliException("Failed to execute the command", e);
}
}

public static void main(String[] args) {
int exitCode = new CommandLine(new Cli()).execute(args);
System.exit(exitCode);
System.exit(new CommandLine(new Cli()).execute(args));
}
}
Loading
Loading