1

This question is related to How to code a utility msbuild project so that it depends on a "real" C# project?

So I have a working sample code where there is a utility project depending on a "real" C# project. It works fine when building on the console. Here are the files:

C:\work\a [master]> tree /F
Folder PATH listing for volume OSDisk
Volume serial number is F6C4-7BEF
C:.
│   .gitignore
│   run.ps1
│   Subject.sln
│   Utility.targets
│
├───Subject
│       Subject.csproj
│
└───Utility.Subject
        Utility.Subject.proj

C:\work\a [master]>

Subject\Subject.csproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}</ProjectGuid>
    <OutputType>Library</OutputType>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

Utility.Subject\Utility.Subject.proj

<Project ToolsVersion="Current">
  <PropertyGroup>
    <MasterProject>..\Subject\Subject.csproj</MasterProject>
    <MasterAsmName>Subject</MasterAsmName>
  </PropertyGroup>
  <Import Project="..\Utility.targets" />
</Project>

Utility.targets

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <OutputPath>bin</OutputPath>
    <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="$(MasterProject)" />
  </ItemGroup>

  <Import Project="$(MSBuildBinPath)\Microsoft.Common.CurrentVersion.targets" />

  <Target Name="Build" DependsOnTargets="ResolveProjectReferences">
    <PropertyGroup>
      <DllPath>$(MasterProject)\..\bin\$(MasterAsmName).dll</DllPath>
    </PropertyGroup>
    <Message Text="*** Good" Importance="High" Condition="Exists('$(DllPath)')"/>
    <Message Text="*** Bad" Importance="High" Condition="!Exists('$(DllPath)')"/>
  </Target>
</Project>

Subject.sln

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Subject", "Subject\Subject.csproj", "{D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utility.Subject", "Utility.Subject\Utility.Subject.proj", "{EA5BE444-40F0-4972-A03E-5B794FAECF21}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
        Release|Any CPU = Release|Any CPU
    EndGlobalSection
    GlobalSection(ProjectConfigurationPlatforms) = postSolution
        {D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}.Release|Any CPU.Build.0 = Release|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Release|Any CPU.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {4DD676B7-ECF7-413E-9FC6-D4ED7EC73889}
    EndGlobalSection
EndGlobal

And it works fine on the command line:

C:\work\a [master]> MSBuild.exe /v:m /nologo
  Subject -> C:\work\a\Subject\Bin\Subject.dll
  *** Good
C:\work\a [master]>

The problem is Visual Studio. When opening the solution in VS the latter modifies the solution file by changing the target platform of the utility project from Any CPU to x86. This causes VS to skip the utility project altogether. We can actually see it when using devenv on the command line. Please, observe:

No modified files initially:

C:\work\a [master]> git st
## master
C:\work\a [master]>

Run devenv on the command line:

C:\work\a [master]> devenv .\Subject.sln /build debug

Microsoft Visual Studio 2019 Version 16.11.3.
Copyright (C) Microsoft Corp. All rights reserved.
Build started...
1>------ Build started: Project: Subject, Configuration: Debug Any CPU ------
1>  Subject -> C:\work\a\Subject\Bin\Subject.dll
2>------ Skipped Build: Project: Utility.Subject, Configuration: Debug x86 ------
2>Project not selected to build for this solution configuration
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 1 skipped ==========
C:\work\a [master +0 ~1 -0 !]>

Notice in particular:

2>------ Skipped Build: Project: Utility.Subject, Configuration: Debug x86 ------
2>Project not selected to build for this solution configuration

That means VS skips the utility project. But it also modifies the solution file so as to skip it in the future:

C:\work\a [master +0 ~1 -0 !]> git st
## master
 M Subject.sln
C:\work\a [master +0 ~1 -0 !]>

Visual Studio replaces the lines

        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Release|Any CPU.Build.0 = Release|Any CPU

with

        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|Any CPU.ActiveCfg = Debug|x86
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|x86.ActiveCfg = Debug|x86
        {EA5BE444-40F0-4972-A03E-5B794FAECF21}.Debug|x86.Build.0 = Debug|x86

How can I prevent it?

mark
  • 59,016
  • 79
  • 296
  • 580
  • Q: Have you been convinced to move from aspnet_compiler to [Rosyln](https://stackoverflow.com/a/70342126/421195) yet? With regard to your current question, look here: https://stackoverflow.com/a/51310727/421195 – paulsm4 Dec 15 '21 at 23:26
  • I was just planning to investigate the Roslyn option, because of the answer to my other question - https://stackoverflow.com/questions/70103543/how-to-speed-up-aspnet-compiler-exe – mark Dec 15 '21 at 23:29
  • @paulsm4 - I looked at the second link. How does it help me? – mark Dec 16 '21 at 00:30

1 Answers1

1

I found a solution. 2 files must be changed:

Subject/Subject.csproj

This change is necessary because this file is a contrived example of a non SDK style project. Visual Studio is in pain because it does not find something that serves like a marker for it, namely PropertyGroup elements conditional on Configuration and Platform. So, here is the modified content of that project:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{D2CF0D00-8B1B-4FCC-AF4B-5A354044F786}</ProjectGuid>
    <OutputType>Library</OutputType>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

Notice the new lines near the bottom of the file:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />

It turns out, our utility project must have these as well, otherwise VS botches its configuration in the solution file, so here we go:

Utility.targets

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <OutputPath>bin</OutputPath>
  </PropertyGroup>

  <!-- These two are expected by Visual Studio. Not needed when building with msbuild on the console. -->
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />

  <ItemGroup>
    <ProjectReference Include="$(MasterProject)" />
  </ItemGroup>

  <Import Project="$(MSBuildBinPath)\Microsoft.Common.CurrentVersion.targets" />

  <!-- This target is expected by Visual Studio. Not needed when building with msbuild on the console. -->
  <Target Name="CreateManifestResourceNames" />

  <Target Name="Build" DependsOnTargets="ResolveProjectReferences">
    <PropertyGroup>
      <DllPath>$(MasterProject)\..\bin\$(MasterAsmName).dll</DllPath>
    </PropertyGroup>
    <Message Text="*** Good" Importance="High" Condition="Exists('$(DllPath)')"/>
    <Message Text="*** Bad" Importance="High" Condition="!Exists('$(DllPath)')"/>
  </Target>
</Project>

Two changes are needed here. First, the same markers as in the previous file:

  <!-- These two are expected by Visual Studio. Not needed when building with msbuild on the console. -->
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />

In the previous file these markers were less critical, but in here they are what makes all the difference.

And the second change:

  <!-- This target is expected by Visual Studio. Not needed when building with msbuild on the console. -->
  <Target Name="CreateManifestResourceNames" />

With these two changes it all works. However, there is also a weird catch with the solution file. One must make sure not to mess with the indentation in that file. It must be tabs, not spaces! I have noticed a weird thing. At the end of this file I have these lines:

    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {4DD676B7-ECF7-413E-9FC6-D4ED7EC73889}
    EndGlobalSection
EndGlobal

If the SolutionGuid is not indented by exactly 2 tabs, then VS insists on adding a new SolutionGuid as if not seeing the already existing one. So, beware.

There is another nuisance - devenv command line hangs after having finished. I do not really care, since I use it just to demo the change in behavior. Anyway, here is the result after modifying the files:

C:\work\a [master]> devenv .\Subject.sln /build debug

Microsoft Visual Studio 2019 Version 16.11.3.
Copyright (C) Microsoft Corp. All rights reserved.
Build started...
1>------ Build started: Project: Subject, Configuration: Debug Any CPU ------
1>  Subject -> C:\work\a\Subject\Bin\Subject.dll
2>------ Build started: Project: Utility.Subject, Configuration: Debug Any CPU ------
2>  *** Good
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
C:\work\a [master]>
mark
  • 59,016
  • 79
  • 296
  • 580