Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
279 changes: 279 additions & 0 deletions .github/workflows/build-payload.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
name: build-payload
# jruby feedstock: image (once, UNIVERSAL) → pair (per triplet) → (on
# tags) publish. spec 33's runtime-on-runtime form: the env image is the
# jruby home (bytecode + every platform's jffi stub — ONE universal
# asset), composed ON the published temurin java owner at dispatch.
#
# kind: runtime (spec 28/29/33): the image job ships the universal .tfs
# (+ sidecar + the release-tier manifest); each pair leg ships the
# wrapper exe (tebako-runtime-launcher, byte-identical to
# tamatebako/tebako's own asset) + sidecar + the per-triplet release
# shard (image.filename = the universal asset — entry_asset_names flows
# it verbatim, no product change). The pair legs' smokes: the spec 33 §5
# standalone fail-closed gate (exit 65) + the COMPOSED wire (shim → jruby
# → temurin owner) + the cext negative cell (a native-abi payload fails
# by name at dispatch — the shard's abi:"none" sentinel; TODO.jruby/02's
# acceptance). The publish job uploads all of it, derives the monoliths
# (manifest.json + SHA256SUMS.txt, trr#140) from the shards + sidecars,
# renders the registry entry from manifests/registry.yaml, and lands it
# by bot PR. `tebako publish` is NOT used: the CLI refuses kind: runtime.
#
# PIN DISCIPLINE (the owner's rule): this workflow carries NO version or
# digest literals. recipe.yml is the SSOT — the `tools:` block pins the
# tfs CLI + tebako-shim (release + per-platform sha256, cross-checked
# against the release's own SHA256SUMS), the `runtime:` block pins the
# wrapper's product line, flavors.jvm.owner_smoke pins the published
# temurin pair (two-anchor verified at fetch); tools/pins.rb reads them
# into $GITHUB_ENV.
on:
push:
branches: [main]
tags: ["*"]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
image:
# The UNIVERSAL env image, built once (the dist has no per-platform
# variants; mkimage is not byte-reproducible across legs — the
# created timestamp — so ONE leg's image is THE published asset).
name: image / universal
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read the toolchain pins (recipe.yml is the SSOT)
shell: bash
run: ruby tools/pins.rb linux-gnu-x86_64 jvm --env >> "$GITHUB_ENV"
- name: Fetch the imager (pinned by recipe.yml, sha256-verified)
shell: bash
env:
TFS_BIN: .packager/tfs
run: |
set -euo pipefail
mkdir -p .packager
base="https://github.com/tamatebako/tebako/releases/download/$TEBAKO_RELEASE"
curl -sL --fail -o "$TFS_BIN" "$base/$TFS_ASSET"
curl -sL --fail -o SHA256SUMS "$base/SHA256SUMS"
echo "$TFS_SHA256 $TFS_BIN" | sha256sum -c -
# The pin must agree with the release's own sums (both anchored).
got=$(awk -v a="$TFS_ASSET" '$2 == a {print $1}' SHA256SUMS)
[ "$got" = "$TFS_SHA256" ] || { echo "::error::pin mismatch for $TFS_ASSET: recipe.yml=$TFS_SHA256 release=${got:-ABSENT}"; exit 1; }
chmod +x "$TFS_BIN"
"$TFS_BIN" > /dev/null
- name: Build the universal env image
shell: bash
env:
ROLE: image
TFS_CLI: .packager/tfs
run: tools/build
- name: Upload the image artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: image-universal
path: out/image/
retention-days: 7

pair:
# Per triplet: the wrapper exe + sidecar + the per-triplet shard, and
# the smokes against THIS platform's published temurin owner.
name: pair / ${{ matrix.platform.triplet }}
runs-on: ${{ matrix.platform.os }}
needs: image
strategy:
fail-fast: false
matrix:
# Identity only. The matrix MIRRORS recipe.yml's
# flavors.jvm.platforms coverage (the recipe is the SSOT; the
# registry rows name the same list). windows-ucrt64 is a named
# follow-up (the universal image already serves it).
platform:
- triplet: aarch64-macos
asset_platform: macos-arm64
os: macos-14
- triplet: x86_64-linux-gnu
asset_platform: linux-gnu-x86_64
os: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read the toolchain pins (recipe.yml is the SSOT)
shell: bash
run: ruby tools/pins.rb "${{ matrix.platform.asset_platform }}" jvm --env >> "$GITHUB_ENV"
- name: Fetch the imager (pinned by recipe.yml, sha256-verified)
shell: bash
env:
TFS_BIN: .packager/tfs
run: |
set -euo pipefail
mkdir -p .packager
base="https://github.com/tamatebako/tebako/releases/download/$TEBAKO_RELEASE"
curl -sL --fail -o "$TFS_BIN" "$base/$TFS_ASSET"
curl -sL --fail -o SHA256SUMS "$base/SHA256SUMS"
echo "$TFS_SHA256 $TFS_BIN" | { sha256sum -c - 2>/dev/null || shasum -a 256 -c -; }
got=$(awk -v a="$TFS_ASSET" '$2 == a {print $1}' SHA256SUMS)
[ "$got" = "$TFS_SHA256" ] || { echo "::error::pin mismatch for $TFS_ASSET: recipe.yml=$TFS_SHA256 release=${got:-ABSENT}"; exit 1; }
chmod +x "$TFS_BIN"
"$TFS_BIN" > /dev/null
- name: Fetch the wrapper (pinned by recipe.yml)
shell: bash
env:
WRAPPER_BIN: .packager/tebako-runtime-launcher
run: |
set -euo pipefail
mkdir -p .packager
base="https://github.com/tamatebako/tebako/releases/download/$WRAPPER_RELEASE"
curl -sL --fail -o "$WRAPPER_BIN" "$base/$WRAPPER_ASSET"
echo "$WRAPPER_SHA256 $WRAPPER_BIN" | { sha256sum -c - 2>/dev/null || shasum -a 256 -c -; }
chmod +x "$WRAPPER_BIN"
echo "WRAPPER_BIN=$PWD/.packager/tebako-runtime-launcher" >> "$GITHUB_ENV"
# The dispatcher for the composed smoke (spec 33's real wire: shim
# → dep runtime → owner runtime). Same two-anchor rule as the
# imager.
- name: Fetch the dispatcher (tebako-shim, pinned by recipe.yml)
shell: bash
run: |
set -euo pipefail
mkdir -p .packager
base="https://github.com/tamatebako/tebako/releases/download/$TEBAKO_RELEASE"
curl -sL --fail -o .packager/tebako-shim "$base/$TEBAKO_SHIM_ASSET"
curl -sL --fail -o SHA256SUMS "$base/SHA256SUMS"
echo "$TEBAKO_SHIM_SHA256 .packager/tebako-shim" | { sha256sum -c - 2>/dev/null || shasum -a 256 -c -; }
got=$(awk -v a="$TEBAKO_SHIM_ASSET" '$2 == a {print $1}' SHA256SUMS)
[ "$got" = "$TEBAKO_SHIM_SHA256" ] || { echo "::error::pin mismatch for $TEBAKO_SHIM_ASSET: recipe.yml=$TEBAKO_SHIM_SHA256 release=${got:-ABSENT}"; exit 1; }
chmod +x .packager/tebako-shim
echo "SHIM_BIN=$PWD/.packager/tebako-shim" >> "$GITHUB_ENV"
# The image leg's universal image (the pair leg never rebuilds it —
# one image, one digest).
- name: Download the universal image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: image-universal
path: out/image/
- name: Pair the wrapper + run the smokes
shell: bash
env:
ROLE: pair
TRIPLET: ${{ matrix.platform.triplet }}
ASSET_PLATFORM: ${{ matrix.platform.asset_platform }}
TFS_CLI: .packager/tfs
IMAGE_TARBALL: out/image
# WRAPPER_BIN / SHIM_BIN flow through $GITHUB_ENV — process env,
# not the static env: context.
run: tools/build
- name: Upload payload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: payload-${{ matrix.platform.triplet }}
path: out/pair-${{ matrix.platform.triplet }}/
retention-days: 7

release:
name: publish
if: startsWith(github.ref, 'refs/tags/')
needs: [image, pair]
runs-on: macos-14
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Collect payloads (all legs)
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "*"
path: out/
merge-multiple: false
- name: Restore layout
shell: bash
run: |
set -e
for d in out/payload-*/; do
p="${d#out/payload-}"; p="${p%/}"
mkdir -p "out/pair-$p"
mv "$d"* "out/pair-$p/"
rmdir "$d"
done
# out/image-universal/ → out/image/
mkdir -p out/image
mv out/image-universal/* out/image/
rmdir out/image-universal
# The runtime release flow (spec 28 §6): the tag IS the tebako
# product line (v<line>, the trr convention — the resolver's
# {base}/v<tebako_version>/<asset> URL shape answers it). jruby has
# ONE flavor on ONE line — a tag ships everything here.
- name: Read the toolchain pins (recipe.yml is the SSOT)
run: ruby tools/pins.rb macos-arm64 jvm --env >> "$GITHUB_ENV"
- name: Create the release + upload the runtime pair assets
shell: bash
run: |
set -euo pipefail
tag="${{ github.ref_name }}"
gh release create "$tag" --repo tebako-packages/jruby \
--title "$tag — tebako-runtime jruby (JRuby $PKG_VERSION, universal image on the java owner)" \
--notes "tebako ruby:jruby runtime (spec 33 on_runtime): ONE universal env image + the per-triplet wrapper exe; manifest.json is the release index, SHA256SUMS.txt the trust anchor." \
|| gh release view "$tag" --repo tebako-packages/jruby > /dev/null
# The universal image + its sidecar (the image leg's artifact).
gh release upload --clobber "$tag" --repo tebako-packages/jruby \
"out/image/$RUNTIME_IMAGE_STEM.tfs" "out/image/$RUNTIME_IMAGE_STEM.tfs.sha256"
# Per-triplet: the exe + sidecar + shard (never the sibling
# payload-*.yaml).
for shard in out/pair-*/tebako-runtime-*.manifest.json; do
dir="$(dirname "$shard")"
stem="$(basename "$shard" .manifest.json)"
gh release upload --clobber "$tag" --repo tebako-packages/jruby "$dir/$stem" "$dir/$stem.sha256" "$shard"
done
- name: Derive + upload the monoliths (manifest.json + SHA256SUMS.txt)
shell: bash
run: |
set -euo pipefail
tag="${{ github.ref_name }}"
ruby -rjson -e 'puts JSON.pretty_generate(ARGV.sort.map { |f| JSON.parse(File.read(f)) })' \
out/pair-*/tebako-runtime-*.manifest.json > manifest.json
cat out/pair-*/tebako-runtime-*.sha256 "out/image/$RUNTIME_IMAGE_STEM.tfs.sha256" > SHA256SUMS.txt
gh release upload --clobber "$tag" --repo tebako-packages/jruby manifest.json SHA256SUMS.txt
# The registry mirrors resolution fields only (L3): the env image
# is the named artifact per platform — the SAME universal asset
# under every platform key (TODO.jruby/02 item 4's pinned
# consequence); kind: runtime carries NO entrypoints key.
- name: Render the registry entry
shell: bash
run: |
set -euo pipefail
tag="${{ github.ref_name }}"
img_sha="$(cut -d' ' -f1 "out/image/$RUNTIME_IMAGE_STEM.tfs.sha256")"
sed -e "s|@@VERSION@@|$PKG_VERSION|g" \
-e "s|@@IMAGE_STEM@@|$RUNTIME_IMAGE_STEM|g" \
-e "s|@@TAG@@|$tag|g" \
-e "s|@@IMAGE_SHA256@@|$img_sha|g" \
manifests/registry.yaml > tpkg-registry.yaml
# main is branch-protected (and the GitHub-Actions bypass is not
# available on this plan): the registry lands by bot PR + auto-merge,
# never by direct push. Idempotent across re-runs of the same tag.
- name: Publish the registry entry via pull request
run: |
set -euo pipefail
git config user.name "tebako-ci"
git config user.email "tebako@ribose.com"
git fetch origin main
if git diff --quiet origin/main -- tpkg-registry.yaml; then
echo "registry unchanged (or the entry already landed on main)"
exit 0
fi
branch="publish/${{ github.ref_name }}-registry"
git checkout -b "$branch"
git add tpkg-registry.yaml
git commit -m "publish ${{ github.ref_name }}: registry entry"
# force-with-lease on the bot branch: a re-publish re-creates it
git push --force-with-lease origin "$branch"
if ! gh pr view "$branch" >/dev/null 2>&1; then
gh pr create --base main --head "$branch" \
--title "publish ${{ github.ref_name }}: registry entry" \
--body "Automated registry entry for ${{ github.ref_name }} (the build-payload publish job; main is protected, so the registry lands by PR)."
fi
gh pr merge --auto --squash "$branch" || gh pr merge --squash "$branch"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
out/
20 changes: 20 additions & 0 deletions manifests/layout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Env-image layout declaration for the jruby runtime (spec 33 §5 — the
# depending runtime's env image declares NO interpreter: a STANDALONE boot
# of this pair fails closed by name; the runtime runs ON its java owner
# via the manifest's on_runtime composition). Grammar owner:
# docs/spec/schemas/layout.yaml in tamatebako/tebako (schema_minor 4); the
# writer here FLOWS the same owned values. Rendered by tools/build into
# /lib/tebako/layout.yaml inside the image.
schema_version: 1
era: 2
image_layout: 1
# The mount root the image was built for — must equal the wrapper exe's
# compiled-in WRAPPER_RUNTIME_ROOT (/__tfs__ on POSIX — every leg of this
# feedstock is POSIX; crates/tebako-driver/src/wrapper.rs). A mismatch is
# exit 78, never a broken boot.
mount_root: "@@MOUNT_ROOT@@"
interpreter_api_version: "@@API_VERSION@@"
# No interpreter key (spec 33 §5), no preload_shim grant — the dep image
# never execs a process of its own; the OWNER pair carries the preload
# binding for the composed process (the java runtime's own layout).
mount_root_override: false
37 changes: 37 additions & 0 deletions manifests/registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
schema_version: 1
# Registry template for the jruby RUNTIME entry — spec 33's
# runtime-on-runtime form, and the FIRST universal runtime image: the
# dist is bytecode + the ruby home with every platform's jffi stub
# (lib/jni/), so ONE asset serves all triplets. Every platform key below
# names the SAME universal artifact with the SAME digest — the registry
# grammar consequence TODO.jruby/02 item 4 pins: the triplet binding of
# a composed jruby comes from the java OWNER pair, never from this image.
# Rendered by the publish job (sed tokens from the built sidecars) and
# landed on main by bot PR (the CLI refuses kind: runtime — runtimes
# publish through the runtime release flow). kind: runtime entries carry
# NO entrypoints key (the registry validator: only apps and toolkits
# declare entrypoints). The on_runtime edge is NOT mirrored here (L3
# mirrors resolution fields only): it lives in the release shard (the
# shim's plan-time read) and the in-image manifest (the authority).
# Platform matrix: temurin owner coverage is the constraint
# (tebako-packages/openjdk v2.5.0 ships macos-arm64 + linux-gnu-x86_64 +
# windows-ucrt64). This first release declares the two POSIX rows the
# acceptance gate covers (TODO.jruby/02); windows-ucrt64 is a named
# follow-up (the universal image already serves it — the row lands with
# its CI leg, never silently).
payloads:
- name: jruby
kind: runtime
versions:
- version: '@@VERSION@@'
implementation: jruby
platforms:
aarch64-macos:
artifact: @@IMAGE_STEM@@.tfs
sha256: '@@IMAGE_SHA256@@'
x86_64-linux-gnu:
artifact: @@IMAGE_STEM@@.tfs
sha256: '@@IMAGE_SHA256@@'
release:
ref: tfs:github:tebako-packages/jruby:@@TAG@@
default: '@@VERSION@@'
Loading