-
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
Support reference assemblies in build flows #1986
Comments
Implementation option: find ref assembly by filename transformation convention. For example: after RAR and before
|
Implementation option: pass ref assembly path as metadata on the primary compiler output For example: after RAR and before
|
Ok, I've gotten a proof-of-concept put together based on the just-look-for-a-filename-pattern strategy, and it looks fairly reasonable. Very little of this is production-ready, but it does work as hoped for: a whitespace change in
diff --git a/MSBuild/15.0/Bin/MSBuild.exe.config b/MSBuild/15.0/Bin/MSBuild.exe.config
index eafd444..bb0f31a 100644
--- a/MSBuild/15.0/Bin/MSBuild.exe.config
+++ b/MSBuild/15.0/Bin/MSBuild.exe.config
@@ -46,6 +46,15 @@
<assemblyIdentity name="XamlBuildTask" culture="neutral" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="4.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.IO.FileSystem" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
+ </dependentAssembly>
+
<!-- Workaround for crash in C++ CodeAnalysis scenarios due to https://github.com/Microsoft/msbuild/issues/1675 -->
<dependentAssembly>
diff --git a/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets b/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
index 8aa426d..6f58d49 100644
--- a/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
+++ b/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
@@ -347,6 +347,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
</PropertyGroup>
<ItemGroup>
<IntermediateAssembly Include="$(IntermediateOutputPath)$(TargetName)$(TargetExt)"/>
+ <IntermediateRefAssembly Include="$(IntermediateOutputPath)$(TargetName).interface$(TargetExt)"/>
<FinalDocFile Include="@(DocFileItem->'$(OutDir)%(Filename)%(Extension)')"/>
</ItemGroup>
@@ -4003,6 +4004,17 @@ Copyright (C) Microsoft Corporation. All rights reserved.
</Copy>
+ <!-- Copy the build product (.dll or .exe). -->
+ <CopyRefAssembly
+ SourcePath="@(IntermediateRefAssembly)"
+ DestinationPath="$(OutDir)@(IntermediateRefAssembly->'%(Filename)%(Extension)')"
+ Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true' and Exists('@(IntermediateRefAssembly)')"
+ >
+
+ <Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/>
+
+ </CopyRefAssembly>
+
<Message Importance="High" Text="$(MSBuildProjectName) -> @(MainAssembly->'%(FullPath)')" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />
<!-- Copy the additional modules. -->
diff --git a/MSBuild/15.0/Bin/Microsoft.Common.tasks b/MSBuild/15.0/Bin/Microsoft.Common.tasks
index d284df7..4c7fbee 100644
--- a/MSBuild/15.0/Bin/Microsoft.Common.tasks
+++ b/MSBuild/15.0/Bin/Microsoft.Common.tasks
@@ -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)' != ''" />
</Project>
diff --git a/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets b/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
index 5612c75..020adbb 100644
--- a/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
+++ b/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
@@ -1,13 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Target Name="MungeRefs" BeforeTargets="CoreCompile">
+ <!--<Warning Text="RP: @(ReferencePath) %(RootDir)%(Directory)%(Filename).interface%(Extension)" />-->
+ <ItemGroup>
+ <TrueReferences Include="@(ReferencePath)" Condition="!Exists('%(RootDir)%(Directory)%(Filename).interface%(Extension)')" />
+ <TrueReferences Include="@(ReferencePath->'%(RootDir)%(Directory)%(Filename).interface%(Extension)')" Condition="Exists('%(RootDir)%(Directory)%(Filename).interface%(Extension)')" />
+ </ItemGroup>
+ </Target>
<Target Name="CoreCompile"
Inputs="$(MSBuildAllProjects);
@(Compile);
@(_CoreCompileResourceInputs);
$(ApplicationIcon);
$(AssemblyOriginatorKeyFile);
- @(ReferencePath);
+ @(TrueReferences);
@(CompiledLicenseFile);
@(LinkResource);
@(EmbeddedDocumentation);
@@ -117,7 +124,7 @@
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
- References="@(ReferencePath)"
+ References="@(TrueReferences)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="@(_CoreCompileResourceInputs);@(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)" Explanations:
|
Design points to be settled:
@jaredpar @jcouv @Microsoft/msbuild-maintainers: does that sound good? Anything I've forgotten? current implementationdiff --git a/MSBuild/15.0/Bin/MSBuild.exe.config b/MSBuild/15.0/Bin/MSBuild.exe.config
index eafd444..bb0f31a 100644
--- a/MSBuild/15.0/Bin/MSBuild.exe.config
+++ b/MSBuild/15.0/Bin/MSBuild.exe.config
@@ -46,6 +46,15 @@
<assemblyIdentity name="XamlBuildTask" culture="neutral" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="4.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.IO.FileSystem" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
+ </dependentAssembly>
+
<!-- Workaround for crash in C++ CodeAnalysis scenarios due to https://github.com/Microsoft/msbuild/issues/1675 -->
<dependentAssembly>
diff --git a/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets b/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
index 8aa426d..4881519 100644
--- a/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
+++ b/MSBuild/15.0/Bin/Microsoft.Common.CurrentVersion.targets
@@ -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>
+
<!-- Example, c:\MyProjects\MyProject\ -->
<ProjectDir Condition=" '$(ProjectDir)' == '' ">$(MSBuildProjectDirectory)\</ProjectDir>
@@ -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. -->
+ <IntermediateRefAssembly Include="$(IntermediateOutputPath)ref\$(TargetName)$(TargetExt)" Condition="'@(IntermediateRefAssembly)' == ''" />
+ <CreateDirectory Include="@(IntermediateRefAssembly->'%(RootDir)%(Directory)')" />
+ <CreateDirectory Include="$(OutDir)ref" />
+ </ItemGroup>
+
<ItemGroup Condition="'$(_DebugSymbolsProduced)' == 'true'">
<_DebugSymbolsIntermediatePath Include="$(IntermediateOutputPath)$(TargetName).compile.pdb" Condition="'$(OutputType)' == 'winmdobj' and '@(_DebugSymbolsIntermediatePath)' == ''"/>
<_DebugSymbolsIntermediatePath Include="$(IntermediateOutputPath)$(TargetName).pdb" Condition="'$(OutputType)' != 'winmdobj' and '@(_DebugSymbolsIntermediatePath)' == ''"/>
@@ -771,7 +780,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
- Returns="$(TargetPath)" />
+ Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!--
============================================================
@@ -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>
</TargetPathWithTargetPlatformMoniker>
</ItemGroup>
</Target>
@@ -2015,8 +2025,18 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Output TaskParameter="FilesWritten" ItemName="FileWrites"/>
<Output TaskParameter="DependsOnSystemRuntime" PropertyName="DependsOnSystemRuntime"/>
</ResolveAssemblyReference>
+
+ <ItemGroup>
+ <ReferencePathWithInterfaceOnlyAssemblies Include="@(ReferencePath->'%(ReferenceAssembly)')" />
+ </ItemGroup>
</Target>
+ <ItemDefinitionGroup>
+ <ReferencePath>
+ <ReferenceAssembly>%(FullPath)</ReferenceAssembly>
+ </ReferencePath>
+ </ItemDefinitionGroup>
+
<!--
====================================================================================================
@@ -4003,6 +4023,17 @@ Copyright (C) Microsoft Corporation. All rights reserved.
</Copy>
+ <!-- Copy the build product (.dll or .exe). -->
+ <CopyRefAssembly
+ SourcePath="@(IntermediateRefAssembly)"
+ DestinationPath="$(TargetRefPath)"
+ Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true' and Exists('@(IntermediateRefAssembly)')"
+ >
+
+ <Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/>
+
+ </CopyRefAssembly>
+
<Message Importance="High" Text="$(MSBuildProjectName) -> @(MainAssembly->'%(FullPath)')" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />
<!-- Copy the additional modules. -->
diff --git a/MSBuild/15.0/Bin/Microsoft.Common.tasks b/MSBuild/15.0/Bin/Microsoft.Common.tasks
index d284df7..4c7fbee 100644
--- a/MSBuild/15.0/Bin/Microsoft.Common.tasks
+++ b/MSBuild/15.0/Bin/Microsoft.Common.tasks
@@ -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)' != ''" />
</Project>
diff --git a/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets b/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
index 5612c75..2aee7ca 100644
--- a/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
+++ b/MSBuild/15.0/Bin/Roslyn/Microsoft.CSharp.Core.targets
@@ -7,7 +7,7 @@
@(_CoreCompileResourceInputs);
$(ApplicationIcon);
$(AssemblyOriginatorKeyFile);
- @(ReferencePath);
+ @(TrueReferences);
@(CompiledLicenseFile);
@(LinkResource);
@(EmbeddedDocumentation);
@@ -117,7 +117,7 @@
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
- References="@(ReferencePath)"
+ References="@(TrueReferences)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="@(_CoreCompileResourceInputs);@(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)"
|
These all look good to me. |
Looks good. I especially like the leveraging of existing /obj and /bin distinction. At least we don't have to create a third copy of the ref assembly just to manage timestamps :-) |
Had a design review with @AndyGerlicher and @jeffkl today. No changes to the plan, but a couple of options were discussed and dismissed:
Also discussed was dotnet/roslyn#19103. I'm going to go forward with PRs now based on this design. |
Will this be property based? I.e. will be be a property based switch that turns reference assembly making on/off? Please allow compilers other than roslyn to implement the feature. |
There isn't anything specific to Roslyn that I'm aware of here. Most of the work is around making the target graphs able to benefit from reference assemblies in general. |
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.
@borgdylan I'm planning to implement that property next up in PR #2039. This is absolutely available to compilers other than Roslyn--the core compilation target just needs to consume the new item rather than |
@davkean @jviau I'm proposing to change the item that gets passed to To summarize: I'm concerned about swapping in the ref assembly everywhere because I don't know what targets (including third-party extension things) have a dependency on |
Thanks for clarifying. I will try to implement this for my compiler since it would be for me to tell thr compiler to forgo generation of method bodies. That would allow me to implement the MSBuild specific bits that interface with your PR. Would MSBuild suggest a path where the reference assembly should be placed? |
@borgdylan Yes, in my PR the common targets suggest a default--though you'll want to opt in to the feature once I've made that possible. Also possibly relevant: dotnet/roslyn#19133 adds some requirements to the shape of the ref assembly to keep it compatible with the new incremental |
Thanks for heads up. I did not know of |
@borgdylan I have a meeting set up with a doc writer to figure out how to start to document some of the soup that is common targets. That kind of detail will likely be low on the priority list since it's primarily useful for compiler writers, but maybe someday. . . |
In the mean time I will look at the CSharp targets for inspiration once this makes its way into the mono fork. |
@rainersigwald I had a quick look, I don't know of anything that would break by using a different item to pass to the compiler instead @(ReferencePath). I do suspect, however, that the legacy project system (and our yet to be written up-to-date check) would need to be taught about these concepts, preferable via MSBuild items. |
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 when `$(ProduceReferenceAssembly)` 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. Introduce $(CompileUsingReferenceAssemblies), which can avoid passing reference assemblies to the compiler when set to false. This is not expected to be commonly used. 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 OriginalPathmetadata on the adjusted references that points back to the implementation assembly if a ref assembly was found. 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. See #1986, dotnet/roslyn#2184.
The initial implementation of this is complete, but opt-in. |
@rainersigwald how does msbuild signal that it wants a reference assembly? |
The design we landed on was:
So there's no real way to indicate from a referencing project that you want ref assemblies, it's up to the producing project to provide them or not. Does that answer the question? |
On the receiving end I am just using Thanks for answering the part on the producing end. For now I just want to have my targets set up to handle the reference assembly stuff prior to my compiler actually emitting them. In the mean time I will be tricking msbuild by giving a full assembly regardless of what it asks for. However I plan to be writing the metadata and passing a flag to the compiler in the reference assembly production case. |
What I did not understand: How do I change the values for the mentioned targets from within |
Also, what causes |
@borgdylan Right now ref assemblies are an opt-in feature, so a project must explicitly set I don't think I understand this:
Why? It's not a great situation, but it's perfectly valid to have a non-ref-assembly-aware project depend on a ref assembly but not produce one in turn. You'd still get incremental build benefits since it would only rebuild when its own implementation or the reference's interface changes. |
What I meant is that a dependency project should know that it is currently treated as a dependency and is not the focus, so it would be enough for it to produce a reference assembly only. Making both a normal and a reference assembly is too wasteful in the same build and defeats the use of reference assemblies to get a faster build. |
Ah, I see. That's not a scenario in the current design. The Roslyn compiler simultaneously emits the reference and implementation assemblies, so it's not significantly slower than emitting only the reference assembly (AFAIK). Even if that wasn't the case, I don't think I agree with your premise. If you didn't compile the implementation assembly when it was out of date but the build was triggered from a reference, you would potentially be using stale assemblies downstream. Consider this situation (apologies if I'm butchering the Dylan): define method adder (a, b :: <integer>)
a - b
end; in one assembly |
Apologies accepted. As things stand, I will be holding off my implementation as I will not have any advantages if I do. If a file in a project changes, the build would still trigger making a new reference assembly anyways. I may do a partial implementation to make distributable reference assemblies be possible. Side note: dylan.NET is a misnamed language as it does not use the original "Dylan" syntax. An "adder" method would be written as
or inspired by C# 6 arrow methods
|
The C# and VB compilers are building support for emitting a reference assembly while compiling. The reference assembly contains the accessible interface of the assembly, but no implementation details. That means it changes less often than the full assembly--many common development activities don't change the interface, only the implementation. That means that incremental builds can be much faster--if you invert the sense of an
if
in a common library, today you must rebuild every assembly that references it, but with reference assemblies, you would only rebuild the library and copy it around to a few new output directories.Roslyn spec for reference assemblies. Initial implementation: dotnet/roslyn#17558
These assemblies should be supported in MSBuild scenarios.
Design criteria
The text was updated successfully, but these errors were encountered: