Skip to content

Build, test, and package ILSpy on Linux and macOS in CI#3768

Merged
christophwille merged 6 commits into
masterfrom
cross-platform-ci
Jun 13, 2026
Merged

Build, test, and package ILSpy on Linux and macOS in CI#3768
christophwille merged 6 commits into
masterfrom
cross-platform-ci

Conversation

@christophwille

@christophwille christophwille commented Jun 11, 2026

Copy link
Copy Markdown
Member

Why

With the move to Avalonia, ILSpy runs on Linux and macOS, but CI only built Windows artifacts. This adds parallel Linux and macOS jobs so every platform gets compiled, UI-tested, and packaged on its native OS, per the requirements gathered in NewBuildPipeline.md:

  • Parallel jobs for Windows, Linux, and macOS; the existing Windows job is untouched (byte-identical), so required status checks keep working.
  • Headless UI tests (ILSpy.Tests) and the decompiler tests run on each platform (on non-Windows the suite self-restricts to the dotnet-hosted Roslyn configs via Tester.SupportedOnCurrentPlatform / [Platform("Win")]). NuGet packing, MSI, VSIX, and ILSpy.Tests.Windows stay Windows-only.
  • No signing anywhere in CI and no release pipeline: artifacts are unsigned; signing happens offline, releases stay manual.
  • Logic lives in locally runnable pwsh scripts, not in long YAML commands.

What

Publishing (publish.ps1): new -Platform windows|linux|macos parameter; default is the existing Windows behavior verbatim. Linux/macOS publish self-contained linux-x64 / osx-arm64 bundles. On macOS, ILSpy.ReadyToRun is published before ILSpy because the BuildMacAppBundle target snapshots the publish directory into ILSpy.app when ILSpy publishes.

Linux packaging (BuildTools/package-linux.ps1, modeled on sourcegit-scm/sourcegit):

  • zip via Info-ZIP (preserves the execute flag, which Compress-Archive/7z drop)
  • .deb via dpkg-deb, with the libicu OR-chain in Depends and a .desktop file + 256x256 icon (extracted from the existing .ico)
  • .rpm via rpmbuild, with AutoReqProv: no so the self-contained payload does not generate bogus auto-requires
  • pre-release versions map - to ~ (11.0.0.8948-preview1 -> 11.0.0.8948~preview1), legal in both formats and sorts before the final release

macOS packaging (BuildTools/package-macos.ps1): stamps the version placeholders in the bundle Info.plist (replacing the stale hardcoded 10.1.0) and zips ILSpy.app with symlinks and exec bits preserved.

Offline release tooling (never called from CI): BuildTools/packaging/macos/create-dmg.ps1 builds the dmg with optional codesign/notarization parameters, and BuildTools/packaging/homebrew/ilspy.rb is the cask template for a future icsharpcode/homebrew-ilspy tap. Zero signing secrets in any CI; the tap holds only the cask file.

Per the "simplicity wins" rule, Flatpak, PPA, OBS, and AppImage were considered and dropped; deb/rpm are distributed as GitHub release attachments only.

Verification done

  • All three publish branches ran locally (Windows natively, linux/macos cross-published; the .app assembled with plugin, icns, and plist).
  • Both packaging scripts ran end-to-end on Ubuntu 24.04 (WSL): zip exec bit verified via zipinfo, no pdbs; dpkg -I/dpkg -c metadata and payload correct; apt-get install --simulate resolves all deb dependencies; rpm -qpi/-qpR/-qpl correct; plist stamped to 11.0.0.8948/11.0.0.
  • This run also caught a real bug: CRLF in the rpm spec breaks rpmbuild, hence the .gitattributes LF rules for the Linux-consumed files.

First-run checklist (things only CI can prove)

  • rpmbuild present on ubuntu-latest (script has a tool guard with an actionable message; add an apt-get step only if missing)
  • Linux Skia screenshot tests find fontconfig + fonts on the stock image (fallback: sudo apt-get install -y fontconfig fonts-dejavu-core)
  • Windows artifact list unchanged vs. previous master run
  • Version strings consistent across the three jobs for the same commit

🤖 Generated with Claude Code

@christophwille

Copy link
Copy Markdown
Member Author

First-run checklist validated against run 27337878106 (all 4 jobs green):

  • rpmbuild on ubuntu-latest: present and working; the Linux job log shows all three packages created (ILSpy_linux-x64_*.zip, ilspy_*_amd64.deb, ilspy-*.x86_64.rpm), no install step needed.
  • fontconfig/fonts on the stock image: the Linux job ran the full UI suite with zero failures; the only 3 skips (from the TRX) are the intentional ones (BindTree_With_Large_AssemblyList_Reports_Phase_Timings, UI_Stays_Responsive_While_Many_Assemblies_Load, Capture_Before_And_After_Toggle behind ILSPY_TESTS_VISIBLE=1) - same as on Windows. Per-assembly TRX cross-check: ILSpy.Tests is exactly 817 total / 814 passed / 3 skipped on Windows-Release, Linux, and macOS.
  • Windows artifact parity: artifact list of this run vs the latest master run (27334069256) is identical after stripping version stamps - same 14 artifacts.
  • Version consistency: every artifact across all three jobs carries 11.0.0.8953-preview1-pr3768; deb/rpm use the tilde mapping (11.0.0.8953~preview1~pr3768), the .app Info.plist is stamped 11.0.0.8953 / 11.0.0.

Downloaded-artifact inspection (Ubuntu 24.04): launcher is -rwxr-xr-x in the Linux zip and the .app zip (exec bit survives), zero .pdb files in either; dpkg -I/dpkg -c show the expected control fields, payload, symlink and 644 desktop/icon; rpm -qpi/-qpR show the manual requires only; apt-get install --simulate resolves the deb cleanly.

🤖 Generated with Claude Code

With the move to Avalonia the app runs on Linux and macOS, so the
distribution tooling grows beyond Windows: publish.ps1 gains a
-Platform parameter (the default keeps the Windows behavior unchanged),
and new BuildTools scripts package the self-contained publish outputs
into a zip, deb, and rpm on Linux and a zipped ILSpy.app on macOS.

Nothing is signed at package time; signing happens offline on the
release manager machine. Info-ZIP zip is used instead of
Compress-Archive because the latter drops unix mode bits. deb/rpm are
built with dpkg-deb/rpmbuild directly (both preinstalled on GitHub
ubuntu runners) following sourcegit-scm/sourcegit's proven model,
including the libicu OR-chain in the deb control file and disabled
auto-requires for the self-contained rpm payload. Pre-release versions
map '-' to '~' so both package managers sort them before the release.
The macOS bundle's Info.plist now carries placeholders stamped at
package time; on Windows the spec/control/desktop files are kept LF via
.gitattributes because Linux tools consume them verbatim.

Assisted-by: Claude:claude-fable-5:Claude Code
Two new jobs run alongside the Windows matrix: each builds the desktop
solution filter on its own OS, runs the headless UI tests there, and
packages Release self-contained bundles (zip/deb/rpm on Linux, zipped
ILSpy.app on macOS) via the BuildTools packaging scripts. Decompiler
tests, NuGet packing, and the Windows installers stay in the Windows
job, and neither new job checks out the ILSpy-tests submodule, since
that only feeds the decompiler tests. The Windows job is untouched so
existing required-status-check names keep working.

Assisted-by: Claude:claude-fable-5:Claude Code
The macOS CI artifact is an unsigned zipped ILSpy.app; the signed
distribution channel is a dmg the release manager creates offline with
create-dmg.ps1 (codesign and notarization stay optional parameters, so
no signing secrets ever live in CI). Distribution via Homebrew uses a
tap repository holding only a cask file that points at the dmg attached
to the GitHub release; the cask template and per-release update steps
are documented next to the scripts.

Assisted-by: Claude:claude-fable-5:Claude Code
The first CI run on a macOS runner surfaced that these tests encoded
the Windows/Linux menu shape, while MainMenu.Attach intentionally
diverges on macOS: TranslateGesturesForMacOS rewrites Control to Meta
so shortcuts follow the Cmd-key convention, and PromoteHelpToMacAppMenu
moves the Help items into the application menu and drops the _Help
top-level. The tests now assert the macOS-correct expectations on
macOS, including that the Help content lands in the app menu rather
than vanishing.

Assisted-by: Claude:claude-fable-5:Claude Code
@siegfriedpammer

Copy link
Copy Markdown
Member
  • run decompiler tests on all platforms

The decompiler test suite is platform-aware: on non-Windows,
Tester.SupportedOnCurrentPlatform keeps only the dotnet-hosted Roslyn
configs and [Platform("Win")] gates the fixtures that truly need
Windows (legacy csc, mcs, 32-bit, roundtrip), so running the suite on
the Linux and macOS jobs gives real coverage of the non-Windows
decompilation paths instead of leaving them untested. Both jobs now
also check out the ILSpy-tests submodule, because the TargetNet40
configs compile against its legacy reference assemblies even when the
compiled output is never executed.

Assisted-by: Claude:claude-fable-5:Claude Code
The Linux and macOS jobs were near-identical copies; their shared head
(checkout, SDK setup, version, restore, build, both test steps, test-log
upload) drifts the moment one is edited and the other is not. Collapse
them into a single platform-parameterized matrix job, and hoist the .NET
SDK version/quality and the staging directory to workflow-level env so an
SDK bump is a one-line change across all jobs.

The Windows job keeps its id (Build) so internal references stay stable,
but gains a display name to read as "Desktop (Windows)" alongside the
matrix "Desktop (linux)/(macos)" checks. Branch-protection required
checks must be repointed to the new check names.

Assisted-by: Claude:claude-opus-4-8:Claude Code
@christophwille christophwille added this pull request to the merge queue Jun 13, 2026
Merged via the queue into master with commit c6ec2c5 Jun 13, 2026
13 checks passed
@christophwille christophwille deleted the cross-platform-ci branch June 13, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants