Skip to content

Commit 91e6535

Browse files
committed
Migrate build and deployment logic from GitLab CI to GitHub Actions
1 parent e15e76f commit 91e6535

9 files changed

Lines changed: 550 additions & 10 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Gradle + GraalVM setup
2+
description: >-
3+
Installs a GraalVM 21 JDK and
4+
configures the Gradle build with dependency caching.
5+
6+
runs:
7+
using: composite
8+
steps:
9+
# GraalVM (not just any JDK ): needed for :rell-base:testTruffle
10+
- name: Set up GraalVM 21
11+
uses: graalvm/setup-graalvm@v1
12+
with:
13+
java-version: "21"
14+
distribution: graalvm
15+
github-token: ${{ github.token }}
16+
17+
- name: Set up Gradle
18+
uses: gradle/actions/setup-gradle@v4
19+
with:
20+
cache-read-only: ${{ github.ref != 'refs/heads/dev' }}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Start Postgres
2+
description: >-
3+
Starts the PostgreSQL the test suite connects to (POSTCHAIN_DB_URL ->
4+
localhost:5432, user/pass/db = postchain) using the runner's native PostgreSQL
5+
service. A composite because GitHub Actions can't share `services:` blocks
6+
across jobs (no YAML anchors). Server version is whatever the runner image
7+
ships (Ubuntu 24.04 -> PostgreSQL 16.x).
8+
9+
runs:
10+
using: composite
11+
steps:
12+
- name: Start Postgres
13+
shell: bash
14+
run: |
15+
set -euo pipefail
16+
17+
# Runner images ship PostgreSQL preinstalled but stopped; install it if absent.
18+
if ! command -v pg_lsclusters >/dev/null || [ -z "$(pg_lsclusters -h 2>/dev/null)" ]; then
19+
sudo apt-get update -qq
20+
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq postgresql postgresql-client
21+
fi
22+
23+
sudo systemctl start postgresql
24+
25+
for _ in $(seq 1 30); do
26+
if pg_isready -h localhost -p 5432 -q; then break; fi
27+
sleep 1
28+
done
29+
pg_isready -h localhost -p 5432
30+
31+
# Superuser: the suite creates/drops per-test databases. Default pg_hba already allows
32+
# scram password auth from localhost, so TCP connections from JDBC work once it has a password.
33+
sudo -u postgres psql -v ON_ERROR_STOP=1 -c \
34+
"CREATE ROLE postchain WITH LOGIN SUPERUSER PASSWORD 'postchain';"
35+
sudo -u postgres createdb -O postchain postchain

.github/workflows/benchmarks.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Benchmarks (Pages)
2+
3+
# Manual run that publishes the kotlinx-benchmark HTML report (plus the raw main.json) to GitHub
4+
# Pages under a per-commit path so same-branch reruns coexist for commit-to-commit diffs.
5+
# https://chromiaproject.github.io/rell-performance-reports/bench-<branch-slug>-<sha>/report.html
6+
# https://chromiaproject.github.io/rell-performance-reports/bench-<branch-slug>-<sha>/data/main.json
7+
# Published to the dedicated ChromiaProject/rell-performance-reports repo; the index app there
8+
# discovers deployments client-side, so there's no aggregation step.
9+
on:
10+
workflow_dispatch:
11+
12+
# Serialize the deployment workflows: each force-pushes the reports repo's gh-pages, so concurrent
13+
# runs would clobber each other.
14+
concurrency:
15+
group: gh-pages
16+
cancel-in-progress: false
17+
18+
permissions:
19+
contents: write
20+
21+
env:
22+
# linux-arm64-medium (4 vCPU / 16 GiB). No JUnit suite runs here (kotlinx-benchmark forks its own
23+
# JVM), so the test-heap props are omitted: Gradle daemon 4g + Kotlin daemon 2g + the benchmark JVM
24+
# (default ergonomic heap ~4g) ≈ 10g, well within 16g.
25+
GRADLE_FLAGS: >-
26+
-Dorg.gradle.caching=true
27+
--max-workers=1
28+
-Dorg.gradle.jvmargs=-Xmx4g
29+
-Pkotlin.daemon.jvmargs=-Xmx2g
30+
31+
jobs:
32+
benchmarks:
33+
name: Run benchmarks + publish
34+
runs-on: linux-arm64-medium
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: ./.github/actions/gradle-setup
38+
39+
- name: Compute deployment prefix
40+
id: prefix
41+
run: |
42+
slug=$(echo "${GITHUB_REF_NAME}" | tr -c 'A-Za-z0-9' '-' | sed 's/-\+/-/g; s/^-//; s/-$//')
43+
echo "slug=$slug" >> "$GITHUB_OUTPUT"
44+
echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
45+
echo "dir=bench-${slug}-${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
46+
47+
- name: Run benchmarks
48+
run: ./gradlew $GRADLE_FLAGS :performance:mainBenchmark
49+
50+
- name: Assemble site
51+
env:
52+
DIR: ${{ steps.prefix.outputs.dir }}
53+
SLUG: ${{ steps.prefix.outputs.slug }}
54+
SHA: ${{ steps.prefix.outputs.sha }}
55+
run: |
56+
dest="site/$DIR"
57+
mkdir -p "$dest/data"
58+
cp -r performance/build/reports/benchmarks/html/. "$dest/"
59+
# Newest kotlinx-benchmark JSON -> stable data/main.json for programmatic ingestion.
60+
latest_json=$(find performance/build/reports/benchmarks/main -type f -name '*.json' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)
61+
if [ -z "$latest_json" ]; then
62+
echo "ERROR: no benchmark JSON found under performance/build/reports/benchmarks/main" >&2
63+
exit 1
64+
fi
65+
cp "$latest_json" "$dest/data/main.json"
66+
# meta.json drives the client-side index; index.html at the root is refreshed each run.
67+
jq -n --arg kind benchmarks --arg slug "$SLUG" --arg sha "$SHA" \
68+
--arg title "$(git log -1 --format=%s)" --arg ts "$(git log -1 --format=%cI)" \
69+
--argjson data true \
70+
'{kind:$kind, slug:$slug, sha:$sha, title:$title, ts:$ts, data:$data}' > "$dest/meta.json"
71+
cp performance/ci-index/index.html site/index.html
72+
73+
- name: Publish to reports repo
74+
uses: peaceiris/actions-gh-pages@v4
75+
with:
76+
deploy_key: ${{ secrets.RELL_REPORTS_DEPLOY_KEY }}
77+
external_repository: ChromiaProject/rell-performance-reports
78+
publish_dir: ./site
79+
keep_files: true
80+
commit_message: "benchmarks: ${{ steps.prefix.outputs.dir }}"

.github/workflows/build.yml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: Build
2+
3+
# `check` runs on every branch push (not just PRs) for ad-hoc debugging; publication is gated
4+
# to the protected branches (the `publish` job).
5+
on:
6+
push:
7+
branches: ["**"]
8+
pull_request:
9+
workflow_dispatch:
10+
inputs:
11+
publish_build_scan:
12+
description: "Publish a Develocity build scan (scans.gradle.com) for this run"
13+
type: boolean
14+
default: false
15+
16+
# Supersede in-flight runs of the same ref.
17+
concurrency:
18+
group: build-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
permissions:
22+
contents: read
23+
24+
env:
25+
# Heap budget on linux-arm64-xlarge (16 vCPU / 64 GiB). Concurrent test JVMs are capped at
26+
# --max-workers — each Test task is maxParallelForks=1 (one fork per task), and junitParallelThreads
27+
# is in-JVM ForkJoinPool parallelism, not extra JVMs — so the test footprint can't exceed 8 x 4g.
28+
# Peak: Gradle daemon 12g + Kotlin daemon 6g + 8 test forks x 4g = 50g heap (~60g resident with JVM
29+
# overhead), leaving room for native Postgres + OS. Native Postgres (no dockerd/DIND) on the 600 GB
30+
# SSD gives more margin than a tmpfs PGDATA would. If the peak ever OOMs, drop to --max-workers=7.
31+
GRADLE_FLAGS: >-
32+
-Dorg.gradle.caching=true
33+
--max-workers=8
34+
-Dorg.gradle.jvmargs=-Xmx12g
35+
-Pkotlin.daemon.jvmargs=-Xmx6g
36+
-PtestJvmMaxHeap=4g
37+
-PjunitParallelThreads=4
38+
POSTCHAIN_DB_URL: jdbc:postgresql://localhost:5432/postchain
39+
JACOCO_REPORT_DIR: coverage-report-aggregate/build/reports/jacoco/testCodeCoverageReport
40+
41+
jobs:
42+
build:
43+
name: Check + coverage
44+
# jacoco-report posts coverage as a PR comment (or a commit comment on push).
45+
permissions:
46+
contents: write
47+
pull-requests: write
48+
runs-on: linux-arm64-xlarge
49+
steps:
50+
- uses: actions/checkout@v4
51+
- uses: ./.github/actions/gradle-setup
52+
- uses: ./.github/actions/postgres
53+
54+
# The Develocity plugin is deliberately not committed (so local devs neither pull it nor have
55+
# its Terms of Use auto-accepted), and termsOfUseAgree can only be expressed from a DSL block —
56+
# hence appending it at runtime. Manual-dispatch only (the input is empty on push/PR).
57+
- name: Enable build scan
58+
if: github.event.inputs.publish_build_scan == 'true'
59+
run: |
60+
cat >> settings.gradle.kts <<'EOF'
61+
62+
plugins { id("com.gradle.develocity") version "4.3.2" }
63+
64+
develocity {
65+
buildScan {
66+
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
67+
termsOfUseAgree = "yes"
68+
uploadInBackground = false
69+
}
70+
}
71+
EOF
72+
73+
- name: Check + aggregate coverage
74+
run: ./gradlew $GRADLE_FLAGS check :coverage-report-aggregate:testCodeCoverageReport
75+
76+
- name: Coverage report
77+
if: always()
78+
uses: madrapps/jacoco-report@v1.7.2
79+
with:
80+
paths: ${{ env.JACOCO_REPORT_DIR }}/testCodeCoverageReport.xml
81+
token: ${{ github.token }}
82+
title: Coverage
83+
84+
- name: Upload test results
85+
if: always()
86+
uses: actions/upload-artifact@v4
87+
with:
88+
name: test-results
89+
path: |
90+
${{ env.JACOCO_REPORT_DIR }}/
91+
**/build/test-results/**/*
92+
retention-days: 7
93+
if-no-files-found: ignore
94+
95+
# Gated continuation of `build`: runs only after tests+coverage pass, and only on the protected
96+
# branches. Publishing to the GitLab Package Registry (gitlab.com project 32802097) compiles and
97+
# assembles the jars but runs no tests, so it needs neither `check` nor Postgres here.
98+
publish:
99+
name: Publish to GitLab Packages
100+
needs: build
101+
if: >-
102+
github.event_name == 'push' &&
103+
(github.ref == 'refs/heads/dev' ||
104+
github.ref == 'refs/heads/master' ||
105+
startsWith(github.ref, 'refs/heads/version-'))
106+
runs-on: linux-arm64-xlarge
107+
steps:
108+
- uses: actions/checkout@v4
109+
- uses: ./.github/actions/gradle-setup
110+
111+
# Auth via ORG_GRADLE_PROJECT_* (Gradle reads them as the gitlabAuthHeaderName/Value project
112+
# properties) rather than -P flags, so the token never lands on the command line.
113+
- name: Publish
114+
env:
115+
ORG_GRADLE_PROJECT_gitlabAuthHeaderName: Private-Token
116+
ORG_GRADLE_PROJECT_gitlabAuthHeaderValue: ${{ secrets.GITLAB_PUBLISH_TOKEN }}
117+
run: ./gradlew $GRADLE_FLAGS publish
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Dependency check
2+
3+
on:
4+
workflow_dispatch:
5+
6+
concurrency:
7+
group: dependency-check-${{ github.ref }}
8+
cancel-in-progress: true
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
dependency-check:
15+
name: OWASP dependency check
16+
runs-on: linux-arm64-medium
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: ./.github/actions/gradle-setup
20+
21+
# --no-parallel / --no-configuration-cache: dependencyCheckAggregate resolves every
22+
# subproject's configurations from the root project, which Gradle 9 rejects under
23+
# org.gradle.parallel=true.
24+
# Memory is not a constraint on linux-arm64-medium (16 GiB): no tests and no Postgres, just the
25+
# Gradle (4g) + Kotlin (2g) daemons.
26+
- name: Run dependencyCheckAggregate
27+
run: >-
28+
./gradlew -Dorg.gradle.caching=true --max-workers=1
29+
-Dorg.gradle.jvmargs=-Xmx4g -Pkotlin.daemon.jvmargs=-Xmx2g
30+
--no-configuration-cache --no-parallel dependencyCheckAggregate
31+
32+
- name: Upload report
33+
if: always()
34+
uses: actions/upload-artifact@v4
35+
with:
36+
name: dependency-check-report
37+
path: build/reports/dependency-check-report.*
38+
if-no-files-found: ignore

.github/workflows/profile.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Profile (Pages)
2+
3+
# Manual end-to-end profiler run (Postgres + chr node + async-profiler + workload + HTML),
4+
# published to GitHub Pages per commit.
5+
# https://chromiaproject.github.io/rell-performance-reports/profile-<branch-slug>-<sha>/report.html
6+
on:
7+
workflow_dispatch:
8+
9+
# Serialize the deployment workflows: each force-pushes the reports repo's gh-pages, so concurrent
10+
# runs would clobber each other.
11+
concurrency:
12+
group: gh-pages
13+
cancel-in-progress: false
14+
15+
permissions:
16+
contents: write
17+
18+
env:
19+
# linux-arm64-medium (4 vCPU / 16 GiB). No JUnit suite (the profiler drives a chr node), so the
20+
# test-heap props are omitted: Gradle 4g + Kotlin 2g + chr node JVM (~4g default) + native Postgres
21+
# ~1g ≈ 11g of 16g, and the build/compile and workload phases don't peak together.
22+
GRADLE_FLAGS: >-
23+
-Dorg.gradle.caching=true
24+
--max-workers=1
25+
-Dorg.gradle.jvmargs=-Xmx4g
26+
-Pkotlin.daemon.jvmargs=-Xmx2g
27+
POSTCHAIN_DB_URL: jdbc:postgresql://localhost:5432/postchain
28+
# `chr` reads its connection from CHR_DB_*; without these `chr node start` fails connection-refused.
29+
CHR_DB_URL: jdbc:postgresql://localhost:5432/postchain
30+
CHR_DB_USER: postchain
31+
CHR_DB_PASSWORD: postchain
32+
33+
jobs:
34+
profile:
35+
name: Profile + publish
36+
runs-on: linux-arm64-medium
37+
steps:
38+
- uses: actions/checkout@v4
39+
- uses: ./.github/actions/gradle-setup
40+
- uses: ./.github/actions/postgres
41+
42+
# Persist the local Maven repo the chr bootstrap (:performance:buildLocalChr) populates,
43+
# so chromia-cli deps survive across runs.
44+
- name: Cache local-chr Maven repo
45+
uses: actions/cache@v4
46+
with:
47+
path: ~/.m2/repository
48+
key: local-chr-m2-${{ github.ref_name }}-${{ github.sha }}
49+
restore-keys: |
50+
local-chr-m2-${{ github.ref_name }}-
51+
local-chr-m2-dev-
52+
53+
- name: Compute deployment prefix
54+
id: prefix
55+
run: |
56+
slug=$(echo "${GITHUB_REF_NAME}" | tr -c 'A-Za-z0-9' '-' | sed 's/-\+/-/g; s/^-//; s/-$//')
57+
echo "slug=$slug" >> "$GITHUB_OUTPUT"
58+
echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
59+
echo "dir=profile-${slug}-${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
60+
61+
- name: Run profiler
62+
run: ./gradlew $GRADLE_FLAGS :performance:profile
63+
64+
- name: Assemble site
65+
env:
66+
DIR: ${{ steps.prefix.outputs.dir }}
67+
SLUG: ${{ steps.prefix.outputs.slug }}
68+
SHA: ${{ steps.prefix.outputs.sha }}
69+
run: |
70+
dest="site/$DIR"
71+
mkdir -p "$dest"
72+
cp -r performance/reports/. "$dest/"
73+
# meta.json drives the client-side index; index.html at the root is refreshed each run.
74+
jq -n --arg kind profile --arg slug "$SLUG" --arg sha "$SHA" \
75+
--arg title "$(git log -1 --format=%s)" --arg ts "$(git log -1 --format=%cI)" \
76+
--argjson data true \
77+
'{kind:$kind, slug:$slug, sha:$sha, title:$title, ts:$ts, data:$data}' > "$dest/meta.json"
78+
cp performance/ci-index/index.html site/index.html
79+
80+
- name: Publish to reports repo
81+
uses: peaceiris/actions-gh-pages@v4
82+
with:
83+
deploy_key: ${{ secrets.RELL_REPORTS_DEPLOY_KEY }}
84+
external_repository: ChromiaProject/rell-performance-reports
85+
publish_dir: ./site
86+
keep_files: true
87+
commit_message: "profile: ${{ steps.prefix.outputs.dir }}"

0 commit comments

Comments
 (0)