-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Incremental build improvements for generated AssemblyInfo.cs using inputs cache #1255
Incremental build improvements for generated AssemblyInfo.cs using inputs cache #1255
Conversation
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.
Much better. Thanks!
--> | ||
<Target Name="_CreateGeneratedAssemblyInfoInputsCacheFile" | ||
DependsOnTargets="_CalculateAssemblyAttributes" | ||
BeforeTargets="CoreGenerateAssemblyInfo"> |
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.
Opinion nit: since "ownership" of both of these targets is in the same place, I'd prefer seeing the dependency expressed as a DependsOnTargets
on CoreGenerateAssemblyInfo
. Reading the dependencies in that direction "feels right" to me, in the absence of other factors.
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.
The idea was that CoreGenerateAssemblyInfo
can be overwritten in language-specific targets. Having ppl override and use a "private" target seemed a bit weird to me. And a CoreGenerateAssemblyInfoDependsOn
property seemed a bit.. overblown.. - but would still be an option. Alternative would be to make the targets public. What do you think is better here?
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 an interesting idea. I'm convinced!
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.
Another option that's arguably more consistent with what we have now, but not necessarily better is to slot this in to the dependencies of outer GenerateAssemblyInfo. The target can also be public and called GetAssemblyAttributes matching GetAssemblyVersion.
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 the DependsOnTargets="A;B;C"
set the build dependency of "b depends on a"?
Option: Merge _CreateGeneratedAssemblyInfoInputsCacheFile
and _CalculateAssemblyAttributes
into GetAssemblyAttributes
, add DependsOnTargets="GetAssemblyAttributes"
to CoreGenerateAssemblyInfo
.
That would merge both concerns of emitting the items and writing the cache but leave nicely named public targets. @nguerrera your call
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 the DependsOnTargets="A;B;C" set the build dependency of "b depends on a"?
Depends on how precise you're being with your language 😁
DependsOnTargets="a;b;c"
does guarantee an order, unless there's another factor like a DependsOnTargets="c"
. That happens because MSBuild uses a stack to order target execution, and encountering a target's DependsOnTargets
pushes onto the stack in the given order. This is (almost) documented at https://docs.microsoft.com/en-us/visualstudio/msbuild/target-build-order.
There's another internal concept of dependency, though--when MSBuild considers a target and push prerequisites onto the stack, they get annotated with a "Parent". Then, if the target fails, the engine can additionally mark its parent as failed (and skip other targets that were building because of that parent). dotnet/msbuild#2133 is currently tweaking this for a corner case.
The latter is not done by an ordered DependsOnTargets
--but that rarely matters.
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 the DependsOnTargets="A;B;C" set the build dependency of "b depends on a"?
No, but it ensures A,B,C ordering when it triggers them to run. IIRC, there is precedent in Common targets to slot CoreXxx in via DependsOnTargets of Xxx order without explicitly saying that CoreXxx depends on everything before it in the DependsOnTargets of Xxx. That was the idiom I had in mind when I first wrote this, but I think you're on to something nicer. We should make sure that GetAssemblyVersion is also sequenced by the same mechanism:
<Target Name="GenerateAssemblyInfo"
DependsOnTargets="PrepareForBuild;CoreGenerateAssemblyInfo"
BeforeTargets="CoreCompile" ... />
<Target Name="GetAssemblyAttributes"
DependsOnTargets="GetAssemblyVersion" .. />
<Target Name="_CreateGeneratedAssemblyInfoInputsCacheFile"
DependsOnTargets="GetAssemblyAttributes"
BeforeTargets="CoreGenerateAssemblyInfo" ... />
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 asking because a huge
<Target Name="GenerateAssemblyInfo" … DependsOnTargets="PrepareForBuild;GetAssemblyVersion;GetAssemblyAttributes;_CreateGeneratedAssemblyInfoInputsCacheFile;CoreGenerateAssemblyInfo" … />
works as well and I wanted to make sure it doesn't do so by accident. But it somehow feels morally 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.
Nothing accidental, but it removes flexibility to invoke the individual targets via something other than GenerateAssemblyInfo and still have its dependencies satisfied. You've convinced me that the explicit approach is nicer.
Thinking further, given
- Error handling subtleties of Fix issue where targets are executed after an error. msbuild#2133
- That we need CoreGenerateAssemblyInfo overrides to re-declare the inputs and outputs. (I had put them on outer GenerateAssemblyInfo and that didn't work.)
- That one of the inputs is the generated cache file
It makes sense for CoreGenerateAssemblyInfo to explicitly state that it depends on creating the cache file.
So here's my proposal:
<Target Name="GenerateAssemblyInfo"
DependsOnTargets="PrepareForBuild;CoreGenerateAssemblyInfo"
BeforeTargets="CoreCompile" ... />
<Target Name="GetAssemblyAttributes"
DependsOnTargets="GetAssemblyVersion" .. />
<Target Name="CreateGeneratedAssemblyInfoInputsCacheFile"
DependsOnTargets="GetAssemblyAttributes" />
<Target Name="CoreGenerateAssemblyInfo"
DependsOnTargets="CreateGeneratedAssemblyInfoInputsCacheFile"
Inputs="$(GeneratedAssemblyInfoCacheFile)"
Outputs="$(GeneratedAssemblyInfoFile)" ... >
It's one more line to repeat in the CoreGenerateAssemblyInfo override, but it feels right next to the declared inputs.
@dotnet-bot test OSX10.12 Debug |
@livarcocc @nguerrera Should we take this into release/2.0.0? |
@dsplaisted Yes. We've gotten several different reports about the incremental build problem and F# needs the target refactoring. |
@dasMulli I've updated this PR to target the release/2.0.0 branch |
Common.props
Outdated
@@ -23,9 +23,9 @@ | |||
<!-- Reset $(BuildEpoch) whenever $(VersionPrefix) increments. We subtract this from YYYYMMDD portion of build | |||
number below to obtain the fourth part of file version that must fit in 16 bits. We can produce builds | |||
for seven years from every epoch reset. --> | |||
<VersionPrefix Condition="'$(VersionPrefix)' == ''">2.0.0</VersionPrefix> | |||
<VersionPrefix Condition="'$(VersionPrefix)' == ''">2.1.0</VersionPrefix> |
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.
You need to rebase this out.
…ttributes and CreateGeneratedAssemblyInfoInputsCacheFile targets.
8e9c83b
to
f9ddc86
Compare
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.
Looks great. Thanks again, @dasMulli for your hard work on this and working through the complexities discussed on both PRs!
@MattGertz for approval Customer scenario Changing assembly version or other assembly metadata via CLI switches or msbuild global properties. Incremental builds would silently use the assembly metadata from a prior build. Assembly metadata generation for F#. F# targets need to override a target for this and without the refactoring that was a prereq for this fix, they would have to duplicate all of the assembly attribute computation and not just implement writing them out in F# syntax. This would be fragile because F# would have to be kept in sync if any new attributes were added, etc. Bugs this fixes: Workarounds, if any Always rebuild when changing assembly metadata via command line. That assumes you're aware of the issue (since it's silently bad) and never forget about it. It also slows you down. Duplicate a bunch of code from SDK targets in to F# targets. Risk Low. Performance impact Slight. There may be some overhead for the computation to do a correct up-to-date check, but it's needed for correctness. Is this a regression from a previous update? No Root cause analysis: Lack of test coverage for the scenario. Test gap is closed by the PR. How was the bug found? Reported (and fixed 😄) by customer. |
Approved & thanks! |
…utes-using-lock-file
@dotnet-bot test Windows_NT_FullFramework Debug |
Alternative to #1007 (fixes #967) using a mechanism similar to
CoreCompileInputs.cache
:Splits
CoreGenerateAssemblyInfo
into:GetAssemblyAttributes
which creates allAssemblyAttribute
items.CreateGeneratedAssemblyInfoInputsCacheFile
creates anobj/tfm/projectname.AssemblyInfoInputs.cache
file containing the hash of allAssemblyAttribute
items. This filename is available as$(GeneratedAssemblyInfoInputsCacheFile)
. The target uses msbuild 15'sWriteOnlyWhenDifferent="True"
feature forWriteLinesToFile
so the file doesn't change unnecessarily.CoreGenerateAssemblyInfo
which generates theprojectname.AssemblyInfo.cs
file, using the cache file as input for incremental build.cc @nguerrera @dsplaisted @rainersigwald
Also cc @enricosada @KevinRansom who want to use the refactor to overwrite
CoreGenerateAssemblyInfo
in F# specific targets (dotnet/fsharp#3113)