@@ -7,6 +7,13 @@ name: Build native
77# They are committed rather than built during a normal build so that a plain
88# `dotnet build src --configuration Release` produces a shippable package on any machine, and
99# contributors never need a C++ toolchain. Run this whenever native/ changes.
10+ #
11+ # The work is PlatformHeads' build-native workflow, which this one was moved into so that
12+ # BuildMonitor, built the same way, runs it too: the Linux library built in a manylinux_2_28
13+ # container and checked for the glibc it needs and the libraries it records as needed, the
14+ # universal dylib, and the pull request with the binaries. BuildMonitor had a copy of it, and
15+ # three fixes made here were waiting to be found again there.
16+ # The tag is the one native/CMakeLists.txt fetches and Directory.Packages.props names.
1017on :
1118 push :
1219 paths :
@@ -27,210 +34,18 @@ concurrency:
2734 cancel-in-progress : true
2835
2936jobs :
30- build :
31- name : ${{ matrix.rid }}
32- runs-on : ${{ matrix.os }}
33- timeout-minutes : 45
34- strategy :
35- fail-fast : false
36- matrix :
37- include :
38- # No Windows entries. That head renders with WinForms and loads no native library.
39- - rid : linux-x64
40- os : ubuntu-24.04
41- - rid : linux-arm64
42- os : ubuntu-24.04-arm
43- # One universal binary covers both macOS RIDs; it is copied into each below.
44- - rid : osx
45- os : macos-14
46- steps :
47- - name : Checkout
48- uses : actions/checkout@v7
49-
50- # In a manylinux_2_28 container rather than on the runner, for its glibc 2.28: the floor
51- # .NET 10 itself supports, which is RHEL 8. A library records the glibc symbol versions of
52- # the headers it was compiled against, and the loader refuses it anywhere older - built on
53- # the runner's own Ubuntu 24.04 it needed GLIBC_2.38, so the viewer could not start on
54- # Ubuntu 22.04, Debian 12, or RHEL 8 or 9, and every inline snapshot sent to it was lost.
55- - name : Build
56- if : runner.os == 'Linux'
57- run : docker run --rm -v "$PWD:/src" -w /src "quay.io/pypa/manylinux_2_28_$(uname -m)" bash native/build-linux.sh
58-
59- # The floor the container is there to hold, checked rather than trusted, since nothing else
60- # would notice until a user on an older distribution did.
61- - name : Check glibc floor
62- if : runner.os == 'Linux'
63- env :
64- GLIBC_FLOOR : GLIBC_2.28
65- run : |
66- highest=$(objdump -T build/libdiffengine_viewer.so | grep -o 'GLIBC_[0-9][0-9.]*' | sort -uV | tail -n 1)
67- echo "Highest glibc symbol version required: $highest"
68- if [ "$(printf '%s\n' "$GLIBC_FLOOR" "$highest" | sort -V | tail -n 1)" != "$GLIBC_FLOOR" ]; then
69- echo "::error::libdiffengine_viewer.so requires $highest, above the $GLIBC_FLOOR floor"
70- exit 1
71- fi
72- # libGL.so.1 is on every machine with a GL driver; libOpenGL.so.0 is not
73- if objdump -p build/libdiffengine_viewer.so | grep -q 'NEEDED.*libOpenGL'; then
74- echo "::error::libdiffengine_viewer.so links libOpenGL.so.0 rather than libGL.so.1"
75- exit 1
76- fi
77-
78- # Everything recorded as needed has to be on a machine for the library to load at all, so
79- # the list is held to what any machine that runs .NET and has a display already has. It grew
80- # once without anybody choosing it: the container's linker records every library it is
81- # handed, used or not, and raylib hands on libSM and libICE, which a stock Ubuntu lacks.
82- # The viewer could not start there, and nothing noticed until somebody ran it on one.
83- - name : Check needed libraries
84- if : runner.os == 'Linux'
85- env :
86- ALLOWED : >-
87- libc.so.6 libm.so.6 libdl.so.2 libpthread.so.0 librt.so.1 libstdc++.so.6 libgcc_s.so.1
88- libX11.so.6 libGL.so.1 ld-linux-x86-64.so.2 ld-linux-aarch64.so.1
89- run : |
90- needed=$(objdump -p build/libdiffengine_viewer.so | awk '$1 == "NEEDED" { print $2 }')
91- echo "Needed:" $needed
92- failed=0
93- for library in $needed; do
94- case " $ALLOWED " in
95- *" $library "*) ;;
96- *)
97- echo "::error::libdiffengine_viewer.so needs $library, which is not in the list of libraries it may need"
98- failed=1
99- ;;
100- esac
101- done
102- exit $failed
103-
104- # macOS draws with AppKit and Core Text rather than raylib and ImGui, so it is a Swift
105- # package rather than a CMake project. Both --arch flags in one invocation produce a
106- # universal binary, so there is no separate lipo step.
107- #
108- # Nothing of the Swift runtime is shipped: it has been part of macOS since 10.14.4, which is
109- # why this dylib is a fraction of the size of the one it replaced.
110- - name : Build
111- if : matrix.rid == 'osx'
112- run : swift build -c release --arch arm64 --arch x86_64 --package-path native/swift
113-
114- - name : Collect
115- shell : bash
116- run : |
117- # Laid out as src/{head}/runtimes/{rid}/native, so the propose job below can merge every
118- # artifact straight into src and the binaries land in the head that loads them.
119- collect() {
120- mkdir -p "artifacts/$1/runtimes/$2/native"
121- cp "$3" "artifacts/$1/runtimes/$2/native/"
122- }
123- case "${{ matrix.rid }}" in
124- linux-*)
125- # Already stripped, inside the container that owns the build directory
126- collect DiffEngineViewer.Linux "${{ matrix.rid }}" build/libdiffengine_viewer.so
127- ;;
128- osx)
129- # swift build leaves a per architecture dylib in more than one place, so taking the
130- # first one found gives a single slice binary that loads on half the Macs in the
131- # world. Every candidate is checked, and if none is already universal they are
132- # merged, so the only thing that can be collected is a fat binary.
133- # The dSYM contains a DWARF file of the same name which is also universal, so the
134- # arch check below would happily accept it and ship debug symbols as the library.
135- candidates=$(find native/swift/.build -name libdiffengine_viewer.dylib -not -path '*.dSYM/*')
136- if [ -z "$candidates" ]; then
137- echo "::error::swift build produced no libdiffengine_viewer.dylib"
138- exit 1
139- fi
140-
141- echo "$candidates" | while read -r candidate; do
142- echo "$candidate: $(lipo -archs "$candidate" 2>/dev/null)"
143- done
144-
145- dylib=""
146- for candidate in $candidates; do
147- archs=$(lipo -archs "$candidate" 2>/dev/null || echo "")
148- if [[ "$archs" == *x86_64* && "$archs" == *arm64* ]]; then
149- dylib="$candidate"
150- break
151- fi
152- done
153-
154- if [ -z "$dylib" ]; then
155- dylib=universal/libdiffengine_viewer.dylib
156- mkdir -p universal
157- # shellcheck disable=SC2086
158- lipo -create $candidates -output "$dylib"
159- fi
160-
161- archs=$(lipo -archs "$dylib")
162- echo "collecting $dylib: $archs"
163- for arch in x86_64 arm64; do
164- case " $archs " in
165- *" $arch "*) ;;
166- *) echo "::error::$dylib is missing the $arch slice"; exit 1 ;;
167- esac
168- done
169-
170- strip -x "$dylib"
171- # The dylib is universal, so both macOS RIDs get the same file.
172- collect DiffEngineViewer.Mac osx-x64 "$dylib"
173- collect DiffEngineViewer.Mac osx-arm64 "$dylib"
174- ;;
175- esac
176- ls -lhR artifacts
177-
178- - name : Upload
179- uses : actions/upload-artifact@v7
180- with :
181- name : native-${{ matrix.rid }}
182- path : artifacts/
183- retention-days : 7
184-
185- propose :
186- name : propose update
187- needs : build
188- # Also on push, not just manual dispatch. workflow_dispatch does not appear in the Actions UI
189- # until the workflow is on the default branch, so on a feature branch a push is the only way
190- # to trigger this, and leaving the binaries as bare artifacts to copy by hand is worse.
191- #
192- # Branches only. Never on pull_request, which would mean opening a PR from a PR, and never on
193- # a tag: a tag checkout is a detached HEAD with no base branch to target, and proposing a
194- # binaries PR against a release tag is meaningless anyway.
195- if : >-
196- github.event_name != 'pull_request' &&
197- startsWith(github.ref, 'refs/heads/') &&
198- (github.event_name != 'workflow_dispatch' || inputs.commit)
199- runs-on : ubuntu-latest
37+ native :
38+ uses : SimonCropp/PlatformHeads/.github/workflows/build-native.yml@0.1.0
20039 permissions :
20140 contents : write
20241 pull-requests : write
203- steps :
204- - name : Checkout
205- uses : actions/checkout@v7
206-
207- - name : Download
208- uses : actions/download-artifact@v4
209- with :
210- pattern : native-*
211- merge-multiple : true
212- path : src
213-
214- - name : Show what changed
215- run : |
216- ls -lhR src/DiffEngineViewer.Linux/runtimes src/DiffEngineViewer.Mac/runtimes
217- git status --short
218-
219- # A PR rather than a direct push: these are binaries, so the diff is not reviewable and the
220- # change deserves an explicit approval.
221- - name : Open pull request
222- uses : peter-evans/create-pull-request@v7
223- with :
224- # Scoped to the triggering branch, which is also the base, so a rebuild on a feature
225- # branch does not collide with one on main.
226- branch : native-binaries-${{ github.ref_name }}
227- title : ' Rebuild native renderer binaries'
228- commit-message : ' Rebuild native renderer binaries'
229- body : |
230- Rebuilt `diffengine_viewer` from `native/` for the four RIDs that load one.
231- Windows is not among them: that head renders with WinForms.
232-
233- Produced by the `build-native` workflow from ${{ github.sha }}.
234- add-paths : |
235- src/DiffEngineViewer.Linux/runtimes
236- src/DiffEngineViewer.Mac/runtimes
42+ with :
43+ library : diffengine_viewer
44+ linux-runtimes : src/DiffEngineViewer.Linux/runtimes
45+ swift-package : native/swift
46+ mac-runtimes : src/DiffEngineViewer.Mac/runtimes
47+ # Also on push, not just manual dispatch. workflow_dispatch does not appear in the Actions
48+ # UI until the workflow is on the default branch, so on a feature branch a push is the only
49+ # way to trigger this, and leaving the binaries as bare artifacts to copy by hand is worse.
50+ propose : ${{ github.event_name != 'workflow_dispatch' || inputs.commit }}
51+ pull-request-title : Rebuild native renderer binaries
0 commit comments