Skip to content

.NET 8: [breaking] dotnet build/publish produces RID-specific apps by default #23540

Closed
@richlander

Description

@richlander

[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: #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:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions