Open sbomer opened 6 years ago
Thanks for putting this together, @sbomer. Having a supported way to find and change all of the files that would be included in publishing (including all managed assemblies and even miscellaneous files in the app) would make constructing these tools much more straightforward. Phase ordering will also help a lot -- I could easily imagine users wanting to run ILLink, followed by CrossGen, Single exe tooling, or ILC, followed by installer packaging and that ordering is very important. Of course, they might only want to run one or two of those steps as well.
I'd also emphasize the importance of being able to run tools in the build phase instead of publish -- some of these tools, such as ILLink can significantly change application behavior, so developers should be able to debug their app in the same way it will ship.
Cc @peterhuene
@sbomer This is a great write up and exactly what I was hoping to see next after our chat. Thanks!
Cc @tmat
I'd also emphasize the importance of being able to run tools in the build phase instead of publish
Good point. We are also working to effectivity make publish an optional step for common cases in 3.0. Build output will include nuget deoendencies and be xcopyable to other machines.
I'm hoping we can avhieve this while reducing code duplication between build and publish (for example, today they have separate paths for deps.json generation). Ideally this could help mean that such a tools like this can write code once that can easily be configured to run on build or only on publish.
Numerous tools exist today which perform some transformation on IL or other files that are deployed with a published app. Examples include:
These tools all need to run at similar places during the build, and many of the tools can usefully be used independently. It is desirable for developers to have the final say over which tools run during a build, allowing them to weigh tradeoffs and make the right decision for their apps. I'll be focusing on the linker, but it's worth keeping these other tools in mind.
Some of these tools only provide commandline executables, leaving the developer to write MSBuild logic or scripts to run the tool during a build. Others attempt to provide full integration with the SDK, providing tasks and targets that can be turned on or off with a simple boolean property.
This proposal is for the SDK to provide a supported, documented way for such tools to make themselves a part of the build, reducing the amount of duplicated effort it takes for tool authors to ship tools that are properly integrated with the rest of the build logic.
This may involve coordination with MSBuild, since some of the relevant targets belong to MSBuild itself. See this issue on MSBuild transformations for details.
Problems with the existing approach
Some of the existing tools (ILLink, CoreRT, Crossgen) ship with their own targets that hook into the SDK to run at the right place. They use
BeforeTargets
to run just beforeComputeFilesToPublish
, where they rewriteResolvedAssembliesToPublish
,IntermediateAssembly
, and other items to point to the transformed assemblies:https://github.com/mono/linker/blob/7c11deffc05005b6b60eeccb80a7cf133e24c007/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets#L91-L117
The existing targets are factored in a way that makes transparently rewriting these inputs difficult. These are some of the issues that come up:
Downstream targets implicitly rely on invariants broken by the linker
Microsoft.NET.PreserveCompilationContext.targets
adds ref assemblies to the files to publish afterComputeFilesToPublish
. https://github.com/dotnet/sdk/blob/0eb68de1dd44834e3966cf02cafaff16b71d92d7/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PreserveCompilationContext.targets#L69-L71Upstream targets capture pre-transform values of these ItemGroups
Microsoft.Common.CurrentVersion.targets
in a few places capturesIntermediateAssembly
(these examples are taken from https://github.com/Microsoft/msbuild/issues/3128).Dependencies between the publish set and other targets are not expressed
GeneratePublishDependencyFile
is unaware of modifications to publish set, and includes everything in the pre-transformed set in the .deps file, resulting in failures at runtime._PublishConflictPackageFiles
to exclude removed files from the .deps file: https://github.com/mono/linker/blob/7c11deffc05005b6b60eeccb80a7cf133e24c007/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets#L510-L526Duplicated work to understand inputs to
ComputeFilesToPublish
ComputeFilesToPublish
, rewrite them correctly, and work around any SDK logic that breaks as a result. For example,ResolvedAssembliesToPublish
contains resource assemblies that the linker filters out. I had to discover this experimentally. https://github.com/mono/linker/blob/7c11deffc05005b6b60eeccb80a7cf133e24c007/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets#L393-L395SDK changes break tools that rely on this approach in subtle ways
Duplicate work to handle common build concerns
Cooperative phase ordering
Proposal
We should work together to come up with a contract that defines a single place where the linker and similar rewriting tools can hook in, without having to work around unrelated logic elsewhere in the SDK and MSBuild. As a start, here are the current behaviors I'm aware of, and some requirements for the contract to be useful to each tool. Note that there are many similarities between these tools.
ILLink
Current behavior
ComputeFilesToPublish
, picks out fromResolvedAssembliesToPublish
the managed assemblies that aren't resources. These and theIntermediateAssembly
are linker inputs.RuntimeIdentifier
andTargetFramework
. Includes System.Private.CoreLib.dll as a special case (because it ships in the "native" directory of the runtime package, while everything else comes from "lib").ResolvedAssembliesToPublish
,IntermediateAssembly
, and_DebugSymbolsIntermediatePath
with the linked outputs._PublishConflictPackageFiles
to include managed assemblies that were removed, so that they will be kept out of the generated deps.json.Requirements
Native dependency trimming (part of ILLink.Tasks)
Current behavior
ResolvedAssembliesToPublish
.ResolvedAssembliesToPublish
._PublishConflictPackageFiles
to include native files that were removed, so that they will be kept out of the generated deps.json.Requirements
Crossgen
Current behavior
ComputeFilesToPublish
, picks out fromResolvedAssembliesToPublish
the managed assemblies that aren't resources. Since this is after linking,ResolvedAssembliesToPublish
has already been rewritten. This andIntermediateAssembly
(also rewritten by the linker) are inputs to crossgen.ResolvedAssembliesToPublish
andIntermediateAssembly
.ResolvedAssembliesToPublish
andIntermediateAssembly
.Requirements
ILC
I'm less aware of the details of this tool, but here's my understanding:
Current behavior
ComputeFilesToPublish
, processesResolvedAssembliesToPublish
to pick out the managed assemblies that are referenced by ilc subject to certain constraints, and also a set of assemblies to skip.IntermediateAssembly
to point to the optimized binary.Requirements
Other tools to consider
It may be helpful to keep in mind Fody, ILMerge, ILRepack, single exe, installer technology, and any tools that filter the publish set, like the runtime store, because they could all benefit from a documented set of behavior around publish, enabling them to work well with the .NET SDK out of the box.
@nguerrera @zamont @swaroop-sridhar @morganbr @jeffschwMSFT