-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Common.targets support for reference assemblies #2039
Conversation
Provides an item, `ReferencePathWithInterfaceOnlyAssemblies`, that consists of the reference (interface-only) versions of assemblies where available, allowing a consuming task to be incrementally up-to-date after implementation-only changes have been made to a reference. When `%(ReferenceAssembly)` metadata is unavailable for a given reference, the new item contains the implementation assembly directly. Additionally creates a new output item for the current project's reference assembly by default when `$(Deterministic)` is `true`. If this is created, it is copied to the output directory using the new `CopyRefAssembly` task, which copies only if the ref assembly is different from the current file. If present, the ref asm is passed to the project's consumes in metadata. See #1986, dotnet/roslyn#2184.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prelim self-review.
@@ -350,6 +352,13 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<FinalDocFile Include="@(DocFileItem->'$(OutDir)%(Filename)%(Extension)')"/> | |||
</ItemGroup> | |||
|
|||
<ItemGroup Condition="'$(Deterministic)' == 'true'"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs an explicit "no ref asm" off switch, as well as defaulting for Deterministic
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Condition must also apply to $(TargetRefPath)
.
@@ -350,6 +352,13 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<FinalDocFile Include="@(DocFileItem->'$(OutDir)%(Filename)%(Extension)')"/> | |||
</ItemGroup> | |||
|
|||
<ItemGroup Condition="'$(Deterministic)' == 'true'"> | |||
<!-- TODO: should this be configurable? Default path obeys conventions. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking "no", let the user define the item themselves if they want a nonstandard path.
</Target> | ||
|
||
<ItemDefinitionGroup> | ||
<ReferencePath> | ||
<ReferenceAssembly>%(FullPath)</ReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment this.
Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true' and Exists('@(IntermediateRefAssembly)')" | ||
> | ||
|
||
<Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add output to the outputs-of-this-project item so clean works right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned about how this will work with Clean
, have you tested it?
@@ -281,6 +281,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<!-- Example, c:\MyProjects\MyProject\bin\debug\MyAssembly.dll --> | |||
<TargetPath Condition=" '$(TargetPath)' == '' ">$(TargetDir)$(TargetFileName)</TargetPath> | |||
|
|||
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(Deterministic)' == 'true' ">$([System.IO.Path]::Combine(`$([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetFullPath(`$(TargetPath)`))))`, 'ref', `$(TargetFileName)`))</TargetRefPath> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this just use $(TargetDir)
?
Then you could use $([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my original plan, but it would break the case where TargetPath
is explicitly specified, which seems to be legal from my reading of the prior set of conditions.
I'd be fairly ok with saying "if you want to do that odd thing, you must also specify this" but it needs to be an explicit choice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if a user wants to set ref\$(TargetFileName)
to whatever they specified as TargetPath
would make sense. I'll let you decide...
@@ -1797,6 +1806,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<TargetPathWithTargetPlatformMoniker Include="$(TargetPath)"> | |||
<TargetPlatformMoniker>$(TargetPlatformMoniker)</TargetPlatformMoniker> | |||
<TargetPlatformIdentifier>$(TargetPlatformIdentifier)</TargetPlatformIdentifier> | |||
<ReferenceAssembly Condition="'$(TargetRefPath)' != ''">$(TargetRefPath)</ReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be conditioned? It seemed unusual to me, most other places just copy the empty value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only field that's optional, right? If this particular project doesn't have a ref asm for whatever reason, we don't want to populate this field because it'll break downstream projects.
I think this'll be clearer when I just use a big off switch for the entire feature, rather than checking whether that path is populated. Seem reasonable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I've seen, setting metadata is never conditioned because the getting of the value is checked later. But if you like this pattern more I'll let you do what you think is best.
Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true' and Exists('@(IntermediateRefAssembly)')" | ||
> | ||
|
||
<Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this be in FileWrites
? It probably needs to be in this item group almost always otherwise it will get deleted in between builds right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed that after my last changes Clean
now deletes the ref assembly from both bin and obj, and IncrementalClean
leaves it alone. Is there another scenario to test?
FYI @jasonmalinowski (this is the core change for MSBuild to handle ref assemblies). |
@@ -154,6 +154,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<_DocumentationFileProduced>true</_DocumentationFileProduced> | |||
<_DocumentationFileProduced Condition="'$(DocumentationFile)'==''">false</_DocumentationFileProduced> | |||
|
|||
<!-- Whether or not a reference assembly is produced--> | |||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are users supposed to use this property to turn ref assemblies off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To not create them in this project. They'll still be consumed if produced in other projects.
Better names welcome.
@@ -2015,8 +2028,21 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<Output TaskParameter="FilesWritten" ItemName="FileWrites"/> | |||
<Output TaskParameter="DependsOnSystemRuntime" PropertyName="DependsOnSystemRuntime"/> | |||
</ResolveAssemblyReference> | |||
|
|||
<ItemGroup> | |||
<ReferencePathWithInterfaceOnlyAssemblies Include="@(ReferencePath->'%(ReferenceAssembly)')" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it OK to have empty strings for the ReferencePath items that do not have ref assemblies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, the item def group adding a ref assembly value for each item is in the next diff, ignore :)
In reply to: 114407016 [](ancestors = 114407016)
@@ -154,6 +154,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<_DocumentationFileProduced>true</_DocumentationFileProduced> | |||
<_DocumentationFileProduced Condition="'$(DocumentationFile)'==''">false</_DocumentationFileProduced> | |||
|
|||
<!-- Whether or not a reference assembly is produced--> | |||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not already defined!
@dotnet-bot test this please |
@dotnet-bot test this please (New CI jobs might be borked) |
@@ -1797,6 +1809,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<TargetPathWithTargetPlatformMoniker Include="$(TargetPath)"> | |||
<TargetPlatformMoniker>$(TargetPlatformMoniker)</TargetPlatformMoniker> | |||
<TargetPlatformIdentifier>$(TargetPlatformIdentifier)</TargetPlatformIdentifier> | |||
<ReferenceAssembly Condition="'$(ProduceReferenceAssembly)' != ''">$(TargetRefPath)</ReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this condition be if ProduceReferenceAssembly == true? Otherwise if false we'll still be setting this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes 😳
if this metadatum is always populated. This item definition ensures that it points | ||
to the implementation assembly unless specified. --> | ||
<ReferencePath> | ||
<ReferenceAssembly>%(FullPath)</ReferenceAssembly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is magic. ✨
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alas, it was too magic: #2165
@@ -172,5 +172,6 @@ | |||
<!-- Roslyn tasks are now in an assembly owned and shipped by Roslyn --> | |||
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.Csc" AssemblyFile="$(RoslynTargetsPath)\Microsoft.Build.Tasks.CodeAnalysis.dll" Condition="'$(MSBuildAssemblyVersion)' != ''" /> | |||
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.Vbc" AssemblyFile="$(RoslynTargetsPath)\Microsoft.Build.Tasks.CodeAnalysis.dll" Condition="'$(MSBuildAssemblyVersion)' != ''" /> | |||
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.CopyRefAssembly" AssemblyFile="$(RoslynTargetsPath)\Microsoft.Build.Tasks.CodeAnalysis.dll" Condition="'$(MSBuildAssemblyVersion)' != ''" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the expected behavior if the user has a newer version of the Roslyn package installed? I believe that package does UsingTasks on Csc/Vbc so those get overridden. Should we be doing the UsingTask too there so that way an upgrade also means you get a newer version here? (cc @jcouv)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent catch! I added it to the Roslyn package props in dotnet/roslyn@ae50fe8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks 😄
Use the new item provided by common targets to avoid recompiling a project when a reference has changed only in implementation, not interface. Include a shim so that these targets can continue to work on older MSBuild distributions. That keeps the NuGet-package-for-downlevel scenario working, as well as making an easy transition period to the new logic. Requires dotnet/msbuild#2039 to enable end-to-end use of reference assemblies, but is compatible with earlier MSBuilds (without using reference assemblies).
…get, and shim (#19146) * Use refasm-only items for compilation Use the new item provided by common targets to avoid recompiling a project when a reference has changed only in implementation, not interface. Include a shim so that these targets can continue to work on older MSBuild distributions. That keeps the NuGet-package-for-downlevel scenario working, as well as making an easy transition period to the new logic. Requires dotnet/msbuild#2039 to enable end-to-end use of reference assemblies, but is compatible with earlier MSBuilds (without using reference assemblies). * Use CopyRefAssembly from NuGet package The Microsoft.Net.Compilers package delivers UsingTasks to override the "standard" ones provided by MSBuild's Microsoft.Common.tasks. That should include the new CopyRefAssembly.
Relates to dotnet/roslyn#18612 |
This should avoid problems with missing references caused by ImplicitlyExpandDesignTimeFacades running after RAR (and therefore after the creation of ReferencePathWithRefAssemblies prior to this commit).
Introduce $(CompileUsingReferenceAssemblies), which can avoid passing reference assemblies to the compiler when set to false.
IDE fast up-to-date checks need to be able to compare timestamps on _both_ the reference assembly (to see if a recompile is needed) and the implementation assembly (because it needs to be copied to an output location). Provide metadata on the adjusted references that points back to the implementation assembly if a ref assembly was found.
197900d
to
f357dc1
Compare
Since there's a single clear off switch, depend on it instead of various Exists checks (avoiding I/O) and instead of $(Deterministic).
And decouple it from $(Deterministic) entirely.
When a project both uses and produces reference assemblies, its primary output assembly might be up to date while (transitive) references need to be copied to its output directory. This case fooled the Visual Studio Fast Up-To-Date check, because the transitive dependencies aren't listed as project inputs (because design-time builds disable RAR's FindDependencies for performance reasons). To account for this, introduce a new file that is updated whenever _CopyFilesMarkedCopyLocal does actual work, pass it along as part of the project's output, and consider it a project input for the FUTD check.
3aceaa8
to
a5cf059
Compare
Provides an item,
ReferencePathWithInterfaceOnlyAssemblies
, thatconsists of the reference (interface-only) versions of assemblies where
available, allowing a consuming task to be incrementally up-to-date
after implementation-only changes have been made to a reference.
When
%(ReferenceAssembly)
metadata is unavailable for a givenreference, the new item contains the implementation assembly directly.
Additionally creates a new output item for the current project's
reference assembly by default when
$(Deterministic)
istrue
. If thisis created, it is copied to the output directory using the new
CopyRefAssembly
task, which copies only if the ref assembly isdifferent from the current file. If present, the ref asm is passed to
the project's consumes in metadata.
See #1986, dotnet/roslyn#2184.