|
| 1 | +name: ci-harmonyos |
| 2 | + |
| 3 | +# mcpp → HarmonyOS / OpenHarmony (aarch64-linux-ohos), verified end to end. |
| 4 | +# |
| 5 | +# ── What this proves, and why it needs its own workflow ──────────────────── |
| 6 | +# |
| 7 | +# cross-build-test.yml is the home of "which cross targets does mcpp support", |
| 8 | +# and it carries this comment: |
| 9 | +# |
| 10 | +# * llvm/clang cross : clang is inherently a cross-compiler, but mcpp does |
| 11 | +# not yet inject `-target <triple>` + a cross sysroot |
| 12 | +# for a clang toolchain; cross `--target` resolves to |
| 13 | +# gcc musl only. Wire the clang cross path first, then |
| 14 | +# add a row. |
| 15 | +# |
| 16 | +# HarmonyOS is the target that forces that row to exist: GCC has no `ohos` |
| 17 | +# target at all, so the gcc-musl shape mcpp's other cross rows use cannot be |
| 18 | +# spelled here. This workflow is deliberately SEPARATE from |
| 19 | +# cross-build-test.yml for one reason — it depends on a ~2.5 GB vendor SDK |
| 20 | +# that mcpp does not and cannot ship, so a failure here must never be |
| 21 | +# confusable with a failure of mcpp's own cross matrix. |
| 22 | +# |
| 23 | +# ── The two tiers ───────────────────────────────────────────────────────── |
| 24 | +# |
| 25 | +# tier compiler C++ stdlib import std |
| 26 | +# ------------ ---------------- ------------------------- ---------- |
| 27 | +# stock SDK mcpp's llvm@20 SDK's libc++ 15.0.4 no |
| 28 | +# + overlay mcpp's llvm@20 libc++ built for the yes |
| 29 | +# target from LLVM sources |
| 30 | +# |
| 31 | +# Both are built and RUN under qemu-aarch64. The second job builds the overlay |
| 32 | +# from source in ~10 minutes, which is expensive — but it is the only way to |
| 33 | +# show that the "no import std" limit is a missing PAYLOAD, not a missing |
| 34 | +# capability, and that distinction is the whole argument of |
| 35 | +# .agents/docs/2026-08-04-harmonyos-target-design.md. |
| 36 | +# |
| 37 | +# ── What is NOT proven here ─────────────────────────────────────────────── |
| 38 | +# |
| 39 | +# qemu-user runs the artefact's instructions, not HarmonyOS. It says nothing |
| 40 | +# about the .hnp/.hap packaging path, about linking the platform's own NDK |
| 41 | +# libraries (libace_napi.z.so and friends), or about anything that touches |
| 42 | +# a real device. Those need hardware or the emulator and stay out of scope — |
| 43 | +# see the design doc's "what CI can and cannot show" section. Verification |
| 44 | +# here is the same claim the aarch64-linux-musl row makes: this artefact is |
| 45 | +# for the right machine and it really executes. |
| 46 | + |
| 47 | +on: |
| 48 | + pull_request: |
| 49 | + branches: [ main ] |
| 50 | + paths: |
| 51 | + - 'src/toolchain/**' |
| 52 | + - 'src/build/**' |
| 53 | + - 'tests/e2e/10[34]_harmonyos*' |
| 54 | + - 'tests/unit/test_ohos_target.cpp' |
| 55 | + - 'examples/05-harmonyos/**' |
| 56 | + - '.github/workflows/ci-harmonyos.yml' |
| 57 | + workflow_dispatch: |
| 58 | + |
| 59 | +concurrency: |
| 60 | + group: ci-${{ github.workflow }}-${{ github.ref }} |
| 61 | + cancel-in-progress: true |
| 62 | + |
| 63 | +jobs: |
| 64 | + # ── Tier 1: stock SDK, named modules ────────────────────────────────────── |
| 65 | + harmonyos-cross: |
| 66 | + name: HarmonyOS cross-build + qemu run (stock SDK) |
| 67 | + runs-on: ubuntu-24.04 |
| 68 | + timeout-minutes: 60 |
| 69 | + env: |
| 70 | + MCPP_HOME: /home/runner/.mcpp |
| 71 | + MCPP_VERBOSE: "1" |
| 72 | + steps: |
| 73 | + - uses: actions/checkout@v4 |
| 74 | + |
| 75 | + # Do NOT restore target/ in a cross job: this job builds twice (host, |
| 76 | + # then aarch64-linux-ohos) and a restored BMI tree makes the second |
| 77 | + # build read `std` BMIs that no longer match what the rest was compiled |
| 78 | + # against — `import 'std' has CRC mismatch`. Same rule as |
| 79 | + # windows-host-linux-cross in cross-build-test.yml, same reason. |
| 80 | + - uses: ./.github/actions/bootstrap-mcpp |
| 81 | + with: |
| 82 | + cache-target: 'false' |
| 83 | + |
| 84 | + - name: Install qemu-user-static |
| 85 | + run: | |
| 86 | + sudo apt-get update -qq |
| 87 | + sudo apt-get install -y qemu-user-static |
| 88 | + qemu-aarch64-static --version | head -1 |
| 89 | +
|
| 90 | + - name: Setup OpenHarmony SDK |
| 91 | + id: ohos |
| 92 | + uses: openharmony-rs/setup-ohos-sdk@v1.0.1 |
| 93 | + with: |
| 94 | + version: '6.1' |
| 95 | + components: 'native' |
| 96 | + |
| 97 | + - name: Point mcpp at the SDK |
| 98 | + run: | |
| 99 | + NATIVE="${{ steps.ohos.outputs.ohos_sdk_native }}" |
| 100 | + test -d "$NATIVE" || { echo "FAIL: action produced no native dir"; exit 1; } |
| 101 | + # The two files mcpp's own detection requires; asserted here so a |
| 102 | + # layout change in the action fails with a clear message instead of |
| 103 | + # inside a compile command 10 minutes later. |
| 104 | + test -f "$NATIVE/sysroot/usr/include/stdlib.h" |
| 105 | + test -d "$NATIVE/llvm/lib/aarch64-linux-ohos" |
| 106 | + echo "OHOS_NDK_HOME=$NATIVE" >> "$GITHUB_ENV" |
| 107 | + echo "== SDK ==" |
| 108 | + cat "$NATIVE/oh-uni-package.json" || true |
| 109 | + # Recorded, not used: this is the fact the whole design rests on. |
| 110 | + # If a future SDK ships a modern clang, this line is where it shows. |
| 111 | + echo "== the SDK's own clang (mcpp does NOT use it) ==" |
| 112 | + "$NATIVE/llvm/bin/clang++" --version | head -1 |
| 113 | +
|
| 114 | + - name: Build mcpp from source (self-host) |
| 115 | + run: | |
| 116 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 117 | + "$MCPP" self config --mirror GLOBAL 2>/dev/null || true |
| 118 | + "$MCPP" build |
| 119 | + # Newest, not first: target/ keeps a directory per build fingerprint, |
| 120 | + # so `find | head -1` can hand back a previous build's binary. |
| 121 | + MCPP_SELF=$(find target -type f -name mcpp -path '*/bin/*' \ |
| 122 | + -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2-) |
| 123 | + test -x "$MCPP_SELF" |
| 124 | + MCPP_SELF=$(realpath "$MCPP_SELF") |
| 125 | + "$MCPP_SELF" --version |
| 126 | + echo "MCPP=$MCPP_SELF" >> "$GITHUB_ENV" |
| 127 | +
|
| 128 | + - name: "Target is listed as available once the SDK is present" |
| 129 | + run: | |
| 130 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 131 | + "$MCPP" toolchain list | tee /tmp/tclist.txt |
| 132 | + # `available`, not `planned`: host_can_serve() answers this by |
| 133 | + # probing for the SDK, so this asserts the detection wired up — the |
| 134 | + # same row reads `planned` on a runner without the SDK. |
| 135 | + grep -q "aarch64-linux-ohos" /tmp/tclist.txt \ |
| 136 | + || { echo "FAIL: ohos target not listed"; exit 1; } |
| 137 | +
|
| 138 | + - name: "e2e: cross-build + qemu run" |
| 139 | + run: | |
| 140 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 141 | + bash tests/e2e/103_harmonyos_cross_qemu.sh |
| 142 | +
|
| 143 | + - name: "Example project builds for HarmonyOS" |
| 144 | + run: | |
| 145 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 146 | + cd examples/05-harmonyos |
| 147 | + "$MCPP" build --target aarch64-linux-ohos |
| 148 | + BIN=$(find target/aarch64-linux-ohos -type f -path '*/bin/*' | head -1) |
| 149 | + file "$BIN" |
| 150 | + file "$BIN" | grep -q "ARM aarch64" |
| 151 | + qemu-aarch64-static "$BIN" |
| 152 | +
|
| 153 | + # ── Tier 2: + a libc++ built for the target ⇒ import std ────────────────── |
| 154 | + harmonyos-import-std: |
| 155 | + name: HarmonyOS import std (libc++ built for the target) |
| 156 | + runs-on: ubuntu-24.04 |
| 157 | + timeout-minutes: 90 |
| 158 | + env: |
| 159 | + MCPP_HOME: /home/runner/.mcpp |
| 160 | + MCPP_VERBOSE: "1" |
| 161 | + # Must match the LLVM the target pin resolves to |
| 162 | + # (triple::pins::kOhosLlvm). A libc++ built by one clang and used by |
| 163 | + # another is a version skew that works until it does not; keeping the |
| 164 | + # two equal is what makes this job evidence rather than anecdote. |
| 165 | + LLVM_TAG: llvmorg-20.1.7 |
| 166 | + steps: |
| 167 | + - uses: actions/checkout@v4 |
| 168 | + - uses: ./.github/actions/bootstrap-mcpp |
| 169 | + with: |
| 170 | + cache-target: 'false' |
| 171 | + |
| 172 | + - name: Install qemu-user-static + build tools |
| 173 | + run: | |
| 174 | + sudo apt-get update -qq |
| 175 | + sudo apt-get install -y qemu-user-static cmake ninja-build |
| 176 | + qemu-aarch64-static --version | head -1 |
| 177 | +
|
| 178 | + - uses: openharmony-rs/setup-ohos-sdk@v1.0.1 |
| 179 | + id: ohos |
| 180 | + with: |
| 181 | + version: '6.1' |
| 182 | + components: 'native' |
| 183 | + |
| 184 | + - name: Build mcpp from source (self-host) |
| 185 | + run: | |
| 186 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 187 | + echo "OHOS_NDK_HOME=${{ steps.ohos.outputs.ohos_sdk_native }}" >> "$GITHUB_ENV" |
| 188 | + "$MCPP" self config --mirror GLOBAL 2>/dev/null || true |
| 189 | + "$MCPP" build |
| 190 | + MCPP_SELF=$(find target -type f -name mcpp -path '*/bin/*' \ |
| 191 | + -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2-) |
| 192 | + MCPP_SELF=$(realpath "$MCPP_SELF") |
| 193 | + "$MCPP_SELF" --version |
| 194 | + echo "MCPP=$MCPP_SELF" >> "$GITHUB_ENV" |
| 195 | +
|
| 196 | + - name: Locate mcpp's LLVM payload |
| 197 | + run: | |
| 198 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 199 | + # Installing it explicitly rather than letting the first ohos build |
| 200 | + # do it: the runtimes build below needs the same clang, and taking |
| 201 | + # it from anywhere else would silently reintroduce the skew the |
| 202 | + # LLVM_TAG comment warns about. |
| 203 | + "$MCPP" toolchain install llvm 20.1.7 |
| 204 | + CLANGXX=$(find "$MCPP_HOME/registry/data/xpkgs/xim-x-llvm/20.1.7" \ |
| 205 | + -type f -name 'clang++' | head -1) |
| 206 | + test -x "$CLANGXX" || { echo "FAIL: no clang++ in the llvm payload"; exit 1; } |
| 207 | + echo "OHOS_CLANGXX=$CLANGXX" >> "$GITHUB_ENV" |
| 208 | + echo "OHOS_CLANG=${CLANGXX%++}" >> "$GITHUB_ENV" |
| 209 | + "$CLANGXX" --version | head -1 |
| 210 | +
|
| 211 | + - name: Cache the target libc++ |
| 212 | + id: libcxx-cache |
| 213 | + uses: actions/cache@v4 |
| 214 | + with: |
| 215 | + path: ~/ohos-libcxx |
| 216 | + key: ohos-libcxx-${{ env.LLVM_TAG }}-sdk6.1-v1 |
| 217 | + |
| 218 | + - name: Build libc++/libc++abi for aarch64-linux-ohos |
| 219 | + if: steps.libcxx-cache.outputs.cache-hit != 'true' |
| 220 | + run: | |
| 221 | + set -euo pipefail |
| 222 | + NATIVE="${{ steps.ohos.outputs.ohos_sdk_native }}" |
| 223 | + RES=$(find "$NATIVE/llvm/lib/clang" -maxdepth 1 -mindepth 1 -type d | sort | tail -1) |
| 224 | + # Sparse: the runtimes build needs cmake/, runtimes/, the three |
| 225 | + # runtime trees, llvm/cmake and libc/ (libc++'s charconv includes |
| 226 | + # `shared/fp_bits.h` from it — a missing `libc` fails ~1800 objects |
| 227 | + # into the build, which is an expensive way to learn that). |
| 228 | + git clone --depth 1 --branch "$LLVM_TAG" --filter=blob:none --sparse \ |
| 229 | + https://github.com/llvm/llvm-project /tmp/llvm-src |
| 230 | + git -C /tmp/llvm-src sparse-checkout set \ |
| 231 | + cmake runtimes libcxx libcxxabi libunwind libc llvm/cmake third-party |
| 232 | +
|
| 233 | + cmake -G Ninja -S /tmp/llvm-src/runtimes -B /tmp/build-ohos-libcxx \ |
| 234 | + -DCMAKE_BUILD_TYPE=Release \ |
| 235 | + -DCMAKE_INSTALL_PREFIX="$HOME/ohos-libcxx" \ |
| 236 | + -DCMAKE_C_COMPILER="$OHOS_CLANG" \ |
| 237 | + -DCMAKE_CXX_COMPILER="$OHOS_CLANGXX" \ |
| 238 | + -DCMAKE_C_COMPILER_TARGET=aarch64-linux-ohos \ |
| 239 | + -DCMAKE_CXX_COMPILER_TARGET=aarch64-linux-ohos \ |
| 240 | + -DCMAKE_SYSROOT="$NATIVE/sysroot" \ |
| 241 | + -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ |
| 242 | + -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \ |
| 243 | + -DCMAKE_C_FLAGS="--no-default-config" \ |
| 244 | + -DCMAKE_CXX_FLAGS="--no-default-config" \ |
| 245 | + -DCMAKE_EXE_LINKER_FLAGS="-resource-dir=$RES -fuse-ld=lld" \ |
| 246 | + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \ |
| 247 | + -DLLVM_INCLUDE_TESTS=OFF -DLIBCXX_INCLUDE_TESTS=OFF \ |
| 248 | + -DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXXABI_INCLUDE_TESTS=OFF \ |
| 249 | + -DLIBUNWIND_INCLUDE_TESTS=OFF \ |
| 250 | + -DLIBCXX_CXX_ABI=libcxxabi \ |
| 251 | + -DLIBCXX_HAS_MUSL_LIBC=ON \ |
| 252 | + -DLIBCXX_ENABLE_SHARED=OFF -DLIBCXXABI_ENABLE_SHARED=OFF \ |
| 253 | + -DLIBUNWIND_ENABLE_SHARED=OFF \ |
| 254 | + -DLIBCXXABI_USE_LLVM_UNWINDER=ON \ |
| 255 | + -DLIBCXX_INSTALL_MODULES=ON |
| 256 | + ninja -C /tmp/build-ohos-libcxx install |
| 257 | +
|
| 258 | + # The runtimes build compiles libunwind's .S sources for the HOST |
| 259 | + # (CMake's ASM language does not inherit CMAKE_CXX_COMPILER_TARGET), |
| 260 | + # so the installed libunwind.a carries x86_64 objects and lld |
| 261 | + # rejects it with "incompatible with aarch64linux". The platform's |
| 262 | + # own unwinder is the right one to use anyway; removing the broken |
| 263 | + # archive is what makes `-L<overlay> -L<sdk>` resolve to it. |
| 264 | + rm -f "$HOME/ohos-libcxx/lib/libunwind.a" |
| 265 | + test -f "$HOME/ohos-libcxx/share/libc++/v1/std.cppm" |
| 266 | +
|
| 267 | + - name: "e2e: import std on HarmonyOS + qemu run" |
| 268 | + run: | |
| 269 | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" |
| 270 | + export MCPP_OHOS_LIBCXX="$HOME/ohos-libcxx" |
| 271 | + bash tests/e2e/104_harmonyos_import_std.sh |
0 commit comments