mirror of https://github.com/openclaw/openclaw.git
ci: speed up scoped workflow lanes
This commit is contained in:
parent
a423b1d936
commit
d17490ff54
|
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||||
|
|
@ -38,9 +38,8 @@ jobs:
|
||||||
id: check
|
id: check
|
||||||
uses: ./.github/actions/detect-docs-changes
|
uses: ./.github/actions/detect-docs-changes
|
||||||
|
|
||||||
# Detect which heavy areas are touched so PRs can skip unrelated expensive jobs.
|
# Detect which heavy areas are touched so CI can skip unrelated expensive jobs.
|
||||||
# Push to main keeps broad coverage, but this job still needs to run so
|
# Fail-safe: if detection fails, downstream jobs run.
|
||||||
# downstream jobs that list it in `needs` are not skipped.
|
|
||||||
changed-scope:
|
changed-scope:
|
||||||
needs: [docs-scope]
|
needs: [docs-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
|
|
@ -82,7 +81,7 @@ jobs:
|
||||||
# Build dist once for Node-relevant changes and share it with downstream jobs.
|
# Build dist once for Node-relevant changes and share it with downstream jobs.
|
||||||
build-artifacts:
|
build-artifacts:
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -141,7 +140,7 @@ jobs:
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
@ -149,6 +148,13 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
|
shard_index: 1
|
||||||
|
shard_count: 2
|
||||||
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
|
- runtime: node
|
||||||
|
task: test
|
||||||
|
shard_index: 2
|
||||||
|
shard_count: 2
|
||||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: extensions
|
task: extensions
|
||||||
|
|
@ -179,11 +185,18 @@ jobs:
|
||||||
|
|
||||||
- name: Configure Node test resources
|
- name: Configure Node test resources
|
||||||
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
|
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
|
||||||
|
env:
|
||||||
|
SHARD_COUNT: ${{ matrix.shard_count || '' }}
|
||||||
|
SHARD_INDEX: ${{ matrix.shard_index || '' }}
|
||||||
run: |
|
run: |
|
||||||
# `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes.
|
# `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes.
|
||||||
# Default heap limits have been too low on Linux CI (V8 OOM near 4GB).
|
# Default heap limits have been too low on Linux CI (V8 OOM near 4GB).
|
||||||
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
|
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
|
||||||
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
|
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
|
||||||
|
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
|
||||||
|
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
|
||||||
|
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||||
if: matrix.runtime != 'bun' || github.event_name != 'push'
|
if: matrix.runtime != 'bun' || github.event_name != 'push'
|
||||||
|
|
@ -193,7 +206,7 @@ jobs:
|
||||||
check:
|
check:
|
||||||
name: "check"
|
name: "check"
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -239,7 +252,7 @@ jobs:
|
||||||
compat-node22:
|
compat-node22:
|
||||||
name: "compat-node22"
|
name: "compat-node22"
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -272,7 +285,7 @@ jobs:
|
||||||
|
|
||||||
skills-python:
|
skills-python:
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true' || needs.changed-scope.outputs.run_skills_python == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_skills_python == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -365,7 +378,7 @@ jobs:
|
||||||
|
|
||||||
checks-windows:
|
checks-windows:
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_windows == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_windows == 'true'
|
||||||
runs-on: blacksmith-32vcpu-windows-2025
|
runs-on: blacksmith-32vcpu-windows-2025
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
env:
|
env:
|
||||||
|
|
@ -727,7 +740,7 @@ jobs:
|
||||||
|
|
||||||
android:
|
android:
|
||||||
needs: [docs-scope, changed-scope]
|
needs: [docs-scope, changed-scope]
|
||||||
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_android == 'true')
|
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_android == 'true'
|
||||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
|
||||||
36
docs/ci.md
36
docs/ci.md
|
|
@ -9,32 +9,32 @@ read_when:
|
||||||
|
|
||||||
# CI Pipeline
|
# CI Pipeline
|
||||||
|
|
||||||
The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only docs or native code changed.
|
The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only unrelated areas changed.
|
||||||
|
|
||||||
## Job Overview
|
## Job Overview
|
||||||
|
|
||||||
| Job | Purpose | When it runs |
|
| Job | Purpose | When it runs |
|
||||||
| ----------------- | ------------------------------------------------------- | ------------------------------------------------- |
|
| ----------------- | ------------------------------------------------------- | ---------------------------------- |
|
||||||
| `docs-scope` | Detect docs-only changes | Always |
|
| `docs-scope` | Detect docs-only changes | Always |
|
||||||
| `changed-scope` | Detect which areas changed (node/macos/android/windows) | Non-docs PRs |
|
| `changed-scope` | Detect which areas changed (node/macos/android/windows) | Non-doc changes |
|
||||||
| `check` | TypeScript types, lint, format | Push to `main`, or PRs with Node-relevant changes |
|
| `check` | TypeScript types, lint, format | Non-docs, node changes |
|
||||||
| `check-docs` | Markdown lint + broken link check | Docs changed |
|
| `check-docs` | Markdown lint + broken link check | Docs changed |
|
||||||
| `code-analysis` | LOC threshold check (1000 lines) | PRs only |
|
| `code-analysis` | LOC threshold check (1000 lines) | PRs only |
|
||||||
| `secrets` | Detect leaked secrets | Always |
|
| `secrets` | Detect leaked secrets | Always |
|
||||||
| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes |
|
| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes |
|
||||||
| `release-check` | Validate npm pack contents | After build |
|
| `release-check` | Validate npm pack contents | After build |
|
||||||
| `checks` | Node/Bun tests + protocol check | Non-docs, node changes |
|
| `checks` | Node/Bun tests + protocol check | Non-docs, node changes |
|
||||||
| `checks-windows` | Windows-specific tests | Non-docs, windows-relevant changes |
|
| `checks-windows` | Windows-specific tests | Non-docs, windows-relevant changes |
|
||||||
| `macos` | Swift lint/build/test + TS tests | PRs with macos changes |
|
| `macos` | Swift lint/build/test + TS tests | PRs with macos changes |
|
||||||
| `android` | Gradle build + tests | Non-docs, android changes |
|
| `android` | Gradle build + tests | Non-docs, android changes |
|
||||||
|
|
||||||
## Fail-Fast Order
|
## Fail-Fast Order
|
||||||
|
|
||||||
Jobs are ordered so cheap checks fail before expensive ones run:
|
Jobs are ordered so cheap checks fail before expensive ones run:
|
||||||
|
|
||||||
1. `docs-scope` + `code-analysis` + `check` (parallel, ~1-2 min)
|
1. `docs-scope` + `changed-scope` + `check` + `secrets` (parallel, cheap gates first)
|
||||||
2. `build-artifacts` (blocked on above)
|
2. `build-artifacts` + `release-check`
|
||||||
3. `checks`, `checks-windows`, `macos`, `android` (blocked on build)
|
3. `checks` (Linux Node test split into 2 shards), `checks-windows`, `macos`, `android`
|
||||||
|
|
||||||
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
|
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { appendFileSync } from "node:fs";
|
||||||
|
|
||||||
const DOCS_PATH_RE = /^(docs\/|.*\.mdx?$)/;
|
const DOCS_PATH_RE = /^(docs\/|.*\.mdx?$)/;
|
||||||
const SKILLS_PYTHON_SCOPE_RE = /^skills\//;
|
const SKILLS_PYTHON_SCOPE_RE = /^skills\//;
|
||||||
|
const CI_WORKFLOW_SCOPE_RE = /^\.github\/workflows\/ci\.yml$/;
|
||||||
const MACOS_PROTOCOL_GEN_RE =
|
const MACOS_PROTOCOL_GEN_RE =
|
||||||
/^(apps\/macos\/Sources\/OpenClawProtocol\/|apps\/shared\/OpenClawKit\/Sources\/OpenClawProtocol\/)/;
|
/^(apps\/macos\/Sources\/OpenClawProtocol\/|apps\/shared\/OpenClawKit\/Sources\/OpenClawProtocol\/)/;
|
||||||
const MACOS_NATIVE_RE = /^(apps\/macos\/|apps\/ios\/|apps\/shared\/|Swabble\/)/;
|
const MACOS_NATIVE_RE = /^(apps\/macos\/|apps\/ios\/|apps\/shared\/|Swabble\/)/;
|
||||||
|
|
@ -55,6 +56,12 @@ export function detectChangedScope(changedPaths) {
|
||||||
runSkillsPython = true;
|
runSkillsPython = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CI_WORKFLOW_SCOPE_RE.test(path)) {
|
||||||
|
runMacos = true;
|
||||||
|
runAndroid = true;
|
||||||
|
runSkillsPython = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!MACOS_PROTOCOL_GEN_RE.test(path) && MACOS_NATIVE_RE.test(path)) {
|
if (!MACOS_PROTOCOL_GEN_RE.test(path) && MACOS_NATIVE_RE.test(path)) {
|
||||||
runMacos = true;
|
runMacos = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,16 @@ describe("detectChangedScope", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("runs platform lanes when the CI workflow changes", () => {
|
||||||
|
expect(detectChangedScope([".github/workflows/ci.yml"])).toEqual({
|
||||||
|
runNode: true,
|
||||||
|
runMacos: true,
|
||||||
|
runAndroid: true,
|
||||||
|
runWindows: true,
|
||||||
|
runSkillsPython: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("treats base and head as literal git args", () => {
|
it("treats base and head as literal git args", () => {
|
||||||
const markerPath = path.join(
|
const markerPath = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue