diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml
index dd6033f..6fa5f70 100644
--- a/.github/workflows/build-native.yml
+++ b/.github/workflows/build-native.yml
@@ -7,6 +7,12 @@ name: Build native
# They are committed rather than built during a normal build so that a plain
# `dotnet build src --configuration Release` produces a shippable package on any machine, and
# contributors never need a C++ toolchain. Run this whenever native/ changes.
+#
+# The work is PlatformHeads' build-native workflow, which DiffEngine's viewer is built by too: the
+# Linux library built in a manylinux_2_28 container and checked for the glibc it needs and the
+# libraries it records as needed, the universal dylib, and the pull request with the binaries.
+# Each of those was once a copy here, and a fix made to the other copy did not reach this one.
+# The tag is the one native/CMakeLists.txt fetches and Directory.Packages.props names.
on:
push:
paths:
@@ -27,206 +33,22 @@ concurrency:
cancel-in-progress: true
jobs:
- build:
- name: ${{ matrix.rid }}
- runs-on: ${{ matrix.os }}
- timeout-minutes: 45
- strategy:
- fail-fast: false
- matrix:
- include:
- # No Windows entries. That head renders with WinForms and loads no native library.
- - rid: linux-x64
- os: ubuntu-24.04
- - rid: linux-arm64
- os: ubuntu-24.04-arm
- # One universal binary covers both macOS RIDs; it is copied into each below.
- - rid: osx
- os: macos-14
- steps:
- - name: Checkout
- uses: actions/checkout@v7
-
- # 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: docker run --rm -v "$PWD:/src" -w /src "quay.io/pypa/manylinux_2_28_$(uname -m)" bash native/build-linux.sh
-
- # 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
- # universal binary, so there is no separate lipo step.
- - name: Build
- if: matrix.rid == 'osx'
- run: swift build -c release --arch arm64 --arch x86_64 --package-path native/swift
-
- # The folder panel, and turning AppKit's frames into the ABI's placements, are what this head
- # decides by itself rather than drawing from a screen the managed side built, so they are
- # what has tests here. A scratch path of its
- # own, because a test build is debug and for this machine's architecture alone: left in .build
- # its dylib would be among the candidates the collect step below merges.
- - name: Test
- if: matrix.rid == 'osx'
- run: swift test --package-path native/swift --scratch-path native/swift/.build-tests
-
- - name: Collect
- shell: bash
- run: |
- # Laid out as src/{head}/runtimes/{rid}/native, so the propose job below can merge every
- # artifact straight into src and the binaries land in the head that loads them.
- collect() {
- mkdir -p "artifacts/$1/runtimes/$2/native"
- cp "$3" "artifacts/$1/runtimes/$2/native/"
- }
- case "${{ matrix.rid }}" in
- linux-*)
- # Already stripped, inside the container that owns the build directory
- collect BuildMonitor.Native "${{ matrix.rid }}" build/libbuildmonitor_ui.so
- ;;
- osx)
- # swift build leaves a per architecture dylib in more than one place. Every candidate
- # is checked, and if none is already universal they are merged, so the only thing
- # that can be collected is a fat binary. The dSYM holds a DWARF file of the same
- # name which is also universal, so it is excluded by path.
- candidates=$(find native/swift/.build -name libbuildmonitor_ui.dylib -not -path '*.dSYM/*')
- if [ -z "$candidates" ]; then
- echo "::error::swift build produced no libbuildmonitor_ui.dylib"
- exit 1
- fi
-
- dylib=""
- for candidate in $candidates; do
- archs=$(lipo -archs "$candidate" 2>/dev/null || echo "")
- if [[ "$archs" == *x86_64* && "$archs" == *arm64* ]]; then
- dylib="$candidate"
- break
- fi
- done
-
- if [ -z "$dylib" ]; then
- dylib=universal/libbuildmonitor_ui.dylib
- mkdir -p universal
- # shellcheck disable=SC2086
- lipo -create $candidates -output "$dylib"
- fi
-
- archs=$(lipo -archs "$dylib")
- echo "collecting $dylib: $archs"
- for arch in x86_64 arm64; do
- case " $archs " in
- *" $arch "*) ;;
- *) echo "::error::$dylib is missing the $arch slice"; exit 1 ;;
- esac
- done
-
- strip -x "$dylib"
- # The dylib is universal, so both macOS RIDs get the same file.
- collect BuildMonitor.Native osx-x64 "$dylib"
- collect BuildMonitor.Native osx-arm64 "$dylib"
- ;;
- esac
- ls -lhR artifacts
-
- - name: Upload
- uses: actions/upload-artifact@v7
- with:
- name: native-${{ matrix.rid }}
- path: artifacts/
- retention-days: 7
-
- propose:
- name: propose update
- needs: build
- # Also on push, not just manual dispatch. workflow_dispatch does not appear in the Actions UI
- # until the workflow is on the default branch, so on a feature branch a push is the only way
- # to trigger this.
- #
- # Branches only. Never on pull_request, which would mean opening a PR from a PR, and never on
- # a tag: a tag checkout is a detached HEAD with no base branch to target.
- if: >-
- github.event_name != 'pull_request' &&
- startsWith(github.ref, 'refs/heads/') &&
- (github.event_name != 'workflow_dispatch' || inputs.commit)
- runs-on: ubuntu-latest
+ native:
+ uses: SimonCropp/PlatformHeads/.github/workflows/build-native.yml@0.1.0
permissions:
contents: write
pull-requests: write
- steps:
- - name: Checkout
- uses: actions/checkout@v7
-
- - name: Download
- uses: actions/download-artifact@v8
- with:
- pattern: native-*
- merge-multiple: true
- path: src
-
- - name: Show what changed
- run: |
- ls -lhR src/BuildMonitor.Native/runtimes
- git status --short
-
- # A PR rather than a direct push: these are binaries, so the diff is not reviewable and the
- # change deserves an explicit approval.
- - name: Open pull request
- uses: peter-evans/create-pull-request@v8
- with:
- branch: native-binaries-${{ github.ref_name }}
- title: 'Rebuild native UI binaries'
- commit-message: 'Rebuild native UI binaries'
- body: |
- Rebuilt `buildmonitor_ui` from `native/` for the four RIDs that load one.
- Windows is not among them: that head renders with WinForms.
-
- Produced by the `build-native` workflow from ${{ github.sha }}.
- add-paths: |
- src/BuildMonitor.Native/runtimes
+ with:
+ library: buildmonitor_ui
+ linux-runtimes: src/BuildMonitor.Native/runtimes
+ swift-package: native/swift
+ mac-runtimes: src/BuildMonitor.Native/runtimes
+ # The folder panel, and turning AppKit's frames into the ABI's placements, are what the
+ # macOS head decides by itself rather than drawing from a screen the managed side built, so
+ # they are what has tests there.
+ swift-test: true
+ # Also on push, not just manual dispatch. workflow_dispatch does not appear in the Actions
+ # UI until the workflow is on the default branch, so on a feature branch a push is the only
+ # way to trigger this.
+ propose: ${{ github.event_name != 'workflow_dispatch' || inputs.commit }}
+ pull-request-title: Rebuild native UI binaries
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 26dbec6..cf77038 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -70,7 +70,7 @@ jobs:
if-no-files-found: ignore
retention-days: 14
- # BM0001 warns when a RID has no native renderer. Shipping a package that cannot run on a
+ # PH0001 warns when a RID has no native renderer. Shipping a package that cannot run on a
# platform it claims to support is worse than failing the release.
- name: Verify every RID has a native renderer
if: startsWith(github.ref, 'refs/tags/')
diff --git a/claude.md b/claude.md
index 589d9cd..ae49cfb 100644
--- a/claude.md
+++ b/claude.md
@@ -61,7 +61,8 @@ 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 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.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, 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.
+- The build and packaging of the heads is PlatformHeads' (`D:\Code\PlatformHeads`, github.com/SimonCropp/PlatformHeads), which DiffEngine's viewer is to share: it was a copy here, and fixes made to DiffEngine's copy did not reach this one. Three pins name one tag of it and move together: the `uses:` of `.github/workflows/build-native.yml`, the `GIT_TAG` in `native/CMakeLists.txt`, and `PlatformHeads` in `Directory.Packages.props`. The workflow is its reusable `build-native`; the CMake module pins raylib and Dear ImGui and carries the raylib 6.0 option workarounds; the package's targets publish and bundle the `PlatformHead` items of `BuildMonitor.csproj` (`PH0001` for a native head with no library, `PH0002` for an apphost that was not staged). A fix to any of that goes there, not here. To build the Linux library by hand, from the repository root: `docker run --rm -v "$PWD:/src" -v "D:/Code/PlatformHeads/native:/ph:ro" -w /src -e LIBRARY=buildmonitor_ui "quay.io/pypa/manylinux_2_28_$(uname -m)" bash /ph/build-linux.sh`.
- `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.
diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt
index 80bd485..176e2d0 100644
--- a/native/CMakeLists.txt
+++ b/native/CMakeLists.txt
@@ -1,137 +1,28 @@
cmake_minimum_required(VERSION 3.24)
project(buildmonitor_ui LANGUAGES C CXX)
-set(CMAKE_C_STANDARD 11)
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-set(CMAKE_C_VISIBILITY_PRESET hidden)
-set(CMAKE_CXX_VISIBILITY_PRESET hidden)
-set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
-
-if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
- set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
-endif()
-
#
-# Sources are fetched rather than vendored as submodules.
+# Everything about this build that is not BuildMonitor's own is PlatformHeads', fetched at a tag:
+# the raylib and Dear ImGui it pins, the raylib 6.0 options it switches off, and how the library
+# is compiled and linked. It was a copy of DiffEngine's here, and three fixes made to that copy
+# were found again in this one, each by a user's machine: a library that needed glibc 2.38, a
+# window that stayed blank, and libraries recorded as needed that a stock Ubuntu lacks.
#
-# Nothing in a normal `dotnet build` touches this directory: the built binaries are committed to
-# the runtimes folder of the native head, so only this file's own build needs raylib and imgui.
-# The versions are pinned by tag here, in the same file as the build configuration.
+# The tag is the one .github/workflows/build-native.yml calls and Directory.Packages.props names.
#
include(FetchContent)
-
-# raylib, static so the shipped artifact is a single file per RID.
-set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
-set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
-set(BUILD_GAMES OFF CACHE BOOL "" FORCE)
-set(CUSTOMIZE_BUILD ON CACHE BOOL "" FORCE)
-# raylib 6.0 reads every SUPPORT_ flag in its config.h into an option that defaults to ON, the ones
-# config.h defines as 0 included (raysan5/raylib#5844, fixed after 6.0), and CUSTOMIZE_BUILD skips
-# config.h's own values. So each of those is on unless it is switched off here.
-# SUPPORT_CUSTOM_FRAME_CONTROL was: EndDrawing then neither puts the frame on the screen, reads
-# input nor waits for the next frame, and bm_present leaves all three to it for every frame it
-# draws. So the Linux window stayed black, and from the first input bm_present drew one frame
-# after another with no wait between them and read nothing more. A capture draws into a texture
-# and never gets that far, which is why the pixel snapshots passed throughout;
-# PixelTests.PresentWaitsForTheNextFrame is what notices. The busy wait loop was on the same way,
-# and WaitTime is how bm_present waits out a frame it does not draw, so a tray with its window
-# hidden spun through every one of them: a seventh of a core, for as long as it ran. The rest are
-# formats and features nothing here uses.
-foreach(flag IN ITEMS
- SUPPORT_CUSTOM_FRAME_CONTROL
- SUPPORT_BUSY_WAIT_LOOP
- SUPPORT_GPU_SKINNING
- SUPPORT_FILEFORMAT_TGA
- SUPPORT_FILEFORMAT_JPG
- SUPPORT_FILEFORMAT_PSD
- SUPPORT_FILEFORMAT_HDR
- SUPPORT_FILEFORMAT_PIC
- SUPPORT_FILEFORMAT_PNM
- SUPPORT_FILEFORMAT_KTX
- SUPPORT_FILEFORMAT_ASTC
- SUPPORT_FILEFORMAT_PKM
- SUPPORT_FILEFORMAT_PVR
- SUPPORT_FILEFORMAT_BDF
- SUPPORT_FILEFORMAT_FLAC)
- set(${flag} OFF CACHE BOOL "" FORCE)
-endforeach()
-# raylib spins for the last part of every wait by default, for timing precision nothing here
-# needs: most of a millisecond of a core per frame, for as long as the tray runs.
-set(SUPPORT_PARTIALBUSY_WAIT_LOOP OFF CACHE BOOL "" FORCE)
-# F12 saves a screenshot into the working directory, which is wherever the tray was started from.
-# raylib looks for the key at the end of a frame it has drawn, so that is F12 pressed while frames
-# are being drawn, as they are for as long as the pointer is crossing the window.
-set(SUPPORT_SCREEN_CAPTURE OFF CACHE BOOL "" FORCE)
-set(SUPPORT_MODULE_RAUDIO OFF CACHE BOOL "" FORCE)
-set(SUPPORT_MODULE_RMODELS OFF CACHE BOOL "" FORCE)
-# PNG is the one picture format anything here reads or writes: a row's icons arrive as PNG, and a
-# capture is saved as one. The other four that config.h has on, nothing here loads or saves.
-foreach(flag IN ITEMS
- SUPPORT_FILEFORMAT_BMP
- SUPPORT_FILEFORMAT_GIF
- SUPPORT_FILEFORMAT_QOI
- SUPPORT_FILEFORMAT_DDS)
- set(${flag} OFF CACHE BOOL "" FORCE)
-endforeach()
-set(SUPPORT_FILEFORMAT_PNG ON CACHE BOOL "" FORCE)
-
FetchContent_Declare(
- raylib
- GIT_REPOSITORY https://github.com/raysan5/raylib.git
- GIT_TAG 6.0
+ platformheads
+ GIT_REPOSITORY https://github.com/SimonCropp/PlatformHeads.git
+ GIT_TAG 0.1.0
GIT_SHALLOW ON)
+FetchContent_MakeAvailable(platformheads)
+include(${platformheads_SOURCE_DIR}/cmake/PlatformHeads.cmake)
-# imgui has no CMake build of its own, so it is fetched as plain sources and compiled below.
-FetchContent_Declare(
- imgui
- GIT_REPOSITORY https://github.com/ocornut/imgui.git
- GIT_TAG v1.92.9b
- GIT_SHALLOW ON)
+platformheads_defaults()
-FetchContent_MakeAvailable(raylib)
-FetchContent_MakeAvailable(imgui)
-
-add_library(buildmonitor_ui SHARED
- src/bm.cpp
- ${imgui_SOURCE_DIR}/imgui.cpp
- ${imgui_SOURCE_DIR}/imgui_draw.cpp
- ${imgui_SOURCE_DIR}/imgui_tables.cpp
- ${imgui_SOURCE_DIR}/imgui_widgets.cpp)
-
-target_include_directories(buildmonitor_ui PRIVATE include ${imgui_SOURCE_DIR})
-# ImWchar is sixteen bits unless this is defined, and the robot a Dependabot row carries is
-# U+1F916. Without it the codepoint does not survive ImGui's own UTF-8 decoding, whatever font is
-# loaded. Set on the target, so imgui's own sources are compiled with it too: it changes a type
-# they share with this one.
-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
- OUTPUT_NAME "buildmonitor_ui"
- C_VISIBILITY_PRESET hidden
- CXX_VISIBILITY_PRESET hidden)
-
-if(MSVC)
- target_compile_options(buildmonitor_ui PRIVATE /W3 /permissive-)
- target_compile_definitions(buildmonitor_ui PRIVATE _CRT_SECURE_NO_WARNINGS)
-else()
- target_compile_options(buildmonitor_ui PRIVATE -Wall -Wextra -Wno-unused-parameter)
-endif()
+# PNG is the one picture format anything here reads or writes: a row's icons arrive as PNG, and a
+# capture is saved as one.
+platformheads_fetch(FORMATS PNG)
-if(APPLE)
- set_target_properties(buildmonitor_ui PROPERTIES SUFFIX ".dylib")
-endif()
+platformheads_add_library(buildmonitor_ui SOURCES src/bm.cpp)
diff --git a/native/build-linux.sh b/native/build-linux.sh
deleted file mode 100644
index df9e2f2..0000000
--- a/native/build-linux.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/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
diff --git a/src/BuildMonitor.Native/BuildMonitor.Native.csproj b/src/BuildMonitor.Native/BuildMonitor.Native.csproj
index 7d1f6fe..8ba4d72 100644
--- a/src/BuildMonitor.Native/BuildMonitor.Native.csproj
+++ b/src/BuildMonitor.Native/BuildMonitor.Native.csproj
@@ -10,6 +10,14 @@
true
false
LatestMajor
+
+ true
@@ -21,23 +29,6 @@
-
-
-
-
-
-
-
-
-
+
diff --git a/src/BuildMonitor/BuildMonitor.csproj b/src/BuildMonitor/BuildMonitor.csproj
index 386a1af..6f4a2c9 100644
--- a/src/BuildMonitor/BuildMonitor.csproj
+++ b/src/BuildMonitor/BuildMonitor.csproj
@@ -43,7 +43,7 @@
@@ -58,78 +58,37 @@
-
- $(MSBuildProjectDirectory)\obj\heads\
- true
- AddHeadsToPackage;$(GenerateNuspecDependsOn)
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
+ tools\$(TargetFramework)\any\heads
-
-
- <_RidWithoutApphost Include="@(HeadRid)"
- Condition="!Exists('$(HeadStageDir)%(HeadRid.Identity)\%(HeadRid.Apphost)')" />
-
-
-
-
-
-
-
- <_HeadFile Include="$(HeadStageDir)**\*" />
-
-
-
-
-
-
-
- <_PackageFiles Include="$(HeadStageDir)**\*"
- BuildAction="None"
- PackagePath="tools\$(TargetFramework)\any\heads" />
-
-
+ heads
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index e0625bf..f56b8b5 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -14,6 +14,7 @@
+