2

I'm working with an application that includes an un-managed client DLL and a managed COM server DLL (which was a challenge in itself: Managed Reg-Free COM Server Won't Activate), and now I'm wondering what is the best way to keep the version numbers in sync. Since we are building both the client and the server, and we try to keep the version numbers of all our files in sync for every build, it looks like I need to have a process that edits all my manifest files on both the client and server ends of all my isolated COM references before a full build happens. Is there an easier way?

Example (client manifest):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
   <assemblyIdentity type="win32" name="globals" version="1.0.0.0" />
   <dependency>
      <dependentAssembly>
         <assemblyIdentity type="win32" name="SoftBrands.FourthShift.FSCulture" version="8.0.0.999" publicKeyToken="541b4aff0f04b60a" />
      </dependentAssembly>
   </dependency>
</assembly>

Server Manifest:

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <assemblyIdentity type="win32" name="SoftBrands.FourthShift.FSCulture" version="8.0.0.999" publicKeyToken="541b4aff0f04b60a" />
   <clrClass   clsid="{23D4FF3D-EEDF-4F68-AD65-749958EE3B2A}"
               name="SoftBrands.FourthShift.FSCulture.FSCulture"
               tlbid="{8D480B22-D603-309F-9A26-EA9E9B020207}">
   </clrClass>
</asmv1:assembly>

I could just do a global search and replace on version="8.0.0.999" with the current version for every build, but I suspect there might be a better way.

Community
  • 1
  • 1
BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
  • Have you considered a custom MSBuild task to do the job? – Randy Dec 07 '15 at 18:03
  • No. I'm not familiar with custom MSBuild tasks. It might be an option assuming that they wouldn't cause other developers' systems to break (due to lack of having the customization installed). At first glance, it looks like it would require another assembly to be installed on every developer's system. I think I'd be inclined to use a global replace solution before that one. – BlueMonkMN Dec 07 '15 at 18:05
  • 1
    You can include the assembly for the custom task as part of your project so everyone will have it relative to the project path. We generally check these kinds of files into our source code repository and usage is transparent to all developers. – Randy Dec 07 '15 at 18:10

1 Answers1

0

Your best bet is to leverage a custom MSBuild task to manipulate the project artifacts prior to compilation.

The task should accept three properties. One property for the client manifest, another property for the server manifest and the third property for the version.

Here is what the targets file might look like in MSBuild.

Manifests.targets

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
  <!-- Import Tasks -->
  <!-- Be sure to sue the full namespace of the task to import -->
  <UsingTask TaskName="Full.Namespace.To.UpdateManifestsTask" AssemblyFile="$(MSBuildThisFileDirectory)\MyCustomTasks.dll" />
  <!-- Define the location of the client and server manifest files -->
  <PropertyGroup>
    <ClientManifest><!-- Location of the client manifest --></ClientManifest>
    <ServerManifest><!-- Location of the server manifest --></ServerManifest>
  </PropertyGroup>
  <!-- Define Generate App Config Target -->
  <Target Name="UpdateManifests">
    <UpdateManifests ClientManifest="$(ClientManifest)" ServerManifest="$(ServerManifest)" Version="$(Version)" />
  </Target>
  <!-- Define Before Build Target -->
  <!-- This will ensure the above target gets executed prior to compilation -->
  <Target Name="BeforeBuild">
    <CallTarget Targets="UpdateManifests;" />
  </Target>
</Project>

Here is what the custom task might look like -

UpdateManifestsTask.cs

public class UpdateManifestsTask : Task
{
    public ITaskItem ClientManifest { get; set; }
    public ITaskItem ServerManifest { get; set; }
    public ITaskItem Version { get; set; }

    public override bool Execute()
    {
        var newVersion = string.Format("name=\"SoftBrands.FourthShift.FSCulture\" version=\"{0}\"", this.Version.ItemSpec);
        var clientFile = File.ReadAllText(this.ClientManifest.ItemSpec);
        clientFile = Regex.Replace(clientFile, "name=\"SoftBrands.FourthShift.FSCulture\" version=\"\\d*\\.\\d*\\.\\d*\\.\\d*\"", newVersion);
        File.WriteAllText(this.ClientManifest.ItemSpec, clientFile);
        var serverFile = File.ReadAllText(this.ClientManifest.ItemSpec);
        serverFile = Regex.Replace(clientFile, "name=\"SoftBrands.FourthShift.FSCulture\" version=\"\\d*\\.\\d*\\.\\d*\\.\\d*\"", newVersion);
        File.WriteAllText(this.ServerManifest.ItemSpec, serverFile);

        return true;
    }
}

The RegEx is a little sloppy as this was a quick and dirty example but this is effectively how you would go about doing this kind of thing.

Be sure to add references to the MSBuild libraries.

http://blogs.msdn.com/b/msbuild/archive/2006/01/21/515834.aspx

If you don't want to build a custom task you can write a small console app that does the same thing but in my opinion a custom task is cleaner.

Should you decide to go the console app route, you can leverage the BeforeBuild and AfterBuild events in your project file.

  <Target Name="BeforeBuild">
      <!-- Invoke console app -->
  </Target>
  <Target Name="AfterBuild">
  </Target>
BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
Randy
  • 2,270
  • 1
  • 15
  • 24
  • Do you have a thought about what it is that the custom task would do? I can think of a few possibilities: set a $(ProductVersion) macro to match that from a shared RC file (then use that macro in the Manifest File section of the projects Linker settings); search the project for all .manifest files and replace version attributes contained in assemblyIdentity elements; Run mt.exe to replace version information within manifests after the build... – BlueMonkMN Dec 07 '15 at 18:35
  • See updated answer which provides more details as to how you might go about this. – Randy Dec 07 '15 at 19:02
  • I'm trying to imagine how this would look when it's in use, and I'm suspecting that it would simple expose some additional project properties. As such, I'm not sure how this simplifies the process of synchronizing the version numbers across all projects in a build. Is there some mechanism, by which a whole tree of projects spanning many solutions can share the same property values for certain properties? – BlueMonkMN Dec 07 '15 at 21:31
  • Absolutely. MSBuild allows you do this. Just put your properties in a *.properties file and include it in any other MSBuild file that would need those properties. – Randy Dec 07 '15 at 21:38
  • A properties file is nothing but an MSBuild file containing properties. This would work for C# projects as well. – Randy Dec 11 '15 at 16:43
  • So the extension "properties" doesn't really mean anything except to indicate to users that the file probably contains a PropertyGroup? – BlueMonkMN Dec 11 '15 at 16:57
  • That's correct – same would be true for a targets file. It's kind of a self-documenting naming convention. – Randy Dec 11 '15 at 17:01
  • I managed to get some debug output from the task with `this.Log.LogError`. Is there a better way? `this.Log.LogMessage` doesn't seem to output anything. It seems the C++ project is not even running the task, and I don't know why. I can only get the task to run by including `` in my C# project, and the same doesn't work for the C++ project. – BlueMonkMN Dec 11 '15 at 20:06
  • You can run MSBuild on the VC++ project from a Visual Studio command prompt. Then simply attach the Visual Studio debugger to the MSBuild process and you should be able to tell if your task is being executed. Also, make sure that you have imported your task in your VC++ project file. – Randy Dec 11 '15 at 20:07
  • The detailed MSBuild output in both the C++ and C# projects reports that the BeforeBuild target is being overridden from my .targets file, but there are no more references to it in the C++ project whereas the next reference in the C# build reports `Target "BeforeBuild" in file "F:\FSBuildTasks\ManifestVersion.targets" from project "F:\Src\FSCulture\FSCulture\FSCulture.csproj" (target "Build" depends on it):` Checking into https://social.msdn.microsoft.com/Forums/vstudio/en-US/21720645-fed8-4a5d-84ea-1cbcf2796920/beforecompile-beforebuild-fail-to-execute-for-vcxproj-c-projects?forum=msbuild – BlueMonkMN Dec 11 '15 at 20:23
  • Yes, seems to be an issue for VC++ projects. The solution proposed in the article you mentioned seems like it should be a viable workaround. – Randy Dec 11 '15 at 20:32