Skip to content

Up-to-date check awareness of reference assemblies #2254

Closed
@rainersigwald

Description

@rainersigwald

The C# and VB compilers introduced “reference assemblies”, a new compiler output that contains only the public interface of an assembly, and that does not change when private implementation details are changed: dotnet/roslyn#2184. This allows projects to avoid compile time when the output of recompilation would be identical, because no local source files have changed and the interface of referenced assemblies is identical.

This presents new scenarios for the fast up-to-date check, because

  • The set of inputs to projects that use reference assemblies has expanded.
    • The new inputs are the implementation assemblies of references. The reference assemblies are passed to the compiler and thus discovered by the existing design-time build.
  • Some assumptions of the FUTD check no longer hold.
    • Specifically, if a transitive dependency has its implementation updated, that no longer ensures that the direct dependency referencing it is updated.

Scenarios

Several new scenarios arise in a solution that uses reference assemblies.

For illustrative purposes, consider a solution consisting of Client.csproj that depends on Lib1.csproj that depends on Lib2.csproj. In this way Client is "downstream" of Lib1 and both Lib1 and Client are downstream of Lib2.

  • Change the interface of Lib2, debug Client.
    • Expected behavior: The compiler is run for Lib1 and Lib2; an updated copy of Lib2.dll is copied to the output folder of Client and used at debug time.
  • Change the implementation but not the interface of Lib2, debug Client.
    • Expected behavior: an updated copy of Lib2.dll is copied to the output folder of Client and used at debug time. The compiler is not run for Lib1 or Client.
  • Change nothing, debug Client.
    • Expected behavior: MSBuild is not invoked, the extant copy of Client is used at debug time.

Proposed design

To account for the transitive-dependency-copying behavior, I propose extending the up-to-date check with an additional set of inputs and outputs.

The MSBuild item that represents the output of a project is annotated with metadata for ReferenceAssembly and a new concept, the CopyUpToDateMarker. This is done in dotnet/msbuild#2039.

The CopyUpToDateMarker for a project is touched when references are copied to its output folder. This can then indicate that downstream projects need to build in order to copy the references along.

The timestamp of references’ CopyUpToDateMarkers must be compared only to the current project's CopyUpToDateMarker, because copying references will not update the primary output of the project.

Let Inputs = sources + references.select( impl and ref assemblies )
Let CopyMarkerInputs = references.select( copy markers )
Let Outputs = dll + pdb + xml
if oldest(Outputs) older than any Inputs:
    build(reason: compiler output change expected)
else if CurrentCopyMarker older than any CopyMarkerInputs:
    build(reason: copied reference update expected)
else
    skip

image

Alternatives considered

  • Use the UpToDateCheckInput item to avoid having detailed knowledge of reference assemblies in the project system.
    • The value of the item is used after the project is evaluated, but no targets are run, so it's impossible to dynamically discover the outputs of a ProjectReference to populate the item in time.
  • Discover the complete closure of assemblies that will be copied to the project's output, for example by passing FindDependencies=true to ResolveAssemblyReference within a design-time build.
    • This is currently explicitly disabled for performance reasons—doing this discovery as RAR does requires recursively loading and analyzing every assembly referenced by each reference, and doing a fair amount of path probing to find the location of each.
    • Without more sophisticated input:output correlation, this is susceptible to the subsequent-builds-not-up-to-date problem described next.
  • Add the copy marker file of referenced projects to the standard set of inputs and do not update the FUTD algorithm.
    • This works for the first build after a dependency's reference-only update. But because the primary output of the project wouldn't change (its reference inputs haven't changed), all subsequent checks would continue to require building. That would invalidate the fast up-to-date-check in many scenarios.
  • Updating the timestamp of the primary output assembly when any references are copied to its output folder.
    • Would result in copy cascades of identical-but-newer files in subsequent builds without the FUTD check (command line builds).

This is the result of extensive offline discussions with @jcouv, @tmeschter, @panopticoncentral, @davkean, and others.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions