dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.23k stars 1.35k forks source link

BeforeBuild should be available in outer builds #4490

Open mwpowellhtx opened 5 years ago

mwpowellhtx commented 5 years ago

Steps to reproduce

I have a Bump Assembly Versions project which to this point has worked fine for single target projects. However, in multi-target projects, I am noticing that I may see a +N or sometimes a +N-1 increment, depending on the bump instructions, i.e. like one of the interior platforms unwound its build asynchronously last. This is incorrect, I only want there to be at most a +1 when there is an increment involved. So... Is there a target I can trigger against that represents the overall build and not each target framework?

mwpowellhtx commented 5 years ago

@rainersigwald Any ideas on this one, good sir?

mwpowellhtx commented 5 years ago

Short of having to do more bookkeeping on the version(s) involved, that is. I could open that can of worms in my tool, but I would rather simply trigger on a single build event for the build as a whole instead of for each of the targets involved during the build.

mwpowellhtx commented 5 years ago

That or separate my single multi-targeted project file to several individually targets project files, each of which having its own uniquely tracked version numbers, but this does not seem like an especially great workaround, to me. ... Kind of defeats the purpose of it being multi-targeted in the first place; never mind the packaging headache that would occur.

jnm2 commented 5 years ago

Maybe you would find the buildMultiTargeting folder useful? I can't find official documentation at all, but these links might help (Ctrl+F for buildMultiTargeting on each page):

https://natemcmaster.com/blog/2017/07/05/msbuild-task-in-nuget/#step-4---shipping-your-task-in-a-nuget-package https://oren.codes/2017/01/04/multi-targeting-the-world-a-single-project-to-rule-them-all/#how-it-works

mwpowellhtx commented 5 years ago

@jnm2 Will have to look into that a bit further, thank you for the tidbits.

rainersigwald commented 5 years ago

I'm going to answer the question you're asking, but I'm a bit concerned based on your description of the symptoms that it may not be the root cause of your problem, so we may have to follow up again.

If you're not already delivering this target in a NuGet package, consider making the target conditional.

The condition isn't trivial, because there are a few interesting cases:

  1. The project has a single target.
  2. The project multitargets, and this instance of the project is the "outer" build that calls the individual ones.
  3. The project multitargets and this instance of the project is one of the "inner" builds with a specific TargetFramework.

Common.targets provides $(IsCrossTargetingBuild) for the third case, but we don't have a single propery for "this project multitargets". We generally use:

  1. '$(TargetFrameworks)' == ''
  2. '$(TargetFrameworks)' != '' and '$(TargetFramework)' == ''
  3. '$(TargetFrameworks)' != '' and '$(TargetFramework)' != ''

see the definition of IsCrossTargetingBuild itself:

https://github.com/microsoft/msbuild/blob/07d3c25a1461dfba3fcc1cc9b28cb8accd4e32b9/src/Tasks/Microsoft.Managed.targets#L19-L21

If you know the project multitargets and want the smallest possible thing, you can use Condition=" '$(TargetFramework)' == '' ".

mwpowellhtx commented 5 years ago

@rainersigwald I'm not sure I understand, so this will yield a single overall build, that in turn would trigger my version bump once and only once? If I understand correctly, would yield multiple subordinate packages organized by the parent package?

For reference, currently the bump tasking is invoked BeforeTargets="BeforeBuild".

rainersigwald commented 5 years ago

Thanks for linking to your code. I see now that you're providing a NuGet package, so the right thing to do is to use the buildCrossTargeting folder, which handles the conditionality for you (NuGet imports it only for the "outer" build). @jnm2 was right on target.

so this will yield a single overall build

I don't think I understand this. "Build" is used for a lot of things (formally and informally) in MSBuild-land; can you ask this question without using that word, please?

If I understand correctly, would yield multiple subordinate packages organized by the parent package?

I don't understand this question, can you rephrase?

mwpowellhtx commented 5 years ago

@rainersigwald Sounds like buildCrossTargeting is the right path for me to investigate. Basically, I want to bump once and only once; that is, once regardless whether the subscribing project is single- or multi-targeting.

mwpowellhtx commented 5 years ago

@rainersigwald So the current target would work in the single-targeting use case under the, Condition="'$(IsCrossTargetingBuild)' != 'true'". However, what would the analog outer loop target be when Condition="'$(IsCrossTargetingBuild)' == 'true'"?

mwpowellhtx commented 5 years ago

@rainersigwald I've dug into these a bit and I still do not see anything that jumps out at me as being a once and only once cross targets analog to the BeforeBuild target. Can you confirm? Better yet, inform me as to what those target(s) might be? With the goal being a resilient version bumping strategy that bumps once and only once, regardless of whether single- or multi-targeting.

mwpowellhtx commented 5 years ago

@rainersigwald Perhaps we could trigger on GetAllRuntimeIdentifiers at a level or two prior to even the Build targets?

rainersigwald commented 5 years ago

You could use BeforeTargets="DispatchToInnerBuilds" but we should expose BeforeBuild in that case too. Updating the bug title.

mwpowellhtx commented 5 years ago

@rainersigwald Good name for it. Agreed; is there anything we can do in the near term? At the moment I basically have bumping disabled, bypassing, my multi-target projects. Not a great workaround, manually bumping, but better than if we bump multiple times over a single outer build.

Edit: So if I take that correctly, that is a refactor of BeforeBuild to the outer build scope?

Edit: Or at least identifying a CrossTargetBeforeBuild target name.

mwpowellhtx commented 5 years ago

Attached build events, logged via messaging.

vs-ide-msbuild-event-messages.txt

Along the lines of:

<Target Name="PreBeforeBuild" BeforeTargets="BeforeBuild">
  <Message Text="PreBeforeBuild" Importance="high" />
</Target>

<Target Name="PreDispatchToInnerBuilds" BeforeTargets="DispatchToInnerBuilds">
  <Message Text="PreDispatchToInnerBuilds" Importance="high" />
</Target>

That could work, gauging by the order of logged events.

mwpowellhtx commented 5 years ago

@rainersigwald This is a qualified might work... Apparently IsCrossTargetingBuild is never defined when we meet the BeforeBuild target, regardless whether the project was a multi-target build. So my question shifts to a slightly different one, is there a target preceding BeforeBuild for single-target builds? And/or a better indicator, perhaps we need to test for existence of files, that sort of thing. Kind of a kludge workaround, but I do not know of a better way to signal when this happens.

Log attached, one single targeted, one multi-targeted, lines of interest highlighted with ^s:

vs-ide-msbuild-event-messages-3.txt

mwpowellhtx commented 5 years ago

One approach seems to have some promise, involving TargetFrameworks:

vs-ide-msbuild-event-messages-4.txt

mwpowellhtx commented 5 years ago

@rainersigwald As I mentioned a qualified might work. Another key property when DispatchToInnerBuilds occurs as the absence of ProjectPath. This is also key for my targets. However, MSBuildProjectFullPath does appear to exist, so we may be able to leverage that property instead.

Edit: however, this also raises a bit of a question for me, does this mean that key Properties or Items would not be available during the outer build? This is also an essential part of my bump versions strategy.

mwpowellhtx commented 5 years ago

@rainersigwald This is turning into a really curious case, indeed. I think that the inner/outer loop issue is part of it, but I do not think that is the entirety of the issue, having some difficulty pinpointing the other variable(s) influencing the apparent lack of a version bump going on.

I've reported a couple of key metrics if it helps. Continuing to dig into it further:

msbuild-bumpassemblyversions-not-working.txt

Edit: It is almost as though Directory.Build.props, when my bump targets would be included, is being partially, if not completely, ignored, at that step in the outer build loop. Otherwise, version bumping is working just fine for single-targeted projects.

mwpowellhtx commented 5 years ago

My workaround seems to be along these lines, at the project level:

<Import Project="..\packages\bumpassemblyversions\$(BumpAssemblyVersionsVersion)\build\BumpAssemblyVersions.targets" Condition="'$(TargetFrameworks)' != '' And Exists('..\packages\BumpAssemblyVersions\$(BumpAssemblyVersionsVersion)\build\BumpAssemblyVersions.targets')" />

Where:

<BumpAssemblyVersionsVersion>1.3.0</BumpAssemblyVersionsVersion>