Skip to content

W-23599683: CI layered composite actions (foundation + per-artifact) - #150

Merged
mlischetti merged 15 commits into
masterfrom
ci-composite-action
Jul 30, 2026
Merged

W-23599683: CI layered composite actions (foundation + per-artifact)#150
mlischetti merged 15 commits into
masterfrom
ci-composite-action

Conversation

@mlischetti

@mlischetti mlischetti commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Restructures CI into layered composite actions so adding a new product
artifact touches the workflows as little as possible and everything about one
artifact lives in one place. Two layers:

  • .github/actions/build-foundation — Gradle + GraalVM setup and the single
    gradlew build that compiles both native images (the dw CLI and the dwlib
    shared library).
  • One action per artifactcli, python, node, native-lib — each
    owning its full lifecycle (produce → optional test/tck → optional publish),
    with the phases selected by inputs.

ci.yml, main.yml, and release.yml now read
checkout → build-foundation → cli → python → node → native-lib: one call per
artifact. Follow-up to the runner-migration work (#148).

This evolves the earlier build-native monolith (the first form of this PR)
into per-artifact actions — build-native is removed; master never carried it.

Why

The roadmap adds Go/Rust bindings. With a single "generic" build action, each new
binding meant editing a shared block that already baked in Node- and
Python-specific setup. With per-artifact actions, adding Go is one new action
directory + one line per workflow that wants it
— no change to the foundation
or to any other artifact.

GitHub Actions constraint that decided the shape

A composite action is one directory / one action.yml / one callable unit — you
can't define produce-cli, upload-cli, … as separate sub-actions in one file.
A reusable workflow (workflow_call) runs as its own job on its own runner, so
it wouldn't see the native images the foundation compiled without uploading and
re-downloading them between jobs — breaking "build once, package many." So each
artifact is one composite action that performs all its phases, gated by
inputs, named by artifact (cli, not produce-cli).

How

Every artifact action shares an input convention:

Input Meaning
native-version -PnativeVersion value; empty omits the flag (Gradle default 100.100.100)
run-tck 'true' runs that artifact's master-only tck phase (cli, node only)
publish 'none' | 'artifact' | 'release' — selects the publish phase
repo-token / arch / script-name / distro-os / tag publish-only, per artifact
break-system-packages python only — PEP 668 --break-system-packages

Per-workflow composition:

Workflow Runners native-version run-tck publish break-system-packages
ci.yml self-hosted mulesoft (omit → default) (false) none (false)
main.yml ubuntu-latest / windows-2022 / macos-latest 100.100.100 ${{ github.ref == 'refs/heads/master' }} (cli, node) artifact true
release.yml same GitHub-hosted matrix ${{ env.NATIVE_VERSION }} (tag-derived) (false) release true
  • Publish now lives inside each artifact action: artifact =
    actions/upload-artifact@v7.0.1 (main's CI retention, incl. the OS-qualified
    staging/rename each artifact needs); release =
    svenstaro/upload-release-action@v2 (per-OS asset names, tag). The workflows no
    longer carry divergent upload tails.
  • Test/tck phases are asymmetric and preserved exactly: CLI regression
    (native-cli-integration-tests:test, ×2 suite versions, master-only);
    Node nodeTest always + master-only TCK conformance (×2); Python has
    no tck today.
  • dwlib (raw .so/.dll/.dylib + .h) is its own native-lib action
    (native-lib:stagePythonNativeLib + the multi-file / per-OS uploads), separate
    from the wheel/tgz that embed it.
  • Composite actions can't read secrets or the caller's matrix, so tokens,
    arch, script-name, and distro-os are inputs. checkout stays first;
    main.yml's Derive platform tokens and release.yml's Guess Extension Version run before build-foundation.

Design/plan:
docs/superpowers/specs/2026-07-30-ci-layered-actions-design.md.

Verification

Pure restructuring — effective Gradle commands, artifacts produced, their names,
and which tests run per trigger are identical to the pre-refactor state.

  • All five action files and three workflows parse; no build-native reference
    remains; each workflow composes build-foundation → cli → python → node → native-lib.
  • Behavioral equivalence audited against the merge-base baseline: version arg,
    --break-system-packages, master-only tck gating, and every
    upload-artifact / svenstaro asset name confirmed byte-identical.
  • The Build Native CLI (main.yml) PR run is the live cross-OS proof
    (ubuntu-latest / windows-2022 / macos-latest). ci.yml (self-hosted) and
    release.yml (tags) don't run on PRs; validated by parse + command inspection.

🤖 Generated with Claude Code

mlischetti and others added 5 commits July 30, 2026 10:06
Design for extracting the shared ~9-step build prefix (Gradle/GraalVM setup,
build, distro, Python wheel, Node package + tests) from ci.yml/main.yml/
release.yml into one .github/actions/build-native composite action, with
inputs for github-token, native-version, and break-system-packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mlischetti
mlischetti requested a review from a team as a code owner July 30, 2026 13:32
mlischetti and others added 10 commits July 30, 2026 11:31
Evolve the monolithic build-native action into build-foundation (shared
Gradle/GraalVM setup + build) plus one package-* action per artifact
(cli/python/node, extensible to go/rust). Moves the Python-only
--break-system-packages flag onto package-python where it belongs. Pure
restructuring — rendered commands stay identical to PR #150's green state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shape A: each artifact (cli/python/node, later go/rust) is one composite
action owning produce + optional tck + optional publish, gated by run-tck
and publish (none/artifact/release) inputs, named by artifact. Foundation
stays build-foundation. Adding an artifact = 1 new action dir + 1 line per
workflow. Records the GHA one-action-per-directory constraint, the verified
per-artifact test asymmetry (Python has no tck; CLI/Node do), and defers the
raw dwlib upload placement to the plan. Pure restructuring — behavior stays
identical to PR #150.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the build-native monolith and six svenstaro upload steps with
the composed action sequence: build-foundation → cli → python → node →
native-lib. Each artifact action now publishes to release assets via
publish: 'release'. Preserves Guess Extension Version step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mlischetti mlischetti changed the title W-23599683: CI extract build-native composite action W-23599683: CI layered composite actions (foundation + per-artifact) Jul 30, 2026
@mlischetti
mlischetti merged commit 3f01bb3 into master Jul 30, 2026
5 checks passed
@mlischetti
mlischetti deleted the ci-composite-action branch July 30, 2026 17:04
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.

2 participants