Skip to content

Commit d2413d8

Browse files
CI: Unify pipeline into single main.yml with composite actions.
1 parent 16be0e6 commit d2413d8

21 files changed

Lines changed: 873 additions & 866 deletions

File tree

.github/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Edge Python CI/CD
2+
3+
One workflow, [`main.yml`](main.yml), drives the whole monorepo, so the Actions tab
4+
shows a single "CI / CD" run per push/PR with every job as a node in one graph. Each
5+
package's logic lives in a **composite action** under [`../actions/`](../actions);
6+
`main.yml` only wires the dependency graph. The composite actions are not workflows
7+
and do not appear in the Actions tab.
8+
9+
```
10+
compiler-check ┐
11+
runtime-lint ─┴─► compiler ─► runtime ─┬─► host (matrix) ─┐
12+
└─► std (matrix) ─┤
13+
cli-release (matrix, starts at t=0) ───────────────────────┴─► cli-test
14+
│ (push to main)
15+
cdn ◄── demo ◄── docs ◄───────────┘
16+
(docs builds on every run; deploys only on push to main)
17+
```
18+
19+
`compiler-check`, `runtime-lint` and `cli-release` start at t=0. If any job fails the
20+
dependents never run (`needs:`), so a red build stops the deploys, including the docs
21+
deploy. The
22+
`host` and `std` matrices use `fail-fast: false` so one capability / package failure
23+
still reports the others. `cli-release` is the slow heavy build, so it starts
24+
immediately and `cli-test` waits on `host`, `std`, and the release artifacts.
25+
26+
## Composite actions
27+
28+
| Action | Inputs | Role |
29+
|--------|--------|------|
30+
| `compiler` | `mode: check\|build` | check: `cargo shear` + clippy (host and wasm targets). build: build + optimize `compiler_lib.wasm`, test, upload the artifact (and attach it to the GitHub Release on tags) |
31+
| `runtime` | `mode: lint\|test` | lint: `deno lint runtime/`. test: Deno + Playwright suite (Chromium driving `createWorker` against the CDN wasm) |
32+
| `host` | `capability` | Deno-lints and smoke-tests one capability (`dom`, `network`, `storage`, `time`) in headless Chromium. All JS, no release |
33+
| `std` | `package` | Clippy + build + optimize + corpus test for one stdpkg (`json`, `re`, `math` as wasm; `test` is pure Edge Python, so it skips the wasm build and only runs the corpus). Stages `<pkg>.wasm` / `<pkg>.py`. No release |
34+
| `cli` | `mode: release\|test`, `target` | release: lint/check (once) + `cargo build --release` per target → tarball artifact. test: `cargo test` (drives a real Chromium) |
35+
| `demo` | CF token + account | Hashes deps into `version.json` (cache-busting), builds Tailwind, deploys `demo/` to `edge-python-demo` |
36+
| `docs` | `deploy`, CF token + account | `npm ci` + `next build` static export (`docs/out`, sitemap via `postbuild`). Deploys to `edge-python-docs` only when `deploy=true` |
37+
| `cdn-deploy` | CF token + account | Pulls every artifact, stages `./compiler ./runtime ./std ./host ./cli`, one `wrangler pages deploy` to `edge-python-cdn` |
38+
39+
## Cloudflare Pages
40+
41+
Three **Direct Upload** projects. Actions push prebuilt directories via
42+
`wrangler pages deploy`; Cloudflare doesn't clone or build.
43+
44+
| Project | Source | Production URL |
45+
|---------|--------|----------------|
46+
| `edge-python-cdn` | `_site/{compiler,runtime,std,host,cli}` (consolidates the old per-package `-runtime` / `-host` / `-std` projects) | `https://edge-python-cdn.pages.dev` |
47+
| `edge-python-demo` | `demo/` (wasm hashed for `version.json`, not bundled) | `https://edge-python-demo.pages.dev` |
48+
| `edge-python-docs` | `docs/out` (Nextra static export) | `https://edgepython.com` (custom domain; also `https://edge-python-docs.pages.dev`) |
49+
50+
All deploys run **only on pushes to `main`** and are pinned to the production `main`
51+
branch. PRs and tags never deploy; the next `main` push refreshes the projects.
52+
53+
### Cloudflare and GitHub setup
54+
55+
```bash
56+
# Wrangler CLI (Node 22+)
57+
npx wrangler login
58+
npx wrangler pages project create edge-python-cdn --production-branch=main
59+
npx wrangler pages project create edge-python-demo --production-branch=main
60+
npx wrangler pages project create edge-python-docs --production-branch=main
61+
```
62+
63+
`edge-python-docs` serves `edgepython.com` (replacing the old Mintlify docs): after
64+
the first deploy, add `edgepython.com` as a custom domain on the project
65+
(Pages -> Custom domains) and remove it from Mintlify.
66+
67+
Repo secrets (*Settings -> Secrets and variables -> Actions*):
68+
69+
- `CLOUDFLARE_API_TOKEN`, `Account -> Cloudflare Pages -> Edit`. Create via dashboard: <https://dash.cloudflare.com/profile/api-tokens>.
70+
- `CLOUDFLARE_ACCOUNT_ID`, from `npx wrangler whoami` or any dashboard sidebar.
71+
72+
Rotate: create new token -> update secret -> revoke old token.
73+
74+
## Releases
75+
76+
Pushing a `v*` tag runs the pipeline; the `compiler` build job uploads
77+
`compiler_lib.wasm` to the matching Release. Tag must match workspace version.
78+
79+
1. Bump `version` under `[workspace.package]` in root `Cargo.toml` (every crate inherits via `version.workspace = true`). Run `cargo check` to refresh `Cargo.lock`, commit.
80+
2. Tag and push:
81+
82+
```bash
83+
git tag v0.1.0
84+
git push origin v0.1.0
85+
```
86+
87+
On tag push: `compiler-check` lints, then the `compiler` build job optimizes the
88+
artifact and attaches it to a fresh Release with auto-generated notes. The CDN, demo
89+
and docs deploys do not run on tags; they already deployed from the preceding `main` push.
90+
91+
Nothing is published to crates.io, distribution is the `.wasm` on the Release.
92+
`starter-module` carries its own version and isn't bumped with the workspace.
93+
94+
Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares
95+
`links = "compiler_lib"` and `compiler/build.rs` downloads
96+
`<repository>/releases/download/v<version>/compiler_lib.wasm` into `OUT_DIR`.
97+
Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see
98+
[root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`.
99+
100+
Gated behind the default-on `prebuilt` feature. Producer-side compiler steps pass
101+
`--no-default-features` to avoid fetching the asset that this same pipeline uploads later.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: CDN deploy
2+
description: Stage every package output under one tree and deploy it to edge-python-cdn.
3+
4+
inputs:
5+
cloudflare-api-token:
6+
description: Cloudflare API token.
7+
required: true
8+
cloudflare-account-id:
9+
description: Cloudflare account id.
10+
required: true
11+
12+
runs:
13+
using: composite
14+
steps:
15+
- name: Download compiler wasm
16+
uses: actions/download-artifact@v8
17+
with:
18+
name: compiler_lib_wasm
19+
path: /tmp/compiler/
20+
21+
- name: Download std artifacts
22+
uses: actions/download-artifact@v8
23+
with:
24+
pattern: dist-*
25+
path: /tmp/std/
26+
merge-multiple: true
27+
28+
- name: Download cli artifacts
29+
uses: actions/download-artifact@v8
30+
with:
31+
pattern: edge-*
32+
path: /tmp/cli/
33+
merge-multiple: true
34+
35+
- name: Stage site
36+
shell: bash
37+
run: |
38+
mkdir -p _site/compiler _site/runtime _site/std _site/host _site/cli
39+
40+
# compiler: the wasm module.
41+
cp /tmp/compiler/compiler_lib.wasm _site/compiler/
42+
43+
# runtime: JS sources plus the wasm it bundles.
44+
cp -r runtime/. _site/runtime/
45+
cp /tmp/compiler/compiler_lib.wasm _site/runtime/
46+
47+
# std: <pkg>.wasm / <pkg>.py.
48+
cp -r /tmp/std/. _site/std/
49+
50+
# host: flatten each capability's src/ into _site/host/<cap>/.
51+
for dir in host/*/src; do
52+
cap="$(basename "$(dirname "$dir")")"
53+
mkdir -p "_site/host/$cap"
54+
cp -r "$dir"/. "_site/host/$cap/"
55+
done
56+
57+
# cli: release tarballs.
58+
cp -r /tmp/cli/. _site/cli/
59+
60+
- name: Deploy to Cloudflare Pages
61+
uses: cloudflare/wrangler-action@v4
62+
with:
63+
apiToken: ${{ inputs.cloudflare-api-token }}
64+
accountId: ${{ inputs.cloudflare-account-id }}
65+
command: pages deploy _site --project-name=edge-python-cdn --branch=main

.github/actions/cli/action.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: CLI
2+
description: cli/ is its own workspace. release = lint/check + build per target; test = cargo test.
3+
4+
inputs:
5+
mode:
6+
description: "release | test"
7+
required: true
8+
target:
9+
description: Rust target triple (release mode).
10+
required: false
11+
default: ""
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Toolchain (release)
17+
if: inputs.mode == 'release'
18+
uses: dtolnay/rust-toolchain@stable
19+
with:
20+
targets: ${{ inputs.target }}
21+
components: clippy
22+
23+
- name: Install musl tools
24+
if: inputs.mode == 'release' && contains(inputs.target, 'linux-musl')
25+
shell: bash
26+
run: sudo apt-get update && sudo apt-get install -y musl-tools
27+
28+
- name: Cache Rust (release)
29+
if: inputs.mode == 'release'
30+
uses: Swatinem/rust-cache@v2
31+
with:
32+
workspaces: cli -> cli/target
33+
key: ${{ inputs.target }}
34+
35+
# Lint + check once, anchored to the linux x86 target so the matrix doesn't repeat it.
36+
- name: Clippy and check
37+
if: inputs.mode == 'release' && inputs.target == 'x86_64-unknown-linux-musl'
38+
shell: bash
39+
working-directory: cli
40+
run: |
41+
cargo clippy --release --target "${{ inputs.target }}" -- -D warnings
42+
cargo check --release --target "${{ inputs.target }}"
43+
44+
- name: Build release
45+
if: inputs.mode == 'release'
46+
shell: bash
47+
working-directory: cli
48+
run: cargo build --release --target "${{ inputs.target }}"
49+
50+
- name: Tar
51+
if: inputs.mode == 'release'
52+
shell: bash
53+
working-directory: cli
54+
run: tar -C "target/${{ inputs.target }}/release" -czf "edge-${{ inputs.target }}.tar.gz" edge
55+
56+
- name: Upload release artifact
57+
if: inputs.mode == 'release'
58+
uses: actions/upload-artifact@v6
59+
with:
60+
name: edge-${{ inputs.target }}
61+
path: cli/edge-${{ inputs.target }}.tar.gz
62+
retention-days: 1
63+
64+
- name: Toolchain (test)
65+
if: inputs.mode == 'test'
66+
uses: dtolnay/rust-toolchain@stable
67+
68+
- name: Cache Rust (test)
69+
if: inputs.mode == 'test'
70+
uses: Swatinem/rust-cache@v2
71+
with:
72+
workspaces: cli -> cli/target
73+
74+
# Drives a real Chromium; ubuntu-latest ships google-chrome-stable.
75+
- name: Test
76+
if: inputs.mode == 'test'
77+
shell: bash
78+
working-directory: cli
79+
run: cargo test
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
name: Compiler
2+
description: Rust compiler crate. check = shear + clippy; build = wasm build/opt + test + upload.
3+
4+
inputs:
5+
mode:
6+
description: "check | build"
7+
required: true
8+
github-token:
9+
description: Token for the tag Release upload (build mode, tags only).
10+
required: false
11+
default: ""
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Cache Rust
17+
uses: Swatinem/rust-cache@v2
18+
19+
- name: Toolchain (clippy)
20+
if: inputs.mode == 'check'
21+
uses: dtolnay/rust-toolchain@stable
22+
with:
23+
components: clippy
24+
25+
- name: Install cargo-shear
26+
if: inputs.mode == 'check'
27+
shell: bash
28+
run: cargo install cargo-shear
29+
30+
# Detects declared but unused dependencies across the workspace.
31+
- name: Shear
32+
if: inputs.mode == 'check'
33+
shell: bash
34+
run: cargo shear
35+
36+
# --no-default-features skips the prebuilt wasm download in build.rs.
37+
- name: Clippy (host)
38+
if: inputs.mode == 'check'
39+
shell: bash
40+
run: cargo clippy --all-targets --no-default-features -- -D warnings
41+
42+
- name: Install wasm target (check)
43+
if: inputs.mode == 'check'
44+
shell: bash
45+
run: rustup target add wasm32-unknown-unknown
46+
47+
- name: Clippy (wasm)
48+
if: inputs.mode == 'check'
49+
shell: bash
50+
run: cargo clippy --lib --target wasm32-unknown-unknown -p edge-python -p slugify-mod -- -D warnings
51+
52+
# build-std needs nightly plus rust-src.
53+
- name: Toolchain (nightly)
54+
if: inputs.mode == 'build'
55+
uses: dtolnay/rust-toolchain@nightly
56+
with:
57+
components: rust-src
58+
59+
- name: Install wasm target (build)
60+
if: inputs.mode == 'build'
61+
shell: bash
62+
run: rustup target add wasm32-unknown-unknown
63+
64+
# apt ships an old binaryen, so fetch the upstream release.
65+
- name: Install wasm-opt
66+
if: inputs.mode == 'build'
67+
shell: bash
68+
run: |
69+
curl -sSL https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-linux.tar.gz \
70+
| tar -xz --strip-components=2 -C /usr/local/bin binaryen-version_121/bin/wasm-opt
71+
wasm-opt --version
72+
73+
- name: Build
74+
if: inputs.mode == 'build'
75+
shell: bash
76+
run: |
77+
RUSTFLAGS="-Z location-detail=none -Z fmt-debug=none -Z unstable-options -C panic=immediate-abort" \
78+
cargo +nightly build \
79+
--target wasm32-unknown-unknown \
80+
--lib \
81+
--release \
82+
-p edge-python \
83+
-Z build-std=std,panic_abort
84+
85+
- name: Size (unoptimized)
86+
if: inputs.mode == 'build'
87+
shell: bash
88+
run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm
89+
90+
# Two passes: -Oz with traps-never-happen, then reflatten for a fresh CFG.
91+
- name: Optimize
92+
if: inputs.mode == 'build'
93+
shell: bash
94+
run: |
95+
INPUT=target/wasm32-unknown-unknown/release/compiler_lib.wasm
96+
97+
wasm-opt -Oz --converge \
98+
--generate-global-effects \
99+
--strip-debug --strip-producers \
100+
--enable-bulk-memory-opt \
101+
--enable-nontrapping-float-to-int \
102+
--enable-sign-ext \
103+
-tnh \
104+
-o /tmp/wasm_stage1.wasm "$INPUT"
105+
106+
wasm-opt --flatten --rereloop -Oz -Oz \
107+
--enable-bulk-memory-opt \
108+
--enable-nontrapping-float-to-int \
109+
--enable-sign-ext \
110+
-o "$INPUT" /tmp/wasm_stage1.wasm
111+
112+
rm /tmp/wasm_stage1.wasm
113+
114+
- name: Size (optimized)
115+
if: inputs.mode == 'build'
116+
shell: bash
117+
run: ls -lh target/wasm32-unknown-unknown/release/compiler_lib.wasm
118+
119+
- name: Test
120+
if: inputs.mode == 'build'
121+
shell: bash
122+
run: cargo test -p edge-python --no-default-features
123+
124+
- name: Upload wasm artifact
125+
if: inputs.mode == 'build'
126+
uses: actions/upload-artifact@v6
127+
with:
128+
name: compiler_lib_wasm
129+
path: target/wasm32-unknown-unknown/release/compiler_lib.wasm
130+
retention-days: 1
131+
132+
- name: Upload WASM Release
133+
if: inputs.mode == 'build' && startsWith(github.ref, 'refs/tags/')
134+
uses: softprops/action-gh-release@v2
135+
with:
136+
token: ${{ inputs.github-token }}
137+
files: target/wasm32-unknown-unknown/release/compiler_lib.wasm
138+
fail_on_unmatched_files: true
139+
generate_release_notes: true

0 commit comments

Comments
 (0)