How do I merge properties in a Directory.Build.props file instead of overwriting properties?
Asked Answered
M

2

6

I have the following Directory.Build.props files:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LibraryPath>$(BOOST_ROOT)\lib32-msvc-14.2</LibraryPath>
    <IncludePath>$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LibraryPath>$(BOOST_ROOT)\lib64-msvc-14.2</LibraryPath>
    <IncludePath>$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LibraryPath>$(BOOST_ROOT)\lib32-msvc-14.2</LibraryPath>
    <IncludePath>$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LibraryPath>$(BOOST_ROOT)\lib64-msvc-14.2</LibraryPath>
    <IncludePath>$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
</Project>

Whenever I load a (C++) project in a subfolder (in visual Studio 2019), the project loses all inherited include and library paths:

Inherited Values: Inherited Values

I was thinking this might be due to me not appending to IncludePath and LibraryPath, so I did that and changed the props files as follow:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LibraryPath>$(LibraryPath);$(BOOST_ROOT)\lib32-msvc-14.2</LibraryPath>
    <IncludePath>$(IncludePath);$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LibraryPath>$(LibraryPath);$(BOOST_ROOT)\lib64-msvc-14.2</LibraryPath>
    <IncludePath>$(IncludePath);$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LibraryPath>$(LibraryPath);$(BOOST_ROOT)\lib32-msvc-14.2</LibraryPath>
    <IncludePath>$(IncludePath);$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LibraryPath>$(LibraryPath);$(BOOST_ROOT)\lib64-msvc-14.2</LibraryPath>
    <IncludePath>$(IncludePath);$(BOOST_ROOT)\</IncludePath>
  </PropertyGroup>
</Project>

Then reloading the project, results in missing inherited values, again.

I double checked to see if the macros are present, and they are (VC_ and WindowsSDK_ macros):

macros are present

I also tried changing Directory.Build.props to Directory.Build.targets to make it load after the projects have been loaded, but then the file does not get loaded at all.

How can I make sure that my Directory.Build.props inherits the default values, and appends my custom options?

The reason I want to do this is because I'm migrating away from the deprecated Microsoft.Cpp.Win32.user.props file.

Changing the extention to .targets works only for the build process, but intellisense / visual Studio doesn't pick up on the include files, and shows a lot of error (before compilation). This is not preferable.

Malcolmmalcom answered 7/1, 2020 at 9:45 Comment(7)
l am afraid you can not use Directory.Build.props to overwrite the values because it runs before Microsoft.Common.props which defines the default inherited values. So it always cannot overwrite the system values but only create some custom properties for overwriting or using later.Carbrey
It looks like this is non-solvable right now. Using Microsoft.Cpp.Win32.user.props is not an option as it's deprecated.Malcolmmalcom
l wonder why you want to migrate them from Microsoft.Cpp.Win32.user.props file, and it is the best way to achieve it. I guess you just want to tie these Custom properties to a specific project instead of acting on all projects created in VS, or you just want to automatically migrate to a new environment with the project,these properties are only the address of the class library, and invalid address VS will be skipped without any other effect on VS. So you don't have to worry about it.Carbrey
If you just want the project to migrate to a new environment, you can only need to reconfigure these properties into icrosoft.Cpp.Win32.user.props. So if you agree with me, you could consider accepting my answer.Carbrey
Actually, I would need to re-create what Linux has, global system wide include and library paths. I could put the props file on all the root drives so they would apply to all projects.Malcolmmalcom
l think you can just import your Directory.Build.props into Microsoft.Cpp.Win32.user.props so that it can avoid missing system property values.Carbrey
Note that you can put your UserMacros PropertyGroup in Directory.Build.props, so it's available to everything, and just put your IncludePath/LibraryPath extensions in Directory.Build.targets. This seems to work with intellisense.Curiosity
C
4

Perry Qian's Answer needs an improvement. Here are my concerns:

  1. INCLUDE/LIB env vars are global for all projects that want to use this method.
    MSBuild args have to be set for every project
  2. use of user.props is deprecated and also global for all projects

The problem with Directory.Build.props is that its included before $(IncludePath) and $(LibraryPath) are defined. You could manually set the default paths of VS, but this is bad practice.

To make use of the user.props successor Directory.Build.props and IntelliSense i suggest using AdditionalIncludeDirectories and AdditionalLibraryDirectories.
Here's an example of a Directory.Build.props configuration:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros">
    <BoostPath>$(BOOST_ROOT)</BoostPath>    
    <BoostPlatform Condition="'$(Platform)'=='Win32'">lib32</BoostPlatform>
    <BoostPlatform Condition="'$(Platform)'=='x64'">lib64</BoostPlatform>
    <BoostDebug Condition="'$(Configuration)'=='Debug'">\debug</BoostDebug>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>$(BoostPath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(BoostPath)\$(BoostPlatform)-msvc-$([System.Text.RegularExpressions.Regex]::replace($(PlatformToolset), "v(\d+)(\d)$", "$1.$2"))$BoostDebug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
</Project>

If you want to set additional paths inside the project, make sure to add ;%(AdditionalIncludeDirectories) or ;%(AdditionalLibraryDirectories) respectively to include the paths from the props file.

Set the BoostDebug property to wherever you compiled the debug libs. In this example it would be lib32-msvc-14.2/debug/

The regex term $([System.Text.RegularExpressions.Regex]::replace($(PlatformToolset), "v(\d+)(\d)$", "$1.$2")) converts the platform toolset, for instance v142 to the lib path appendix 14.2.

For readability it would be better to initialize it in the PropertyGroup, but $(PlatformToolset) won't be initialized as early as the props file is parsed. Luckily AdditionalLibraryDirectories is evaluated during link time when $(PlatformToolset) will be set.

If you want better readability or need more complex operations on $(PlatformToolset), i suggest using the Directory.Build.targets config additionally. When this file is parsed, $(PlatformToolset) will already be initialized.
Example Directory.Build.targets:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros">
    <BoostToolset>$([System.Text.RegularExpressions.Regex]::replace($(PlatformToolset), "v(\d+)(\d)$", "$1.$2"))</BoostPlatform>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <Link>
      <AdditionalLibraryDirectories>$(BoostPath)\$(BoostPlatform)-msvc-$(BoostToolset)$BoostDebug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
</Project>
Conure answered 24/2, 2020 at 17:6 Comment(0)
C
2

How can I make sure that my Directory.Build.props inherits the default values, and appends my custom options?

I think you cannot use Directory.Build.props to overwrite a system property in the Project Property UI.

From your info, the LibraryPath and IncludePath has lost there values. Please note this info about the Import order of Directory.Build.props:

Directory.Build.props is imported very early in Microsoft.Common.props, and properties defined later are unavailable to it. So, avoid referring to properties that are not yet defined (and will evaluate to empty).

Directory.Build.targets is imported from Microsoft.Common.targets after importing .targets files from NuGet packages. So, it can override properties and targets defined in most of the build logic, but sometimes you may need to customize the project file after the final import.

You can refer to this link.

In detail,Directory.Build.props like a step to initialize the project and always run before Microsoft.Common.props which set default values to most of the system properties such as LibraryPath and IncludePath. So at the beginning,it is normal that the values are empty . After this, when it executes the Microsoft.Common.props, it has this below:

<Includepath Condidtion="'$(Includepath)'==''">$(VC_IncludePath)......</Includepath >

And you have add $(BOOST_ROOT)\lib32-msvc-14.2 to Includepath in the Directory.Build.props, so when you execute next step, the Includepath has a value, so it will skip the operation of assigning system values and do not overwrite the values in this step and causes this strange behavior.

For Directory.Build.targets, it performs the override operation at the end, when both the includepath and LibraryPath properties have got the system values, and there is no problem with the override. But you can only get these values when you run the project, as you showed later.

In brief, Directory.Build.props is suitable for defining some properties which would be used or overwrote in later and Directory.Build.targets suits for overwriting the values which are already defined during build process.

Suggestion

1) To set the include directories, you can add them into your INCLUDE environment variable. you can set the INCLUDE and LIB variables, like so:

set INCLUDE=xxxx
set LIB=xxxx\lib32-msvc-14.2

And then add /p:"VCBuildAdditionalOptions= /useenv" to MSBuild arguments so that it takes the INCLUDE and LIB variables.

You can refer to this.

2) Or you can use your initial function in the path C:\Users\UserName\AppData\Local\Microsoft\MSBuild\v4.0\Microsoft.Cpp.xxx.user.props to add the extra includepath,librarypath in it like:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets">
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup>
    <IncludePath>C:\any-name\include;$(IncludePath)</IncludePath>
    <LibraryPath>C:\any-name\lib;$(LibraryPath)</LibraryPath>
  </PropertyGroup>
  <ItemDefinitionGroup />
  <ItemGroup />
</Project>

Hope it could help you.

Carbrey answered 8/1, 2020 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.