Skip to content

Reduce the amount of work required to build apps for multiple platforms #42344

Open
@baronfel

Description

@baronfel

Is your feature request related to a problem? Please describe.

Today a project with multiple RuntimeIdentifiers that is published for each of those RuntimeIdentifiers does a significant amount of re-work - that is, work that must be re-computed because some of the inputs of the overall compilation changed. After some research, I think I've identified the causes of most of the sources of rework and have a proposal for what we should do to eliminate a large amount of re-compilation and re-generation done when publishing for multiple platforms.

As a broad sense of the potential gains, a single console project that Publishes ReadyToRun for

  • platform-independent
  • win-x64
  • win-arm64

currently takes ~5s on my machine (2.25s of which is the Csc task across 2 invocations). With the changes proposed below this goes to 3.5s total (~1.25s in the Csc task across a single invocation).

Describe the solution you'd like

In general, for publishing using PublishReadyToRun there are two phases:

  • compile the project
  • run crossgen on the project to specialize the project for a given RID

Each of these steps has a set of input and output data that, when out of date, triggers build Targets that generate the outputs from the inputs. After analysis of Roslyn compilations for different RIDs, it turns out the primary inputs and outputs share quite a few parameters across platforms. The differences that cause recompilation across the platforms seem to be:

  • MSBuild-generated files
    • Implicit/Global Usings
    • AssemblyInfo
    • Roslyn Analyzer EditorConfig
    • TFM Attribute file
  • Expected Compilation outputs
    • DLL and Reference DLL output paths
  • Compiler property inputs
    • Platform

If each of these can be rationalized to be more stable/fixed even in the face of different RIDs, then compiler invocations can be skipped and the outputs reused. In the most extreme case, if these features are disabled or pinned to a stable value, a single compilation can be reused for all platforms. You can see this in action in my sample repo.

A graph of the current state might look like this:

flowchart TD
    A[Start Build] --> B[Builds for no RID]
    B --> C[Calls the C# compiler for no RID]
    A --> D[Builds for the linux-x64 RID]
    D --> E[Calls the C# compiler for the linux-x64 RID]
    A --> F[Builds for the linux-arm64 RID]
    F --> G[Calls the C# compiler for the linux-arm64 RID]
    C --> H[Creates a platform-agnostic payload]
    E --> I[ReadyToRun compiles a linux-x64 payload]
    G --> J[ReadyToRun compiles a linux-arm64 payload]
Loading

A graph of a proposed state might look like this:

flowchart TD
    A[Start Build] --> B[Builds for no RID]
    B --> C[Calls the C# compiler for no RID]
    A --> D[Builds for the linux-x64 RID]
    D --> C
    A --> F[Builds for the linux-arm64 RID]
    F --> C
    C --> H[Creates a platform-agnostic payload]
    C --> I[ReadyToRun compiles a linux-x64 payload]
    C --> J[ReadyToRun compiles a linux-arm64 payload]
Loading

Managing generated files

The generated files above don't seem to have any content that changes between a platform-independent and the various platform-specific builds - the reason they trigger rebuilds is because they implicitly rely on the IntermediateOutputPath property. For a RID-specific build this property contains the RID and so any file written to this location is assumed to be RID specific as well.

I propose that we have two logical properties for intermediate outputs:

  • IntermediatePlatformAgnosticOutputPath
  • IntermediatePlatformSpecificOutputPath

And the above inputs and outputs should be changed to be derived from IntermediatePlatformAgnosticOutputPath.

Managing expected compilation outputs

Similar to the above, the compilers use the IntermediateAssembly and IntermediateRefAssembly MSBuild Items, which are defined in the Microsoft.Common.CurrentVersion.targets as directly using IntermediateOutputPath - we should consider making them use one of the two above properties as well.

Managing compiler inputs

The only varying property is Platform - and as of my investigations right now the default platform is AnyCPU. If this is satisfactory for the majority of managed .NET Applications then we could change RID-specific compilations to not pass Platform and remove this delta.

Wrapping up

If we can eliminate these sources of variation in a principled way, and potentially expand the use of the two proposed properties throughout the rest of the SDK, we could potentially eliminate quite a lot of re-work for use cases we care about like multi-RID publishing.

Metadata

Metadata

Assignees

Labels

Area-NetSDKEpicGroups multiple user stories. Can be grouped under a theme.untriagedRequest triage from a team member

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions