[Opt-in] Parallelize Targets when building a solution#7512
[Opt-in] Parallelize Targets when building a solution#7512Forgind merged 9 commits intodotnet:mainfrom
Conversation
…ct property in MSBuild task.
…huang/main_solution
src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs
Outdated
Show resolved
Hide resolved
src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs
Outdated
Show resolved
Hide resolved
src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs
Outdated
Show resolved
Hide resolved
src/Tasks/MSBuild.cs
Outdated
|
|
||
| if (FileSystems.Default.FileExists(projectPath) || (_skipNonexistentProjects == SkipNonexistentProjectsBehavior.Build)) | ||
| // Try to get the behavior from metadata if it is undefined. | ||
| var skipNonexistPropjects = _skipNonexistentProjects; |
There was a problem hiding this comment.
It looks like this was essentially copied from SolutionProjectGenerator—is it possible to merge them? Are there major differences? Why are they separate in the first place?
There was a problem hiding this comment.
I don't know why there are two copies of MSBuild.cs and MSBuild_Tests.cs.
When tests are running, they resolve to BackEnd.
There was a problem hiding this comment.
The story here is that long ago, the MSBuild task was just a task that used the IBuildEngine APIs like any other task would be able to do. At some later time, as a performance optimization, the MSBuild task was pulled into the engine itself, and task resolution short-circuits to prefer that version.
The one in Tasks exists only because it was a public non-sealed type, so user types might have inherited from it. I generally wouldn't worry about adding new features to it, since it should be almost entirely dead code.
rainersigwald
left a comment
There was a problem hiding this comment.
When we talked about this problem, I expected something along the lines of synthesizing a target like
<MSBuild Projects="@(Projects)" Targets="Foo;Bar" />Instead of the existing mechanism of synthesizing a Foo that calls all Foo and a Bar that calls all bar, then calling them in sequence.
Is that viable?
How would the target interface with the entry point work? @rainersigwald |
…huang/main_solution
Sorry, missed this. I don't think I understand the question. I was thinking of something like synthesizing a new target based on the <Target Name="Build_A_B_C">
<MSBuild BuildInParallel="True"
SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)"
Projects="@(ProjectReference)"
Targets="A;B;C"
Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" />
</Target> |
@rainersigwald, it is doing creating a new single target with the MSBuild which invokes all targets. The question is how would you expected the initial targets to invoke this new target? |
rainersigwald
left a comment
There was a problem hiding this comment.
Sorry for the delay on this. You're right that the general approach matches what I was thinking--not sure what confused me there.
-
Can you elaborate on how metadata-
SkipNonexistentProjectsis tied to the solution-batching stuff? -
Looking at the metaproject, I see
<Target Name="foo" DependsOnTargets="SlnProjectResolveProjectReference" />
<Target Name="bar" DependsOnTargets="SlnProjectResolveProjectReference" />
<Target Name="SlnProjectResolveProjectReference">
<MSBuild ToolsVersion="$(ProjectToolsVersion)" BuildInParallel="True" Projects="@(ProjectReference)" Targets="foo;bar" />
</Target>Replacing
<Target Name="foo">
<MSBuild BuildInParallel="True" Projects="@(ProjectReference)" Targets="foo" Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" />
</Target>
<Target Name="bar">
<MSBuild BuildInParallel="True" Projects="@(ProjectReference)" Targets="bar" Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" />
</Target>Can you pass along the properties? Otherwise I think it'll go wrong in Build on non-default solution configurations.
src/Tasks/MSBuild.cs
Outdated
|
|
||
| if (FileSystems.Default.FileExists(projectPath) || (_skipNonexistentProjects == SkipNonexistentProjectsBehavior.Build)) | ||
| // Try to get the behavior from metadata if it is undefined. | ||
| var skipNonexistPropjects = _skipNonexistentProjects; |
There was a problem hiding this comment.
The story here is that long ago, the MSBuild task was just a task that used the IBuildEngine APIs like any other task would be able to do. At some later time, as a performance optimization, the MSBuild task was pulled into the engine itself, and task resolution short-circuits to prefer that version.
The one in Tasks exists only because it was a public non-sealed type, so user types might have inherited from it. I generally wouldn't worry about adding new features to it, since it should be almost entirely dead code.
…huang/main_solution
This reverts commit f563816.
It is related as the SkipNonexistentProjects is splitting the build into two batches. This batch is serializing them which this will optimize away.
This isn't actually needed as ProjectReference item already has those Properties as metadata. |
|
@rainersigwald Ping. |
rainersigwald
left a comment
There was a problem hiding this comment.
Sorry for the delay!
It is related as the SkipNonexistentProjects is splitting the build into two batches. This batch is serializing them which this will optimize away.
Ah, and this is happening inside solution builds because anything with a solution build dependency generates a metaproject that gets SkipNonexistent.
This isn't actually needed as ProjectReference item already has those Properties as metadata.
I'd really like to leave this in case a user injects a target that changes the properties, since it's been done that way for the normal build targets for a very long time.
src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs
Outdated
Show resolved
Hide resolved
src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs
Outdated
Show resolved
Hide resolved
|
@rainersigwald Ping. |
Forgind
left a comment
There was a problem hiding this comment.
Tested this out, and it works!
| } | ||
| else | ||
| { | ||
| task.SetParameter("Properties", SolutionProperties); |
There was a problem hiding this comment.
This feels like taking something global and making it local to every project. Is that wrong? I would've imagined properties would flow automatically from the metaproj down to each individual project, but its absence here makes me wonder if we should continue to leave it empty.
There was a problem hiding this comment.
@rainersigwald suggested to add it.
This isn't actually needed as ProjectReference item already has those Properties as metadata.
I'd really like to leave this in case a user injects a target that changes the properties, since it's been done that way for the normal build targets for a very long time.
Fixes
#5072 (comment)
Context
When building a SLN, a metaproj is used to represent the build behavior. When there are multiple targets (ex clean;build), the current behavior is to run all of first Target in the projects, then run second Target. To improve the parallelism, the solution can pass both target to the project. Each project can start the second target without waiting for all of the first Target to finish.
When the feature is enabled via environment variable, MSBuildSolutionBatchTargets, Solution Generator will create a "SlnProjectResolveProjectReference" target to build all the project/targets. All targets will depend on this new target.
Add support for "SkipNonexistentProjects" as a metadata in MSBuild task. This allow the removal of it as a parameter during solution generation.
Testing
Add unit tests.
Before:
After: