Search code examples
visual-studiomsbuildvisual-studio-2017visual-studio-2017-build-toolsvcxproj

How to set linker's OutputFile (in .vcxproj) from external list


I have a bunch of C++ projects (.vcxproj) for VS2017, which produce dynamic libraries (DLL), which are handled like "plugins" (i.e. they are loaded dynamically by the application).

Occasionally I want to change the produced DLLs names based on some list in text file (the format may be changed):

ProjName1;DLL1
ProjName2;DLL2
...

The first column specifies the project name ($(ProjectName)) and the second is for desired DLL name ($(TargetName)).

I want this file to be parsed at build-time, so each read DLL name goes into $(OutputFile) of the appropriate project.

For instance, ProjName1.vcxproj will have following fragment (at build time):

<Link>
  <OutputFile>$(OutDir)DLL1$(TargetExt)</OutputFile>
</Link>

I guess, certain capabilities of msbuild can be utilized for this (e.g., ReadLinesFromFile task and FindInList task), but I'm not sure how to put these pieces together).

There are some examples of such automation to consider, though I doubt they are relevant to .vcxproj format:

The straightforward approach is to modify content of each .vcxproj-file to match the list (either manually, or with some script).

Are there any other options?


Update 1: I've managed to implement a partial solution, which is based on a separate file with DLL name (module name) for each project (refer to Property Functions for more hints).

With property sheets it was easy in my case to 'inject' the fix into all projects:

<?xml version="1.0" encoding="utf-8"?> 
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
  <PropertyGroup Condition="exists('$(MSBuildProjectDirectory)\_MODULE.name')">
    <ModuleName>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\_MODULE.name'))</ModuleName>
    <TargetName>$(ModuleName)</TargetName>
  </PropertyGroup>
...
</Project>

Though it works quite good, the approach doesn't meet my needs perfectly - I'd like to have all module names in one file.


Solution

  • After some investigations and tryouts I've finally managed to make a working solution for my case. Basically it is built on top of my previous partial solution by using magic powers of Regex. The hint how to handle regex in my context was found in another answer to similar question.

    I've also tuned the list format a bit to my taste:

    DLL1=ProjName1
    DLL2=ProjName2
    ...
    

    I've placed the changes in the property sheet, so they can be easily picked up by all dependent projects:

    <?xml version="1.0" encoding="utf-8"?> 
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    ...
      <PropertyGroup Condition="exists('$(MSBuildThisFileDirectory)..\_MODULES.txt')">
        <ModulesNames>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\_MODULES.txt'))</ModulesNames>
        <ModuleName>$([System.Text.RegularExpressions.Regex]::Match($(ModulesNames), '\W?(\w*)\W?=\W?$(MSBuildProjectName)\W?').get_Groups().get_Item(1))</ModuleName>
        <TargetName>$(ModuleName)</TargetName>
      </PropertyGroup>
    ...
    </Project>
    

    If the project isn't listed in _MODULES.txt, its $(TargetName) isn't affected.

    If you see $(ModuleName) in the project settings, then everything is tuned correctly: VS2017 project settings

    The solution seems to work fine except for one minor drawback: the build system (VS2017 v.15.9.15) is quite "lazy" in detecting changed in _MODULES.txt while the solution is open, so you'll have to close it and reopen. And chances are high the rebuild will be needed after you change the list.