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

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.
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
Scenarios
Several new scenarios arise in a solution that uses reference assemblies.
For illustrative purposes, consider a solution consisting of
Client.csprojthat depends onLib1.csprojthat depends onLib2.csproj. In this wayClientis "downstream" ofLib1and bothLib1andClientare downstream ofLib2.Lib1andLib2; an updated copy ofLib2.dllis copied to the output folder ofClientand used at debug time.Lib2.dllis copied to the output folder ofClientand used at debug time. The compiler is not run forLib1orClient.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
ReferenceAssemblyand a new concept, theCopyUpToDateMarker. This is done in dotnet/msbuild#2039.The
CopyUpToDateMarkerfor 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'sCopyUpToDateMarker, because copying references will not update the primary output of the project.Alternatives considered
UpToDateCheckInputitem to avoid having detailed knowledge of reference assemblies in the project system.ProjectReferenceto populate the item in time.FindDependencies=truetoResolveAssemblyReferencewithin a design-time build.This is the result of extensive offline discussions with @jcouv, @tmeschter, @panopticoncentral, @davkean, and others.