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

Prevent duplicate targets Fixes #5071 #5109

Merged
merged 6 commits into from
Feb 12, 2020

Conversation

Forgind
Copy link
Member

@Forgind Forgind commented Feb 5, 2020

Duplicate targets break the build; this prevents MSBuild's default targets from being added if they already exist.

Fixes #5071

Duplicate targets break the build; this prevents MSBuild's default targets from being added if they already exist.
AddReferencesBuildTask(target, targetName, outputItem);
if (!traversalProject.Targets.Select(target => target.Key).Contains(targetName ?? "Build"))
{
ProjectTargetInstance target = traversalProject.AddTarget(targetName ?? "Build", string.Empty, string.Empty, outputItemAsItem, null, string.Empty, string.Empty, string.Empty, string.Empty, false /* legacy target returns behaviour */);
Copy link
Contributor

Choose a reason for hiding this comment

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

This fix looks right, but can you add a unit test that proves it does what we think?

@@ -1969,8 +1969,11 @@ private static void AddTraversalReferencesTarget(ProjectInstance traversalProjec
outputItemAsItem = "@(" + outputItem + ")";
}

ProjectTargetInstance target = traversalProject.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, String.Empty, String.Empty, false /* legacy target returns behaviour */);
AddReferencesBuildTask(target, targetName, outputItem);
if (!traversalProject.Targets.Select(target => target.Key).Contains(targetName ?? "Build"))
Copy link
Member

Choose a reason for hiding this comment

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

nit: extract targetName ?? "Build" to a variable since it's used twice

instances.ShouldHaveSingleItem();
if (!name.Equals("name.that.does.Not.Affect.The.Build.targets"))
{
instances[0].Targets["Build"].AfterTargets.ShouldBe("NonsenseTarget");
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is right. When defined in before, the dummy target from the bug should be overridden by the generated target "in the body" of the metaproject.

Have you validated that this change behaves correctly using the repro steps from #5071 (comment)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I think this was a design issue. I thought it would be best for the user's version to override the generated version no matter what. I can change it.

Forgind and others added 3 commits February 6, 2020 08:31
…s.cs

Co-Authored-By: Rainer Sigwald <raines@microsoft.com>
…s.cs

Co-Authored-By: Rainer Sigwald <raines@microsoft.com>
In 16.3, the default version overrode before/after.<sln>.targets versions. This changes back to that mode.
Comment on lines 1974 to 1977
if (traversalProject.Targets.Select(target => target.Key).Contains(correctedTargetName))
{
traversalProject.RemoveTarget(correctedTargetName);
}
Copy link
Member

Choose a reason for hiding this comment

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

You can just do this unconditionally; RemoveTarget doesn't throw if it's not there.

Copy link
Member

Choose a reason for hiding this comment

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

But this depends on the "how did pre-16.4 behave" question WRT having stub targets in before/after.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry—I should have mentioned this rather than just pushing the change. In 16.3, if you have a before...targets or an after...targets with a Build target, the Build target is overwritten by the generated target.

@Forgind Forgind merged commit 77da97f into dotnet:master Feb 12, 2020
@Forgind Forgind deleted the duplicate-build-target branch February 12, 2020 23:34
@Forgind Forgind restored the duplicate-build-target branch March 11, 2020 21:07
@Forgind Forgind deleted the duplicate-build-target branch March 12, 2020 15:42
@tg73
Copy link

tg73 commented Jul 4, 2021

I've just run into the situation where I can no longer redefine the Build target, for example via Directory.Solution.targets - my redefinition of the Build target is silently ignored. This appears to be due to this 'fix'. I can redefine any other target (such as Restore). Why is this considered a desirable restriction?

UPDATE:

I've come up with a workaround of sorts:

<Target
  Name="_CustomBeforeBuild"
  BeforeTargets="Build"
  Outputs="@(CollectedBuildOutput)">

  <!-- DO YOUR ACUTAL Build TARGET WORK HERE (or via DependsOnTargets etc) -->

  <!-- Remove all @(ProjectReference) which effectively turns the default Build target into a no-op -->
  <ItemGroup>
    <_OriginalProjectReference Include="@(ProjectReference)"/>
    <ProjectReference Remove="@(ProjectReference)" />
  </ItemGroup>

</Target>

<Target
  Name="_CustomAfterBuild"
  AfterTargets="Build">

  <!-- Restore all @(ProjectReference) in case another target is due to be executed after Build -->
  <ItemGroup>
    <ProjectReference Include="@(_OriginalProjectReference)" />
  </ItemGroup>

</Target>

@Forgind
Copy link
Member Author

Forgind commented Jul 13, 2021

Can you define the Build target by putting it in a separate file that you import in Directory.Build/Solution.props? I think Directory.Solution.targets comes in after the dummy Build target was added.

@tg73
Copy link

tg73 commented Jul 25, 2021

@Forgind Apologies for the delay, vacation. No, your suggestion does not work. The custom build target is ignored. Looking at the code, and with the disclaimer that this is my naive, non-expert understanding:

These lines in CreateTraversalInstance appear to seek to retain any custom definition of the standard targets, removing only the placeholder empty targets, and then call AddStandardTraversalTargets apparently with the intention to add in the full versions only of those targets that have already been removed:

https://github.com/Forgind/msbuild/blob/1d845f30213e9ba4f36d4d5a366c0cc8285eed6e/src/Build/Construction/Solution/SolutionProjectGenerator.cs#L986-L997

However, AddStandardTraversalTargets calls AddTraversalReferencesTarget for each standard target, which forcibly removes any existing target. This appears to undermine the intent of CreateTraversalInstance:

https://github.com/Forgind/msbuild/blob/3e49893950c9a5e9f39a4d887b93810946361eef/src/Build/Construction/Solution/SolutionProjectGenerator.cs#L1961-L1977

An earlier version of AddTraversalReferencesTarget appears to have suitable conditional logic in place:

https://github.com/Forgind/msbuild/blob/fb82bc8216051c467850de04fe24e42026a02167/src/Build/Construction/Solution/SolutionProjectGenerator.cs#L1964-L1977

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.

Defining Build target in before.Solution.sln.targets causes hang, MSB0001
4 participants