-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Move ref assembly to the obj folder #6560
Conversation
The location of this file is mostly irrelevant, and users find it confusing when it's in the output folder.
Missing where |
…cation (and the CopyRefAssembly task can work)
I have decided to do this using the advanced technique of "actually doing what I said I did instead of lying". Sorry! |
@@ -323,7 +323,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 '$(ProduceReferenceAssembly)' == 'true' ">$([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))</TargetRefPath> | |||
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' and ('$(ProduceReferenceAssemblyInBin)' == 'true' or '$([MSBuild]::AreFeaturesEnabled(17.0))' != 'true' ) ">$([MSBuild]::NormalizePath($(TargetDir), '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.
Seems redundant to have this in a change wave and have an opt in for it. By design shouldn't it be one or the other?
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 expect this to be controversial enough to have a permanent flag. The changewave is just there since we have a big lever for "do stuff the old way".
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.
Why not ProduceReferenceAssemblyInOutDir
or ProduceReferenceAssemblyInOutputPath
?
@@ -323,7 +323,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 '$(ProduceReferenceAssembly)' == 'true' ">$([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))</TargetRefPath> | |||
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' and ('$(ProduceReferenceAssemblyInBin)' == 'true' or '$([MSBuild]::AreFeaturesEnabled(17.0))' != 'true' ) ">$([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName)))</TargetRefPath> | |||
<TargetRefPath Condition=" '$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' ">$([MSBuild]::NormalizePath($(IntermediateOutputPath), '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.
Should this one point to refint
?
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.
There are two ref assemblies:
- The one that the compiler produces as an output. This one is written to any time the compiler runs.
- The one that other projects reference. This one is copied from the former but only if it has changed, allowing the better incremental build of referencing projects.
TargetRefPath is the path of the latter; IntermediateRefAssembly the former.
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.
Shouldn't the compiler generate the ref
assembly only, if it detects the change (i.e. when doing a 2nd build or the 3rd, etc…)?
Generating every-time incurs perf cost, doesn't it? Is it possible to input the files already generated by the compiler to determine determinism? 😏
pardon my ignorance!
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.
Shouldn't the compiler generate the ref assembly only, if it detects the change (i.e. when doing a 2nd build or the 3rd, etc…)?
No because that violates the areas of concern. The compiler is not a build system, it's a compiler. It's job is to take source and produce binaries (or more often errors). Once invoked the compiler should always produce the desired outputs. MSBuild, and the compiler MSBuild task, has the responsibilities of the build system and that includes up to date checks and such.
The way this works at a high level is that the CoreCompile
target has two outputs: the ref and the impl assembly. The core compiler itself will always produce the ref and impl assembly when invoked. The Compiler MSBuild task, which wraps the compiler, essentially looks at the ref assembly output and determines if it is equal to the previous ref assembly output (just compare the MVID). If not then it doesn't update the ref assembly output from the task. Hence the output of the CoreCompile
target has two outputs (ref + impl) but one is up to date because it has the same timestamp (the ref assembly) and the other is not up to date (the impl assembly). This lets MSBuild effectively short circuit later evaluations that depend only on the ref assembly.
pardon my ignorance!
Not ignorance at all. This is a tricky area to understand if you're not deeply involved. And most likely @rainersigwald is going to correct a few details I got wrong 😄
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.
And most likely @rainersigwald is going to correct a few details I got wrong 😄
I've only got one small nit . . .
The Compiler MSBuild task, which wraps the compiler, essentially looks at the ref assembly output and determines if it is equal to the previous ref assembly output (just compare the MVID).
This is actually done by a separate task that lives in Roslyn, CopyRefAssembly
, which is called here:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 4524 to 4534 in d150e93
<!-- Copy the reference assembly build product (.dll or .exe). --> | |
<CopyRefAssembly | |
SourcePath="@(IntermediateRefAssembly)" | |
DestinationPath="$(TargetRefPath)" | |
Condition="'$(ProduceReferenceAssembly)' == 'true' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'" | |
> | |
<Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/> | |
<Output TaskParameter="DestinationPath" ItemName="FileWrites"/> | |
</CopyRefAssembly> |
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 was so close!
@@ -392,7 +393,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
</ItemGroup> | |||
|
|||
<ItemGroup Condition="'$(ProduceReferenceAssembly)' == 'true'"> | |||
<IntermediateRefAssembly Include="$(IntermediateOutputPath)ref\$(TargetName)$(TargetExt)" Condition="'@(IntermediateRefAssembly)' == ''" /> | |||
<IntermediateRefAssembly Include="$(IntermediateOutputPath)refint\$(TargetName)$(TargetExt)" Condition="'@(IntermediateRefAssembly)' == ''" /> |
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 should use 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.
see above
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.
Shouldn't this one and the change below be under ProduceReferenceAssemblyInOutputPath
opt-in?
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.
Why do you think this should be conditional? Do you know of people who reach directly into obj\ref
today?
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.
We have a logic that does that. But that is not the only argument…
It would be best for users, to either preserve compat all the way (helps not to have problems down the road) or break it and document the change to agree on one way of doing things by default.
To get the older behavior, right now, I'd have to change atleast three variables. With my proposal, we'll only need to change single variable.
Going OOF soon, don't want to block on this
<CreateDirectory Include="@(IntermediateRefAssembly->'%(RootDir)%(Directory)')" /> | ||
<CreateDirectory Include="$(OutDir)ref" /> | ||
<CreateDirectory Include="$(IntermediateOutputPath)ref" /> |
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 feel like $(IntermediateOutputPath)refint
should be added to this item. Does adding it to IntermediateRefAssembly
ensure the path is getting created elsewhere?
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's what the prior entry does:
@(IntermediateRefAssembly->'%(RootDir)%(Directory)')
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.
Shouldn't this and the above change also be under the opt-in property?
If we have time for another build, can you modify the list in https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves.md to include this PR? |
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.
LGTM
tbh I think it is a non-issue (or rather an issue related to projects that are not I did this for a reason because 1, I think it is too much to package a version for "reference", and then package a separate one for "runtime" when I can produce just 1 package for both and it still works like I intend it to. Other projects might also came to depend on this. Unless this just changes for projects targeting only the 6.0 TFM. But I think the changes should only be targeted towards non-classlibs so classlibs that depend on the behavior before this PR can not be broken much. |
This achieves what I was talking about in the issue, completely preserving the old way of outputting the ref assembly! <PropertyGroup>
<ProduceReferenceAssemblyInOutputPath Condition="'$(ProduceReferenceAssemblyInOutputPath)' == '' and $([MSBuild]::AreFeaturesEnabled('17.0'))">false</ProduceReferenceAssemblyInOutputPath>
<ProduceReferenceAssemblyInOutputPath Condition="'$(ProduceReferenceAssemblyInOutputPath)' == ''">true</ProduceReferenceAssemblyInOutputPath>
</PropertyGroup>
<PropertyGroup>
<TargetRefPath Condition="'$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true' and '$(ProduceReferenceAssemblyInOutputPath)' == 'false'">$([MSBuild]::NormalizePath('$(IntermediateOutputPath)', 'ref', '$(TargetFileName)'))</TargetRefPath>
<TargetRefPath Condition="'$(TargetRefPath)' == '' and '$(ProduceReferenceAssembly)' == 'true'">$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))</TargetRefPath>
</PropertyGroup>
<ItemGroup Condition="'$(ProduceReferenceAssembly)' == 'true' and '$(ProduceReferenceAssemblyInOutputPath)' == 'true'">
<IntermediateRefAssembly Include="$(IntermediateOutputPath)ref\$(TargetName)$(TargetExt)" Condition="'@(IntermediateRefAssembly)' == ''" />
<CreateDirectory Include="@(IntermediateRefAssembly->'%(RootDir)%(Directory)')" />
<!-- Shouldn't this be $(TargetDir) instead of $(OutDir) -->
<CreateDirectory Include="$(OutDir)ref" />
</ItemGroup>
<ItemGroup Condition="'$(ProduceReferenceAssembly)' == 'true' and '$(ProduceReferenceAssemblyInOutputPath)' == 'false'">
<IntermediateRefAssembly Include="$(IntermediateOutputPath)refint\$(TargetName)$(TargetExt)" Condition="'@(IntermediateRefAssembly)' == ''" />
<CreateDirectory Include="@(IntermediateRefAssembly->'%(RootDir)%(Directory)')" />
<CreateDirectory Include="$(IntermediateOutputPath)ref" />
</ItemGroup> |
This is closer to the customization we document. Co-Authored-By: Nirmal Guru <Nirmal4G@gmail.com>
I agree I think moving the ref assemblies to the obj folder should be opt-in only to preserve codebases that expect them in the bin folder. |
I think we need to make this change, and make sure it's in our breaking change docs, along with the information for opting out of the new behavior. For most people, most of the time, this directory makes more sense in obj |
preemptively linking to our change wave docs for info on how to opt out |
It's okay to make a change but how we do it either makes it better or worse. Also, this feature is not for most users. For those who use this, they'd want the final output either in |
Why? The default today is to put the ref assemblies, which have zero impact on program execution, into bin which is where customers typically deploy from. This is particularly confusing for the new user case. They should know, and need to know, nothing about reference assemblies to code in .NET. But we are forcing them to learn about it by putting it directly in the place where we tell them to look for their program / build output. ref assemblies are essentially an implementation detail of the build system. We shouldn't be putting it in place where we have observable output. |
Fixes #6543 ### Context #6560 made `TargetRefPath` relative by mistake, which broke build in Visual Studio. ### Changes Made Use `MSBuildProjectDirectory` to properly root the path. ### Testing Verified that VS build works with this fix. ### Notes ``` 0:010> !pe Exception object: 0000020fa4de3a90 Exception type: System.IO.DirectoryNotFound ExceptionMessage: Could not find a part of the path 'C:\WINDOWS\system32\obj\Debug\net6.0\ref\HelloWorld.dll'. 0:010> !clrstack OS Thread Id: 0x95c (10) Child SP IP Call Site 000000bc6023cf88 00007ffb12634ed9 [HelperMethodFrame: 000000bc6023cf88] 000000bc6023d070 00007ffb037dba49 System.IO.__Error.WinIOError(Int32, System.String) 000000bc6023d0c0 00007ffb0408d7e6 System.IO.File.InternalCopy(System.String, System.String, Boolean, Boolean) 000000bc6023d150 00007ffaa7303d70 Microsoft.CodeAnalysis.BuildTasks.CopyRefAssembly.Copy() 000000bc6023d1b0 00007ffaa7303bad Microsoft.CodeAnalysis.BuildTasks.CopyRefAssembly.Execute() ```
This reverts commit 6dba77a.
Fixes #6543
Context
The location of this file is mostly irrelevant, and users find it
confusing when it's in the output folder.
Changes Made
Changed the compiler's obj output to a folder named
refint
and changed the"final" output ref assembly to live in
obj/ref
(modulo other variables).Did this under a double opt-out: it's in the 17.0 changewave, and there's a new property
$(ProduceReferenceAssemblyInBin)
.Notes
Naming on . . . everything is up for debate.