Skip to content

Fix graph target list for multitargetting projects using BuildProjectReferences=false #8565

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

Merged

Conversation

dfederm
Copy link
Contributor

@dfederm dfederm commented Mar 14, 2023

This change fixes graph builds for multitargetting projects using BuildProjectReferences=false

Previously, when using BuildProjectReferences=false the graph would consider the outer build calling GetTargetPath on the inner builds. However, it actually calls Build when dispatching to the inner builds, and those call GetTargetPath on their dependencies. In effect this is causing graph builds to be non-graph builds since the target list is not accurately described.

This change ensures the correct behavior by separating the ProjectReferenceTargets logic for single and multi-targetting projects.

Before

Only the top-level project is in the graph, and the inner builds do not have "Build" as a predicted target:
image

Dispatching to inner builds does not come from cache:
image

Inner builds resolving dependencies does not come from cache:
image

After

Everything is in the graph properly:
image

Dispatching to inner builds comes from cache:
image

Inner builds resolving dependencies comes from cache:
image

CC @rainersigwald who was hitting this as well.
CC @DmitriyShepelev who may be interested from an isolation standpoint.

targetLists[GetOuterBuild(graph, 2)].ShouldBe(new[] { "GetTargetFrameworks" });
foreach (ProjectGraphNode inner in GetInnerBuilds(graph, 2))
{
// GetTargetFrameworks actually shouldn't be here...
Copy link
Member

Choose a reason for hiding this comment

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

Ok, I'll bite. Why is it? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because we need to rewrite how this all works :)

But specifically it's this line:

<ProjectReferenceTargets Include="Build" Targets="GetTargetFrameworks" OuterBuild="true" SkipNonexistentTargets="true" Condition="'$(IsCrossTargetingBuild)' != 'true'" />

I couldn't think of a way to ensure that only applies from outer build to outer build and not the inner build.

@dfederm dfederm marked this pull request as draft March 22, 2023 21:38
@dfederm dfederm marked this pull request as ready for review March 30, 2023 23:24
Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

The problem was that inner builds were being told to build "what you should build if you're a project reference" instead of what they should be by default, and it seems like the answer you're using here is changing what targets a project reference should build based on whether you're an inner or outer build rather than changing the referencing project's inner builds to use a separate set of targets that includes .default. Is that correct? If so, what makes that way superior to changing the set of targets the inner builds actually build? My (probably wrong) concern is that this will make the inner builds of referenced projects build instead of just assuming they're up-to-date.

<ProjectReferenceTargets Include="Build" Targets="$(ProjectReferenceTargetsForBuild)" Condition=" '$(ProjectReferenceTargetsForBuild)' != '' " />

<ProjectReferenceTargets Include="Clean" Targets="$(ProjectReferenceTargetsForCleanInOuterBuild)" Condition=" '$(ProjectReferenceTargetsForCleanInOuterBuild)' != '' " OuterBuild="true" />
<ProjectReferenceTargets Include="Clean" Targets="GetTargetFrameworks" OuterBuild="true" SkipNonexistentTargets="true" />
<ProjectReferenceTargets Include="Clean" Targets="GetTargetFrameworks" OuterBuild="true" SkipNonexistentTargets="true" Condition="'$(IsCrossTargetingBuild)' != 'true'" />
Copy link
Contributor

Choose a reason for hiding this comment

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

I know you just added the condition here and below, but why are we Include'ing Build and Clean multiple times with different Targets lists? I would read that as that we should execute each target multiple times, but that doesn't sound right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't follow the question. Can you clarify?

Copy link
Contributor Author

@dfederm dfederm Apr 3, 2023

Choose a reason for hiding this comment

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

Maybe it would help if you read it this way?

<ProjectReferenceTargets Include="X" Targets="Y" />

Means "If I'm called with and entry target X, I call Y on my ProjectRefernces".

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, so when you say:

<ProjectReferenceTargets Include="Clean" Targets="GetTargetFrameworks" />
<ProjectReferenceTargets Include="Clean" Targets="GetTargetFrameworksWithPlatformForSingleTargetFramework" />

what you're saying is "If this project builds the Clean target as its entrypoint, build the other two." That makes more sense, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, exactly!

@dfederm
Copy link
Contributor Author

dfederm commented Apr 3, 2023

The problem was that inner builds were being told to build "what you should build if you're a project reference" instead of what they should be by default, and it seems like the answer you're using here is changing what targets a project reference should build based on whether you're an inner or outer build rather than changing the referencing project's inner builds to use a separate set of targets that includes .default. Is that correct? If so, what makes that way superior to changing the set of targets the inner builds actually build?

That doesn't sound right.

The problem is that previously the p2p was describing that the outer build only called GetTargetPath when BPR=false. But that's not what happens. The outer build calls the default targets on the inner builds, and the inner builds are the ones which only call GetTargetPath when BPR=false. So the conditions here accommodate that.

My (probably wrong) concern is that this will make the inner builds of referenced projects build instead of just assuming they're up-to-date.

Because for inner builds we still have it saying it calls GetTargetPath on its references. And GetTargetPath isn't configured to "flow" to any references. Ie there is nothing like <ProjectReferenceTargets Include="GetTargetPath" Targets="Something" />

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@rainersigwald rainersigwald merged commit a52c76e into dotnet:main Apr 5, 2023
@dfederm dfederm deleted the fix-multitargeting-graph-targetlist branch April 5, 2023 19:36
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.

3 participants