.NET 8: [breaking] dotnet build/publish produces RID-specific apps by default
See original GitHub issue[breaking] dotnet build/publish produces RID-specific apps by default
Proposal: dotnet build
(and friends) produce RID-specific apps by default. This change results in apps that are smaller, simpler, faster to startup, and more reliable.
Proposal: any
is the new RID for portable apps, since they will no longer be the default.
Example:
PS C:\Users\rich\app3> dotnet add package SkiaSharp
PS C:\Users\rich\app3> dotnet build
PS C:\Users\rich\app3> Get-ChildItem .\bin\Debug\ -Recurse | Measure-Object -Sum Length | Select-Object Sum
Sum
---
31754654
PS C:\Users\rich\app3> Get-ChildItem .\bin\Debug\*Skia* -Recurse | Select-Object Name, Length, Directory
Name Length Directory
---- ------ ---------
libSkiaSharp.dylib 8495552 C:\Users\rich\app3\bin\Debug\net6.0\runtimes\osx\native
libSkiaSharp.dll 7177104 C:\Users\rich\app3\bin\Debug\net6.0\runtimes\win-arm64\native
libSkiaSharp.dll 8413072 C:\Users\rich\app3\bin\Debug\net6.0\runtimes\win-x64\native
libSkiaSharp.dll 7081872 C:\Users\rich\app3\bin\Debug\net6.0\runtimes\win-x86\native
SkiaSharp.dll 414096 C:\Users\rich\app3\bin\Debug\net6.0
PS C:\Users\rich\app3> Remove-Item -Recurse bin
PS C:\Users\rich\app3> dotnet build -a x64
PS C:\Users\rich\app3> Get-ChildItem .\bin\Debug\ -Recurse | Measure-Object -Sum Length | Select-Object Sum
Sum
---
8998050
PS C:\Users\rich\app3> Get-ChildItem .\bin\Debug\*Skia* -Recurse | Select-Object Name, Length, Directory
Name Length Directory
---- ------ ---------
libSkiaSharp.dll 8413072 C:\Users\rich\app3\bin\Debug\net6.0\win-x64
SkiaSharp.dll 414096 C:\Users\rich\app3\bin\Debug\net6.0\win-x64
This app, with a SkiaSharp dependency, is 31MB by default (with the existing behavior) and ~9MB with the proposed behavior. That’s a significant benefit. If you have multiple RID-specific dependencies, then the benefit could be higher.
Related: https://github.com/dotnet/sdk/issues/23539
Context
.NET Core 1.0 started with the concept of both portable and RID-specific apps. We thought of portable apps as being matched with framework-dependent deployment and RID-specific apps being matched with self-contained deployment. That made good sense at the time. The idea is that portable apps could run on any runtime you paired the app with, and self-contained apps could only run in the specific environment that they were published for. Also, self-contained apps were the only apps with executables. Framework dependent. It wasn’t until .NET Core 3.0 that framework dependent apps got native executables by default. We now have a platform that has a diverse set of application offerings, which bias to portable or RID-specific apps being preferred. It’s worth re-considering which of the two models should be the default.
Portable apps only make sense if the following characteristics are all true:
- App is deployed as framework-dependent.
- App doesn’t require a native executable for the desired UX, and can be launched with the
dotnet MyApp.dll
pattern. - Size isn’t a primary performance metric.
Client apps don’t satisfy those characteristics:
- Desktop client apps require an executable.
- Mobile apps are self-contained.
- Wasm apps are self-contained.
Server apps are mixed:
- For container apps, RID-specific is best since size is a primary performance metric, and because the app will only ever be launched one way. The flexibility offered by portable apps doesn’t apply.
- Apps that are developed and built on one environment (like Windows x64) and uploaded as binaries to a cloud service and then run in another environment (like Linux Arm64) are the poster-child for portable apps.
There is also the scenario where a dependency may have a security issue (CVE) on one platform and not another. It would be nice if you didn’t have to service a Linux binary on Windows (or vice versa) just to satisfy compliance rules.
In looking at the landscape, it becomes obvious that most scenarios benefit significantly from RID-specific deployment (either framework-dependent or self-contained) and that only niche scenarios benefit from portable apps. As a result, we should switch to RID-specific builds as the new SDK default.
Note: It turns out we already did this in very early .NET 7, but it wasn’t quite complete:
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:7 (4 by maintainers)
Top GitHub Comments
I’m against it.
In a microservice environment with IoT (ARM64 in addition to AMD64) and multiple types of developer hardware (Windows PC, Linux PC, MacOS) it’s important to be able to build portable apps so build step can be done once in CI/CD only, not redundant per target platform.
In my opinion platform independent should be default, the argument “size” doesn’t really count when it comes to containers.
It would be more interesting to be able to have dotnet publish being able to publish “external dependencies only” and .NET applications able to use dependencies from a different directory (so we could build images with the dependencies pre-installed).
Things like that.
We won’t be doing this. New proposal for something related, though not the breaking change: https://github.com/dotnet/sdk/issues/29950