Skip to content

Commit 75cae60

Browse files
committed
build(webui): build llama.cpp WebUI once in CI, share to all native builds
Implements the build-once-share-artifact approach for embedding the llama.cpp Svelte WebUI into libjllama, so the in-process server (server-http.cpp) serves the real UI instead of the empty-asset stub. The repo commits no build outputs, so the WebUI is produced per-pipeline and never checked in (same policy as the native libs). CI (.github/workflows/publish.yml): - New build-webui job (ubuntu; the only job that runs npm): resolves the pinned b<nnnn> tag from CMakeLists.txt GIT_TAG, sparse-checks-out ggml-org/llama.cpp@<tag> tools/ui, runs the upstream Svelte build (npm ci && npm run build), gzips dist/ (LLAMA_UI_GZIP parity), builds the self-contained llama-ui-embed host tool (plain C++17, no npm) and runs it to produce the platform-independent webui-generated/ ui.cpp + ui.h, uploaded as the webui-generated artifact. - All 10 release-artifact build jobs now use needs:[startgate, build-webui] and download the artifact into webui-generated/ before building. npm never runs in the dockcross cross-compilers (no node) or per-platform — only once, in one job. CMake (CMakeLists.txt "WebUI assets" block): - When webui-generated/ui.cpp + ui.h are present, compile ui.cpp in and add its dir to the include path; the generated ui.h #defines LLAMA_UI_HAS_ASSETS, activating server-http.cpp's static-asset routes (compiled out under the stub). When absent, fall back to the empty-asset stub webui_stub/ui.h so local builds and any job without the artifact still build and run (no embedded UI). The WebUI version auto-follows GIT_TAG, so a llama.cpp bump needs no extra step. webui-generated/ is git-ignored; CLAUDE.md documents the pipeline + a local recipe. Verified locally (Linux x86_64) with the real llama-ui-embed tool (no npm) on a synthetic 9-asset dist: generated ui.cpp/ui.h carry LLAMA_UI_HAS_ASSETS + use_gzip (9 assets); jllama rebuilds with server-http.cpp's asset routes compiled in, ui.cpp compiled, libjllama.so linked (llama_ui_get_assets/find_asset defined, 0 undefined); NativeLibraryLoadSmokeTest passes; removing webui-generated/ -> CMake reports the stub fallback and jllama still builds. publish.yml parses (pyyaml); exactly the 10 native build jobs gate on build-webui. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdLpWD8nedY7LwNnHefZLF
1 parent c10a6ec commit 75cae60

4 files changed

Lines changed: 218 additions & 17 deletions

File tree

.github/workflows/publish.yml

Lines changed: 141 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,98 @@ jobs:
8080
echo "=== internal package dependency graph (jdeps, bytecode) ==="
8181
jdeps -verbose:package target/classes | grep 'net.ladenthin.llama' || true
8282
83+
# ---------------------------------------------------------------------------
84+
# Build the llama.cpp WebUI ONCE, from the same pinned tag CMakeLists.txt fetches,
85+
# and share it to every native build as the generated, platform-independent
86+
# ui.cpp/ui.h ("webui-generated" artifact). The native builds embed it into
87+
# libjllama (CMake's "WebUI assets" block); when this job's artifact is absent the
88+
# build falls back to the empty-asset stub. npm runs only here, in one controlled
89+
# job — never in the dockcross cross-compilers (which have no node) or per-platform.
90+
# ---------------------------------------------------------------------------
91+
build-webui:
92+
name: Build WebUI assets (shared)
93+
needs: startgate
94+
runs-on: ubuntu-latest
95+
steps:
96+
- uses: actions/checkout@v6
97+
- name: Resolve pinned llama.cpp tag from CMakeLists.txt
98+
id: tag
99+
shell: bash
100+
run: |
101+
TAG=$(grep -oE 'GIT_TAG[[:space:]]+b[0-9]+' CMakeLists.txt | grep -oE 'b[0-9]+' | head -1)
102+
if [ -z "$TAG" ]; then
103+
echo "could not resolve llama.cpp GIT_TAG (b<nnnn>) from CMakeLists.txt" >&2
104+
exit 1
105+
fi
106+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
107+
echo "Pinned llama.cpp WebUI tag: $TAG"
108+
- name: Checkout llama.cpp tools/ui at the pinned tag
109+
uses: actions/checkout@v6
110+
with:
111+
repository: ggml-org/llama.cpp
112+
ref: ${{ steps.tag.outputs.tag }}
113+
path: llamacpp-ui
114+
sparse-checkout: tools/ui
115+
sparse-checkout-cone-mode: true
116+
- uses: actions/setup-node@v6
117+
with:
118+
node-version: '24'
119+
cache: npm
120+
cache-dependency-path: llamacpp-ui/tools/ui/package-lock.json
121+
- name: Build WebUI (Svelte/Vite)
122+
working-directory: llamacpp-ui/tools/ui
123+
env:
124+
HF_UI_VERSION: ${{ steps.tag.outputs.tag }}
125+
LLAMA_BUILD_NUMBER: ${{ steps.tag.outputs.tag }}
126+
run: |
127+
npm ci
128+
npm run build
129+
test -f dist/index.html
130+
- name: Embed assets into ui.cpp / ui.h (gzip parity with upstream)
131+
working-directory: llamacpp-ui/tools/ui
132+
shell: bash
133+
run: |
134+
set -euo pipefail
135+
# gzip every asset into dist/_gzip/<path> so llama-ui-embed embeds the
136+
# compressed bytes (LLAMA_UI_GZIP parity); embed auto-detects _gzip.
137+
( cd dist && find . -type f -not -path './_gzip/*' | while read -r f; do
138+
mkdir -p "_gzip/$(dirname "$f")"
139+
gzip -9 -c "$f" > "_gzip/$f"
140+
done )
141+
# llama-ui-embed is a self-contained C++17 host tool (no npm) — build + run it.
142+
g++ -O2 -std=c++17 -o llama-ui-embed embed.cpp
143+
mkdir -p "$GITHUB_WORKSPACE/webui-generated"
144+
./llama-ui-embed \
145+
"$GITHUB_WORKSPACE/webui-generated/ui.cpp" \
146+
"$GITHUB_WORKSPACE/webui-generated/ui.h" \
147+
dist
148+
echo "=== generated WebUI assets ==="
149+
ls -la "$GITHUB_WORKSPACE/webui-generated"
150+
if grep -q LLAMA_UI_HAS_ASSETS "$GITHUB_WORKSPACE/webui-generated/ui.h"; then
151+
echo "LLAMA_UI_HAS_ASSETS: present (real WebUI embedded)"
152+
else
153+
echo "ERROR: embed produced an empty asset table" >&2
154+
exit 1
155+
fi
156+
- name: Upload WebUI artifact
157+
uses: actions/upload-artifact@v7
158+
with:
159+
name: webui-generated
160+
path: ${{ github.workspace }}/webui-generated/
161+
retention-days: 1
162+
if-no-files-found: error
163+
83164
crosscompile-linux-x86_64-cuda:
84165
name: Cross-Compile manylinux_2_28 x86_64 (CUDA)
85-
needs: startgate
166+
needs: [startgate, build-webui]
86167
runs-on: ubuntu-latest
87168
steps:
88169
- uses: actions/checkout@v6
170+
- name: Download shared WebUI assets
171+
uses: actions/download-artifact@v8
172+
with:
173+
name: webui-generated
174+
path: ${{ github.workspace }}/webui-generated/
89175
- name: Display CPU Info
90176
shell: bash
91177
run: |
@@ -106,10 +192,15 @@ jobs:
106192

107193
crosscompile-linux-x86_64:
108194
name: Cross-Compile manylinux2014 x86_64
109-
needs: startgate
195+
needs: [startgate, build-webui]
110196
runs-on: ubuntu-latest
111197
steps:
112198
- uses: actions/checkout@v6
199+
- name: Download shared WebUI assets
200+
uses: actions/download-artifact@v8
201+
with:
202+
name: webui-generated
203+
path: ${{ github.workspace }}/webui-generated/
113204
- name: Display CPU Info
114205
shell: bash
115206
run: |
@@ -130,10 +221,15 @@ jobs:
130221

131222
crosscompile-linux-aarch64:
132223
name: Cross-Compile Linux aarch64 (LTS)
133-
needs: startgate
224+
needs: [startgate, build-webui]
134225
runs-on: ubuntu-latest
135226
steps:
136227
- uses: actions/checkout@v6
228+
- name: Download shared WebUI assets
229+
uses: actions/download-artifact@v8
230+
with:
231+
name: webui-generated
232+
path: ${{ github.workspace }}/webui-generated/
137233
- name: Display CPU Info
138234
shell: bash
139235
run: |
@@ -154,10 +250,15 @@ jobs:
154250

155251
crosscompile-android-aarch64:
156252
name: Cross-Compile Android aarch64
157-
needs: startgate
253+
needs: [startgate, build-webui]
158254
runs-on: ubuntu-latest
159255
steps:
160256
- uses: actions/checkout@v6
257+
- name: Download shared WebUI assets
258+
uses: actions/download-artifact@v8
259+
with:
260+
name: webui-generated
261+
path: ${{ github.workspace }}/webui-generated/
161262
- name: Display CPU Info
162263
shell: bash
163264
run: |
@@ -178,10 +279,15 @@ jobs:
178279

179280
crosscompile-android-aarch64-opencl:
180281
name: Cross-Compile Android aarch64 (OpenCL/Adreno)
181-
needs: startgate
282+
needs: [startgate, build-webui]
182283
runs-on: ubuntu-latest
183284
steps:
184285
- uses: actions/checkout@v6
286+
- name: Download shared WebUI assets
287+
uses: actions/download-artifact@v8
288+
with:
289+
name: webui-generated
290+
path: ${{ github.workspace }}/webui-generated/
185291
- name: Build libraries
186292
shell: bash
187293
run: |
@@ -198,10 +304,15 @@ jobs:
198304

199305
build-macos-arm64-no-metal:
200306
name: Build and Test macOS 15 arm64 (no Metal)
201-
needs: startgate
307+
needs: [startgate, build-webui]
202308
runs-on: macos-15
203309
steps:
204310
- uses: actions/checkout@v6
311+
- name: Download shared WebUI assets
312+
uses: actions/download-artifact@v8
313+
with:
314+
name: webui-generated
315+
path: ${{ github.workspace }}/webui-generated/
205316
- uses: actions/setup-java@v5
206317
with:
207318
distribution: 'temurin'
@@ -229,10 +340,15 @@ jobs:
229340

230341
build-macos-arm64-metal:
231342
name: Build and Test macOS 14 arm64 (Metal)
232-
needs: startgate
343+
needs: [startgate, build-webui]
233344
runs-on: macos-14
234345
steps:
235346
- uses: actions/checkout@v6
347+
- name: Download shared WebUI assets
348+
uses: actions/download-artifact@v8
349+
with:
350+
name: webui-generated
351+
path: ${{ github.workspace }}/webui-generated/
236352
- uses: actions/setup-java@v5
237353
with:
238354
distribution: 'temurin'
@@ -260,10 +376,15 @@ jobs:
260376

261377
build-windows-x86_64:
262378
name: Build and Test Windows 2025 x86_64 (VS 2026)
263-
needs: startgate
379+
needs: [startgate, build-webui]
264380
runs-on: windows-2025-vs2026
265381
steps:
266382
- uses: actions/checkout@v6
383+
- name: Download shared WebUI assets
384+
uses: actions/download-artifact@v8
385+
with:
386+
name: webui-generated
387+
path: ${{ github.workspace }}/webui-generated/
267388
- name: Display CPU Info
268389
shell: pwsh
269390
run: |
@@ -289,10 +410,15 @@ jobs:
289410

290411
build-windows-x86:
291412
name: Build and Test Windows 2025 x86 (VS 2026)
292-
needs: startgate
413+
needs: [startgate, build-webui]
293414
runs-on: windows-2025-vs2026
294415
steps:
295416
- uses: actions/checkout@v6
417+
- name: Download shared WebUI assets
418+
uses: actions/download-artifact@v8
419+
with:
420+
name: webui-generated
421+
path: ${{ github.workspace }}/webui-generated/
296422
- name: Display CPU Info
297423
shell: pwsh
298424
run: |
@@ -346,10 +472,15 @@ jobs:
346472

347473
build-macos-arm64-metal-15:
348474
name: Build and Test macOS 15 arm64 (Metal)
349-
needs: startgate
475+
needs: [startgate, build-webui]
350476
runs-on: macos-15
351477
steps:
352478
- uses: actions/checkout@v6
479+
- name: Download shared WebUI assets
480+
uses: actions/download-artifact@v8
481+
with:
482+
name: webui-generated
483+
path: ${{ github.workspace }}/webui-generated/
353484
- uses: actions/setup-java@v5
354485
with:
355486
distribution: 'temurin'

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ src/main/resources/**/*.dll
4646
src/main/resources/**/*.metal
4747
src/test/resources/**/*.gbnf
4848

49+
# Generated WebUI assets (ui.cpp/ui.h) produced once by the build-webui CI job and
50+
# downloaded into every native build; embedded into libjllama, never committed
51+
# (this repo commits no build outputs — same policy as the native libs above).
52+
/webui-generated/
53+
4954
**/*.etag
5055
**/*.lastModified
5156
src/main/cpp/llama.cpp/

CLAUDE.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,50 @@ At runtime the device must provide its own OpenCL ICD (`libOpenCL.so`);
121121
Qualcomm Adreno drivers do. Devices without an ICD should use the default
122122
CPU-only Android JAR.
123123

124+
## WebUI (llama.cpp Svelte UI) embedding
125+
126+
The llama.cpp WebUI is **built once in CI and shared to every native build**, then
127+
compiled into `libjllama` so the embedded server (`server-http.cpp`) can serve it.
128+
This repo commits no build outputs, so the assets are produced per-pipeline, never
129+
checked in (same policy as the native libs).
130+
131+
Pipeline (`.github/workflows/publish.yml`):
132+
133+
1. **`build-webui` job** (ubuntu — the *only* job that runs `npm`): resolves the
134+
pinned `b<nnnn>` tag from `CMakeLists.txt`'s `GIT_TAG`, sparse-checks-out
135+
`ggml-org/llama.cpp@<tag>` `tools/ui`, runs the upstream Svelte build
136+
(`npm ci && npm run build`), gzips `dist/` into `dist/_gzip/` (LLAMA_UI_GZIP
137+
parity), builds the self-contained `llama-ui-embed` host tool (plain C++17, **no
138+
npm**) and runs it to produce the platform-independent **`webui-generated/ui.cpp`
139+
+ `ui.h`**, uploaded as the `webui-generated` artifact.
140+
2. **Every native build job** (`needs: [startgate, build-webui]`) downloads that
141+
artifact into `webui-generated/` before building. npm never runs in the dockcross
142+
cross-compilers (which have no node) or per-platform.
143+
3. **CMake** (the "WebUI assets" block in `CMakeLists.txt`): if
144+
`webui-generated/ui.cpp` + `ui.h` exist, compiles `ui.cpp` in and adds its dir to
145+
the include path — the generated `ui.h` `#define`s `LLAMA_UI_HAS_ASSETS`, which
146+
activates `server-http.cpp`'s static-asset routes. If absent, it falls back to the
147+
empty-asset stub `src/main/cpp/webui_stub/ui.h` (no embedded UI) so local builds —
148+
and any job without the artifact — still build and run.
149+
150+
The WebUI version **auto-follows** the pinned `GIT_TAG`: a llama.cpp version bump
151+
needs no extra step here, `build-webui` re-reads the tag and rebuilds the matching UI.
152+
153+
**Building the WebUI locally** (optional — a plain `cmake` build uses the stub and
154+
ships no UI):
155+
```bash
156+
# needs node/npm + network; embed.cpp is plain C++17 (no npm)
157+
git clone --depth 1 --branch b9682 https://github.com/ggml-org/llama.cpp /tmp/lc
158+
( cd /tmp/lc/tools/ui && npm ci && npm run build \
159+
&& ( cd dist && find . -type f -not -path './_gzip/*' \
160+
| while read -r f; do mkdir -p "_gzip/$(dirname "$f")"; gzip -9 -c "$f" > "_gzip/$f"; done ) \
161+
&& g++ -O2 -std=c++17 -o /tmp/llama-ui-embed embed.cpp )
162+
mkdir -p webui-generated
163+
/tmp/llama-ui-embed webui-generated/ui.cpp webui-generated/ui.h /tmp/lc/tools/ui/dist
164+
cmake -B build && cmake --build build --target jllama # now embeds the real UI
165+
```
166+
`webui-generated/` is git-ignored.
167+
124168
## Upgrading/Downgrading llama.cpp Version
125169

126170
To change the llama.cpp version, update the following **three** files:

CMakeLists.txt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,13 @@ endif()
286286
# and link.
287287
#
288288
# server-http.cpp does `#include "ui.h"` — the WebUI asset table that tools/ui
289-
# normally GENERATES. We do not ship the Svelte WebUI (it needs npm / a prebuilt
290-
# asset download), so src/main/cpp/webui_stub/ui.h supplies the upstream "empty
291-
# asset table" interface instead (see that file). <cpp-httplib/httplib.h> already
292-
# resolves via llama-common's vendor/ include dir, whose bundled nlohmann/json is
293-
# the same 3.12.0 as our FetchContent copy, so adding nothing there shadows it.
289+
# normally GENERATES. The WebUI is built once in CI (the build-webui job) and
290+
# shared to every native build as a generated, platform-independent ui.cpp/ui.h;
291+
# the "WebUI assets" block below compiles it in when present and otherwise falls
292+
# back to the empty-asset stub (src/main/cpp/webui_stub/ui.h).
293+
# <cpp-httplib/httplib.h> already resolves via llama-common's vendor/ include dir,
294+
# whose bundled nlohmann/json is the same 3.12.0 as our FetchContent copy, so
295+
# adding nothing there shadows it.
294296
target_sources(jllama PRIVATE
295297
${llama.cpp_SOURCE_DIR}/tools/server/server-http.cpp
296298
${llama.cpp_SOURCE_DIR}/vendor/cpp-httplib/httplib.cpp
@@ -315,14 +317,33 @@ if(WIN32 AND NOT MSVC)
315317
target_link_libraries(jllama PRIVATE ws2_32)
316318
endif()
317319

320+
# WebUI assets. The llama.cpp WebUI is built once in CI by the build-webui job
321+
# (upstream Svelte build + llama-ui-embed, run against the pinned llama.cpp tag)
322+
# and shared to every native build as the generated, platform-independent
323+
# ui.cpp / ui.h under webui-generated/ (git-ignored; downloaded as the
324+
# "webui-generated" artifact — this repo commits no build outputs). When present,
325+
# compile it in so libjllama serves the real WebUI: the generated ui.h #defines
326+
# LLAMA_UI_HAS_ASSETS, which activates server-http.cpp's static-asset routes. When
327+
# absent (local builds, or any job without the artifact) fall back to the empty-
328+
# asset stub so the server still builds and runs, just without an embedded WebUI.
329+
set(JLLAMA_WEBUI_GENERATED_DIR ${CMAKE_SOURCE_DIR}/webui-generated)
330+
if(EXISTS ${JLLAMA_WEBUI_GENERATED_DIR}/ui.cpp AND EXISTS ${JLLAMA_WEBUI_GENERATED_DIR}/ui.h)
331+
message(STATUS "WebUI: embedding generated assets from ${JLLAMA_WEBUI_GENERATED_DIR}")
332+
target_sources(jllama PRIVATE ${JLLAMA_WEBUI_GENERATED_DIR}/ui.cpp)
333+
target_include_directories(jllama PRIVATE ${JLLAMA_WEBUI_GENERATED_DIR})
334+
else()
335+
message(STATUS "WebUI: no generated assets found; using empty-asset stub (no embedded WebUI)")
336+
target_include_directories(jllama PRIVATE ${CMAKE_SOURCE_DIR}/src/main/cpp/webui_stub)
337+
endif()
338+
318339
set_target_properties(jllama PROPERTIES POSITION_INDEPENDENT_CODE ON)
319340
target_include_directories(jllama PRIVATE
320341
src/main/cpp
321-
# webui_stub/ui.h stands in for the generated llama-ui header (see Phase 2 above)
322-
src/main/cpp/webui_stub
323342
${JNI_INCLUDE_DIRS}
324343
${llama.cpp_SOURCE_DIR}/tools/mtmd
325344
${llama.cpp_SOURCE_DIR}/tools/server)
345+
# Note: the WebUI ui.h include dir (generated webui-generated/ or the empty stub
346+
# src/main/cpp/webui_stub) is added by the "WebUI assets" conditional above.
326347
target_link_libraries(jllama PRIVATE llama-common mtmd llama nlohmann_json)
327348
target_compile_features(jllama PRIVATE cxx_std_11)
328349

0 commit comments

Comments
 (0)