MsBuild condition for new ("sdk-style") vs. old project format

Viewed 3216

I want to write an msbuild "include" (.props) file that is to be imported in both the new ("sdk-style") projects as well as the old ones.

Is there a way to find out into which type of project the .props file is imported so I could use that in a condition? For example,

<PropertyGroup Condition="'$(ProjectType)'=='sdk'">
   <SomeProperty>SomeValue</SomeProperty>
</PropertyGroup>
4 Answers

I encountered this same need in my project. At first I went with the solution @wexman, but I found that variable not to be reliable. I considered the approach of opening the csproj file and using regexes to look for the SDK attribute, but it felt too brittle to me - especially given that the SDK can be specified either in an element or an attribute. Finally I created a build with binary logging turned on (option /bl) and went hunting through the various files that get imported when doing a build of an SDK-style project.

I discovered the property UsingMicrosoftNETSdk which is set to true in the props file which auto-imports Microsoft.Common.props when you are using an SDK-style project. This auto-import (which leads to the auto-import of Directory.Build.props seems to be one of the most important defining characteristics of SDK-style projects, and the name of the property makes it look like something more likely to stick around.

So in the end my solution was to have:

Condition="'$(UsingMicrosoftNETSdk)' == 'true'"

(or its inverse) as the reliable way to determine which type of project is in use.

I seem to have found a working solution. I'm checking against the property $(_PlatformWithoutConfigurationInference) declared in Microsoft.NET.Sdk.Props. If it is not empty, I'll be dealing with an sdk style project, if it is empty, it's a legacy project:

<Project>

  <PropertyGroup>
    <ProjectStyle Condition="'$(_PlatformWithoutConfigurationInference)'!=''">sdk</ProjectStyle>
    <ProjectStyle Condition="'$(_PlatformWithoutConfigurationInference)'==''">legacy</ProjectStyle>
  </PropertyGroup>

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Message Importance="High" Text="$(MsBuildProjectName) is a $(ProjectStyle) style project" />
  </Target>

</Project>

While testing for property values (or the lack thereof) is how almost everything gets done in MSBuild, sometimes there are other ways.

How do you know whether the current MSBuild project is a SDK project?

In other words:

  • given an XML file...
  • whose full path is in the MSBuildProjectFullPath well-known property...
  • and whose first tag is Project...
  • does said tag have a Sdk attribute?

Thanks to MSBuild's property functions, you can do the following:

  • use [System.IO.File]::ReadAllText to read the project file's contents as a string;
  • then use [System.Text.RegularExpressions.Regex]::IsMatch to test for a Project tag with a Sdk attribute.
<PropertyGroup>
  <_ProjectContents>$([System.IO.File]::ReadAllText(`$(MSBuildProjectFullPath)`))</_ProjectContents>
  <_SdkRegex>(?s-i)(^|\s|&gt;)&lt;Project\s(([^&gt;]*?)\s)?Sdk(\s*?)=(\s*?)"</_SdkRegex>
  <_IsSdkProject>$([System.Text.RegularExpressions.Regex]::IsMatch(`$(_ProjectContents)`, `$(_SdkRegex)`))</_IsSdkProject>
</PropertyGroup>

Or, if you prefer a one-liner:

<PropertyGroup>
  <_IsSdkProject>$([System.Text.RegularExpressions.Regex]::IsMatch(`$([System.IO.File]::ReadAllText(`$(MSBuildProjectFullPath)`))`, `(?s-i)(^|\s|&gt;)&lt;Project\s(([^&gt;]*?)\s)?Sdk(\s*?)=(\s*?)"`))</_IsSdkProject>
</PropertyGroup>

Then you can use the value of _IsSdkProject, which will be either True OR False, in a Condition, like this:

<PropertyGroup Condition="'$(_IsSdkProject)'=='True'">
  <SomeProperty>SomeValue</SomeProperty>
</PropertyGroup>

The regular expression explained

In the above regular expression, the < and > characters have been replaced with XML entities (&lt; and &gt; respectively). The original regex is as follows:

(?s-i)(^|\s|>)<Project\s(([^>]*?)\s)?Sdk(\s*?)=(\s*?)"

Which means:

  • In single-line, case-sensitive mode...
  • Either at the start of the file, or following a white-space character, or following a > character...
  • find the literal string <Project...
  • then a white-space character...
  • then, optionally, any number of any character except >, and another white-space character...
  • then the literal string Sdk...
  • then, optionally, any number of white-space characters...
  • then the character =...
  • then, optionally, any number of white-space characters...
  • then the character "

A better explanation, together with an online tester where you can insert your project files' contents and test in real time, is on regex101.com. The site was made for Javascript regular expressions, which do not behave exactly like in .NET, but it is useful anyway.

MsBuild condition for new (“sdk-style”) vs. old project format

There is no directly solution for this question. Because there is no such property ProjectType in MSBuild/Visual Studio.

I would like provide a workaround here, you can check if it works for you. As we know, in the old project format, There will usually be a file AssemblyInfo.cs, which is not exists on the new "sdk-style" project format. So we could use the Condition="Exists('$(ProjectDir)\Properties\AssemblyInfo.cs')" to find out into which type of project the .props file is imported:

The content of my test .props file:

 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="Exists('$(ProjectDir)\Properties\AssemblyInfo.cs')">
    <SomeProperty>SomeValue1</SomeProperty>
  </PropertyGroup>

  <PropertyGroup Condition="!Exists('$(ProjectDir)\Properties\AssemblyInfo.cs')">
    <SomeProperty>SomeValue2</SomeProperty>
  </PropertyGroup>

  <Target Name="Test" AfterTargets="AfterBuild">
    <Message Text="$(SomeProperty)"></Message>
  </Target>
</Project>

Hope this helps.

Related