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
278 changes: 278 additions & 0 deletions .agents/docs/2026-08-04-harmonyos-target-design.md

Large diffs are not rendered by default.

278 changes: 278 additions & 0 deletions .github/workflows/ci-harmonyos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
name: ci-harmonyos

# mcpp → HarmonyOS / OpenHarmony (aarch64-linux-ohos), verified end to end.
#
# ── What this proves, and why it needs its own workflow ────────────────────
#
# cross-build-test.yml is the home of "which cross targets does mcpp support",
# and it carries this comment:
#
# * llvm/clang cross : clang is inherently a cross-compiler, but mcpp does
# not yet inject `-target <triple>` + a cross sysroot
# for a clang toolchain; cross `--target` resolves to
# gcc musl only. Wire the clang cross path first, then
# add a row.
#
# HarmonyOS is the target that forces that row to exist: GCC has no `ohos`
# target at all, so the gcc-musl shape mcpp's other cross rows use cannot be
# spelled here. This workflow is deliberately SEPARATE from
# cross-build-test.yml for one reason — it depends on a ~2.5 GB vendor SDK
# that mcpp does not and cannot ship, so a failure here must never be
# confusable with a failure of mcpp's own cross matrix.
#
# ── The two tiers ─────────────────────────────────────────────────────────
#
# tier compiler C++ stdlib import std
# ------------ ---------------- ------------------------- ----------
# stock SDK mcpp's llvm@20 SDK's libc++ 15.0.4 no
# + overlay mcpp's llvm@20 libc++ built for the yes
# target from LLVM sources
#
# Both are built and RUN under qemu-aarch64. The second job builds the overlay
# from source in ~10 minutes, which is expensive — but it is the only way to
# show that the "no import std" limit is a missing PAYLOAD, not a missing
# capability, and that distinction is the whole argument of
# .agents/docs/2026-08-04-harmonyos-target-design.md.
#
# ── What is NOT proven here ───────────────────────────────────────────────
#
# qemu-user runs the artefact's instructions, not HarmonyOS. It says nothing
# about the .hnp/.hap packaging path, about linking the platform's own NDK
# libraries (libace_napi.z.so and friends), or about anything that touches
# a real device. Those need hardware or the emulator and stay out of scope —
# see the design doc's "what CI can and cannot show" section. Verification
# here is the same claim the aarch64-linux-musl row makes: this artefact is
# for the right machine and it really executes.

on:
pull_request:
branches: [ main ]
paths:
- 'src/toolchain/**'
- 'src/build/**'
- 'tests/e2e/10[34]_harmonyos*'
- 'tests/unit/test_ohos_target.cpp'
- 'examples/05-harmonyos/**'
- '.github/workflows/ci-harmonyos.yml'
workflow_dispatch:

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# ── Tier 1: stock SDK, named modules ──────────────────────────────────────
harmonyos-cross:
name: HarmonyOS cross-build + qemu run (stock SDK)
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
MCPP_HOME: /home/runner/.mcpp
MCPP_VERBOSE: "1"
steps:
- uses: actions/checkout@v4

# Do NOT restore target/ in a cross job: this job builds twice (host,
# then aarch64-linux-ohos) and a restored BMI tree makes the second
# build read `std` BMIs that no longer match what the rest was compiled
# against — `import 'std' has CRC mismatch`. Same rule as
# windows-host-linux-cross in cross-build-test.yml, same reason.
- uses: ./.github/actions/bootstrap-mcpp
with:
cache-target: 'false'

- name: Install qemu-user-static
run: |
sudo apt-get update -qq
sudo apt-get install -y qemu-user-static
qemu-aarch64-static --version | head -1

- name: Setup OpenHarmony SDK
id: ohos
uses: openharmony-rs/setup-ohos-sdk@v1.0.1
with:
version: '6.1'
components: 'native'

- name: Point mcpp at the SDK
run: |
NATIVE="${{ steps.ohos.outputs.ohos_sdk_native }}"
test -d "$NATIVE" || { echo "FAIL: action produced no native dir"; exit 1; }
# The two files mcpp's own detection requires; asserted here so a
# layout change in the action fails with a clear message instead of
# inside a compile command 10 minutes later.
test -f "$NATIVE/sysroot/usr/include/stdlib.h"
test -d "$NATIVE/llvm/lib/aarch64-linux-ohos"
echo "OHOS_NDK_HOME=$NATIVE" >> "$GITHUB_ENV"
echo "== SDK =="
cat "$NATIVE/oh-uni-package.json" || true
# Recorded, not used: this is the fact the whole design rests on.
# If a future SDK ships a modern clang, this line is where it shows.
echo "== the SDK's own clang (mcpp does NOT use it) =="
"$NATIVE/llvm/bin/clang++" --version | head -1

- name: Build mcpp from source (self-host)
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
"$MCPP" build
# Newest, not first: target/ keeps a directory per build fingerprint,
# so `find | head -1` can hand back a previous build's binary.
MCPP_SELF=$(find target -type f -name mcpp -path '*/bin/*' \
-printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2-)
test -x "$MCPP_SELF"
MCPP_SELF=$(realpath "$MCPP_SELF")
"$MCPP_SELF" --version
echo "MCPP=$MCPP_SELF" >> "$GITHUB_ENV"

- name: "Target is listed as available once the SDK is present"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" toolchain list | tee /tmp/tclist.txt
# `available`, not `planned`: host_can_serve() answers this by
# probing for the SDK, so this asserts the detection wired up — the
# same row reads `planned` on a runner without the SDK.
grep -q "aarch64-linux-ohos" /tmp/tclist.txt \
|| { echo "FAIL: ohos target not listed"; exit 1; }

- name: "e2e: cross-build + qemu run"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
bash tests/e2e/103_harmonyos_cross_qemu.sh

- name: "Example project builds for HarmonyOS"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd examples/05-harmonyos
"$MCPP" build --target aarch64-linux-ohos
BIN=$(find target/aarch64-linux-ohos -type f -path '*/bin/*' | head -1)
file "$BIN"
file "$BIN" | grep -q "ARM aarch64"
qemu-aarch64-static "$BIN"

# ── Tier 2: + a libc++ built for the target ⇒ import std ──────────────────
harmonyos-import-std:
name: HarmonyOS import std (libc++ built for the target)
runs-on: ubuntu-24.04
timeout-minutes: 90
env:
MCPP_HOME: /home/runner/.mcpp
MCPP_VERBOSE: "1"
# Must match the LLVM the target pin resolves to
# (triple::pins::kOhosLlvm). A libc++ built by one clang and used by
# another is a version skew that works until it does not; keeping the
# two equal is what makes this job evidence rather than anecdote.
LLVM_TAG: llvmorg-20.1.7
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/bootstrap-mcpp
with:
cache-target: 'false'

- name: Install qemu-user-static + build tools
run: |
sudo apt-get update -qq
sudo apt-get install -y qemu-user-static cmake ninja-build
qemu-aarch64-static --version | head -1

- uses: openharmony-rs/setup-ohos-sdk@v1.0.1
id: ohos
with:
version: '6.1'
components: 'native'

- name: Build mcpp from source (self-host)
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
echo "OHOS_NDK_HOME=${{ steps.ohos.outputs.ohos_sdk_native }}" >> "$GITHUB_ENV"
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
"$MCPP" build
MCPP_SELF=$(find target -type f -name mcpp -path '*/bin/*' \
-printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2-)
MCPP_SELF=$(realpath "$MCPP_SELF")
"$MCPP_SELF" --version
echo "MCPP=$MCPP_SELF" >> "$GITHUB_ENV"

- name: Locate mcpp's LLVM payload
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
# Installing it explicitly rather than letting the first ohos build
# do it: the runtimes build below needs the same clang, and taking
# it from anywhere else would silently reintroduce the skew the
# LLVM_TAG comment warns about.
"$MCPP" toolchain install llvm 20.1.7
# Named directly, NOT discovered with `find`: in the LLVM payload
# `clang++` is a symlink chain (clang++ -> clang -> clang-20), so a
# `find -type f -name clang++` finds nothing and the step fails one
# line after mcpp has just printed "Installed llvm@20.1.7 → …/clang++".
CLANGXX="$MCPP_HOME/registry/data/xpkgs/xim-x-llvm/20.1.7/bin/clang++"
test -x "$CLANGXX" || {
echo "FAIL: no clang++ at $CLANGXX; payload bin/ was:"
ls -la "$(dirname "$CLANGXX")" | head -20
exit 1
}
echo "OHOS_CLANGXX=$CLANGXX" >> "$GITHUB_ENV"
echo "OHOS_CLANG=${CLANGXX%++}" >> "$GITHUB_ENV"
"$CLANGXX" --version | head -1

- name: Cache the target libc++
id: libcxx-cache
uses: actions/cache@v4
with:
path: ~/ohos-libcxx
key: ohos-libcxx-${{ env.LLVM_TAG }}-sdk6.1-v1

- name: Build libc++/libc++abi for aarch64-linux-ohos
if: steps.libcxx-cache.outputs.cache-hit != 'true'
run: |
set -euo pipefail
NATIVE="${{ steps.ohos.outputs.ohos_sdk_native }}"
RES=$(find "$NATIVE/llvm/lib/clang" -maxdepth 1 -mindepth 1 -type d | sort | tail -1)
# Sparse: the runtimes build needs cmake/, runtimes/, the three
# runtime trees, llvm/cmake and libc/ (libc++'s charconv includes
# `shared/fp_bits.h` from it — a missing `libc` fails ~1800 objects
# into the build, which is an expensive way to learn that).
git clone --depth 1 --branch "$LLVM_TAG" --filter=blob:none --sparse \
https://github.com/llvm/llvm-project /tmp/llvm-src
git -C /tmp/llvm-src sparse-checkout set \
cmake runtimes libcxx libcxxabi libunwind libc llvm/cmake third-party

cmake -G Ninja -S /tmp/llvm-src/runtimes -B /tmp/build-ohos-libcxx \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$HOME/ohos-libcxx" \
-DCMAKE_C_COMPILER="$OHOS_CLANG" \
-DCMAKE_CXX_COMPILER="$OHOS_CLANGXX" \
-DCMAKE_C_COMPILER_TARGET=aarch64-linux-ohos \
-DCMAKE_CXX_COMPILER_TARGET=aarch64-linux-ohos \
-DCMAKE_SYSROOT="$NATIVE/sysroot" \
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_C_FLAGS="--no-default-config" \
-DCMAKE_CXX_FLAGS="--no-default-config" \
-DCMAKE_EXE_LINKER_FLAGS="-resource-dir=$RES -fuse-ld=lld" \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
-DLLVM_INCLUDE_TESTS=OFF -DLIBCXX_INCLUDE_TESTS=OFF \
-DLIBCXX_INCLUDE_BENCHMARKS=OFF -DLIBCXXABI_INCLUDE_TESTS=OFF \
-DLIBUNWIND_INCLUDE_TESTS=OFF \
-DLIBCXX_CXX_ABI=libcxxabi \
-DLIBCXX_HAS_MUSL_LIBC=ON \
-DLIBCXX_ENABLE_SHARED=OFF -DLIBCXXABI_ENABLE_SHARED=OFF \
-DLIBUNWIND_ENABLE_SHARED=OFF \
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
-DLIBCXX_INSTALL_MODULES=ON
ninja -C /tmp/build-ohos-libcxx install

# The runtimes build compiles libunwind's .S sources for the HOST
# (CMake's ASM language does not inherit CMAKE_CXX_COMPILER_TARGET),
# so the installed libunwind.a carries x86_64 objects and lld
# rejects it with "incompatible with aarch64linux". The platform's
# own unwinder is the right one to use anyway; removing the broken
# archive is what makes `-L<overlay> -L<sdk>` resolve to it.
rm -f "$HOME/ohos-libcxx/lib/libunwind.a"
test -f "$HOME/ohos-libcxx/share/libc++/v1/std.cppm"

- name: "e2e: import std on HarmonyOS + qemu run"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
export MCPP_OHOS_LIBCXX="$HOME/ohos-libcxx"
bash tests/e2e/104_harmonyos_import_std.sh
14 changes: 10 additions & 4 deletions .github/workflows/cross-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ name: cross-build-test
# ci-linux.yml's "Toolchain: musl-gcc" step, and release.yml for the static
# release artefact. Keep them there; this file is cross-arch only.
#
# ── Related, but deliberately NOT here ────────────────────────────────────
# * llvm/clang cross : WIRED — Toolchain::crossTarget retargets a clang
# driver with `--target=` + an external sysroot. Its
# first consumer is HarmonyOS, and that row lives in
# ci-harmonyos.yml rather than this matrix for one
# reason: it needs a ~2.5 GB vendor SDK mcpp cannot
# ship, and a failure to obtain that SDK must never be
# confusable with a failure of mcpp's own cross matrix.
# See .agents/docs/2026-08-04-harmonyos-target-design.md.
#
# ── Planned cross rows (documented; NOT yet wired in mcpp — keep as comments) ─
# * llvm/clang cross : clang is inherently a cross-compiler, but mcpp does not
# yet inject `-target <triple>` + a cross sysroot for a
# clang toolchain; cross `--target` resolves to gcc musl
# only. Wire the clang cross path first, then add a row.
# * riscv64-linux-musl: add once xim:riscv64-linux-musl-gcc ships to
# xlings-res + xim-pkgindex.

Expand Down
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,43 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [Unreleased] — RFC:HarmonyOS / OpenHarmony 目标

> **本节对应一个探针 PR,不打算合入。** 引擎改动已实现并端到端验证;
> 设计与实测数据见 `.agents/docs/2026-08-04-harmonyos-target-design.md`。

### 新增

- **`aarch64-linux-ohos`(HarmonyOS / OpenHarmony)成为一等目标。** 设好
`OHOS_NDK_HOME`,`mcpp build --target aarch64-linux-ohos` 产出静态 aarch64
鸿蒙 ELF,CI 在 `qemu-aarch64` 下**真的执行**它。C++23 具名模块对着原版 SDK
即可用;再配一份为该目标编译的 libc++(`MCPP_OHOS_LIBCXX`),`import std;`
也可用 —— 两档都有 e2e。

**mcpp 只把 SDK 当 sysroot,不当工具链。** 这不是偏好:实测 OpenHarmony SDK
**6.1(API 23)自带 clang 仍是 15.0.4**,比 C++20 模块所需的
`-fmodule-output`(clang 16)差一代,比 `import std` 差四代。而 GCC 根本没有
`ohos` target。所以 `.agents/docs/2026-07-24-embedded-platform-support-design.md`
的 config②(mcpp 带编译器 + 消费外部 sysroot)在这里从「两种可行架构之一」
变成**唯一解**,并且只能用 clang —— 决策 #8 存档的那条路线被现实提前触发。

- **clang 交叉通道(driver retarget)。** `cross-build-test.yml` 里那条
"llvm/clang cross: … Wire the clang cross path first" 的注释现在有实现了:
`Toolchain::crossTarget` 让一个 clang driver 带着 `--target=` + 外部 sysroot
+ 目标 libc++ 工作。**这不是鸿蒙专用的** —— 鸿蒙只是第一个非它不可的消费者,
`aarch64-linux-gnu`(树莓派滩头)之后可以走同一条缝。

### 注意

- `ohos` 在 triple 语言里是 **env 而非 os**(`aarch64` + `linux` + `ohos`),
与上游 LLVM 一致:内核确实是 Linux,所以已有包里的 `cfg(os = "linux")` /
`cfg(family = "unix")` 必须继续匹配。反过来,ABI 维度上 `libc = "ohos"` 自成
取值、`is_musl()` 返回 **false** —— OHOS libc 是 musl 的 fork,但与上游 musl
产物不可互换,而 mcpp 里 "musl" 处处指的是后者。
- CI 验证的是「产物面向正确的机器且真的执行」,与 `aarch64-linux-musl` 同级。
它**不**覆盖 `.hnp`/`.hap` 打包、链接平台 NDK 库、或任何需要真机/模拟器的
行为。

## [2026.8.4.1] — 2026-08-04

### 修复
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,12 @@ the right toolchain payload is resolved and installed automatically.
| `x86_64-windows-gnu` | gcc 16 MinGW-w64 — native on Windows, cross from Linux (wine-verified) *(Windows default without Visual Studio)* | ✅ |
| `x86_64-windows-msvc` | `msvc@system` (detected VS/BuildTools) or llvm ¹ *(Windows default with Visual Studio)* | ✅ |
| `aarch64-macos` | llvm *(macOS default)* | ✅ |
| `aarch64-linux-ohos` | llvm 20 + OpenHarmony SDK sysroot ² — cross from any host (qemu-verified) | ✅ |
| `riscv64-linux-musl` | — | 🔄 |
| `aarch64-linux-gnu` | — | 🔄 |
| `x86_64-macos` | — | 🔄 |
| `x86_64-linux-ohos` | llvm 20 + OpenHarmony SDK sysroot ² | 🔄 |
| `arm-linux-ohos` | llvm 20 + OpenHarmony SDK sysroot ² | 🔄 |

✅ verified — CI builds **and executes** the artifact end-to-end (qemu/wine included) | 🔄 planned

Expand All @@ -326,6 +329,16 @@ the right toolchain payload is resolved and installed automatically.
> or configure; `mcpp new && mcpp build` just works on a stock Windows box.
> An explicit `[toolchain]` in `mcpp.toml` is always respected as written —
> mcpp revises its own default, never yours.
>
> ² HarmonyOS / OpenHarmony is the one target where mcpp needs something it
> cannot ship: the platform SDK. Point `OHOS_NDK_HOME` at the unpacked
> `native` directory and `mcpp build --target aarch64-linux-ohos` works.
> mcpp uses the SDK for its **sysroot and runtime libraries only** — the
> compiler stays mcpp's own LLVM, because the SDK's bundled clang is 15.0.4
> even in SDK 6.1 (API 23) and cannot build C++20 modules at all. Named
> modules work against a stock SDK; `import std` additionally needs a libc++
> built for the target — see
> [Toolchain Management](docs/03-toolchains.md#harmonyos--openharmony).

## Documentation

Expand Down
1 change: 1 addition & 0 deletions docs/01-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ examples.
| 01 | [`examples/01-hello`](../examples/01-hello/) | Minimal single-file project with `import std` | The minimal package shape (`mcpp new` also emits `tests/test_smoke.cpp`) |
| 02 | [`examples/02-with-deps`](../examples/02-with-deps/) | Adds the `mcpplibs.cmdline` dependency to parse command-line arguments | `[dependencies]`, SemVer, `mcpp.lock` |
| 03 | [`examples/03-pack-static`](../examples/03-pack-static/) | Produces a fully static release package via `mcpp pack --mode static` | `[target.<triple>]` and `[pack]` configuration |
| 05 | [`examples/05-harmonyos`](../examples/05-harmonyos/) | Cross-builds a C++23 named-module program for HarmonyOS/OpenHarmony | `--target aarch64-linux-ohos`, consuming an external platform SDK as a sysroot |

## Suggested Reading Order

Expand Down
Loading
Loading