Using Visual Studio project properties effectively for multiple projects and configurations
Asked Answered
B

7

70

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.

Bouillabaisse answered 17/8, 2010 at 12:48 Comment(1)
I feel your pain: We have over 600 vcproj's in our product at work. :(Niersteiner
B
82

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.

Bouillabaisse answered 17/8, 2010 at 14:50 Comment(4)
Yeah, property sheets are far more powerful in VS2k10. Just a shame none of it is exposed through the IDE. I've done something similar and it really simplifies things.Piles
Should you need more advanced String functions, you can use the System.String methods: msdn.microsoft.com/en-us/library/… Example: `<ShortHasRelPos>$(Configuration.ToLower().LastIndexOf('rel'))</ShortHasRelPos>Tameshatamez
How can I apply this solution? Where should I put it, and how can I let Visual Studio know I would like it used in a default fashion?Fourgon
Ive never done it as a "global default" because it might mess with projects that are not my own (e.g. 3rd party libraries and example projects) although there is the default property sheets stored somewhere (forget) you could edit. As such I just put them in my solution directory, then in the Visual Studio "Property Manager" window you can just use "Add Existing Property Sheet" (you can also create new ones there, but the GUI still provides no means to even do basic Platform/Configuration conditionals on property sheets). This also works with multiple devs / source control correctly.Bouillabaisse
M
28

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!

Mathew answered 4/2, 2011 at 6:36 Comment(2)
When will this be evaluated? When loading the project? I would like to add custom post-build copy rules to a property sheet to only copy DLLs that have actually been linked to the project. Do you think this would be possible?Timberlake
Bim, I think its evaluated before build, as it read variables from registry (see $(registry:key)).Mathew
L
12

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 ...

Lucinalucinda answered 11/1, 2011 at 0:50 Comment(1)
VS 2015 does not recognize the "VisualStudioPropertySheet" element:Wilhelminawilhelmine
D
5

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

Defendant answered 16/4, 2012 at 18:34 Comment(1)
PERFECT! thanks. So you can also create new pages in each config node. Easy to fall into the trap of editing the shared page which effects all configurations even though it is accessed under each configurations node.Agincourt
N
4

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).

Nd answered 17/8, 2010 at 12:58 Comment(4)
I have already done some bits like this a bit, but then there is libraries that use something else in their names (eg opt instead of release, -d instead of -Debug) etc, which still requires a lot of special rules (my debug.props and release.props set up user macros to help with this with common.props actually setting the include/lib paths). Hadn't considered it with boost, good idea (be even nicer if there auto-linking included not using the same names for x86 and x64 builds but the boost.build people dont seem to think its an issue)Bouillabaisse
I guess your PlatformShortName came from a x86.props and x64.props that must be correctly inherited or something or is it just undocumented?Bouillabaisse
@Fire Lancer: to be honest I'm not sure where PlatformShortName is coming from. I think it must be a built-in, because we don't have anything special in our property sheets.Nd
@FireLancer: We hacked the boost build-script to include the platform in the lib-name. It does require us to have a custom boost_version.h but auto-linking works like a charm, we only need to add the boost.props file to a project and we're ready to go. Of course had we known about conditions in props-files that might have been a better way to go.. Definitely less painful..Commodity
W
1

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

Wilhelminawilhelmine answered 21/12, 2021 at 15:48 Comment(0)
N
0

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/

Nipple answered 13/1, 2011 at 19:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.