dotnet / project-system

The .NET Project System for Visual Studio
MIT License
967 stars 386 forks source link

Multi-repo solution with targets fails to build from Visual Studio; works from .NET CLI #9502

Open just-ero opened 2 months ago

just-ero commented 2 months ago

Versions

Visual Studio: 17.11.0 Preview 3 .NET SDK: 8.0.303

Summary

We have a multi-repo solution at LiveSplit. Projects are pulled in via git submodules and included in the main repo's solution file. These projects act as plugins (referred to as "components") for visualization of information on the app. Including them in the solution like this makes them easy to debug. When the app is built, these components must also be built, as there would be nothing to visualize otherwise. They must be placed in a Components sub-directory of the app's output directory.

Currently, we hard-code the output paths of the required main repo projects via $(OutDir) in a Directory.Build.props file (.\bin\$(Configuration.ToLowerInvariant())) and do the same for the projects in the .\components directory ($(OutDir)\Components). We would then build the entire solution and end up with the expected production output of the app and the components being placed in those specified destinations. The output looks something like this:

$(OutDir)
|-- LiveSplit.exe
|-- LiveSplit.csproj Dependencies
|-- LiveSplit.Register.exe
|-- LiveSplit.Register.csproj Dependencies
\-- Components
    |-- **.dll
    \-- **.csproj Dependencies

However, I've been wanting to remove those hard-coded paths, as they don't sit right with me and I would like to allow users to set -o from the command line to provide their own output path. Additionally, I want the production output to be produced only when the main executable project is built (.\src\LiveSplit). To achieve this, I've removed all of the $(OutDir) properties and added some .targets instead, which collect the .\components projects, restores them and builds them:

<Target Name="_BuildComponents" AfterTargets="Build">
  <ItemGroup>
    <_Components Include="$(ComponentsPath)\*\src\*\*.csproj" />
  </ItemGroup>

  <MSBuild Projects="@(_Components)" Targets="Restore" Properties="IsRestoring=true" />
  <MSBuild Projects="@(_Components)" Targets="Build" Properties="OutputPath=$(OutputPath)\Components" />
</Target>

This is working fantastic from the command line, since it only builds the specified project. Visual Studio instead still builds the entire solution, even if only the project was marked for building. This results in conflicts and dependency files which seemingly cannot be found. I believe this is because Visual Studio builds the components into their own output directories first, but looks for them in $(OutputPath)\Components later in the target. This is further supported by the fact that the build succeeds when passing "OutputPath=$(OutputPath)" as the properties, since the required assemblies do exist there (they are dependencies of the main executable).

7>CSC : error CS0006: Metadata file '~\LiveSplit\artifacts\bin\LiveSplit\debug\Components\CustomFontDialog.dll' could not be found
7>CSC : error CS0006: Metadata file '~\LiveSplit\artifacts\bin\LiveSplit\debug\Components\LiveSplit.Core.dll' could not be found
7>CSC : error CS0006: Metadata file '~\LiveSplit\artifacts\bin\LiveSplit\debug\Components\UpdateManager.dll' could not be found
7>CSC : error CS0006: Metadata file '~\LiveSplit\artifacts\bin\LiveSplit\debug\Components\WinFormsColor.dll' could not be found

Unfortunately this has led to some frustration for me, since it means any user who wants to build the app from Visual Studio wouldn't be able to do so. This is obviously unacceptable, because Visual Studio provides a lot of valuable functionality for profiling and debugging.

If there's another approach we can take here, please let me know. I've provided some logs and relevant links below. Please ask any clarifying questions.

Logs: LiveSplit-build-logs.zip
PR & discussion: https://github.com/LiveSplit/LiveSplit/pull/2484
Branch: https://github.com/just-ero/LiveSplit/tree/use-targets

Steps to Reproduce

  1. Clone https://github.com/just-ero/LiveSplit and enter the repo directory.
  2. Switch to branch use-targets.
  3. Run dotnet build ./src/LiveSplit, observe the build succeeding.
  4. Open LiveSplit.sln in Visual studio.
  5. Build or rebuild the LiveSplit project, observe the build failing.

I have to mention here that I have tried to create a sample project at https://github.com/just-ero/ProjectSystemIssueSample, but this one does succeed. I'm not sure what makes the one from LiveSplit fail. Perhaps it's the large amount of projects and references?

Expected Behavior

Visual Studio only builds the selected project.

Actual Behavior

Visual Studio appears to build the entire solution in a very jumbled order. The selected project isn't even the first one that builds. Comparing the binlogs of the .NET CLI build and the Visual Studio build makes this very obvious.

User Impact

It is impossible for us to move forward without hard-coding the output paths of every single one of our projects.

just-ero commented 2 months ago

cc @baronfel