Skip to content

perf(installer): shrink MSI size#19

Merged
hoobio merged 1 commit into
mainfrom
perf/earmark-msi-size
Apr 27, 2026
Merged

perf(installer): shrink MSI size#19
hoobio merged 1 commit into
mainfrom
perf/earmark-msi-size

Conversation

@hoobio
Copy link
Copy Markdown
Owner

@hoobio hoobio commented Apr 27, 2026

Summary

Drops the Earmark MSI from 91 MB to 68 MB (-26%) and the uncompressed self-contained publish output from 310 MB to 233 MB (-25%) by removing two transitively-bundled payloads that Earmark does not use.

Size before / after

Metric Before After Delta
MSI (compressed) 91 MB 68 MB -23 MB (-26%)
Publish dir (uncompressed) 310 MB 233 MB -77 MB (-25%)
Files in publish dir 639 554 -85

Changes

  • NAudio metapackage swapped for NAudio.Wasapi. The metapackage transitively pulls NAudio.WinForms, which framework-references Microsoft.WindowsDesktop.App.WindowsForms and brought ~24 MB of WinForms binaries (System.Windows.Forms.dll, .Design, .Primitives, .Private.Windows.Core, System.Drawing.Common, System.Configuration.ConfigurationManager) into self-contained publish output. Earmark.Audio only uses NAudio.CoreAudioApi (which lives in NAudio.Wasapi), so the swap is API-compatible.
  • Microsoft.WindowsAppSDK.ML payload pruned (~41 MB raw / ~17 MB compressed). WindowsAppSDK 1.8 transitively pulls Microsoft.WindowsAppSDK.ML 1.8.2192, which copies onnxruntime.dll, DirectML.dll, Microsoft.Windows.AI.MachineLearning.dll, plus the Microsoft.ML.OnnxRuntime.dll managed wrapper into self-contained publish output. Earmark does not use Windows ML. There is no supported opt-out yet (microsoft/WindowsAppSDK#5969); two new MSBuild targets in src/Earmark.App/Earmark.App.csproj strip the ML files from ReferenceCopyLocalPaths/None and from ResolvedFileToPublish via a filename regex. If WindowsAppSDK later wires a runtime probe to these files this exclusion will need to come back out, but at runtime today the WindowsAppRuntime bootstrap does not LoadLibrary onnxruntime/DirectML unless Windows ML APIs are actually invoked.
  • JSON serialization moved to source-generated JsonSerializerContext for AppSettings and List<RoutingRule>. Trim/AOT-friendly and slightly faster, even though full trimming is currently disabled.

Deliberately not done

  • PublishTrimmed=true. Tried it. .NET 10's trimmer hard-strips System.StubHelpers.InterfaceMarshaler (the classic-COM marshalling stub) when classic COM is reachable, even with BuiltInComInteropSupport=true and an explicit TrimmerRootDescriptor. The trimmer prints IL2026: Built-in COM support is not trim compatible and the runtime then throws TypeLoadException on the first MMDeviceEnumerator construction. Earmark's Core Audio path goes through NAudio.CoreAudioApi (classic [InterfaceType(InterfaceIsIUnknown)]) plus our own IPolicyConfigVista and IAudioPolicyConfigFactory interop, so trimming would require migrating to ComWrappers source-generators. Left as future work and called out inline in Earmark.App.csproj.
  • Removing NAudio entirely. The remaining NAudio.Core + NAudio.Wasapi together are ~120 KB; replacing them with hand-rolled COM interop (similar to what we already do for IPolicyConfigVista) is roughly five COM interface declarations plus IID/HResult plumbing, for negligible MSI savings. Worth doing only as a precondition for the trimming work above.

Testing

Validated end-to-end on this Windows 11 host:

  • dotnet publish src/Earmark.App/Earmark.App.csproj -c Release -p:Platform=x64 -p:RuntimeIdentifier=win-x64 -p:WindowsPackageType=None -p:WindowsAppSDKSelfContained=true -p:SelfContained=true -o publish/x64-trimmed exit 0, no new warnings beyond the pre-existing CsWinRT/.NET WinRT projection rollups.

  • wix build ... -d PayloadDir=publish/x64-trimmed -out staging/msi-x64/Earmark-1.0.0-x64.msi installer/Earmark.wxs ... exit 0.

  • msiexec /i staging/msi-x64/Earmark-1.0.0-x64.msi /qn exit 0; install dir %LocalAppData%\Earmark\ populated; Earmark.App.exe launches and parks in the system tray; secondary activation surfaces the main window with title "Earmark"; rule application against running processes works (latest log shows AudioPolicyService init: ... using Win11 interface, Activated factory 'Windows.Media.Internal.AudioPolicyConfig' as Win11 layout, Applied rule Media to msedge ... -> Media (Elgato Virtual Audio)).

  • Verified no forms, onnx, directml, or machinelearning files appear in the publish output after the prune.

  • Manual testing on a Windows 10 (19041+) or Windows 11 host

  • Tail of the latest log file under %LocalAppData%\Earmark\logs\ shows the expected Applied rule ... / Skip Set ... / Skip re-apply ... lines after the change

  • Unit tests added/updated under tests/Earmark.Core.Tests

  • Existing tests pass (dotnet test -p:Platform=x64) - no test changes; the existing Core.Tests scaffold has no rule-store tests today

Checklist

  • PR title follows Conventional Commits
  • Code builds without warnings (dotnet build src/Earmark.App/Earmark.App.csproj -c Debug -p:Platform=x64)
  • No emoji / gitmoji in commit messages or PR title (breaks release-please)
  • Architecture boundary respected: domain logic in Earmark.Core, Windows audio interop in Earmark.Audio, UI in Earmark.App
  • CLAUDE.md / README / CONTRIBUTING updated if behavior, build steps, or schema changed - no behavior change requiring doc update; the inline comments in Earmark.App.csproj document the ML prune and trimming-disabled rationale

Three independent changes that together drop the self-contained publish
from 310 MB to 233 MB (-25%) and the MSI from 91 MB to 68 MB (-26%):

1. Swap the NAudio metapackage for NAudio.Wasapi. The metapackage transitively
   pulls NAudio.WinForms, which framework-references
   Microsoft.WindowsDesktop.App.WindowsForms and bundled ~24 MB of WinForms
   binaries into the publish output. Earmark.Audio only uses
   NAudio.CoreAudioApi (which lives in NAudio.Wasapi), so the swap is
   API-compatible.

2. Prune the Microsoft.WindowsAppSDK.ML payload. WAS 1.8 transitively pulls
   Microsoft.WindowsAppSDK.ML 1.8.2192, which copies onnxruntime.dll (~21 MB),
   DirectML.dll (~19 MB), Microsoft.Windows.AI.MachineLearning.dll, and the
   Microsoft.ML.OnnxRuntime.dll managed wrapper into self-contained publish
   output. Earmark does not use Windows ML. There is no supported opt-out
   yet (microsoft/WindowsAppSDK#5969); this PR adds two MSBuild targets that
   strip the ML files from ReferenceCopyLocalPaths/None and from
   ResolvedFileToPublish via a filename regex.

3. Move JSON serialization to source-generated JsonSerializerContext for
   AppSettings (settings.json) and List<RoutingRule> (rules.json). This is
   trim/AOT-friendly and slightly faster, even though full trimming is
   currently disabled.

Trimming was attempted but reverted: .NET 10's trimmer hard-strips
System.StubHelpers.InterfaceMarshaler when classic-COM interop is reachable
("IL2026: Built-in COM support is not trim compatible"), even with
BuiltInComInteropSupport=true and a TrimmerRootDescriptor. Earmark's Core
Audio path runs through NAudio.CoreAudioApi (classic
[InterfaceType(InterfaceIsIUnknown)]) and our own IPolicyConfigVista interop,
so trimming would require migrating to ComWrappers source-generators. Left
as future work and noted inline in the csproj.
@hoobio hoobio merged commit 5666084 into main Apr 27, 2026
11 checks passed
hoobio pushed a commit that referenced this pull request Apr 27, 2026
This PR was generated automatically by
[release-please](https://github.com/googleapis/release-please).
---


## [0.1.4](v0.1.3...v0.1.4)
(2026-04-27)


### Bug Fixes

* **installer:** wire MSI icon and add branded installer chrome
([#17](#17))
([7881d1c](7881d1c))


### Performance Improvements

* **installer:** shrink MSI size
([#19](#19))
([5666084](5666084))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please-hoobi[bot] <279189756+release-please-hoobi[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant