dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.71k stars 1.07k forks source link

Runtime Package Store #14201

Closed bleroy closed 4 years ago

bleroy commented 7 years ago

Runtime Package Store

Starting with .NET Core 2.0, it's possible to package and deploy applications against a known set of packages that exist on the target environment. The benefits in doing so are smaller deployments, lower disk space usage, and in some cases improved startup performance.

This feature is implemented by a runtime package store, which is a location on disk where packages are stored, and that the runtime can find, access, and use. That location is a store directory, next to the dotnet host. Under this directory, there are subdirectories for target frameworks, under which the package store follows a NuGet layout.

The second part of the implementation is a "target manifest", which is a list of packages that compose a runtime package store, and that developers can target when publishing their application. The target manifest is typically provided by the owner of the targeted production environment.

The feature is also used implicitly by ASP.NET applications: the set of packages composing the ASP.NET Web framework is installed as part of the setup packages authored by Microsoft. When publishing an ASP.NET application, the published application is trimmed to only include the application's packages, and not the framework's packages.

Goals

Publishing an application against a target manifest

If you have a target manifest file on disk, you can specify it when publishing your app with the dotnet publish command:

dotnet publish --manifest path/to/the/target-manifest.xml

The resulting published application should only be deployed to an environment that has the packages described in the target manifest. Failing to do so would result in the application not starting.

It's possible to specify multiple target manifests when publishing an application. The application will then be trimmed for the union of packages specified in those target manifests.

Specifying a target manifest in the project file

Instead of specifying a target manifest in a dotnet publish command, it's possible to specify the manifest or manifests to use in the project file as a semicolon-separated list of paths under a TargetManifestFiles tag.

<PropertyGroup>
  <TargetManifestFiles>path/to/the/target-manifest.xml</TargetManifestFiles>
</PropertyGroup>

This should only be done when the target environment for the application is well-known.

A different case would be an open-source project: the users of the project will likely deploy to a variety of different production environments, which may have different sets of packages pre-installed. Maintainers of the project can't make assumptions about the target manifest, and users should instead rely on the --manifest option of dotnet publish instead.

ASP.NET implicit store

The default ASP.NET targets included with the Microsoft.AspNetCore.All meta-package include target manifests. As a consequence, dotnet publish of an ASP.NET application when it references this package will result in a published application that contains only the application and its assets, and not ASP.NET itself.

When an ASP.NET published application gets deployed, one should make sure that the target environment has ASP.NET installed, as the presence of .NET Core alone will not be sufficient.

If the application needs to be published to such an environment that doesn't include ASP.NET, it is possible to opt out of the implicit store by specifying a PublishWithAspNetCoreTargetManifest flag set to false in the project file, as you can see in the following example.

  <PropertyGroup>
    <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
  </PropertyGroup>

The ASP.NET runtime package store will be installed as part of the SDK packages distributed by Microsoft, and will also be available as a separate download so that any deployment environment can be prepared to receive published ASP.NET applications.

Preparing a runtime environment

The administrator of a runtime environment can optimize for certain types of applications by building a runtime package store and the corresponding target manifest.

The first step is to create an XML file that describes the packages that must compose the runtime package store. The format of this file is compatible with the csproj format. Here's an example of such a file that adds Newtonsoft.Json and System.Runtime.Serialization.Primitives to the package store:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
    <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.1.1" />
  </ItemGroup>
</Project>

The runtime package store can then be provisioned by running a dotnet store --manifest [target-manifest.xml] --runtime [runtime id] --framework [target framework] command. Multiple file paths can be passed to a single dotnet store command.

The output of the command is a package store under the .dotnet/store subdirectory of the user profile, unless a specific location has been specified using the --output option. The root directory of the store contains a target manifest artifact.xml file, that can be made available to be downloaded by application authors who want to target this store when publishing.

Feedback

Please share your feedback.

alexwiese commented 7 years ago

So it's kind of a global assembly cache?

muratg commented 7 years ago

:eyes:

bleroy commented 7 years ago

@alexwiese "So it's kind of a global assembly cache?"

That's a good question, and what most seasoned .NET devs think about first ;) There are some significant differences however...

g0t4 commented 7 years ago

What optimization is applied to the assemblies?

Is this optimization something we could apply to an entire app, like build a manifest to put the entire app into the runtime store? I kid, I assume this is only for packages (hence the PacakageReferences) so we'd have to package an app to do this which is odd, but hey might be worthwhile if performance is substantially boosted :)

muratg commented 7 years ago

@g0t4 the assemblies are cross-gen'd. This reduces the JIT time, hence improves startup time.

cwe1ss commented 7 years ago

Will there also be an implicit store for regular Microsoft.NET.Sdk projects (e.g. a simple console app) that contains the .NET core assemblies like System.Runtime etc? If so, will this cover all NETStandard.Library assemblies?

tmds commented 7 years ago

Would it make sense to pack stores as NuGet packages and allow them to be installed from a NuGet server?

DamianEdwards commented 7 years ago

@cwe1ss this scenario is already covered by the shared framework, i.e. Microsoft.NETCore.App. This is installed into [install-location]/dotnet/shared/Microsoft.NETCore.App and is used when you target netcoreapp. It includes everything in the related .NET Standard Library version and the APIs in .NET Core over and above .NET Standard. No need for anything else in the store.

DamianEdwards commented 7 years ago

@tmds it certainly could. We discussed packaging, naming, versioning, and acquisition of runtime stores (and their related target manifests for publishing) during design in 2.0 but ultimately haven't settled on a design yet that's integrated into the CLI experience. For now, one needs to call the dotnet store command to add packages to the store and then specify them in a target manifest during publish. ASP.NET Core's runtime store is included in the .NET Core installers and the target manifest is included in and automatically used during publish via way of the referenced Microsoft.AspNetCore.All package.

leastprivilege commented 7 years ago

Will dotnet publish -r ... still produce a completely self-contained application? Maybe this info needs to get added to the above document.

DamianEdwards commented 7 years ago

@leastprivilege yes. The Runtime Store only applies to applications running on the shared framework ("Portable Apps").

Aardvark71 commented 7 years ago

I'm wondering. Does it make sense to make use of the RPS in a asp dotnet core app targeting the full dotnet framework ? I guess not since it already makes use of the gac and it's kind of the same thing? Working with GAC gives one the possibility of working with assembly redirects which is ot available in the Runtime Package Store?

richlander commented 5 years ago

This feature was implemented in .NET Core 2.0. We have moved to a new scheme called "frameworks" that needs to be documented as part of .NET Core 3.0.

Rick-Anderson commented 5 years ago

We have moved to a new scheme called "frameworks" that needs to be documented as part of .NET Core 3.0

@richlander @BillWagner @mairaw do you have an issue for that?

AraHaan commented 2 years ago

This feature was implemented in .NET Core 2.0. We have moved to a new scheme called "frameworks" that needs to be documented as part of .NET Core 3.0.

@richlander

While that may be true, not everyone needs to put all of their packages into a custom framework (or well should).

While some might want to, others might instead want to use dotnet store for large packages that are not from Microsoft.

However, what if the packages already ship assemblies for runtime that are already crossgen2'd (for .NET 6)? Also what about packages that reference stable packages from .NET releases that are part of the latest servicing release of that specific shared framework version (would that be stored too over the one in the shared framework or if they referenced older stable package version of the one in the framework)?

richlander commented 2 years ago

@Rick-Anderson -- good question but there isn't a real replacement for store to document. We never invested in that.

@AraHaan -- I sympathize. We haven't built a good replacement for store.

Packages with R2R content should work and interact with servicing w/o issue. R2R has the same compatibility contract as IL. Perhaps I misunderstand the question.

AraHaan commented 2 years ago

Basically what I mean is as follows:

Which version would win in this case?

As for a replacement to dotnet store what if we done something like this:

Command Argument Notes
dotnet workload store | --runtime-pack-name <workload runtime pack name> Can be used to generate an ref pack name too, lacks .Runtime suffix but when used to make runtime pack it adds that suffix also RID would be appended at the end as well, can also be defined in the manifest file.
--version <workload version> Can be set in an input manifest file that will define the packages to place inside of the workload runtime and ref packs.
--runtime-identifier <RID> When nuget packages lack an runtimes folder with the target RID for the target runtime(s) the crossgen2 tool will be invoked, if it contains that folder the assets in said folder will be used, can also be defined in the manifest file. For ref packs the ref folder of the nuget packages will be used to generate it.
--runtime <the target framework for which is supported in the generated runtime and ref packs and can define multiple ones> Can be defined in the manifest file.
--manifest <project file containing nuget packages that has an file extension of .csproj>

The command would then generate:

Which would make it to where "it looks" like an actual workload, but it's not and also would do the same functionality of store but in a way that works for .NET 6+.

richlander commented 2 years ago

Ah, I see. We'd want 6.0.5 to be used.

It's possible that workloads could be extended to provide the deployment aspect for the assemblies. Today, the SDK knows about all the workloads (they are hard-coded). That's not a great long-term design. It blocks a number of scenarios.

Could you describe your scenario? That would help.

AraHaan commented 2 years ago

Sure, imagine you depend on nuget packages like Microsoft.Extensions.Hosting (or a package that brings in Microsoft.Extensions.Hosting and has a similar amount of packages it pulls in) which brings in a ton of nuget packages (some of which are needless because they are in the framework as well), as such one might want to offload those assemblies into an separate runtime and ref pack (perhaps make it into an custom workload locally) so that way their build output directories do not end up being 100+ MB in just package dependencies.

I might be exaggerating a little with the 100+ MB since the most on my projects is about 30 MB in just package binaries that gets copied, however some projects have it much worse than mine.

richlander commented 2 years ago

Totally get it. I meant higher level. If you can share, what is it that you are building where you experience these challenges?

AraHaan commented 2 years ago

A Discord bot, using Extensions.Hosting, Remora.Discord, and EFCore to name a few dependencies.

Assuming stable package versions and not prereleases, about ~20 MB (or a little more than that) gets copied to the output.

richlander commented 2 years ago

Got it. Does this run on a server or a user machine? I assume the size would be less relevant on a server but perhaps I'm wrong.

I'm asking for detail so I can use this information to help motivate investing in this scenario. It's not that we haven't talked about. We have. It's a significant project, so we want to ensure there is enough need for it and (more importantly) we know which scenarios to target. We can also move getting more details on your project to email if you'd like.

AraHaan commented 2 years ago

It's on my user machine that runs an local instance of SQL Server 2019 on Windows 11.

richlander commented 2 years ago

OK. I hear you saying your app is a good example of demonstrating this need, not that your users are specifically suffering from it currently. It would be great if we could get access to the source so that we could check this out for ourselves.