Skip to content
Merged
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
67 changes: 52 additions & 15 deletions .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,59 @@ jobs:
- name: Checkout
uses: actions/checkout@v7

- name: Install build dependencies
# In a manylinux_2_28 container rather than on the runner, for its glibc 2.28, which is
# RHEL 8's. A library records the glibc symbol versions of the headers it was compiled
# against, and the loader refuses it anywhere older. Built on the runner's own Ubuntu 24.04
# it needed GLIBC_2.38, so the Linux head could not load it on Ubuntu 22.04, Debian 12, or
# RHEL 8 or 9, and BuildMonitor exited there without opening a window.
- name: Build
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake ninja-build \
libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
libgl1-mesa-dev libglu1-mesa-dev libwayland-dev libxkbcommon-dev
run: docker run --rm -v "$PWD:/src" -w /src "quay.io/pypa/manylinux_2_28_$(uname -m)" bash native/build-linux.sh

- name: Configure
if: matrix.rid != 'osx'
run: cmake -S native -B build -G Ninja -DCMAKE_BUILD_TYPE=Release

- name: Build
if: matrix.rid != 'osx'
run: cmake --build build --config Release
# The floor the container is there to hold, checked rather than trusted, since nothing else
# would notice until a user on an older distribution did.
- name: Check glibc floor
if: runner.os == 'Linux'
env:
GLIBC_FLOOR: GLIBC_2.28
run: |
highest=$(objdump -T build/libbuildmonitor_ui.so | grep -o 'GLIBC_[0-9][0-9.]*' | sort -uV | tail -n 1)
echo "Highest glibc symbol version required: $highest"
if [ "$(printf '%s\n' "$GLIBC_FLOOR" "$highest" | sort -V | tail -n 1)" != "$GLIBC_FLOOR" ]; then
echo "::error::libbuildmonitor_ui.so requires $highest, above the $GLIBC_FLOOR floor"
exit 1
fi
# libGL.so.1 is on every machine with a GL driver; libOpenGL.so.0 is not
if objdump -p build/libbuildmonitor_ui.so | grep -q 'NEEDED.*libOpenGL'; then
echo "::error::libbuildmonitor_ui.so links libOpenGL.so.0 rather than libGL.so.1"
exit 1
fi

# Everything recorded as needed has to be on a machine for the library to load at all, so
# the list is held to what any machine that runs .NET and has a display already has. The
# container's linker records every library it is handed, used or not, and raylib hands on
# libSM and libICE, which a stock Ubuntu lacks. native/CMakeLists.txt links with --as-needed
# so that neither is recorded, and this is what notices the list growing by any other route.
- name: Check needed libraries
if: runner.os == 'Linux'
env:
ALLOWED: >-
libc.so.6 libm.so.6 libdl.so.2 libpthread.so.0 librt.so.1 libstdc++.so.6 libgcc_s.so.1
libX11.so.6 libGL.so.1 ld-linux-x86-64.so.2 ld-linux-aarch64.so.1
run: |
needed=$(objdump -p build/libbuildmonitor_ui.so | awk '$1 == "NEEDED" { print $2 }')
echo "Needed:" $needed
failed=0
for library in $needed; do
case " $ALLOWED " in
*" $library "*) ;;
*)
echo "::error::libbuildmonitor_ui.so needs $library, which is not in the list of libraries it may need"
failed=1
;;
esac
done
exit $failed

# macOS draws with AppKit and Core Text rather than raylib and ImGui, so it is a Swift
# package rather than a CMake project. Both --arch flags in one invocation produce a
Expand Down Expand Up @@ -91,7 +128,7 @@ jobs:
}
case "${{ matrix.rid }}" in
linux-*)
strip build/libbuildmonitor_ui.so
# Already stripped, inside the container that owns the build directory
collect BuildMonitor.Native "${{ matrix.rid }}" build/libbuildmonitor_ui.so
;;
osx)
Expand Down
2 changes: 1 addition & 1 deletion claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ BuildMonitor is a tray app that polls CI services and shows one row per pipeline
- `Auth/` — OAuth browser flow (PKCE + loopback listener), device flow, and per-OS secret stores. Secrets never go in settings.json.
- `Protocol/` — the loopback socket the tray binds (single-instance gate) and the line protocol the launcher and the MCP server use to talk to it.
- `src/BuildMonitor.Windows/` — WinForms head (`BuildMonitor.Tray.exe`): `NotifyIcon` tray, owner-drawn rows, real controls for form pages.
- `src/BuildMonitor.Native/` — macOS and Linux head (`BuildMonitor.Tray`) over the C ABI in `native/include/bm.h`; Swift/AppKit on macOS (`native/swift`), raylib + Dear ImGui on Linux (`native/`). Binaries are committed under `runtimes/{rid}/native` and regenerated by the `build-native` workflow, which opens a PR. The Linux tray is StatusNotifierItem over D-Bus in managed code.
- `src/BuildMonitor.Native/` — macOS and Linux head (`BuildMonitor.Tray`) over the C ABI in `native/include/bm.h`; Swift/AppKit on macOS (`native/swift`), raylib + Dear ImGui on Linux (`native/`). Binaries are committed under `runtimes/{rid}/native` and regenerated by the `build-native` workflow, which opens a PR. The Linux library is built in a `manylinux_2_28` container (`native/build-linux.sh`), so it needs no glibc newer than 2.28: built on the runner's own Ubuntu 24.04 it needed 2.38, and would not load on Ubuntu 22.04 or Debian 12. It is linked with `--as-needed`, since that container's linker otherwise records libSM and libICE, which a stock Ubuntu lacks, and `build-native` fails a build above the floor or one whose `NEEDED` entries are outside a short list. The Linux tray is StatusNotifierItem over D-Bus in managed code.
- `src/BuildMonitor/` — the one package (`dotnet tool install -g BuildMonitor`). A launcher that starts the head for the current RID from `heads/{rid}/`, plus the `mcp` (stdio MCP server) and `status` verbs. It is `WinExe` so the tool shim is GUI subsystem and can sit in the Run-at-login key. Set `BuildMonitor_Head` to a head executable to run a local build through it.
- Heads are bundled, never packaged on their own.

Expand Down
10 changes: 10 additions & 0 deletions native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ target_include_directories(buildmonitor_ui PRIVATE include ${imgui_SOURCE_DIR})
target_compile_definitions(buildmonitor_ui PRIVATE IMGUI_USE_WCHAR32)
target_link_libraries(buildmonitor_ui PRIVATE raylib)

# Only the libraries something is actually called in are recorded as needed. raylib hands on
# everything CMake's FindX11 found, which includes libSM and libICE wherever their headers are
# installed, and nothing here or in raylib calls either. A library recorded as needed has to be
# on the machine for this one to load at all, and a stock Ubuntu has neither of those two.
# Ubuntu's own compiler passes this by default, so a library built on the runner never recorded
# them. The manylinux container build-native builds in does not pass it.
if(UNIX AND NOT APPLE)
target_link_options(buildmonitor_ui PRIVATE LINKER:--as-needed)
endif()

# The managed side probes for a bare name; keep it identical on every platform apart from the
# platform's own prefix and extension.
set_target_properties(buildmonitor_ui PROPERTIES
Expand Down
32 changes: 32 additions & 0 deletions native/build-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Builds build/libbuildmonitor_ui.so inside a manylinux_2_28 container, whose glibc 2.28 is the
# floor the library then needs - see the Linux build step in .github/workflows/build-native.yml.
# Runs from the repository root:
#
# docker run --rm -v "$PWD:/src" -w /src "quay.io/pypa/manylinux_2_28_$(uname -m)" bash native/build-linux.sh
set -euo pipefail

# What raylib's bundled GLFW builds against, for both its X11 and Wayland backends: what the
# runner used to install for it, under this distribution's names.
dnf install -y \
git pkgconfig \
libX11-devel libXext-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel \
mesa-libGL-devel wayland-devel libxkbcommon-devel

# native/CMakeLists.txt needs CMake 3.24, newer than the distribution's own. The image carries
# current releases through pipx, so these are only installed where it does not.
export PATH="$HOME/.local/bin:$PATH"
for tool in cmake ninja; do
if ! command -v "$tool" > /dev/null 2>&1; then
pipx install "$tool"
fi
done

# LEGACY links libGL.so.1, which every GL driver ships. This distribution's CMake otherwise
# prefers the GLVND split and links libOpenGL.so.0, which a machine with only libgl1 lacks.
cmake -S native -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DOpenGL_GL_PREFERENCE=LEGACY
cmake --build build --config Release

# Here rather than in the workflow's collect step: the build directory belongs to this
# container's root, and the runner cannot rewrite what is in it.
strip build/libbuildmonitor_ui.so
Binary file not shown.
Binary file not shown.
Loading