Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.bin binary
*.png binary
*.docx binary
*.dll binary

src/Verify/EmptyFiles/* binary

Expand Down
151 changes: 151 additions & 0 deletions .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: Build native

# Produces the diffengine_viewer binaries committed under
# src/DiffEngineViewer/runtimes/{rid}/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.
on:
push:
paths:
- 'native/**'
- '.github/workflows/build-native.yml'
pull_request:
paths:
- 'native/**'
workflow_dispatch:
inputs:
commit:
description: 'Open a PR with the rebuilt binaries'
type: boolean
default: true

concurrency:
group: build-native-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: ${{ matrix.rid }}
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- rid: win-x64
os: windows-latest
generator: -A x64
- rid: win-arm64
os: windows-latest
generator: -A ARM64
- 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@v4

- name: Install build dependencies
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

- name: Configure
shell: bash
run: |
if [ "${{ matrix.rid }}" = "osx" ]; then
cmake -S native -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
elif [ "${{ runner.os }}" = "Windows" ]; then
cmake -S native -B build ${{ matrix.generator }}
else
cmake -S native -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
fi

- name: Build
run: cmake --build build --config Release

- name: Collect
shell: bash
run: |
collect() {
mkdir -p "artifacts/$1/native"
cp "$2" "artifacts/$1/native/"
}
case "${{ matrix.rid }}" in
win-*)
collect "${{ matrix.rid }}" build/Release/diffengine_viewer.dll
;;
linux-*)
strip build/libdiffengine_viewer.so
collect "${{ matrix.rid }}" build/libdiffengine_viewer.so
;;
osx)
strip -x build/libdiffengine_viewer.dylib
# The dylib is universal, so both macOS RIDs get the same file.
collect osx-x64 build/libdiffengine_viewer.dylib
collect osx-arm64 build/libdiffengine_viewer.dylib
;;
esac
ls -lhR artifacts

- name: Upload
uses: actions/upload-artifact@v4
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, and leaving the binaries as bare artifacts to copy by hand is worse.
# Never on pull_request, which would mean opening a PR from a PR.
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || inputs.commit)
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download
uses: actions/download-artifact@v4
with:
pattern: native-*
merge-multiple: true
path: src/DiffEngineViewer/runtimes

- name: Show what changed
run: |
ls -lhR src/DiffEngineViewer/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@v7
with:
# Scoped to the triggering branch, which is also the base, so a rebuild on a feature
# branch does not collide with one on main.
branch: native-binaries-${{ github.ref_name }}
title: 'Rebuild native renderer binaries'
commit-message: 'Rebuild native renderer binaries'
body: |
Rebuilt `diffengine_viewer` from `native/` for all six RIDs.

Produced by the `build-native` workflow from ${{ github.sha }}.
add-paths: src/DiffEngineViewer/runtimes
77 changes: 77 additions & 0 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Publish NuGet

# Packs DiffEngine, DiffEngineTray and DiffEngineViewer and pushes them to nuget.org using Trusted
# Publishing (OIDC), so no long lived API key is stored. GitHub Actions mints a short lived OIDC
# token; the NuGet/login action exchanges it for a temporary nuget.org API key, issued only because
# a matching trusted-publishing policy is registered for this repo and workflow.
#
# One-time setup:
# 1. nuget.org -> Account -> Trusted Publishing: add a policy for this GitHub owner and
# repository, scoped to this workflow file, covering DiffEngine, DiffEngineTray and
# DiffEngineViewer.
# 2. Repo -> Settings -> Secrets and variables -> Actions -> Variables: set NUGET_USER to the
# nuget.org username. A username is not sensitive, so it lives in a variable not a secret.
on:
push:
tags:
- '*'
workflow_dispatch:

permissions:
id-token: write # required: lets the job request a GitHub OIDC token
contents: read

env:
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true

jobs:
publish:
# Windows, because DiffEngineTray is WinForms and is excluded from the non-Windows
# configuration. Publishing from anywhere else would silently drop it.
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: NuGet OIDC login
uses: NuGet/login@v1
id: login
with:
user: ${{ vars.NUGET_USER }}

# A plain build is the pack step: ProjectDefaults sets GeneratePackageOnBuild for every
# Release package project, so each package is produced by its own build, in dependency
# order, into ./nugets. Deliberately not `dotnet pack`, which on a solution races with
# GeneratePackageOnBuild.
- name: Build and pack
run: dotnet build src --configuration Release

# DE0001 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
shell: bash
run: |
missing=0
for rid in win-x64 win-arm64 linux-x64 linux-arm64 osx-x64 osx-arm64; do
directory="src/DiffEngineViewer/runtimes/$rid/native"
if [ -z "$(ls -A "$directory" 2>/dev/null)" ]; then
echo "::error::No native renderer for $rid. Run the build-native workflow."
missing=1
fi
done
exit $missing

- name: Push to nuget.org
# --skip-duplicate makes re-runs idempotent.
run: >
dotnet nuget push "nugets/*.nupkg"
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}"
--source https://api.nuget.org/v3/index.json
--skip-duplicate
137 changes: 137 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Test

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Cancel in-progress runs if a new push lands on the same branch.
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true

env:
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_NOLOGO: true

jobs:
windows:
name: windows
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

# A plain build is also the pack step: ProjectDefaults sets GeneratePackageOnBuild for every
# Release package project, so this produces ./nugets as a side effect.
- name: Build
run: dotnet build src --configuration Release

- name: Test
run: dotnet test --solution src/DiffEngine.slnx --configuration Release --no-build --no-restore

- name: AOT publish
run: dotnet publish src/DiffEngine.AotTests/DiffEngine.AotTests.csproj --configuration Release

- name: Upload received on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: received-windows
path: '**/*.received.*'
if-no-files-found: ignore
retention-days: 14

- name: Upload packages
if: success()
uses: actions/upload-artifact@v4
with:
name: nupkgs
path: nugets/*.nupkg
if-no-files-found: warn
retention-days: 30

unix:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

# raylib links against X11, GL and friends. xvfb plus the Mesa software rasteriser are what
# the pixel snapshots run against.
- name: Install native dependencies
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 \
xvfb libgl1-mesa-dri

# CMake fetches raylib and imgui, so without this every PR re-clones and recompiles them.
# Keyed on the shim's own sources, which is what actually changes.
- name: Cache native build
if: runner.os == 'Linux'
uses: actions/cache@v4
with:
path: native/build
key: native-linux-x64-${{ hashFiles('native/CMakeLists.txt', 'native/src/**', 'native/include/**') }}

# Built here rather than taken from the committed binaries, so the Linux job tests the
# current native source and the pixel baselines track it.
- name: Build native renderer
if: runner.os == 'Linux'
run: |
cmake -S native -B native/build/linux-x64 -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build native/build/linux-x64
mkdir -p src/DiffEngineViewer/runtimes/linux-x64/native
cp native/build/linux-x64/libdiffengine_viewer.so src/DiffEngineViewer/runtimes/linux-x64/native/

# Release-NotWindows drops the WinForms tray and its tests from the solution.
- name: Build
run: dotnet build src --configuration Release-NotWindows

- name: Test
run: dotnet test --solution src/DiffEngine.slnx --configuration Release-NotWindows --no-build --no-restore

# Pinned to one rasteriser rather than one platform: llvmpipe is pure software and so more
# reproducible than any GPU driver, and ImGui rasterises glyphs itself.
- name: Pixel snapshots
if: runner.os == 'Linux'
env:
DIFFENGINE_VIEWER_PIXEL_TESTS: 'true'
LIBGL_ALWAYS_SOFTWARE: '1'
GALLIUM_DRIVER: llvmpipe
run: >
xvfb-run -a --server-args="-screen 0 1920x1080x24"
dotnet test src/DiffEngineViewer.Tests/DiffEngineViewer.Tests.csproj
--configuration Release-NotWindows --no-build --no-restore

- name: Upload received on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: received-${{ matrix.os }}
path: '**/*.received.*'
if-no-files-found: ignore
retention-days: 14
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ nul
/TestResults
/coverage
BenchmarkDotNet.Artifacts/
native/build/
Loading