Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b752a6a
the container base is debian 13, and the sury suite follows it: the b…
blacktrs Sep 22, 2026
83f57d8
gate.sh asks for difftest and the fixpoint separately, and runs bare …
blacktrs Sep 22, 2026
2b63d5d
CI is armed, warm, and covers macOS; the nightly is a weekly parity g…
blacktrs Sep 22, 2026
b0fa00a
trixie has no software-properties-common, and llvm.sh wants wget: Deb…
blacktrs Sep 22, 2026
4cfe771
the compiler finds its own prelude and stdlib when it is INSTALLED, n…
blacktrs Sep 22, 2026
90f077f
Merge branch 'install-paths' into ci
blacktrs Sep 22, 2026
081fd30
the image splits into base/toolchain/build/runtime, and the gate runs…
blacktrs Sep 22, 2026
58ed70c
a release is an image on Docker Hub and per-platform tarballs, and in…
blacktrs Sep 22, 2026
32894f0
the macOS tarball gets bin/manticore as a FILE inside bin/, not as a …
blacktrs Sep 22, 2026
5546057
the release publishes to ghcr.io/manticorephp/compiler, not docker.io…
blacktrs Sep 22, 2026
cb211e8
musl is prepared, not gated: Dockerfile.alpine is the same four stage…
blacktrs Sep 22, 2026
385d19e
bin/build publishes lib/prelude, as bin/compile always has: the prelu…
blacktrs Sep 22, 2026
fcfa256
0.11.0, and the release tarball is built on debian 12 while developme…
blacktrs Sep 22, 2026
8128add
the compiler cache never updated on Linux, and said so only as 40 lin…
blacktrs Sep 22, 2026
b8e3aff
gate.yml can run the musl job alone: a dispatch that only wants to kn…
blacktrs Sep 22, 2026
0527061
the release step takes a release that already exists: the workflow fi…
blacktrs Sep 22, 2026
7a737e2
the release notes carry the changelog, built from commits and not fro…
blacktrs Sep 22, 2026
6105c7c
the install docs describe what is actually shipped: the Docker sectio…
blacktrs Sep 22, 2026
22de891
the musl image carries tzdata, libxml2 and libiconv, because the firs…
blacktrs Sep 22, 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
133 changes: 119 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# Per-push smoke: cold Zend seed + the whole AOT suite, on both Linux arches.
# Per-push: build the compiler and run the whole AOT suite, on both Linux
# arches and on macOS.
#
# The steps are NOT written here. They live in tools/docker/gate.sh, which is
# also what `bash tools/docker/run_tests.sh` runs locally — one definition, so a
# CI green and a local green mean the same thing. This file only supplies a
# machine, the image, and the environment.
# machine, the image, the cache and the environment.
#
# WARM, NOT COLD. Every job restores the compiler it built last time and
# self-hosts from it (`bin/build`), the way a developer does; the Zend cold seed
# is the fallback, not the loop. gate.sh validates the cache against
# arch + clang + php before trusting it, and a bootstrap gap (a MemoryAbi
# VERSION bump, new syntax) fails `bin/build` and falls through to the seed on
# its own — so a stale cache costs one slow run, never a wrong answer.
name: ci

# PARKED: manual only. The gate is built and lint-clean, but CI is not the priority
# right now — pushing it with `push`/`pull_request` triggers live would spend runner
# minutes on every commit for a signal nobody is reading yet. Re-arm by restoring the
# two triggers below; nothing else in this file changes.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
# push:
# branches: ['**']
# pull_request:

concurrency:
group: ci-${{ github.ref }}
Expand Down Expand Up @@ -51,26 +55,127 @@ jobs:
cache-from: type=gha,scope=toolchain-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=toolchain-${{ matrix.arch }}

# /logs is a mount the unprivileged container user must be able to write;
# /build stays inside the container (the gate copies the tree into it, and
# that copy has no business crossing a bind mount).
- name: Seed + suite
# ~15 MB: bin/manticore + lib/*.o + lib/prelude. The key is unique per run
# (a cache entry is immutable), so the restore-keys prefix is what actually
# hits — the newest entry for this arch and this image definition.
- name: Restore the compiler cache
uses: actions/cache/restore@v4
with:
path: ci-cache
key: mc-compiler-${{ matrix.arch }}-${{ hashFiles('Dockerfile') }}-${{ github.run_id }}
restore-keys: |
mc-compiler-${{ matrix.arch }}-${{ hashFiles('Dockerfile') }}-

# /logs and /compiler-cache are mounts the unprivileged container user
# (uid 1000) must be able to write; /build stays inside the container (the
# gate copies the tree into it, and that copy has no business crossing a
# bind mount).
- name: Build + suite
run: |
mkdir -p ci-logs && chmod 777 ci-logs
# -R, not just the top directory: actions/cache restores the tree as the
# RUNNER, and the container is uid 1000 — unlinking an entry needs write
# permission on the directory that holds it, so a restored lib/ is
# unremovable from inside without this and the cache never updates.
mkdir -p ci-logs ci-cache && chmod -R 777 ci-logs ci-cache
docker run --rm \
-v "$PWD":/repo:ro \
-v "$PWD/ci-logs":/logs \
-v "$PWD/ci-cache":/compiler-cache \
-e MC_GATE=0 \
-e MC_JOBS=0 \
-e MC_LOGDIR=/logs \
-e MC_COMPILER_CACHE=/compiler-cache \
-e MC_COMMIT="${GITHUB_SHA::12} ${GITHUB_REF_NAME}" \
manticore-toolchain:${{ matrix.arch }} \
/bin/bash /repo/tools/docker/gate.sh

# A red suite on top of a good build still leaves a compiler worth keeping —
# save before the job's result is decided, not after.
- name: Save the compiler cache
if: always() && hashFiles('ci-cache/bin/manticore') != ''
uses: actions/cache/save@v4
with:
path: ci-cache
key: mc-compiler-${{ matrix.arch }}-${{ hashFiles('Dockerfile') }}-${{ github.run_id }}

- name: Summary
if: always()
run: |
{
echo "## linux-${{ matrix.arch }} — \`${GITHUB_SHA::12}\`"
echo '```'
tail -15 ci-logs/suite.log 2>/dev/null || echo 'no suite log'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: logs-${{ matrix.arch }}
path: ci-logs/
if-no-files-found: warn

macos:
name: macos-arm64
runs-on: macos-15
timeout-minutes: 120
steps:
- uses: actions/checkout@v4

# Homebrew's `php` is the SEED interpreter and the difftest oracle. With a
# warm cache neither is touched — it is installed for the run where the
# cache misses, which is the only one that needs Zend.
- name: Toolchain
run: |
brew install php pcre2 openssl@3 sqlite
php -v
clang --version | head -1

- name: Restore the compiler cache
uses: actions/cache/restore@v4
with:
path: ci-cache
key: mc-compiler-macos-arm64-${{ github.run_id }}
restore-keys: |
mc-compiler-macos-arm64-

# The same gate.sh, run bare: it copies the checkout to a scratch tree
# (RUNNER_TEMP, never the checkout itself — the build writes bin/ and lib/
# into whatever it is pointed at) and does the same steps the container does.
- name: Build + suite
run: |
mkdir -p ci-logs ci-cache
MC_GATE=0 \
MC_JOBS=0 \
MC_REPO="$PWD" \
MC_WORK="$RUNNER_TEMP/build" \
MC_LOGDIR="$PWD/ci-logs" \
MC_COMPILER_CACHE="$PWD/ci-cache" \
MC_COMMIT="${GITHUB_SHA::12} ${GITHUB_REF_NAME}" \
bash tools/docker/gate.sh

- name: Save the compiler cache
if: always() && hashFiles('ci-cache/bin/manticore') != ''
uses: actions/cache/save@v4
with:
path: ci-cache
key: mc-compiler-macos-arm64-${{ github.run_id }}

- name: Summary
if: always()
run: |
{
echo "## macos-arm64 — \`${GITHUB_SHA::12}\`"
echo '```'
tail -15 ci-logs/suite.log 2>/dev/null || echo 'no suite log'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: logs-macos-arm64
path: ci-logs/
if-no-files-found: warn
158 changes: 158 additions & 0 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# The heavy answer: the AOT suite PLUS difftest (byte parity against the `php`
# interpreter), on both Linux arches. Weekly, and on demand.
#
# Why not nightly, and why no fixpoint by default: ci.yml already answers "is
# the suite green" on every push, and the self-host fixpoint answers a question
# only a bootstrap or a MemoryAbi change can re-open — it is hours, and the tree
# has been at a fixpoint for long enough that running it nightly buys nothing.
# Ask for it explicitly (the `fixpoint` input) after a bootstrap, an ABI VERSION
# bump, or a codegen change big enough to doubt gen2 == gen3.
#
# The steps live in tools/docker/gate.sh — never inline them here.
name: gate

on:
schedule:
- cron: '0 2 * * 0'
workflow_dispatch:
inputs:
parity:
description: 'run the glibc parity gate (suite + difftest) — off to test musl alone'
type: boolean
default: true
fixpoint:
description: 'also run tools/selfhost_fixpoint.sh (hours)'
type: boolean
default: false
alpine:
description: 'also run the musl (Alpine) build — prepared, not yet green'
type: boolean
default: false

concurrency:
group: gate
cancel-in-progress: false

jobs:
linux:
name: gate-${{ matrix.arch }}
# A schedule carries no inputs, so the weekly parity run must not depend on one.
if: github.event_name == 'schedule' || inputs.parity
runs-on: ${{ matrix.runner }}
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: ubuntu-24.04-arm
- arch: amd64
runner: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3

- name: Build the toolchain image
uses: docker/build-push-action@v6
with:
context: .
target: toolchain
tags: manticore-toolchain:${{ matrix.arch }}
load: true
cache-from: type=gha,scope=toolchain-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=toolchain-${{ matrix.arch }}

# Read-only: this job is a verdict on the tree, not a producer of
# compilers. ci.yml owns the cache, and a gate run writing it back would
# hand every later run a compiler built by a path nobody iterates on.
- name: Restore the compiler cache
uses: actions/cache/restore@v4
with:
path: ci-cache
key: mc-compiler-${{ matrix.arch }}-${{ hashFiles('Dockerfile') }}-

- name: Suite + difftest
run: |
# -R, not just the top directory: actions/cache restores the tree as the
# RUNNER, and the container is uid 1000 — unlinking an entry needs write
# permission on the directory that holds it, so a restored lib/ is
# unremovable from inside without this and the cache never updates.
mkdir -p ci-logs ci-cache && chmod -R 777 ci-logs ci-cache
docker run --rm \
-v "$PWD":/repo:ro \
-v "$PWD/ci-logs":/logs \
-v "$PWD/ci-cache":/compiler-cache \
-e MC_DIFFTEST=1 \
-e MC_FIXPOINT=${{ inputs.fixpoint && '1' || '0' }} \
-e MC_JOBS=0 \
-e MC_STABILITY_N=2 \
-e MC_LOGDIR=/logs \
-e MC_COMPILER_CACHE=/compiler-cache \
-e MC_COMMIT="${GITHUB_SHA::12} ${GITHUB_REF_NAME}" \
manticore-toolchain:${{ matrix.arch }} \
/bin/bash /repo/tools/docker/gate.sh

- name: Summary
if: always()
run: |
{
echo "## linux-${{ matrix.arch }} — \`${GITHUB_SHA::12}\`"
echo '```'
tail -15 ci-logs/suite.log 2>/dev/null || echo 'no suite log'
tail -8 ci-logs/difftest.log 2>/dev/null || true
tail -12 ci-logs/fixpoint.log 2>/dev/null || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: gate-logs-${{ matrix.arch }}
path: ci-logs/
if-no-files-found: warn

# musl, opt-in. It is here so that "does Alpine still build" is a button rather
# than an afternoon, and `continue-on-error` because docs/install.md has claimed
# musl works for longer than anything has checked it — the first runs are
# evidence-gathering, and they must not turn the parity gate red while they are.
alpine:
name: alpine-arm64 (musl, experimental)
if: inputs.alpine
runs-on: ubuntu-24.04-arm
continue-on-error: true
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3

- name: Build the musl toolchain image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.alpine
target: toolchain
tags: manticore-toolchain:alpine
load: true
cache-from: type=gha,scope=toolchain-alpine
cache-to: type=gha,mode=max,scope=toolchain-alpine

- name: Seed + suite
run: |
mkdir -p ci-logs && chmod 777 ci-logs
docker run --rm \
-v "$PWD":/repo:ro \
-v "$PWD/ci-logs":/logs \
-e MC_JOBS=0 \
-e MC_LOGDIR=/logs \
-e MC_COMMIT="${GITHUB_SHA::12} ${GITHUB_REF_NAME}" \
manticore-toolchain:alpine \
/bin/bash /repo/tools/docker/gate.sh

- name: Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: gate-logs-alpine
path: ci-logs/
if-no-files-found: warn
Loading
Loading