Description
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
andLib2
; an updated copy ofLib2.dll
is copied to the output folder ofClient
and used at debug time.
- Expected behavior: The compiler is run for
- 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 ofClient
and used at debug time. The compiler is not run forLib1
orClient
.
- Expected behavior: an updated copy of
- 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’ CopyUpToDateMarker
s 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.
- 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
- Discover the complete closure of assemblies that will be copied to the project's output, for example by passing
FindDependencies=true
toResolveAssemblyReference
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.