microsoft / MSBuildSdks

MSBuild project SDKs
MIT License
460 stars 82 forks source link

Microsoft.Build.Traversal conflicts with .NET 8 SDK simplified output paths #509

Open brantburnett opened 11 months ago

brantburnett commented 11 months ago

When using the new .NET 8 simplified output paths feature with multiple dirs.proj projects using Microsoft.Build.Traversal they conflict with each other. The automatic ArtifactsProjectName determined by the .NET 8 SDK for all of the projects is dirs so they end up sharing a obj path within the artifacts directory, overwriting each other.

The only workaround I have found so far is to manually set the ArtifactsProjectName differently for each project. However, this must be done before the Microsoft.Build.Traversal SDK is imported. This means the simple Sdk="Microsoft.Build.Traversal" mechanism doesn't work and props and targets must be imported separately, which is messier and more difficult.

jeffkl commented 11 months ago

Traversal projects don't really produce any output, do you think it would be okay to just disable the simplified output paths feature all together for them?

<PropertyGroup>
  <UseArtifactsOutput Condition="'$(UseArtifactsOutput)' == ''">false</UseArtifactsOutput>
</PropertyGroup>
brantburnett commented 11 months ago

They do produce a variety of outputs, primarily related to the NuGet restore process.

Snag_f771d62

While using the artifacts directory could be disabled, this would lose some of the purpose of the directory, at least in my view. While there are many advantages to it, one advantage is a single directory that can be deleted to do a "clean" rather than something more aggressive like git clean -xdf that also nukes VS caches and user options. This is pretty valuable for large repositories with many projects, which is also the main use case for Microsoft.Build.Traversal.

Regardless, if the decision is not to support the artifacts directory, then it should probably be disabled by default within Microsoft.Build.Traversal before Microsoft.NET.Sdk is imported.

jeffkl commented 11 months ago

Yes the only real output is the intermediate folder (obj) but the project itself doesn't output anything like an assembly (bin folder).

The only other option I can think of is to uniquify the directory name under the artifacts which can be tricky. Since each traversal project is probably named dirs.proj, we could somehow change the full-ish path to a string like:

ArtifactsPath Actual Path
artifacts\root-dirs D:\MyProject\dirs.proj
artifacts\root-src-dirs D:\MyProject\src\dirs.proj
artifacts\root-src-something-dirs D:\MyProject\src\something\dirs.proj

This could end up exceeding path length limits. Alternatively, we could preserve the paths like this:

ArtifactsPath Actual Path
artifacts\dirs D:\MyProject\dirs.proj
artifacts\src\dirs D:\MyProject\src\dirs.proj
artifacts\src\something\dirs D:\MyProject\src\something\dirs.proj

Does anyone else have a suggestion?

brantburnett commented 11 months ago

FWIW this is the workaround I'm using now. It's clearly not quite right for general inclusion because it makes some assumptions, but it may be helpful. It uses the nested directory approach.

Note: This is in Directory.Build.props

<Project>
  <PropertyGroup>
    <!-- Put all artifacts beneath /artifacts instead of project bin/obj directories -->
    <ArtifactsPath Condition=" '$(ArtifactsPath)' == '' ">$(MSBuildThisFileDirectory)artifacts\</ArtifactsPath>
  </PropertyGroup>

  <!-- Avoid conflicts with dirs.proj having the same name -->
  <PropertyGroup Condition=" '$(MSBuildProjectFile)' == 'dirs.proj' ">
    <_TraversalProjectRelativeToRoot>$([MSBuild]::MakeRelative('$(MSBuildThisFileDirectory)', '$(MSBuildProjectDirectory)'))</_TraversalProjectRelativeToRoot>
    <ArtifactsProjectName>dirs\$(_TraversalProjectRelativeToRoot)</ArtifactsProjectName>
  </PropertyGroup>
</Project>