Skip to content

Commit dfb20d0

Browse files
CI: Unify pipeline into single main.yml with composite actions and include config for production and testing.
2 parents 16be0e6 + afc446f commit dfb20d0

22 files changed

Lines changed: 888 additions & 869 deletions

File tree

.github/README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Edge Python CI/CD
2+
3+
One workflow, [`main.yml`](workflows/main.yml), drives the whole monorepo, so the Actions tab shows a single "CI / CD" run per push/PR with every job as a node in one graph. Each package's logic lives in a **composite action** under [`actions/`](actions); `main.yml` only wires the dependency graph. The composite actions are not workflows and do not appear in the Actions tab.
4+
5+
`compiler-check`, `runtime-lint` and `cli-lint` start at t=0. If any job fails the dependents never run (`needs:`), so a red build stops the deploys. `docs-build` runs on every event so a PR that breaks the docs is caught early; the deploy chain (`cdn → demo → docs-deploy`) is gated to `main` and pinned to the production branch. The `host` and `std` matrices use `fail-fast: false` so one capability / package failure still reports the others. `cli-lint` runs clippy + check once, then the heavy per-target `cli-release` build runs; `cli-test` waits on `host`, `std`, and the release artifacts.
6+
7+
## Composite actions
8+
9+
| Action | Inputs | Role |
10+
|--------|--------|------|
11+
| `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) |
12+
| `runtime` | `mode: lint\|test` | lint: `deno lint runtime/`. test: Deno + Playwright suite (Chromium driving `createWorker` against the CDN wasm) |
13+
| `host` | `capability` | Deno-lints and smoke-tests one capability (`dom`, `network`, `storage`, `time`) in headless Chromium. All JS, no release |
14+
| `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 |
15+
| `cli` | `mode: lint\|release\|test`, `target` | lint: `cargo clippy -D warnings` + `cargo check` (once). release: `cargo build --release` per target → tarball artifact. test: `cargo test` (drives a real Chromium) |
16+
| `demo` | CF token + account | Hashes deps into `version.json` (cache-busting), builds Tailwind, deploys `demo/` to `edge-python-demo` |
17+
| `docs` | `mode: build\|deploy`, CF token + account | build: `npm ci` + `next build` static export (`docs/out`, sitemap via `postbuild`), upload artifact. deploy: pull artifact + push to `edge-python-docs` |
18+
| `cdn-deploy` | CF token + account | Pulls every artifact, stages `./compiler ./runtime ./std ./host ./cli`, one `wrangler pages deploy` to `edge-python-cdn` |
19+
20+
## Cloudflare Pages
21+
22+
Three **Direct Upload** projects. Actions push prebuilt directories via `wrangler pages deploy`; Cloudflare doesn't clone or build.
23+
24+
| Project | Source | Production URL |
25+
|---------|--------|----------------|
26+
| `edge-python-cdn` | `_site/{compiler,runtime,std,host,cli}` (consolidates the old per-package `-runtime` / `-host` / `-std` projects) | `https://edge-python-cdn.pages.dev` |
27+
| `edge-python-demo` | `demo/` (wasm hashed for `version.json`, not bundled) | `https://edge-python-demo.pages.dev` |
28+
| `edge-python-docs` | `docs/out` (Nextra static export) | `https://edgepython.com` (custom domain; also `https://edge-python-docs.pages.dev`) |
29+
30+
All deploys run **only on pushes to `main`** and are pinned to the production `main` branch. PRs and tags never deploy; the next `main` push refreshes the projects.
31+
32+
### Cloudflare and GitHub setup
33+
34+
```bash
35+
# Wrangler CLI (Node 22+)
36+
npx wrangler login
37+
npx wrangler pages project create edge-python-cdn --production-branch=main
38+
npx wrangler pages project create edge-python-demo --production-branch=main
39+
npx wrangler pages project create edge-python-docs --production-branch=main
40+
```
41+
42+
`edge-python-docs` serves `edgepython.com` (replacing the old Mintlify docs): after
43+
the first deploy, add `edgepython.com` as a custom domain on the project
44+
(Pages -> Custom domains) and remove it from Mintlify.
45+
46+
Repo secrets (*Settings -> Secrets and variables -> Actions*):
47+
48+
- `CLOUDFLARE_API_TOKEN`, `Account -> Cloudflare Pages -> Edit`. Create via dashboard: <https://dash.cloudflare.com/profile/api-tokens>.
49+
- `CLOUDFLARE_ACCOUNT_ID`, from `npx wrangler whoami` or any dashboard sidebar.
50+
51+
Rotate: create new token -> update secret -> revoke old token.
52+
53+
## Releases
54+
55+
Pushing a `v*` tag runs the pipeline; the `compiler` build job uploads `compiler_lib.wasm` to the matching Release. Tag must match workspace version.
56+
57+
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.
58+
2. Tag and push:
59+
60+
```bash
61+
git tag v0.1.0
62+
git push origin v0.1.0
63+
```
64+
65+
On tag push: `compiler-check` lints, then the `compiler` build job optimizes the artifact and attaches it to a fresh Release with auto-generated notes. The CDN, demo and docs deploys do not run on tags; they already deployed from the preceding `main` push.
66+
67+
Nothing is published to crates.io, distribution is the `.wasm` on the Release. `starter-module` carries its own version and isn't bumped with the workspace.
68+
69+
Consumer crates pick up the release automatically: `compiler/Cargo.toml` declares `links = "compiler_lib"` and `compiler/build.rs` downloads `<repository>/releases/download/v<version>/compiler_lib.wasm` into `OUT_DIR`. Downstreams read `DEP_COMPILER_LIB_WASM` in their own `build.rs`, see [root README](../../README.md#consume-the-release-from-a-rust-host). Tag bumps flow via `cargo update`.
70+
71+
Gated behind the default-on `prebuilt` feature. Producer-side compiler steps pass `--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: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: CLI
2+
description: cli/ is its own workspace. lint = clippy + check; release = build per target; test = cargo test.
3+
4+
inputs:
5+
mode:
6+
description: "lint | 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 (lint)
17+
if: inputs.mode == 'lint'
18+
uses: dtolnay/rust-toolchain@stable
19+
with:
20+
components: clippy
21+
22+
- name: Cache Rust (lint)
23+
if: inputs.mode == 'lint'
24+
uses: Swatinem/rust-cache@v2
25+
with:
26+
workspaces: cli -> cli/target
27+
28+
- name: Clippy and check
29+
if: inputs.mode == 'lint'
30+
shell: bash
31+
working-directory: cli
32+
run: |
33+
cargo clippy --all-targets -- -D warnings
34+
cargo check
35+
36+
- name: Toolchain (release)
37+
if: inputs.mode == 'release'
38+
uses: dtolnay/rust-toolchain@stable
39+
with:
40+
targets: ${{ inputs.target }}
41+
42+
- name: Install musl tools
43+
if: inputs.mode == 'release' && contains(inputs.target, 'linux-musl')
44+
shell: bash
45+
run: sudo apt-get update && sudo apt-get install -y musl-tools
46+
47+
- name: Cache Rust (release)
48+
if: inputs.mode == 'release'
49+
uses: Swatinem/rust-cache@v2
50+
with:
51+
workspaces: cli -> cli/target
52+
key: ${{ inputs.target }}
53+
54+
- name: Build release
55+
if: inputs.mode == 'release'
56+
shell: bash
57+
working-directory: cli
58+
run: cargo build --release --target "${{ inputs.target }}"
59+
60+
- name: Tar
61+
if: inputs.mode == 'release'
62+
shell: bash
63+
working-directory: cli
64+
run: tar -C "target/${{ inputs.target }}/release" -czf "edge-${{ inputs.target }}.tar.gz" edge
65+
66+
- name: Upload release artifact
67+
if: inputs.mode == 'release'
68+
uses: actions/upload-artifact@v6
69+
with:
70+
name: edge-${{ inputs.target }}
71+
path: cli/edge-${{ inputs.target }}.tar.gz
72+
retention-days: 1
73+
74+
- name: Toolchain (test)
75+
if: inputs.mode == 'test'
76+
uses: dtolnay/rust-toolchain@stable
77+
78+
- name: Cache Rust (test)
79+
if: inputs.mode == 'test'
80+
uses: Swatinem/rust-cache@v2
81+
with:
82+
workspaces: cli -> cli/target
83+
84+
# Drives a real Chromium; ubuntu-latest ships google-chrome-stable.
85+
- name: Test
86+
if: inputs.mode == 'test'
87+
shell: bash
88+
working-directory: cli
89+
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)