Skip to content

Emit native armv7 fallback decision artifact from CI signals #10

Emit native armv7 fallback decision artifact from CI signals

Emit native armv7 fallback decision artifact from CI signals #10

name: armv7 cross-prebuild handoff
on:
workflow_dispatch:
inputs:
opencode_version:
description: OpenCode version tag (metadata/source-attempt)
required: true
default: "1.2.10"
bun_version:
description: Bun version tag (metadata/source-attempt)
required: true
default: "1.2.20"
push:
branches: [main, master]
paths:
- .github/workflows/prebuild-armv7.yml
- docs/ci-prebuild-armv7.md
- scripts/ci/build-bun-armv7.sh
- scripts/ci/build-bun-armv7-source.sh
- scripts/ci/build-opencode-armv7.sh
jobs:
armv7-cross-prebuild:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Prepare output directories
run: |
set -euo pipefail
mkdir -p out/assets out/logs out/pkgfile-template out/toolchain out/status
- name: Install cross toolchain and emulation
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y \
gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf \
binutils-arm-linux-gnueabihf libc6-dev-armhf-cross \
qemu-user-static file jq zip unzip curl ca-certificates \
nodejs npm python3
arm-linux-gnueabihf-gcc --version | head -n1 | tee out/toolchain/gcc-version.txt
qemu-arm-static --version | head -n1 | tee out/toolchain/qemu-version.txt
node --version | tee out/toolchain/node-version.txt
npm --version | tee out/toolchain/npm-version.txt
- name: Probe upstream release assets (evidence only)
env:
OPENCODE_VERSION: ${{ github.event.inputs.opencode_version || '1.2.10' }}
BUN_VERSION: ${{ github.event.inputs.bun_version || '1.2.20' }}
run: |
set -euo pipefail
curl -fsSL "https://api.github.com/repos/anomalyco/opencode/releases/tags/v${OPENCODE_VERSION}" -o out/logs/opencode-release.json || true
curl -fsSL "https://api.github.com/repos/oven-sh/bun/releases/tags/bun-v${BUN_VERSION}" -o out/logs/bun-release.json || true
python3 - <<'PY'
import json
from pathlib import Path
variants = ["armv7", "armhf", "arm32", "armv7l", "linux-arm"]
for name in ["opencode-release.json", "bun-release.json"]:
p = Path("out/logs") / name
if not p.exists():
continue
try:
data = json.loads(p.read_text())
except Exception:
continue
assets = [a.get("name", "") for a in data.get("assets", [])]
Path(f"out/logs/{name}.assets.txt").write_text("\n".join(assets) + "\n")
hits = [a for a in assets if any(v in a.lower() for v in variants)]
Path(f"out/logs/{name}.arm-candidates.txt").write_text("\n".join(hits) + "\n")
PY
- name: Install host bun for compile-target probing
run: |
set -euo pipefail
curl -fsSL https://bun.sh/install | bash
"$HOME/.bun/bin/bun" --version | tee out/toolchain/bun-host-version.txt
- name: Attempt bun armv7 binary build (source/probe)
env:
BUN_VERSION: ${{ github.event.inputs.bun_version || '1.2.20' }}
continue-on-error: true
run: |
set -euo pipefail
chmod +x scripts/ci/build-bun-armv7.sh
scripts/ci/build-bun-armv7.sh "$BUN_VERSION" out > out/logs/build-bun-armv7.log 2>&1
- name: Attempt bun armv7 source build (cross-first)
env:
BUN_VERSION: ${{ github.event.inputs.bun_version || '1.2.20' }}
continue-on-error: true
run: |
set -euo pipefail
chmod +x scripts/ci/build-bun-armv7-source.sh
scripts/ci/build-bun-armv7-source.sh "$BUN_VERSION" out > out/logs/build-bun-armv7-source.log 2>&1
- name: Attempt opencode armv7 binary build (compile/probe)
env:
OPENCODE_VERSION: ${{ github.event.inputs.opencode_version || '1.2.10' }}
continue-on-error: true
run: |
set -euo pipefail
chmod +x scripts/ci/build-opencode-armv7.sh
scripts/ci/build-opencode-armv7.sh "$OPENCODE_VERSION" out > out/logs/build-opencode-armv7.log 2>&1
- name: Summarize build attempt status
run: |
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
out = Path('out')
assets = sorted([str(p.relative_to(out)) for p in (out/'assets').rglob('*') if p.is_file()])
status = {
'bun_armv7_bin_present': any('bun' in p and 'armv7' in p for p in assets),
'opencode_armv7_bin_present': any('opencode' in p and 'armv7' in p for p in assets),
'bun_source_attempted': (out/'status'/'bun-source-build-status.json').exists(),
'assets': assets,
}
(out/'status'/'build-attempt-status.json').write_text(json.dumps(status, indent=2)+"\n")
print(json.dumps(status, indent=2))
PY
- name: Decide native armv7 fallback requirement
run: |
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
out = Path('out')
status_dir = out / 'status'
def read_json(name):
p = status_dir / name
if not p.exists():
return None
try:
return json.loads(p.read_text())
except Exception:
return None
build_attempt = read_json('build-attempt-status.json') or {}
bun_target = read_json('bun-target-support.json') or {}
bun_source = read_json('bun-source-build-status.json') or {}
opencode_pack = read_json('opencode-pack-status.json') or {}
bun_bin_present = bool(build_attempt.get('bun_armv7_bin_present'))
bun_target_supported = bool(bun_target.get('supported'))
bun_source_ok = bun_source.get('status') == 'success'
native_required = (not bun_bin_present) and (not bun_target_supported) and (not bun_source_ok)
reasons = []
if not bun_target_supported:
reasons.append('bun compile target bun-linux-armv7 unsupported in host bun build mode')
if bun_source.get('status') == 'failed':
reasons.append('bun source cross-build attempt failed to produce armv7-like binary')
if opencode_pack.get('status') == 'failed':
reasons.append('opencode package source unavailable for current flow')
decision = {
'native_required': native_required,
'next_path': 'native-armv7-runner' if native_required else 'continue-cross-build',
'reasons': reasons,
'signals': {
'bun_armv7_bin_present': bun_bin_present,
'bun_target_supported': bun_target_supported,
'bun_source_status': bun_source.get('status'),
'opencode_pack_status': opencode_pack.get('status'),
},
}
(status_dir / 'next-build-path.json').write_text(json.dumps(decision, indent=2) + "\n")
print(json.dumps(decision, indent=2))
PY
- name: Remove work directory from handoff artifact
run: |
set -euo pipefail
rm -rf out/work
- name: Write pkgfile templates (armv7 handoff)
run: |
set -euo pipefail
cat > out/pkgfile-template/PKGBUILD.template <<'P1'
pkgname=opencode
pkgver=__OPENCODE_VERSION__
pkgrel=1
arch=('armv7l')
source=('opencode-linux-armv7-__OPENCODE_VERSION__.tar.gz')
sha256sums=('__SHA256__')
P1
cat > out/pkgfile-template/debian-control.template <<'P2'
Package: opencode
Version: __OPENCODE_VERSION__
Architecture: armhf
Maintainer: Hope2333(幽零小喵) <u0catmiao@proton.me>
Description: OpenCode armv7 cross-prebuild handoff template for local Termux final packaging
P2
- name: Generate manifest
env:
OPENCODE_VERSION: ${{ github.event.inputs.opencode_version || '1.2.10' }}
BUN_VERSION: ${{ github.event.inputs.bun_version || '1.2.20' }}
run: |
set -euo pipefail
python3 - <<'PY'
import hashlib, json, os
from pathlib import Path
items = []
for p in sorted(Path('out').rglob('*')):
if p.is_file():
items.append({'path': str(p.relative_to('out')), 'size': p.stat().st_size, 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()})
manifest = {
'kind': 'cross-prebuild-handoff',
'target': 'linux-armv7',
'opencode_version': os.environ.get('OPENCODE_VERSION'),
'bun_version': os.environ.get('BUN_VERSION'),
'git_sha': os.environ.get('GITHUB_SHA', ''),
'contains_final_termux_runtime': False,
'goal': 'produce bun and opencode armv7l-linux bins',
'items': items,
}
Path('out/manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
PY
(cd out && find . -type f | sort | xargs sha256sum > checksums.txt)
- name: Upload armv7 prebuild handoff bundle
uses: actions/upload-artifact@v4
with:
name: armv7-cross-prebuild-handoff
path: out/
retention-days: 30