Using Visual Studio project properties effectively for multiple projects and configurations

C++Visual Studio-2010Visual StudioBuildProjects and-Solutions

C++ Problem Overview


I have always used Visual Studios built in GUI support for configuring my projects, often using property sheets so that several projects will use a common set.

One of my main gripes with this is managing multiple projects, configurations and platforms. If you just do everything with the main GUI (right click the project -> properties) it quickly becomes a mess, difficult to maintain and prone to bugs (like failing to correctly define some macro, or using the wrong runtime library, etc). Dealing with the fact that different people put there dependency libraries in different places (eg mine all live in "C:\Libs\[C,C++]\[lib-name]\") and then often manage the different versions of those libraries differently as well (release, debug, x86, x64, etc) is also a large problem since it vastly complicates the time to set it up on a new system, and then there is issues with version-control and keeping everyone's paths separate...

Property sheets make this a bit better, but I cant have one sheet have separate settings for different configurations and platforms (the drop down boxes a greyed out), resulting in me having many sheets which if inherited in the correct order do what I want ("x86", "x64", "debug", "release", "common", "directories" (deals with the previously mentioned dependency issue by defining user macros like BoostX86LibDir), etc) and if inherited in the wrong order (eg "common" before "x64" and "debug") lead to issues like trying to link an incorrect library version, or incorrectly naming the output...

What I want is a way of dealing with all these scattered dependencies and setting up a set of "rules" which are used by all my projects in the solution, like naming an output library as "mylib-[vc90,vc100]-[x86,x64][-d].lib", without having to do all this for each individual project, configuration and platform combination, and then keep them all correctly in sync.

I am aware of moving to entirely different systems like CMake that create the needed files, however this then complicates things elsewhere by making it so even simple tasks like adding a new file to the project then requires additional changes elsewhere, which is not something I am entirely happy with either, unless there is some with VS2010 integration which can keep track of these sorts of changes.

C++ Solutions


Solution 1 - C++

I just found out somthing I didnt think was possible (it is not exposed by the GUI) that helps make property sheet far more useful. The "Condition" attribute of many of the tags in the project property files and it can be used in the .props files as well!

I just put together the following as a test and it worked great and did the task of 5 (common,x64,x86,debug,release) separate property sheets!

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="UserMacros">
    <!--debug suffix-->
    <DebugSuffix Condition="'$(Configuration)'=='Debug'">-d</DebugSuffix>
    <DebugSuffix Condition="'$(Configuration)'!='Debug'"></DebugSuffix>
    <!--platform-->
    <ShortPlatform Condition="'$(Platform)' == 'Win32'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)' == 'x64'">x64</ShortPlatform>
    <!--toolset-->
    <Toolset Condition="'$(PlatformToolset)' == 'v90'">vc90</Toolset>
    <Toolset Condition="'$(PlatformToolset)' == 'v100'">vc100</Toolset>
  </PropertyGroup>
  <!--target-->
  <PropertyGroup>
    <TargetName>$(ProjectName)-$(Toolset)-$(ShortPlatform)$(DebugSuffix)</TargetName>
  </PropertyGroup>
</Project>

Only issue is the properties GUI cant handle it, a project that uses the above property sheet just reports default inherited values like "$(ProjectName)" for the target.

Solution 2 - C++

I made some improvements, may be useful for somebody

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="UserMacros">
    <!--IsDebug: search for 'Debug' in Configuration-->
    <IsDebug>$([System.Convert]::ToString( $([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), '[Dd]ebug'))))</IsDebug>

    <!--ShortPlatform-->
    <ShortPlatform Condition="'$(Platform)' == 'Win32'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)' == 'x64'">x64</ShortPlatform>

    <!--build parameters-->
    <BUILD_DIR>$(registry:HKEY_CURRENT_USER\Software\MyCompany\@BUILD_DIR)</BUILD_DIR>
  </PropertyGroup>

  <Choose>
    <When Condition="$([System.Convert]::ToBoolean($(IsDebug)))">
      <!-- debug macroses -->
      <PropertyGroup Label="UserMacros">
        <MyOutDirBase>Debug</MyOutDirBase>
        <DebugSuffix>-d</DebugSuffix>
      </PropertyGroup>
    </When>
    <Otherwise>
      <!-- other/release macroses -->
      <PropertyGroup Label="UserMacros">
        <MyOutDirBase>Release</MyOutDirBase>
        <DebugSuffix></DebugSuffix>
      </PropertyGroup>
    </Otherwise>
  </Choose>

  <Choose>
    <When Condition="Exists($(BUILD_DIR))">
      <PropertyGroup Label="UserMacros">
        <MyOutDir>$(BUILD_DIR)\Bin\$(MyOutDirBase)_$(ShortPlatform)\</MyOutDir>
		<MyIntDir>$(BUILD_DIR)\Build\$(Configuration)_$(ShortPlatform)_$(PlatformToolset)\$(ProjectGuid)\</MyIntDir>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup Label="UserMacros">
        <MyOutDir>$(SolutionDir)\Bin\$(MyOutDirBase)_$(ShortPlatform)\</MyOutDir>
		<MyIntDir>$(SolutionDir)\Build\$(Configuration)_$(ShortPlatform)_$(PlatformToolset)\$(ProjectGuid)\</MyIntDir>
      </PropertyGroup>
    </Otherwise>
  </Choose>

  <PropertyGroup>
    <OutDir>$(MyOutDir)</OutDir>
    <IntDir>$(MyIntDir)</IntDir>
<!-- some common for projects
    <CharacterSet>Unicode</CharacterSet>
    <LinkIncremental>false</LinkIncremental>
-->	
  </PropertyGroup>
</Project>

have fun!

Solution 3 - C++

I had same pain for the product of my company (200+ projects) before. The way I solved it is to build a nice hierarchy of the property sheets.

The projects inherits the property sheet by its output type, say x64.Debug.Dynamic.Library.vsprops. This vsprops file simply inherits other property sheets using the InheritedPropertySheets attribute

<VisualStudioPropertySheet
    ProjectType="Visual C++"
    Version="8.00"
    Name="x64.Debug.Dynamic.Binary"
    InheritedPropertySheets=".\Common.vsprops;.\x64.vsprops;.\Debug.vsprops;.\Runtime.Debug.Dynamic.vsprops;.\Output.x64.Library.vsprops"
    >

You can also use variables (i.e. UserMacro, whose value can be absolute or even environment variables) in property sheets to customize a lot of things based on your need. For example, defining a BIN variable in Debug.vsprops

<UserMacro name="BIN" Value="Debug" />

then when you set the output name in the series of vsprops, say, Output.x64.Library.vsprops

<VisualStudioPropertySheet
    ProjectType="Visual C++"
    Version="8.00"
    OutputDirectory="$(BIN)"
>

The $(BIN) variable will be expanded to what's been set (in this case, Debug). Use this technique you can easily construct a nice hierarchy of property sheets to meet your demand.

Now there's one more thing you might want to do: build your own project templates that uses your property sheet set. The real difficult part is to enforce proper usage of the templates and property sheets. My personal experience is that even if everything is setup, somebody will still forget to use the template to create new projects ...

Solution 4 - C++

It is possible to create a separate property sheet for each configuration. To do this:

  1. Create a configuration-specific property sheet
  2. Open the Property Manager
  3. Right click the configuration (not the project) that you would like to modify
  4. Click "Add Existing Property Sheet" and add your sheet

This relieves you from inserting conditions into a single sheet for multiple configurations. If you have some common attributes that you would like to share between configurations, then create a hierarchy. The top sheet can be used in all configurations and the nested sheets will only contain the configuration-specific attribut

Solution 5 - C++

As far as the output library goes, you can select all your projects, then bring up the property pages, select All Configurations, All Platforms and then set the Target Name to:

$(ProjectName)-$(PlatformToolset)-$(PlatformShortName)-$(Configuration)

which would give an output like mylib-v100-x86-Debug.lib

We do something similar to this for Additional Library Directories as well, using $(PlatformName) and #(Configuration) to pick out the right library paths, although it does mean some messing around with initial setup of the libraries. eg we have boost install its libs to boost/lib.Win32 or boost/lib.x64.


With regards to libraries, and people installing them in different places, there are a couple of options. If you have a very robust source control system, you can just put everything in source control, living in a libs folder next to your source. That probably won't work if you use more than a few libraries though, or if they are particularly large.

The other option that comes to mind is to set an environment variable on each user machine that points to the root of their libraries folder, eg LIB_ROOT=c:\libraries, and you can then access that in Visual Studio as $(LIB_ROOT).

Solution 6 - C++

It sounds like it could be worth checking out a build tool - at my place we use a custom made tool that watches files and projects for changes and figures the dependencies and compile order. Adding a new file is no big deal - compilation is done with msbuild.

If i had to compile more than a bunch of projects I would use something like nant: http://nant.sourceforge.net/

Solution 7 - C++

If you want to define some UserMacros which should be valid for the entire solution:

  1. Create a solution-global.props file in your solution root folder

  2. Define there your UserMacros:

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup Label="UserMacros" >
        <CommonUtilsDir>$(SolutionDir)Common\Utils\</CommonUtilsDir>
        <DriverBaseDir>$(SolutionDir)Common\DriverBase\</DriverBaseDir>
      </PropertyGroup>
    </Project>
    
  3. insert in each *.vcxproj file in which you want to use your custom macros this line:

    <Import Project="$(SolutionDir)solution-global.props" />

  4. Use your custom macro:

    <AdditionalIncludeDirectories>$(CommonUtilsDir)\Source;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

Works in VS2015

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFire LancerView Question on Stackoverflow
Solution 1 - C++Fire LancerView Answer on Stackoverflow
Solution 2 - C++luniconView Answer on Stackoverflow
Solution 3 - C++Peon the GreatView Answer on Stackoverflow
Solution 4 - C++gogisanView Answer on Stackoverflow
Solution 5 - C++ngoozeffView Answer on Stackoverflow
Solution 6 - C++Rune AndersenView Answer on Stackoverflow
Solution 7 - C++HeriView Answer on Stackoverflow