The CLI is built with Bazelisk (Bazel launcher); the SDK bridge and native plugins are built with CMake. Bazel fetches all CLI-side dependencies automatically.
Install Bazelisk:
- Windows:
winget install --id Bazel.Bazelisk - Linux: install
bazeliskfrom your package manager
Important
Build in this order. In local-SDK mode (the default) the CLI links against the SDK bridge at sdk/pkg-geniex/ — Bazel expects sdk/pkg-geniex/lib/geniex.dll (Windows) or sdk/pkg-geniex/lib/libgeniex.so (Linux) to already exist. So build and install the SDK (Build the SDK) first, then build the CLI (Build and run the CLI).
Run every command below from PowerShell, not cmd.exe, Git Bash, or WSL. The cmd /c '"...\VsDevCmd.bat" ... && ...' commands below rely on PowerShell quoting (outer single quotes, inner double quotes).
Also needed by the SDK's CMake configure: a vendored submodule (sentencepiece, under third-party/geniex-qairt) creates a symlink and fails with "A required privilege is not held by the client" without it.
- Enable Developer Mode: Settings → Privacy & Security → For developers.
- Grant Create symbolic links rights via
gpedit.msc→ Computer Configuration → Windows Settings → Security Settings → Local Policies → User Rights Assignment. - Enable Long paths: Settings → Privacy & Security → For developers.
- Sign out and back in (or reboot) after enabling Developer Mode; open sessions keep the old token. Verify in a new terminal with
whoami /priv | findstr SymbolicLink(no output means it hasn't taken effect). - If symlink errors persist, comment out
startup --windows_enable_symlinksin.bazelrc— but be aware this can break other SDK paths.
Corporate/domain-joined machines: Group Policy can block step 4 even after a reboot. In that case, run the build (cmake --preset ... and bazelisk ...) from an elevated terminal ("Run as Administrator"); local Administrators get SeCreateSymbolicLinkPrivilege once elevated. whoami /groups showing BUILTIN\Administrators as "Group used for deny only" confirms you're an admin filtered by UAC.
The SDK's Rust model manager (sdk/model-manager) is built by cargo from CMake, and its build scripts compile C code, so a native Windows ARM64 build also needs:
-
clang — used by
cc-rsand the Snapdragon presets. Install the ARM64 MSYS2 build (the plain LLVM installer has noaarch64-hostedclang.exe):winget install --id MSYS2.MSYS2 C:\msys64\usr\bin\pacman.exe -S --noconfirm mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-lld
Then add
C:\msys64\clangarm64\bintoPATH(ahead of any otherclang.exe, e.g. one bundled with Visual Studio). -
cargo and the Windows ARM64 target:
winget install --id Rustlang.Rustup rustup target add aarch64-pc-windows-msvc
-
Visual Studio — for the MSVC libs/linker only. Install the "Desktop development with C++" workload with the ARM64 build tools component.
clang targets the MSVC ABI, so it needs MSVC's INCLUDE/LIB and lld-link on PATH. Without them, CMake's compiler check fails with lld-link: error: could not open 'oldnames.lib'. Configure and build from a Visual Studio Developer environment for ARM64 (see Build the SDK).
The arm64-windows-snapdragon-release preset requires:
- Hexagon SDK —
HEXAGON_SDK_ROOT,HEXAGON_TOOLS_ROOT - OpenCL SDK —
OPENCL_SDK_ROOT(headers +OpenCL.lib; runtime ICD ships with the Snapdragon GPU driver) - Windows Driver Kit — provides
inf2cat.exe - Self-signed HTP cert (
.pfx) and Windows test-signing enabled — see run.md § Self-signed fallback for cert generation and test-signing setup
Note
The Hexagon toolchain has a 250-character path limit. Shorten the source path with subst before building:
subst G: C:\path\to\geniex
cd G:\sdkRun from a "Developer PowerShell for VS" (ARM64), elevated if the symlink privilege won't take effect (see Symlink support):
cd sdk
cmake --preset arm64-windows-snapdragon-release -B build
cmake --build build -j
cmake --install build --prefix pkg-geniexOr from a plain PowerShell, chaining through VsDevCmd.bat (adjust the path/edition for your install):
cmd /c '"C:\Program Files\Microsoft Visual Studio\<edition>\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=x64 && cmake --preset arm64-windows-snapdragon-release -B build && cmake --build build -j && cmake --install build --prefix pkg-geniex'Build the SDK inside the derived Snapdragon Linux toolchain container — it extends ghcr.io/snapdragon-toolchain/arm64-linux with build-essential, ccache, rustup, the aarch64-unknown-linux-gnu Rust target, and the cc-rs cross-compiler symlinks baked in (see .github/docker/toolchain-linux.Dockerfile). Run from the repo root.
One-shot:
docker run --rm -u $(id -u):$(id -g) \
--volume $(pwd):/workspace \
--workdir /workspace/sdk \
-e CCACHE_DIR=/workspace/.ccache \
--platform linux/amd64 \
docker.io/qualcomm/geniex-toolchain-linux:v0.2.0 \
bash -c 'cmake --preset arm64-linux-snapdragon-debug -B build-linux . \
&& cmake --build build-linux -j \
&& cmake --install build-linux --prefix pkg-geniex'Interactive:
docker run --rm -it -u $(id -u):$(id -g) \
--volume $(pwd):/workspace \
--workdir /workspace/sdk \
-e CCACHE_DIR=/workspace/.ccache \
--platform linux/amd64 \
docker.io/qualcomm/geniex-toolchain-linux:v0.2.0 bash
# then, inside the container:
cmake --preset arm64-linux-snapdragon-debug -B build-linux .
cmake --build build-linux -j
cmake --install build-linux --prefix pkg-geniexNPU-less Dragonwing IoT boards (unoq) are baseline ARMv8.0 and trap on the LSE
atomics the snapdragon presets inline (see
#1217). Swap the preset for
arm64-linux-cpu-{debug,release} — same container, plain -march=armv8-a, and
CPU-only (no QAIRT, Hexagon, or OpenCL):
cmake --preset arm64-linux-cpu-debug -B build-linux-cpu .
cmake --build build-linux-cpu -j
cmake --install build-linux-cpu --prefix pkg-geniexBuild the SDK inside the derived Snapdragon Android toolchain container — it extends ghcr.io/snapdragon-toolchain/arm64-android with build-essential, ccache, rustup, and the aarch64-linux-android Rust target baked in (see .github/docker/toolchain-android.Dockerfile). Run from the repo root.
One-shot:
docker run --rm -u $(id -u):$(id -g) \
--volume $(pwd):/workspace \
--workdir /workspace/sdk \
-e CCACHE_DIR=/workspace/.ccache \
--platform linux/amd64 \
docker.io/qualcomm/geniex-toolchain-android:v0.2.0 \
bash -c 'cmake --preset arm64-android-snapdragon-debug -B build-android . \
&& cmake --build build-android -j \
&& cmake --install build-android --prefix pkg-geniex'Interactive:
docker run --rm -it -u $(id -u):$(id -g) \
--volume $(pwd):/workspace \
--workdir /workspace/sdk \
-e CCACHE_DIR=/workspace/.ccache \
--platform linux/amd64 \
docker.io/qualcomm/geniex-toolchain-android:v0.2.0 bash
# then, inside the container:
cmake --preset arm64-android-snapdragon-debug -B build-android .
cmake --build build-android -j
cmake --install build-android --prefix pkg-geniexminSdk is 27, so the AAR still targets phones that predate the armv8.7 ISA the
snapdragon presets bake in and trap at startup (see
#1217). Swap the preset for
arm64-android-cpu-{debug,release} — same container, plain -march=armv8-a, and
CPU-only (no QAIRT, Hexagon, or OpenCL):
cmake --preset arm64-android-cpu-debug -B build-android-cpu .
cmake --build build-android-cpu -j
cmake --install build-android-cpu --prefix pkg-geniexbindings/android needs no changes: assembleRelease packages whatever
sdk/pkg-geniex/lib/ holds, so the same gradle project yields the CPU-only AAR
that CI publishes as geniex-android-aar-cpu-<tag>.aar.
Deploy and smoke-test on device:
adb push pkg-geniex /data/local/tmp/geniex
adb push Qwen3-0.6B-Q4_0.gguf /data/local/tmp/geniex/
adb shell "cd /data/local/tmp/geniex && \
LD_LIBRARY_PATH=./lib:./lib/llama_cpp \
ADSP_LIBRARY_PATH=./lib/llama_cpp \
GENIEX_PLUGIN_PATH=./lib \
./bin/geniex-bench --plugin llama_cpp --device npu -m Qwen3-0.6B-Q4_0.gguf"The Android demo app is no longer hosted in this repo — it lives in qualcomm/ai-hub-apps. Build the AAR here, then point the demo app at it.
The qairt plugin currently compiles against QAIRT 2.50.40.260831 QNN C API 2.39 headers vendored in third-party/geniex-qairt/qnn-api/include/; its independent runtime floor remains QNN C API 2.27 (kMinApiMinor = 27). Provider-query checks do not establish end-to-end compatibility with every runtime version. To use another set (e.g. an internal QAIRT checkout), pass -DQAIRT_QNN_HEADERS=... to any preset above:
cmake --preset arm64-windows-snapdragon-release -B build -DQAIRT_QNN_HEADERS=C:\path\to\qairt\include\QNNSee the plugin README for the expected directory shape and caveats.
With the SDK built and installed into sdk/pkg-geniex/, build and run the CLI. Quick smoke test:
bazelisk run //cli -- infer Qwen/Qwen3-0.6B-GGUF
//cliis a convenience alias for//cli/cmd/geniex:geniex. Both are used interchangeably in these docs.
Flags for bazelisk build and bazelisk run:
| Flag | Meaning |
|---|---|
--//sdk:sdk_type={local,s3,bazel} |
SDK source. Only local (default, links against sdk/pkg-geniex) works; s3 and bazel are not wired up yet. |
--config={linux_arm64,windows_arm64} |
Cross-compile to the target platform (Go toolchain + CGO + oci_image base). sdk/pkg-geniex/ must match. |
Development targets:
bazelisk run //cli/release/linux:docker— build and load the Docker image for the Linux release.
Package release artifacts:
| Target | Output |
|---|---|
bazelisk build //cli:artifact |
bazel-bin/cli/artifact.zip |
bazelisk build //cli/release/windows |
bazel-bin/cli/release/windows/geniex-cli-setup.exe |
bazelisk build //cli/release/linux |
bazel-bin/cli/release/linux/geniex-cli-docker.tar |
Generated executable (for manual invocation): bazel-bin/cli/cmd/geniex/geniex_/, with runtime files under geniex.runfiles/_main.
Use bazelisk coverage instead of bazelisk test:
bazelisk coverage //... --combined_report=lcovBazel's default --instrumentation_filter only covers packages that own a test target, so every Go package ships at least a placeholder package_test.go to keep itself in the denominator. Combined lcov lands at bazel-out/_coverage/_coverage_report.dat. Render it with genhtml bazel-out/_coverage/_coverage_report.dat -o coverage-html (gitignored), or summarize on the CLI with lcov --list <report>. lcov 2.x honours LCOV_EXCL_* markers with --filter region --rc c_file_extensions=c,h,cc,cpp,go; lcov 1.x (msys2, older distros) ignores them, so render on Linux with 2.x for a clean report.