Skip to content

Commit

Permalink
Address feedback from @jkotas, and @vitek-karas
Browse files Browse the repository at this point in the history
  • Loading branch information
swaroop-sridhar committed Feb 6, 2020
1 parent fb69d53 commit 11912ba
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 66 deletions.
8 changes: 6 additions & 2 deletions accepted/single-file/bundler.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ Given a .net core app published with respect to a specific runtime, the bundler
* Write meta-data headers and manifest that help locate the contents of the bundle.
* Set the bundle-indicator in the `AppHost` to the offset of the bundle-header.

The bundler should generate the correct format of single-file bundles based on the target framework.

## Implementation

The bundling tool is implemented as a library in the [Microsoft.NET.HostModel](https://www.nuget.org/packages/Microsoft.NET.HostModel/) package. This library is used by the SDK in order to publish a .net core app as a single-file.
The bundling tool is implemented as a library in the [Microsoft.NET.HostModel](https://www.nuget.org/packages/Microsoft.NET.HostModel/) package in the [runtime](https://github.com/dotnet/runtime) repo.

The bundler should generate the correct format of single-file bundles based on the target framework.
* The bundler is located alongside the host components, since their implementations are closely related.
* Separating the bundler (and other AppHost transformers in HostModel) implementation from SDK repo aligns with code ownership, and facilitates maintenance of the library.
* The build targets/tasks that use the HostModel library are in the SDK repo. This facilitates the MSBuild tasks to be multi-targeted. It also helps generate localized error messages, since SDK repo has the localization infrastructure.

## Limitations

Expand Down
103 changes: 39 additions & 64 deletions accepted/single-file/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ In .Net Core 5.0, we plan to implement a solution that:
* Optimizing for development: The single-file publishing is typically not a part of the development cycle, but is rather a packaging step as part of a release. Therefore, the single-file feature will be designed with focus on consumption rather than production.
* Merging IL: Tools like [ILMerge](https://github.com/dotnet/ILMerge) combines the IL from many assemblies into one, but lose assembly identity in the process. This is not a goal for single-file feature.

## User Experience

Here's the overall experience for publishing a HelloWorld single-file app. The new build properties used in this example are explained in the [Build System Interface](#build-system-interface) section.

* Create a new HelloWorld app: `HelloWorld$ dotnet new console`

* Framework Dependent Publish

* Normal publish: `dotnet publish`
* Published files: `HelloWorld.exe`, `HelloWorld.dll`, `HelloWorld.deps.json`, `HelloWorld.runtimeconfig.json`, `HelloWorld.pdb`

* Single-file publish: `dotnet publish -r win-x64 --self-contained=false /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`

* Self-Contained Publish

* Normal publish: `dotnet publish -r win-x64`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`, and 224 more files
* Single-file publish Linux: `dotnet publish -r linux-x64 /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`
* Single-file publish Windows: `dotnet publish -r win-x64 /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`, `coreclr.dll`, `clrjit.dll`, `clrcompression.dll`, `mscordaccore.dll`
* Single-file publish Windows with Extraction: `dotnet publish -r win-x64 /p:PublishSingleFile=true /p:IncludeContentInSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`

## Build System Interface

Publishing to a single file can be triggered by adding the following property to an application's project file:
Expand All @@ -47,19 +72,17 @@ All other files, including platform-specific native binaries and symbol files, a

### Optional Settings

| Property | Behavior when set to `true` |
| ------------------------------- | ------------------------------------------------------------ |
| `IncludeContentInSingleFile` | All published files (except symbol files) are embedded within the single-file bundle. |
| `IncludeSymbolsInSingleFile` | The symbol files (IL `.pdb` file, and the native `.ni.pdb` / `app.guid.map` files generated by ready-to-run compiler) are embedded within the symbol file. |
| `ExtractContentsFromSingleFile` | The files that cannot be processed directly from the bundle are extracted out to disk at startup. |
| Property | Behavior when set to `true` |
| ---------------------------- | ------------------------------------------------------------ |
| `IncludeContentInSingleFile` | All published files (except symbol files) are embedded within the single-file bundle. The files that cannot be processed directly from the bundle are extracted out to disk at startup. |
| `IncludeSymbolsInSingleFile` | The symbol files (IL `.pdb` file, and the native `.ni.pdb` / `app.guid.map` files generated by ready-to-run compiler) are embedded within the symbol file, and extracted out to disk on startup. |

When including all content in single files, certain files can be explicitly excluded from being embedded in the single-file by setting following `ExcludeFromSingleFile` meta-data element. For example, to place some files in the publish directory but not bundle them in the single-file:
When including content in single files, certain files can be explicitly excluded from being embedded in the single-file by setting following `ExcludeFromSingleFile` meta-data element. For example, to place some files in the publish directory but not bundle them in the single-file:

```xml
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<IncludeContentInSingleFile>true</IncludeContentInSingleFile>
<ExtractContentsFromSingleFile>true</ExtractContentsFromSingleFile>
</PropertyGroup>
<ItemGroup>
<Content Update="*.xml">
Expand Down Expand Up @@ -116,7 +139,11 @@ On Startup, the [host components](https://github.com/dotnet/core-setup/blob/mast

* The `bundle_probe` function pointer is passed to the runtime (encoded as a string) through a property named `BUNDLE_PROBE`.

* The contents of the bundle are not included in `TRUSTED_PLATFORM_ASSEMBLIES`. Any additional assemblies on disk (ex: due to `additional-deps` /`AdditionalProbingPaths`) are included in `TRUSTED_PLATFORM_ASSEMBLIES`.
* The contents of the bundle are not included in `TRUSTED_PLATFORM_ASSEMBLIES`. Any additional assemblies on disk (ex: due to `additional-deps` /`AdditionalProbingPaths`) are included in `TRUSTED_PLATFORM_ASSEMBLIES`.

* The runtime probes the satellite assemblies in the bundle before looking in `PLATFORM_RESOURCE_ROOTS`. There is no change anticipated to `PLATFORM_RESOURCE_ROOTS` list.

* The extraction directory (if any) will be added to `NATIVE_DLL_SEARCH_DIRECTORIES` as the first destination to probe for native binaries.

### Host Builds

Expand Down Expand Up @@ -152,7 +179,7 @@ Therefore, we propose that targeting `win7-*` should not be supported when publi

### Dependency Resolution

In order to resolve assemblies, the embedded resources are probed first, followed by other probing paths encoded in `TRUSTED_PLATFORM_ASSEMBLIES`.
In order to resolve assemblies, runtime will first query the bundle probe for it followed by exiting search in `TRUSTED_PLATFORM_ASSEMBLIES` followed by any existing additional probing.

### PEImage loader

Expand All @@ -173,10 +200,7 @@ The Windows mapping routines have the following limitations:
* We cannot use the (SEC_IMAGE) attribute to perform automatic section-wise loading (circumventing alignment requirements) of bundled assemblies directly.
* Instead we need to map each section independently.
* [MapViewOfFile]( https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex) can only map parts of a file aligned at [memory allocation granularity]( https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info), which is 64KB.
This means that:
* Each section within the assemblies should be aligned at 64KB – which is not guaranteed by the crossgen compiler.
* In order to memory-map one embedded assembly at a time, the assemblies in the bundle must be aligned at 64KB boundaries.
* A prototype bundler with this behavior for testing purposes is available in this branch: https://github.com/swaroop-sridhar/core-setup/tree/single-exe
This means that each section within the assemblies should be aligned at 64KB – which is not guaranteed by the crossgen compiler.

We therefore map the assembly and perform an in-memory copy of the sections to appropriate offsets. In the long term, the solution to the mapping problem would involve considerations such as:
* Compile all assemblies in a version bubble into one PE assembly, with a few aligned sections.
Expand All @@ -194,7 +218,7 @@ There are a few options to consider for the `Assembly.Location` property of a bu
* A special UNC notation such as `<bundle-path>/:/asm.dll` to denote files that come from the bundle.
* A configurable selection of the above, etc.

Proposed solution is: `:/<bundle-sub-path>/asm.dll`, where the `:` denotes the location within the bundle. The `:` is not a valid character in file-names on Windows. The `:` is already used as a path-separator on Unix systems.
Proposed solution is for `Assembly.Location` to return `null` for bundled assemblies, which is the default behavior for assemblies loaded from memory.

Most of the app development can be agnostic to whether the app is published as single-file or not. However, the parts of the app that deal with physical locations of files need to be aware of the single-file packaging.

Expand All @@ -204,30 +228,7 @@ Most of the app development can be agnostic to whether the app is published as s

## Handling Content

The app may want to access certain embedded content for reading, rather than loading via host/runtime. For example: bundled payload/data files. In this case, the recommended strategy is to embed the content files within appropriate managed assemblies as PE resources, and access them through [resource handling APIs](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getmanifestresourceinfo?view=netcore-3.1).

However, explicit access to the embedded files is useful in situations such as:
- Reading data-files from the app's native code
- Open an assembly for reflection/inspection

Therefore, we propose adding an API similar to [GetManifestResourceStream](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getmanifestresourcestream?view=netcore-3.1) to obtain a stream corresponding to an embedded file. This is only a draft of the proposed APIs. The actual shape of the APIs will be decided via API review process.

```cs
namespace System.Runtime.Loader
{
public partial class Bundle
{
// Check whether an app is running from a single-file bundle
public static bool IsBundle();

// Get the location where contents of the bundle are extracted
public static string GetContentRoot(Assembly assembly);

// Open a file embedded in the bundle
public static System.IO.Stream GetFileStream(string path);
}
}
```
The app may want to access certain embedded content for reading, rather than loading via host/runtime. For example: bundled payload/data files. In this case, the recommended strategy is to embed the content files within appropriate managed assemblies as resources, and access them through [resource handling APIs](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getmanifestresourceinfo?view=netcore-3.1).

## Testing

Expand Down Expand Up @@ -257,29 +258,3 @@ Collect telemetry for single-file published apps with respect to parameters such

* **Compression**: Currently the bundler does not compress the contents embedded at the end of the host binary. Compressing the bundled files and meta-data can significantly reduce the size of the single-file output (by about 30%-50% as determined by prototyping).
* **Single-file Plugins** Extended the above design to seamlessly support single-file publish for plugins.

## User Experience

To summarize, here's the overall experience for creating a HelloWorld single-file app:

* Create a new HelloWorld app: `HelloWorld$ dotnet new console`

* Framework Dependent Publish

* Normal publish: `dotnet publish`
* Published files: `HelloWorld.exe`, `HelloWorld.dll`, `HelloWorld.deps.json`, `HelloWorld.runtimeconfig.json`, `HelloWorld.pdb`

* Single-file publish: `dotnet publish -r win10-x64 --self-contained=false /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`

* Self-Contained Publish

* Normal publish: `dotnet publish -r win10-x64`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`, and 224 more files
* Single-file publish Linux: `dotnet publish -r linux-x64 /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`
* Single-file publish Windows: `dotnet publish -r win10-x64 /p:PublishSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`, `coreclr.dll`, `clrjit.dll`, `clrcompression.dll`, `mscordaccore.dll`
* Single-file publish Windows with Extraction: `dotnet publish -r win10-x64 /p:PublishSingleFile=true /p:IncludeContentInSingleFile=true /p:ExtractContentsFromSingleFile=true`
* Published files: `HelloWorld.exe`, `HelloWorld.pdb`

0 comments on commit 11912ba

Please sign in to comment.