Skip to content
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

Merged
merged 17 commits into from
May 19, 2017

Conversation

rainersigwald
Copy link
Member

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.

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.
Copy link
Member Author

@rainersigwald rainersigwald left a 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'">
Copy link
Member Author

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.

Copy link
Member Author

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. -->
Copy link
Member Author

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>
Copy link
Member Author

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"/>
Copy link
Member Author

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.

Copy link
Contributor

@jeffkl jeffkl left a 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>
Copy link
Contributor

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)))

Copy link
Member Author

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.

Copy link
Contributor

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 $(TargetPath) they should also have to set $(TargetRefPath) right? There's no guarantee that appending 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>
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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"/>
Copy link
Contributor

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?

Copy link
Member Author

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?

@jcouv
Copy link
Member

jcouv commented May 1, 2017

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>
Copy link
Contributor

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?

Copy link
Member Author

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)')" />
Copy link
Contributor

@cdmihai cdmihai May 2, 2017

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?

Copy link
Contributor

@cdmihai cdmihai May 2, 2017

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>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not already defined!

@AndyGerlicher
Copy link
Contributor

@dotnet-bot test this please

@rainersigwald
Copy link
Member Author

@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>
Copy link
Member

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.

Copy link
Member Author

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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is magic. ✨

Copy link
Member Author

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)' != ''" />
Copy link
Member

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)

Copy link
Member Author

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 😄

rainersigwald added a commit to rainersigwald/roslyn that referenced this pull request May 3, 2017
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).
jcouv pushed a commit to dotnet/roslyn that referenced this pull request May 3, 2017
…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.
@jcouv
Copy link
Member

jcouv commented May 4, 2017

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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants