microsoft / MSBuildSdks

MSBuild project SDKs
MIT License
449 stars 80 forks source link

[Traversal] Filtering projects solution files without kicking off evaluations #522

Open ViktorHofer opened 8 months ago

ViktorHofer commented 8 months ago

The new filter projects in solution files feature in Microsoft.Build.Traversal is phenomenal. I noticed that by default, it still kicks off evaluations for the projects in order to then invoke the custom ShouldSkipProject target. In my situation, I don't want to even trigger evaluations (and with also not trigger msbuild sdk resolver events). I was able to make that work with the following code:

Directory.Solution.targets

<Project>

  <Import Sdk="Microsoft.Build.Traversal" Project="Sdk.targets" />

  <!-- Overwrite target from Traversal Sdk.targets. -->
  <Target Name="GetProjectsToSkip">
    <ItemGroup>
      <ProjectToSkip Include="@(ProjectReference)"
                     Message=""
                     Condition="$([System.String]::new('%(ProjectReference.Identity)').EndsWith('.Tests.csproj'))"
                     OriginalItemSpec="%(Identity)" />
    </ItemGroup>
  </Target>

</Project>

Unfortunately this only works with normal restore and not static graph based restore: https://github.com/microsoft/MSBuildSdks/issues/521

Should we document this option? From my perspective, while this is a static mechanism to filter out projects, it's the only correct way as it doesn't even trigger evaluations of filtered out projects.

ViktorHofer commented 8 months ago

cc @jeffkl

I initially tried to do this without a target but the ItemGroup always gets embedded into the sln before the ItemGroup with the ProjectReference items. That's unfortunate as it would have solved the static graph restore issue.

Is there a hook to import stuff after the ProjectReference ItemGroup? Who would be the best contact on the msbuild/VS side to ask about this?

jeffkl commented 8 months ago

The original idea of dynamic skipping was for each project to indicate if it should be built. So obviously that works well if a target is run ahead of time which modifies the final ProjectReference items. But as you've noticed since static graph only does evaluations it won't quite work in that mode. Instead, the entry project would need to know ahead of time if a particular project reference should be included in the restore/build and your sample above is a good way to do it, based off of a naming convention.

My design came about because I introduced a Visual Studio extension to a repo that only needed to build on Windows and CI is building/testing on Linux and Mac which caused issues. It works pretty well when building solution files since they generally reference every project in the repo. Traversals are potentially more complicated since they represent what to build in that particular folder. So at the root you have a dirs.proj that points to just the next folder down's dirs.proj projects. The transitive closure isn't necessarily represented in that paradigm so skipping transitive projects on-the-fly isn't easy.

I wonder if MSBuild's static graph API needs a way for a project to communicate that it wants to be left out of a build? I suppose in that case the project would still get evaluated but be trimmed away from the final dependency tree. I'm conflicted on whether or not the fix for this needs to avoid the evaluation all together or just skip projects that opted out. What are your thoughts?