Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 51 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,49 @@
name: Build OpenCentauri Image

# RATIONALE — Cache Architecture Change
#
# Problem:
# The previous workflow copied the entire downloads + sstate-cache into and
# out of the workspace on every build (cp -ar). This added ~9.5 GB of I/O
# per run, slowed build startup, and caused intermittent rust crate fetch
# failures when the copy raced with network timeouts.
#
# Solution:
# Set DL_DIR as a global environment variable pointing at the persistent
# cache volume (/data/cache). Downloads are always shared.
# SSTATE_DIR, CCACHE_TOP_DIR, and TMPDIR are set conditionally based on the
# use_cache input so release builds can run from a clean state while test
# builds reuse cached compilation and temporary state.
#
# Phase 2 (planned):
# - Enable ccache (INHERIT += "ccache", CCACHE_TOP_DIR env var)
# to cache C/C++ compilation independently of sstate.
# - Persist tmp/cache/ + tmp/stamps/ via env vars to speed up bitbake
# metadata parsing and task state tracking between runs.
#
# Why this is safe:
# - Single-tenant runner: only OpenCentauri/cosmos builds on this runner.
# - Persistent bind-mounted volume: /data/cache survives container restarts.
# - Identical host/container architecture: sstate is ABI-compatible.
# - No concurrency: GitHub Actions concurrency group prevents simultaneous
# jobs from corrupting shared cache.
#
# Tradeoffs:
# - Cache is live: failed builds may leave partial sstate entries. Bitbake
# handles this gracefully (stale sstate is ignored on hash mismatch).
# - No rollback: cache mutations are immediate. This is acceptable because
# sstate entries are content-addressed and immutable.
#
# References:
# - Bitbake DL_DIR: https://docs.yoctoproject.org/ref-manual/variables.html#term-DL_DIR
# - Bitbake SSTATE_DIR: https://docs.yoctoproject.org/ref-manual/variables.html#term-SSTATE_DIR

on:
workflow_call:
inputs:
use_cache:
type: boolean
default: true
write_back_cache:
type: boolean
default: false
upload_all_artifacts:
type: boolean
default: false
Expand All @@ -21,10 +56,6 @@ on:
description: 'Use cache'
type: boolean
default: true
write_back_cache:
description: 'Write back cache'
type: boolean
default: false
upload_all_artifacts:
description: 'Upload all artifacts'
type: boolean
Expand All @@ -35,26 +66,27 @@ on:
default: ''

env:
# Sims server cache dir
CACHE_DIR: /data/cache
# Always use persistent downloads directory to avoid re-fetching sources.
DL_DIR: /data/cache/downloads

jobs:
build:
runs-on: self-hosted
# Prevent concurrent jobs from writing to shared cache simultaneously.
concurrency:
group: cosmos-build-cache
cancel-in-progress: false

steps:
- name: Ensure cache directories exist
run: mkdir -p /data/cache/{downloads,sstate-cache,ccache,tmp}

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive

- name: Restore cache
if: ${{ inputs.use_cache }}
run: |
mkdir -p ./build
cp -ar ${{ env.CACHE_DIR }}/* ./build

- name: Set distro version
if: ${{ inputs.version != '' }}
run: |
Expand All @@ -63,6 +95,10 @@ jobs:
sed -i -E "s/^DISTRO_VERSION = \".*\"/DISTRO_VERSION = \"${version}\"/" meta-opencentauri/conf/distro/cosmos.conf

- name: Build image
env:
SSTATE_DIR: ${{ inputs.use_cache && '/data/cache/sstate-cache' || '' }}
CCACHE_TOP_DIR: ${{ inputs.use_cache && '/data/cache/ccache' || '' }}
TMPDIR: ${{ inputs.use_cache && '/data/cache/tmp' || '' }}
run: |
source poky/oe-init-build-env build
bitbake opencentauri-upgrade
Expand All @@ -80,13 +116,6 @@ jobs:
name: elegoo-centauri-carbon1-images
path: build/tmp/deploy/images/elegoo-centauri-carbon1/

- name: Write back cache
if: ${{ inputs.write_back_cache }}
run: |
rm -rf ${{ env.CACHE_DIR }}/*
cp -ar ./build/downloads ${{ env.CACHE_DIR }}
cp -ar ./build/sstate-cache ${{ env.CACHE_DIR }}

- name: Clean workspace
if: always()
run: git clean -ffdx
116 changes: 104 additions & 12 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Build OpenCentauri Image (on PR)

on:
pull_request:
types: [opened, synchronize, reopened, labeled]
issue_comment:
types: [created]

permissions:
contents: read
Expand All @@ -20,7 +24,9 @@ jobs:
AUTO_APPROVE: ${{ vars.AUTO_APPROVE }}
with:
script: |
const username = context.payload.pull_request.user.login;
const username = context.payload.pull_request?.user?.login
|| context.payload.issue?.user?.login
|| 'unknown';
const autoApproveUsers = (process.env.AUTO_APPROVE || '')
.split(',')
.map((value) => value.trim().toLowerCase())
Expand All @@ -31,7 +37,6 @@ jobs:
: 'approval-needed';

core.info(`Selected ${environment} for ${username}.`);

core.setOutput('environment', environment);

approve:
Expand All @@ -41,27 +46,114 @@ jobs:
steps:
- run: echo "Using environment ${{ needs.select-approval-environment.outputs.environment }}"

build:
needs: approve
# Resolve PR info for issue_comment triggers (comment payload lacks PR head/ref).
resolve-pr:
if: github.event_name == 'issue_comment'
runs-on: ubuntu-latest
outputs:
number: ${{ steps.pr.outputs.number }}
head_sha: ${{ steps.pr.outputs.head_sha }}
head_ref: ${{ steps.pr.outputs.head_ref }}
steps:
- name: Resolve PR from comment
id: pr
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
if (!issue.pull_request) {
core.setOutput('number', '');
return;
}
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: issue.number,
});
core.setOutput('number', pr.number.toString());
core.setOutput('head_sha', pr.head.sha);
core.setOutput('head_ref', pr.head.ref);

# Decide which build(s) to run based on the event.
decide-build-type:
needs: resolve-pr
if: always() && (github.event_name != 'issue_comment' || needs.resolve-pr.outputs.number != '')
runs-on: ubuntu-latest
outputs:
test_build: ${{ steps.decide.outputs.test_build }}
release_build: ${{ steps.decide.outputs.release_build }}
pr_number: ${{ steps.decide.outputs.pr_number }}
head_sha: ${{ steps.decide.outputs.head_sha }}
head_ref: ${{ steps.decide.outputs.head_ref }}
steps:
- name: Decide build type
id: decide
uses: actions/github-script@v7
with:
script: |
const eventName = context.eventName;
const action = context.payload.action;
const pr = context.payload.pull_request;
const resolvedNumber = '${{ needs.resolve-pr.outputs.number }}';
const resolvedSha = '${{ needs.resolve-pr.outputs.head_sha }}';
const resolvedRef = '${{ needs.resolve-pr.outputs.head_ref }}';

const labels = pr?.labels?.map(l => l.name) || [];
const body = context.payload.comment?.body?.trim() || '';

const isPrSync = eventName === 'pull_request' && ['opened', 'synchronize', 'reopened'].includes(action);
const isReleaseLabel = eventName === 'pull_request' && action === 'labeled' && context.payload.label?.name === 'release-build';
const isReleaseComment = eventName === 'issue_comment' && body === '/release-build';
const hasReleaseLabel = labels.includes('release-build');

// Auto test build on every PR push. If release-build label is present,
// run only the release build (not both).
const testBuild = isPrSync && !hasReleaseLabel;
const releaseBuild = isReleaseLabel || isReleaseComment || (isPrSync && hasReleaseLabel);
core.setOutput('test_build', testBuild.toString());
core.setOutput('release_build', releaseBuild.toString());
core.setOutput('pr_number', pr?.number?.toString() || resolvedNumber || '');
core.setOutput('head_sha', pr?.head?.sha || resolvedSha || '');
core.setOutput('head_ref', pr?.head?.ref || resolvedRef || '');
core.info(`test_build=${testBuild}, release_build=${releaseBuild}`);

test-build:
needs: [approve, decide-build-type]
if: ${{ needs.decide-build-type.outputs.test_build == 'true' }}
uses: ./.github/workflows/build.yml
with:
use_cache: true
write_back_cache: false
upload_all_artifacts: false
version: PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}
version: PR-${{ needs.decide-build-type.outputs.pr_number }}-${{ needs.decide-build-type.outputs.head_sha }}
concurrency:
group: pr-test-${{ needs.decide-build-type.outputs.pr_number }}
cancel-in-progress: true

release-build:
needs: [approve, decide-build-type]
if: ${{ needs.decide-build-type.outputs.release_build == 'true' }}
uses: ./.github/workflows/build.yml
with:
use_cache: false
upload_all_artifacts: true
version: PR-${{ needs.decide-build-type.outputs.pr_number }}-${{ needs.decide-build-type.outputs.head_sha }}
concurrency:
group: pr-release-${{ needs.decide-build-type.outputs.pr_number }}
cancel-in-progress: true

comment:
needs: build
if: always() && needs.build.result == 'success'
needs: [decide-build-type, test-build, release-build]
if: always() && (needs.test-build.result == 'success' || needs.release-build.result == 'success')
runs-on: ubuntu-latest
steps:
- name: Get workflow run info
id: run_info
uses: actions/github-script@v7
with:
script: |
const headSha = context.payload.pull_request.head.sha.substring(0, 7);
const branch = context.payload.pull_request.head.ref;
const prNumber = parseInt('${{ needs.decide-build-type.outputs.pr_number }}', 10);
const headSha = '${{ needs.decide-build-type.outputs.head_sha }}'.substring(0, 7);
const branch = '${{ needs.decide-build-type.outputs.head_ref }}';

const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
Expand All @@ -84,7 +176,7 @@ jobs:

const body = `## ✅ Build Artifacts\n\n` +
`**Branch:** \`${branch}\`\n` +
`**Build:** \`${headSha}\` (merge into \`${context.payload.pull_request.base.ref}\`)\n\n` +
`**Build:** \`${headSha}\`\n\n` +
`| Artifact | Size |\n` +
`| --- | --- |\n` +
artifactRows + `\n` +
Expand All @@ -96,5 +188,5 @@ jobs:
uses: peter-evans/create-or-update-comment@v4
continue-on-error: true
with:
issue-number: ${{ github.event.pull_request.number }}
issue-number: ${{ needs.decide-build-type.outputs.pr_number }}
body: ${{ steps.run_info.outputs.body }}
1 change: 0 additions & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ jobs:
uses: ./.github/workflows/build.yml
with:
use_cache: true
write_back_cache: true
upload_all_artifacts: false
version: ${{ github.sha }}

Expand Down
Loading