Prebuilt OpenHarmony standard-system QEMU images for Linux, macOS, and Windows.
- QEMU installed and available in
PATH(8.1+for accessibility QMP multitouch events). - Bash,
curlorwget, andtar. - Windows installation must be run from Git Bash, MSYS2, or Cygwin.
- Linux x86_64 should provide readable and writable
/dev/kvm. TCG is too slow for a reliable standard-system boot.
curl -fsSL https://raw.githubusercontent.com/harmony-contrib/ohos-qemu/main/scripts/install.sh | bash -s -- --release v20260809The installer downloads the selected Release and installs the package under
~/.ohos-qemu. It selects arm64 on Apple Silicon and x86_64 on x64 hosts.
Set OHOS_QEMU_ARCH to arm64, armv7a, or x86_64 before installation to
override the detected guest architecture.
The installer defaults to phone. Select the device type explicitly when
needed:
bash scripts/install.sh --release RELEASE_TAG --device-type phone
bash scripts/install.sh --release RELEASE_TAG --device-type 2in1OHOS_QEMU_DEVICE_TYPE=phone and OHOS_QEMU_DEVICE_TYPE=2in1 provide the same
selection through the environment. CLI options override environment values.
For phone, the installer first tries the current -phone.tar.gz asset and
falls back to the legacy archive without a device-type suffix when necessary.
It fails only when neither phone asset exists. A 2in1 selection requires the
-2in1.tar.gz asset. The installed directory matches the selected archive, for
example openharmony-qemu-arm64-arm64_virt-2in1.
Linux x86_64:
~/.ohos-qemu/openharmony-qemu-x86_64-x86_64_virt/launch/linux.shmacOS Apple Silicon:
~/.ohos-qemu/openharmony-qemu-arm64-arm64_virt/launch/macos.commandWindows x86_64, from PowerShell:
powershell.exe -ExecutionPolicy Bypass -File "$HOME\.ohos-qemu\openharmony-qemu-x86_64-x86_64_virt\launch\windows.ps1"Stop QEMU with Ctrl+C.
CLI flags override environment variables, which override package defaults:
# Resolution / resources (guest GPU + QEMU -m/-smp)
./launch/linux.sh -r 1280x720 -m 8G -s 8
# Headless / VNC
./launch/linux.sh --headless
./launch/linux.sh --display vnc --vnc-display 21 # TCP 5921
# HDC port when 5555 is busy
./launch/linux.sh --hdc-port 5556
# or: ./launch/linux.sh -c 127.0.0.1:5556
# Acceleration and extra QEMU args
./launch/linux.sh --accel tcg -- -serial mon:stdio
# Accessibility testing: virtio multitouch + QMP control socket
./launch/linux.sh --headless --a11y --qmp-socket /tmp/ohos-a11y.sock
# Override the CPU model on an AMD host using Windows WHPX
powershell.exe -ExecutionPolicy Bypass -File ./launch/windows.ps1 -Cpu EPYC-v5 -Accel whpx| Option | Env | Default |
|---|---|---|
-r, --resolution WxH |
QEMU_XRES / QEMU_YRES |
800x500 |
--width / --height |
QEMU_XRES / QEMU_YRES |
same |
-m, --memory SIZE |
QEMU_MEMORY |
4096 (armv7a: 3072) |
-s, --smp N |
QEMU_SMP |
4 |
--cpu MODEL |
QEMU_CPU |
max (arm64: cortex-a57, armv7a: cortex-a7) |
-d, --display / --headless |
QEMU_DISPLAY |
product default (sdl / none) |
-c, --connect / --hdc-port |
QEMU_HDC_HOST_PORT |
5555 |
--vnc-display N |
QEMU_VNC_DISPLAY |
21 (TCP 5921) |
--serial-port PORT |
QEMU_SERIAL_PORT |
unset |
--a11y |
QEMU_ACCESSIBILITY |
0 |
--qmp-socket PATH |
QEMU_QMP_SOCKET |
/tmp/openharmony-qemu-a11y-<HDC_PORT>.sock with --a11y |
Windows -QmpPort PORT |
QEMU_QMP_PORT |
4445 with -A11y |
-a, --accel |
QEMU_ACCEL |
auto (hvf/kvm/tcg/whpx) |
-q, --qemu PATH |
QEMU_BIN |
product qemu-system-* |
-- ... |
QEMU_EXTRA_ARGS |
empty |
On Windows PowerShell the same knobs are available as parameters
(-Resolution, -Memory, -Smp, -Cpu, -Display, -Headless, …) or via the
environment variables above.
Current full packages pair virtio-tablet with a guest MMI absolute-coordinate
mapping. The mapping uses the active framebuffer dimensions, so pointer clicks
remain aligned after changing --resolution, resizing the host window, or
using HiDPI scaling. The package manifest records this as
capabilities.absolute_pointer_sync=true; older relative-mouse packages do not
gain this capability merely by rewriting their launch scripts.
The ARM64 launcher defaults to QEMU_ACCEL=auto, probes whether HVF is usable,
and falls back to TCG when necessary. Set QEMU_ACCEL=hvf or
QEMU_ACCEL=tcg to force either mode.
For accessibility tests, --a11y adds virtio-multitouch-pci and enables a
QMP server. After installing a HAP containing an AccessibilityExtensionAbility,
switch HDC to root mode and enable it through the guest's built-in QEMU CLI:
hdc smode
hdc tconn 127.0.0.1:5555
hdc shell /system/bin/cli_tool/executable/ohos-a11yManager ability-enable \
--name com.example.app/AccessibilityExtAbility \
--capabilities 7The capability mask must be a subset of the extension's
accessibilityCapabilities metadata. The value 7 matches an extension that
declares retrieve, touchGuide, and gesture.
The package manifest reports this complete host/guest path as
capabilities.accessibility_test=true only when the generic CLI is present in
system.img.
For the ohos-native-bindings accessibility E2E, the packaged launcher and
system CLI replace the manual QEMU_EXTRA_ARGS and uploaded
accessibility-enable helper from
PR #13:
export QEMU_QMP_SOCKET=/tmp/ohos-a11y.sock
export ACCESSIBILITY_E2E_ENABLE_COMMAND=\
'/system/bin/cli_tool/executable/ohos-a11yManager ability-enable '\
'--name com.richerfu.ohos_example/AccessibilityE2ETestExtension --capabilities 7'
pnpm run test:ui:accessibility -- --arch x64 # use arm64 for an ARM64 guestThe extension still has to be enabled after its HAP is installed: its bundle
and ability do not exist when QEMU starts. The E2E runner performs that step by
executing ACCESSIBILITY_E2E_ENABLE_COMMAND after installation.
Source builds use a component-owned patch tree under patches.
Phone and 2in1 are independent top-level entries; the removed overlays/
commands are not retained as compatibility wrappers:
bash patches/phone/apply.sh \
--source-root /path/to/openharmony \
--artifact-root /path/to/jsvm-m144 \
--lfs-asset-root /path/to/openharmony-7.0-lfs \
--product arm64_virt
bash patches/2in1/apply.sh \
--source-root /path/to/openharmony \
--artifact-root /path/to/jsvm-m144 \
--lfs-asset-root /path/to/openharmony-7.0-lfs \
--product arm64_virtEvery leaf component has its own apply.sh, and each stable source change is
an independently reviewable numbered patch. The aggregate entries apply, in
dependency order:
- pinned former-LFS asset restoration for GitHub mirror checkouts;
- the generated phone or 2in1 product profile;
- QoS authority kernel support and
qos_authintegration; - a stateful QEMU vibrator product VDI;
- a generic
ohos-a11yManagerentry for enabling an installed testAccessibilityExtensionAbilitywithout pushing a helper binary; - release-HAP dependency handling that skips registry access only when no runtime package dependency is declared;
- absolute-pointer synchronization and the standard VPN/GPU stack;
- JSVM linked to the pinned ArkWeb/Chromium M144
v8_sharedartifact; - the armv7a product components when
armv7a_virtis selected.
Build the M144 engine artifacts for all supported guest architectures first:
scripts/build_m144_v8.sh arm arm64 x86_64The builder pins Chromium, V8, ArkWeb, CEF, depot_tools, and the OpenHarmony
7.0 WebView interface revisions. It records per-file SHA-256 values in
manifest.json; the JSVM component rejects a wrong milestone, revision,
architecture, checksum, SDK link stub, or non-OpenHarmony libc++ ABI
namespace/version before modifying the OpenHarmony checkout. Image packaging
also verifies that the vibrator VDI exports hdfVdiDesc and that
libv8_shared.so satisfies every V8 C++ symbol required by libjsvm.so.
The launchers forward guest HDC to host TCP port 5555. With hdc from the
OpenHarmony SDK toolchains installed:
hdc tconn 127.0.0.1:5555
hdc list targetsThe v20260809 image requires a HAP signing block. A truly unsigned package is
rejected with bundle-manager error 9568320 (no signature file). The default
installer also installs the Java-free hap-sign CLI under
~/.ohos-qemu/bin; use --without-hap-signer when only the image is wanted.
export PATH="$HOME/.ohos-qemu/bin:$PATH"
hap-sign sign entry-default-unsigned.hap \
--bundle-name com.example.application \
--output entry-default-signed.hap
hdc install entry-default-signed.hapVPN applications commonly need an ACL in the debug profile:
hap-sign sign entry-default-unsigned.hap \
--bundle-name com.example.vpn \
--acl ohos.permission.FILE_ACCESS_PERSIST \
--output entry-default-signed.hapFrom a source checkout, scripts/sign-hap.sh is the equivalent wrapper and
scripts/install-hap-signer.sh installs the signer. Release packages carry the
same scripts under tools/. The signer is implemented in the separate
ohos-rs/hapsigner-rs repository
with RustCrypto CMS, X.509, P-256 ECDSA, and SHA-256 crates. rustls is not used
because the on-disk format is CMS/PKCS#7 rather than TLS.
The embedded OpenHarmony development key is public test material and is only appropriate for QEMU/RD development images.
The full armv7a_virt, arm64_virt, and x86_64_virt packages are built with
OpenHarmony's standard VpnExtension stack:
- built-in guest TUN plus IPv4/IPv6 policy routing;
- fs-verity in the guest kernel and writable F2FS
userdata.img, including OpenHarmony'sFS_IOC_ENABLE_CODE_SIGNpath for signed HAP installation; - native
asm-x86UAPI headers in the x86_64 musl sysroot, plus target-architecturestatx,add_key, andkeyctlselection in the code-sign services; - deterministic HCK JIT-hook fallback when the optional JIT-memory hook is
absent, preventing x86_64 appspawn from rejecting valid
mprotectcalls; - VPN manager System Ability and VpnExtension runtime;
- SettingsData and the system
VpnDialog, signed with a currently valid OpenHarmony system profile containing the dialog's required ACLs. - QEMU RD/developer-device boot mode, so normal DevEco debug HAPs can be installed without manually changing guest authorization state.
No application is pre-authorized. The first VPN request must show the system
authorization dialog, and the user's decision is stored in the guest
userdata.img.
The build applies
patches/common/standard_vpn before compiling.
Packaging then checks the final kernel configuration, the F2FS verity feature
of userdata.img, and the exact signed VpnDialog.hap inside system.img; a
package is marked with "standard_vpn": true only after those checks pass.
The guest VPN uses /dev/tun inside OpenHarmony and does not require a host
TAP device when the default QEMU user-mode network is used.
All three architectures keep RenderService on its normal OpenGL/EGLImage
lifecycle. QEMU exposes a plain virtio-gpu DRM device; the guest Mesa stack
tries the virtio path and falls back to kms_swrast when host 3D is absent.
The 64-bit Mesa libraries are built from the same OHOS Mesa 21.3.3 baseline as
armv7a with the upstream ohos_logger va_list fix, instead of packaging the
older QEMU GPU binaries. Both virtio_gpu_dri.so and swrast_dri.so resolve to
the verified multi-driver kms_swrast_dri.so ELF.
Package validation checks the DRI ELF architecture and aliases. QEMU smoke CI
then opens Settings and Photos three times and fails if render_service
changes PID or emits a new fault log.
The matrix entry point builds both full source profiles for all three supported
standard QEMU architectures and emits exactly six suffixed packages. The
source baseline is the OpenHarmony-7.0-Release manifest pinned at
f079c4ad9848f9cc4a9a4b3a3613ad8fbb142549; master is not used:
PACKAGE_ROOT=/Volumes/PSSD/qemu/packages/device-matrix \
BUILD_JOBS=12 KERNEL_BUILD_JOBS=6 \
scripts/run_device_type_matrix_build_docker.shThe matrix is phone,2in1 × armv7a_virt,arm64_virt,x86_64_virt. It is
restartable (MATRIX_SKIP_EXISTING=1 by default), strictly verifies every
package, and writes SHA256SUMS plus matrix-manifest.json. Before traversing
the six builds it initializes the pinned source, applies the complete patch set
for all selected products, validates the prepared product configurations, and
records a resolved revision manifest. It uses one native Linux source/out
volume and defaults to pruning out/<product> immediately
after its package is archived, while retaining ccache and kernel objects. The
matrix caps ccache at 4 GiB by default so image assembly retains disk headroom;
set CCACHE_MAXSIZE explicitly on hosts with more space. This
keeps the six-package build usable on hosts that cannot hold six complete Ninja
trees at once. PACKAGE_ROOT may be placed outside CACHE_ROOT; the Docker
runner bind-mounts it separately, which is useful for temporary package staging
when the build cache disk is nearly full. Set
PRUNE_PRODUCT_OUT_AFTER_PACKAGE=0 only when enough Docker disk space is
available. When switching between phone and 2in1 profiles, the builder writes
the profile stamp atomically and retries while Docker-backed storage makes
newly reclaimed blocks visible. The retry window can be adjusted with
PROFILE_STAMP_WRITE_RETRIES and PROFILE_STAMP_WRITE_RETRY_DELAY.
Complete phone packages inherit a current-tree-compatible profile derived from
productdefine/common/inherit/phone.json; complete 2in1 packages use the same
strategy with 2in1.json. Both retain the QEMU rich.json base and board
display adaptations, and both carry auditable resolved-part evidence.
Multi-package changes must pass runtime validation on the first newly packaged artifact before the remaining packages are built; see AGENTS.md. The shared system patches fix Native child-process argument queries and the 150 KiB parameter boundary. The direct C API regression provides the corresponding package validation. The six-package release record lists archive hashes, runtime evidence, and ARMv7a validation limits. The enabled six-package record tracks archives with Native child processes available at first boot; all six passed the direct C API regression.
Complete deviceType=2in1 QEMU packages are source-built. The QEMU product
keeps its existing rich.json base (applications, SDK, code signing, and VPN)
and then inherits a current-tree-compatible profile derived from
productdefine/common/inherit/2in1.json. Shared parts use the 2in1 feature
selection, while QEMU-specific board/display requirements remain enabled.
The 2in1 profile enables SceneBoard and the PC window layout, installs
/etc/sceneboard.config as ENABLED, and sets
const.window.multiWindowUIType=FreeFormMultiWindow plus
persist.sceneboard.ispcmode=true. It stages four signed SceneBoard HAPs from
SCENEBOARD_RUNTIME_ASSET_ROOT; see
the asset checksums and provisioning requirements.
The SceneBoard HAPs are checked for privileged-extension authorization,
signature validity, ABI coverage, and exact packaged hashes. These additions
apply only to 2in1 builds; existing phone packages do not require rebuilding.
The 7.0 AbilityManager removes its first-boot UI-ability interceptor only after
it has received both usual.event.USER_UNLOCKED and
usual.event.SCREEN_UNLOCKED for the same user. The upstream SceneBoard
prebuilt publishes the older common.event.UNLOCK_SCREEN; the 2in1 runtime
asset corrects that event name. For headless QEMU boots, a 2in1-only init
configuration publishes both required events after
bootevent.boot.completed=true, when AbilityManager has subscribed and user
100 is ready. USER_UNLOCKED carries 100 in the common-event code, while
SCREEN_UNLOCKED carries it as the Want userId parameter. The CEM change
adds that Want parameter and uses the current-user route so the native process
does not hit CES's system-HAP-only special-user check. Phone images do not
install these services and the existing phone archives remain unchanged.
Applications calling the snapshot and main-window enumeration APIs must request
and receive ohos.permission.CUSTOM_SCREEN_CAPTURE. Privacy-mode and cursor-lock
APIs also require ohos.permission.PRIVACY_WINDOW and
ohos.permission.LOCK_WINDOW_CURSOR, respectively.
The 2in1 source component also adds /system/app/SceneBoard to BMS's
install_list.json and adds the signing certificate and privileged-extension
authorization to install_list_capability.json. Without these first-boot
entries the HAP files remain on disk but SceneBoard is not installed, and the
first user cannot finish starting. The package verifier checks both entries.
# macOS/Apple Silicon host with Docker or OrbStack. OpenHarmony's host
# prebuilts require the default linux/amd64 container.
PRODUCTS=arm64_virt \
PACKAGE_ROOT=/Volumes/PSSD/qemu/packages/2in1-full \
scripts/run_2in1_full_build_docker.shThe runner mounts the checkout and out/ on the case-sensitive Docker volumes
ohos-qemu-7_0-release-source and ohos-qemu-7_0-release-out. By default the
source volume is initialized directly from the pinned 7.0 Release manifest;
the host checkout is not copied into it. The output volume is reused
incrementally. This is required on macOS: Taihe generates case-distinct
paths such as SourceType and sourceType, and a full compile can exhaust
VirtioFS file handles while reading the checkout. Ccache and its temporary
files also live in the output volume. The QEMU runner builds the pinned SDK by
default because ArkWeb M144 consumes its generated NDK libraries. A component
patch makes musl's Cortex-M porting script POSIX-safe so Ubuntu's dash shell
reliably installs the intended empty crtplus.c and the SDK no longer fails on
__aeabi_unwind_cpp_pr0. Set NO_PREBUILT_SDK=1 only for products that do not
consume the generated SDK.
Although the immutable upstream manifest declares GitCode project remotes,
the runner rewrites those URLs to the official github.com/openharmony
mirrors by default. Because the GitHub organization is a read-only mirror and
does not expose the 7.0 branch for every project, the checked-in fallback map
pins affected repositories to their immutable OpenHarmony-v7.0-Release
commit. All 35 fallback repositories are fetched once on the host as shallow
bare caches in $CACHE_ROOT/git-mirrors/openharmony-7.0-release; 25 come from
GitHub and only the 10 repositories without a usable GitHub release tag come
directly from GitCode. Docker reads their exact peeled commits through a local
remote, avoiding both moving branches and annotated-tag checkout ambiguity.
Some 7.0 GitHub mirrors also expose former Git LFS files as explanatory text
stubs that git lfs pull cannot discover. A second pinned map caches the 77
affected objects (about 724 MiB) concurrently on the host, verifies their
OID/size, and restores them before source-profile or architecture traversal.
This includes release HAPs, ArkWebCore, Arkoala packages, ICU, and phone-number
data; archive formats are checked before the build starts. The cache defaults
to $CACHE_ROOT/artifacts/openharmony-7.0-lfs, with 10 download jobs controlled
by LFS_JOBS.
The QEMU GPU source build additionally needs historical Mesa 21.3.3 commit
995d2506d18924b48db0cf40e6ad7de04fc4e558, which is no longer present in a
fresh 7.0 GitHub-mirror checkout. The host runner fetches that exact commit
from GitHub into $CACHE_ROOT/git-mirrors/qemu-mesa, validates its VERSION,
and the Mesa component imports it before build-graph traversal. Ninja therefore
never performs an unpinned network fetch.
Set OHOS_PROJECT_MIRROR to a path-compatible
mirror, or to an empty value to disable rewriting and the fallback manifest.
Source synchronization defaults to 16 network jobs and 4 checkout jobs; tune
them with REPO_JOBS and REPO_CHECKOUT_JOBS. The host fallback cache uses 10
parallel jobs by default (FALLBACK_JOBS).
The documentation-only docs project is explicitly omitted from this build
manifest because it is not an input to any of the six QEMU images and its
multi-gigabyte Git history is not needed for product compilation.
GitHub domains are also added to the build container's NO_PROXY list by
default so multi-gigabyte Git packs do not traverse a local HTTP proxy; adjust
that list with OHOS_GITHUB_NO_PROXY when required by the host network.
Set DOCKER_SOURCE_SEED=1 DOCKER_SOURCE_REFRESH=1 only when intentionally
seeding from a compatible host OpenHarmony checkout. A reused source volume is
rejected when its manifest commit differs from the pinned revision.
Each complete package contains device-profile.json, including the upstream
2in1 profile hash, effective inherit chain, resolved part list, compatibility
adaptations, and validated 2in1/QEMU feature flags. Packaging also requires the
DLP manager/service, UI appearance service, Wukong, HNP, Launcher, and SystemUI
runtime artifacts. The source component also installs
const.bms.supportAppTypes=2in1,phone,default,tablet in the QEMU product parameters:
the current signed Launcher/SystemUI HAPs advertise default/tablet, and BMS
needs this compatibility list to register them and complete first-user account
activation when the runtime device type is 2in1.
Offline verification:
scripts/verify_device_type_package.sh \
--package /path/to/openharmony-qemu-arm64-arm64_virt-2in1 \
--expect-device-type 2in1 \
--require-full-2in1 \
--require-scene-window
scripts/verify_device_type_package.sh \
--package /path/to/openharmony-qemu-arm64-arm64_virt-phone \
--expect-device-type phone \
--require-full-phoneFor a freshly archived 2in1 package, the runtime check boots a private QEMU
guest, runs the PR #156 WindowManager tests, and checks all 21 Native child
regression cases. Set QEMU_SMOKE_ACCEL=hvf for arm64 on Apple Silicon; use
tcg for x86_64 and armv7a on that host.
BINDINGS_EXAMPLES=/path/to/ohos-native-bindings/examples-ui \
QEMU_SMOKE_ACCEL=hvf \
ci/window-manager/validate-package.sh arm64 \
/path/to/openharmony-qemu-arm64-arm64_virt-2in1.tar.gz \
/path/to/validation/arm64-2in1scripts/repackage_device_type.sh remains available for compatibility testing,
but it only injects system parameters into an existing image and marks the
result device_type_profile=param_only. Such a package is not considered a
complete 2in1 build and fails --require-full-2in1. CI coverage for both paths
is in ci/device-type/test.sh.