You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We've been on a journey to rationalize dotnet build and dotnet publish in terms of producing platform-specific apps and how the CLI exposes a natural progression of app specialization options. The overall product started out without sufficient design on this topic and we've been trying to remedy that ever since. We believe we can finally correct these design challenges with .NET 8.
Notes:
build is used a short-hand for build and publish unless specifically called out otherwise.
"platform-specific" is used as a synonym for "RID-specific".
Framework-dependent apps have been the default build output type since .NET Core 1.0. That remains the best default in our view. The primary deployment model for .NET is a globally installed runtime, and framework-dependent apps align with that. We should retain this behavior.
In many scenarios, an app will only ever run in one platform environment (like Linux + x64), and developers know that a priori (for example for containers or client apps). There can be performance benefits by taking advantage of that. In addition, apps built for a single environment are simpler (fewer assets and flat directory structure). We should make it easier to produce platform-specific apps, ideally making it the default.
In contrast, in other scenarios, developers benefit from apps being portable across operating system and architectures. That model has been the default to date and is arguably an advanced pseudo-magic behavior. This behavior should be opt-in since it comes with important trade-offs that developers should understand (or that publishing workflows provide as a requirement).
Platform specialization
Developers can specialize an app for a given platform by specifying a Runtime ID (RID) when building an app, with the -r argument, like with:
dotnet build -r linux-x64
Specializing an app with a RID produces a self-contained app. This design choice has always been unfortunate, since RID specialization equally applies to framework-dependent and self-contained apps.
The SDK provides a warning when a RID is specified without any additional arguments related to output type. This warning was added with .NET 6.
root@efbeccd6a45b:/app# dotnet build -r linux-arm64
MSBuild version 17.4.1+9a89d02ff for .NET
Determining projects to restore...
Restored /app/app.csproj (in 5.16 sec).
/usr/share/dotnet/sdk/7.0.102/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.targets(1142,5): warning NETSDK1179: One of '--self-contained' or '--no-self-contained' options are required when '--runtime' is used. [/app/app.csproj]
The intent of this warning was enabling an eventual change in the default behavior when specifying a RID. Given that the warning has been in place for two versions, including an LTS version, we can reasonably now make the change.
Starting with apps that target .NET 8:
Building with a RID will produce a framework-dependent app.
If --self-contained or --self-contained true is specified, then a self-contained app will be produced. Same when the SelfContained=true.
Warning NETSDK1179 will no longer be produced.
For earlier versions, We should consider changing the wording of the warning to make clear that the behavior has changed for .NET 8+ apps.
As the current warning suggests, developers should specify --self-contained in response for this change, if they haven't already.
Platform specializing by default
We can further refine this proposal by specializing apps by default. We also need to consider compatibility, and an opt-out to enable producing portable apps.
In fact, there is already a CLI argument that provides this behavior:
root@efbeccd6a45b:/app# dotnet build --help | grep runtime
--use-current-runtime Use current runtime as the target
runtime.
We could add a property with that name, but we can probably do better.
Instead, we propose a TargetRuntime property that pairs with TargetFramework. It will be a convenience wrapper over RuntimeIdentifier, meaning that it will always produce a valid value (including empty string) for that property.
TargetRuntime will allow the following values:
portable -- same as specifying no RID and pro
current -- same as specifying --use-current-runtime
any valid RID
.NET 8 templates will be updated to include this property, set to current, making .NET 8 apps RID-specific by default.
The follow project file demonstrates the change for the console template.
We are aware that there are two variants of "current RID", one short, one long.
The --use-current-runtime flag results in an OS-specific RID, like osx-arm64, while dotnet --info prints a RID with a version, as follows.
% dotnet --info | grep RID
RID: osx.13-arm64
That seems odd and asymmetric. Ideally, both variants would be accessible from within a project file and the CLI. More generally, we don't have good names for these different styles of RIDs. We don't need to resolve that for this feature, but it is something that we should separately look into.
Portable apps and apphost
Portable apps include the apphost by default. That doesn't really make sense since the apphost is platform-specific, while the rest of the app is intended to work anywhere. However, it's possible that developers appreciate having an apphost for local dev.
There is no harm to shipping an apphost with a portable app. It is very small and very unlikely to be the source of a vulnerability. However, the platform it supports is arbitrary with respect to the platforms it might run on. As a result, it is hard to describe a general use case (beyond development).
We could adopt the following model:
Portable apps do not include an apphost, the same behavior as early .NET Core versions.
Developers that want an apphost (the existing behavior) can set UseAppHost=true.
Developer that want an apphost only for development can set UseDebugAppHost=true (which would be a new property). An alternative would be UseAppHost=Debug.
As an aside, we notice many Dockerfile examples where apps are launched with the dotnet foo.dll pattern, even those they don't need to. Lauching an app with the dotnet launcher is a fine pattern and should certainly be embraced by portable apps, since it's the only correct cross-platform pattern.
Proposal for platform-specific apps for .NET 8
We've been on a journey to rationalize
dotnet buildanddotnet publishin terms of producing platform-specific apps and how the CLI exposes a natural progression of app specialization options. The overall product started out without sufficient design on this topic and we've been trying to remedy that ever since. We believe we can finally correct these design challenges with .NET 8.Notes:
buildis used a short-hand forbuildandpublishunless specifically called out otherwise.publishUX #26446.Context
Framework-dependent apps have been the default
buildoutput type since .NET Core 1.0. That remains the best default in our view. The primary deployment model for .NET is a globally installed runtime, and framework-dependent apps align with that. We should retain this behavior.In many scenarios, an app will only ever run in one platform environment (like Linux + x64), and developers know that a priori (for example for containers or client apps). There can be performance benefits by taking advantage of that. In addition, apps built for a single environment are simpler (fewer assets and flat directory structure). We should make it easier to produce platform-specific apps, ideally making it the default.
In contrast, in other scenarios, developers benefit from apps being portable across operating system and architectures. That model has been the default to date and is arguably an advanced pseudo-magic behavior. This behavior should be opt-in since it comes with important trade-offs that developers should understand (or that publishing workflows provide as a requirement).
Platform specialization
Developers can specialize an app for a given platform by specifying a Runtime ID (RID) when building an app, with the
-rargument, like with:dotnet build -r linux-x64Specializing an app with a RID produces a self-contained app. This design choice has always been unfortunate, since RID specialization equally applies to framework-dependent and self-contained apps.
The SDK provides a warning when a RID is specified without any additional arguments related to output type. This warning was added with .NET 6.
The intent of this warning was enabling an eventual change in the default behavior when specifying a RID. Given that the warning has been in place for two versions, including an LTS version, we can reasonably now make the change.
Starting with apps that target .NET 8:
--self-containedor--self-contained trueis specified, then a self-contained app will be produced. Same when theSelfContained=true.NETSDK1179will no longer be produced.As the current warning suggests, developers should specify
--self-containedin response for this change, if they haven't already.Platform specializing by default
We can further refine this proposal by specializing apps by default. We also need to consider compatibility, and an opt-out to enable producing portable apps.
In fact, there is already a CLI argument that provides this behavior:
root@efbeccd6a45b:/app# dotnet build --help | grep runtime --use-current-runtime Use current runtime as the target runtime.We could add a property with that name, but we can probably do better.
Instead, we propose a
TargetRuntimeproperty that pairs withTargetFramework. It will be a convenience wrapper overRuntimeIdentifier, meaning that it will always produce a valid value (including empty string) for that property.TargetRuntimewill allow the following values:portable-- same as specifying no RID and procurrent-- same as specifying--use-current-runtime.NET 8 templates will be updated to include this property, set to
current, making .NET 8 apps RID-specific by default.The follow project file demonstrates the change for the
consoletemplate.We are aware that there are two variants of "current RID", one short, one long.
The
--use-current-runtimeflag results in an OS-specific RID, likeosx-arm64, whiledotnet --infoprints a RID with a version, as follows.% dotnet --info | grep RID RID: osx.13-arm64That seems odd and asymmetric. Ideally, both variants would be accessible from within a project file and the CLI. More generally, we don't have good names for these different styles of RIDs. We don't need to resolve that for this feature, but it is something that we should separately look into.
Portable apps and apphost
Portable apps include the apphost by default. That doesn't really make sense since the apphost is platform-specific, while the rest of the app is intended to work anywhere. However, it's possible that developers appreciate having an apphost for local dev.
There is no harm to shipping an apphost with a portable app. It is very small and very unlikely to be the source of a vulnerability. However, the platform it supports is arbitrary with respect to the platforms it might run on. As a result, it is hard to describe a general use case (beyond development).
We could adopt the following model:
UseAppHost=true.UseDebugAppHost=true(which would be a new property). An alternative would beUseAppHost=Debug.As an aside, we notice many
Dockerfileexamples where apps are launched with thedotnet foo.dllpattern, even those they don't need to. Lauching an app with thedotnetlauncher is a fine pattern and should certainly be embraced by portable apps, since it's the only correct cross-platform pattern.@nagilson @DamianEdwards @baronfel @dsplaisted @elinor-fung