From a8757c51f52eb48211fbef0258d7cca771cf3f9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20St=C3=BCrmer?= <michael.stuermer@schaeffler.com>
Date: Tue, 19 Jul 2016 17:18:17 +0200
Subject: [PATCH] improved WIX support

---
 Help/manual/cmake-properties.7.rst                |   2 +
 Help/prop_inst/CPACK_WIX_KEYPATH.rst              |  10 ++
 Help/prop_inst/CPACK_WIX_PROPERTY_tagname.rst     |  48 +++++++++
 Modules/CPackWIX.cmake                            |  44 ++++++++
 Source/CPack/WiX/cmCPackWIXGenerator.cxx          |  26 ++++-
 Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx |  10 +-
 Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx    |   4 +
 Source/CPack/WiX/cmWIXFilesSourceWriter.cxx       | 126 +++++++++++++++++++---
 Source/CPack/WiX/cmWIXFilesSourceWriter.h         |  18 ++++
 Source/cmake.cxx                                  |  13 +++
 10 files changed, 280 insertions(+), 21 deletions(-)
 create mode 100644 Help/prop_inst/CPACK_WIX_KEYPATH.rst
 create mode 100644 Help/prop_inst/CPACK_WIX_PROPERTY_tagname.rst

diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 0f1bfad..5618023 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -380,6 +380,8 @@ Properties on Installed Files
    /prop_inst/CPACK_START_MENU_SHORTCUTS.rst
    /prop_inst/CPACK_STARTUP_SHORTCUTS.rst
    /prop_inst/CPACK_WIX_ACL.rst
+   /prop_inst/CPACK_WIX_KEYPATH.rst
+   /prop_inst/CPACK_WIX_PROPERTY_tagname.rst
 
 
 Deprecated Properties on Directories
diff --git a/Help/prop_inst/CPACK_WIX_KEYPATH.rst b/Help/prop_inst/CPACK_WIX_KEYPATH.rst
new file mode 100644
index 0000000..0f2c3fb
--- /dev/null
+++ b/Help/prop_inst/CPACK_WIX_KEYPATH.rst
@@ -0,0 +1,10 @@
+CPACK_WIX_KEYPATH
+-----------------
+
+Allows setting the ``KeyPath`` attribute of the ``<File>`` element in files.wxs.
+
+If CPACK_WIX_BUNDLE_COMPONENTS is enabled and additional operations such as 
+``<ServiceInstall>`` or ``<ServiceControl>`` are executed in the component, the
+``KeyPath`` is needed to identify the executable of the service to control.
+
+See :doc:`/prop_inst/CPACK_WIX_PROPERTY_tagname` for more details.
\ No newline at end of file
diff --git a/Help/prop_inst/CPACK_WIX_PROPERTY_tagname.rst b/Help/prop_inst/CPACK_WIX_PROPERTY_tagname.rst
new file mode 100644
index 0000000..fa14bcb
--- /dev/null
+++ b/Help/prop_inst/CPACK_WIX_PROPERTY_tagname.rst
@@ -0,0 +1,48 @@
+CPACK_WIX_PROPERTY_<tagname>
+----------------------------
+
+Allows to add custom child elements to the component of the file.
+
+The property name is defined as ``CPACK_WIX_PROPERTY_[##_]<tagname>``,
+``##`` is two-digit decimal (00-99) which can optionally be used to 
+specify the order of execution in the installer.
+
+See http://wixtoolset.org/documentation/manual/v3/xsd/wix/component.html
+for a list of valid children of the Component element.
+
+The property value is a list in the form of ``<attribute_name>=<value>``.
+
+Example, adding installation and removal of a windows service:
+
+  .. code-block:: cmake
+
+    set_property(INSTALL service.exe
+        PROPERTY CPACK_WIX_KEYPATH TRUE
+        )
+    set_property(INSTALL service.exe
+        PROPERTY CPACK_WIX_PROPERTY_00_ServiceInstall
+        "Id=InstallService"
+        "DisplayName=my service"
+        "Name=myservice"
+        "Start=auto"
+        "Type=ownProcess"
+        "ErrorControl=critical"
+        "Vital=yes"
+        )
+    set_property(INSTALL service.exe
+        PROPERTY CPACK_WIX_PROPERTY_01_ServiceControl
+        "Id=StartService"
+        "Name=myservice"
+        "Start=install"
+        "Stop=both"
+        "Remove=both"
+        "Wait=yes"
+        )
+
+This will generate XML entries in files.wxs:
+
+  .. code-block:: xml
+
+    <File Id="XXXXXXX" KeyPath="yes" Source="XXXXXXX/service.exe"/>
+    <ServiceInstall ErrorControl="critical" Id="InstallService" Name="myservice" Start="auto" Type="ownProcess" Vital="yes"/>
+    <ServiceControl Id="StartService" Name="myservice" Remove="both" Start="install" Stop="both" Wait="yes"/>
diff --git a/Modules/CPackWIX.cmake b/Modules/CPackWIX.cmake
index 3c90561..b868bcd 100644
--- a/Modules/CPackWIX.cmake
+++ b/Modules/CPackWIX.cmake
@@ -237,6 +237,50 @@
 #  * ARPURLUPDATEINFO - Update information URL
 #  * ARPHELPTELEPHONE - Help and support telephone number
 #  * ARPSIZE - Size (in kilobytes) of the application
+#
+# .. variable:: CPACK_WIX_ROOT_COMPONENT_DISPLAY_NAME
+#
+# Sets the name of the root install feature in the WIX installer. Same as
+# CPACK_COMPONENT_<compName>_DISPLAY_NAME for components.
+#
+# .. variable:: CPACK_WIX_ROOT_COMPONENT_DESCRIPTION
+#
+# Sets the description of the root install feature in the WIX installer. Same as
+# CPACK_COMPONENT_<compName>_DESCRIPTION for components.
+#
+# .. variable:: CPACK_WIX_BUNDLE_COMPONENTS
+#
+# By default, for each installed file a separate ``<Component>`` is generated in files.wxs.
+# The official microsoft documentation suggests this: 
+#
+# https://msdn.microsoft.com/en-us/library/aa368269(VS.85).aspx
+#
+# In case additional install/uninstall operations are required (such as creating/deleting
+# and starting/stopping services), several files might be necessary to be bundled in
+# one component to ensure correct execution of the additional commands.
+#
+# See also :doc:`/prop_inst/CPACK_WIX_PROPERTY_tagname`.
+#
+# .. variable:: CPACK_WIX_GENERATE_COMPONENT_GUIDS
+#
+# Generates guids for the ``<Component>`` tags in the generated files.wxs file. Normally
+# the ``Guid`` attribute is set to ``"*"``, but this only works if the install location
+# is ProgramFiles or ProgramFiles64. In order to be able to preset the install folder to
+# an arbitrary location, the guids of the components must be set explicitly.
+#
+# The guids are computed from the MD5 hash of the component Id in the .wxs file.
+#
+# The value is undefined/false by default and is required to be true if the default
+# install directory is no supposed to be within the common program install folders.
+#
+# .. variable:: CPACK_WIX_SKIP_PROGRAM_FOLDER
+#
+# If this variable is set to true, the default install location of the generated package
+# will be CPACK_PACKAGE_INSTALL_DIRECTORY directly. The install location will not be 
+# located relatively below ProgramFiles or ProgramFiles64.
+#
+# This feature requires CPACK_WIX_GENERATE_COMPONENT_GUIDS to be true.
+#
 
 #=============================================================================
 # Copyright 2014-2015 Kitware, Inc.
diff --git a/Source/CPack/WiX/cmCPackWIXGenerator.cxx b/Source/CPack/WiX/cmCPackWIXGenerator.cxx
index 97216c3..d1b7fed 100644
--- a/Source/CPack/WiX/cmCPackWIXGenerator.cxx
+++ b/Source/CPack/WiX/cmCPackWIXGenerator.cxx
@@ -18,6 +18,7 @@
 #include <cmGeneratedFileStream.h>
 #include <cmInstalledFile.h>
 #include <cmSystemTools.h>
+#include <cmUuid.h>
 
 #include "cmWIXDirectoriesSourceWriter.h"
 #include "cmWIXFeaturesSourceWriter.h"
@@ -441,6 +442,11 @@ bool cmCPackWIXGenerator::CreateWiXSourceFiles()
   cmWIXFilesSourceWriter fileDefinitions(this->Logger,
                                          fileDefinitionsFilename);
 
+  fileDefinitions.GenerateComponentGuids =
+    cmSystemTools::IsOn(GetOption("CPACK_WIX_GENERATE_COMPONENT_GUIDS"));
+  fileDefinitions.BundleComponents =
+    cmSystemTools::IsOn(GetOption("CPACK_WIX_BUNDLE_COMPONENTS"));
+
   fileDefinitions.BeginElement("Fragment");
 
   std::string featureDefinitionsFilename =
@@ -464,7 +470,14 @@ bool cmCPackWIXGenerator::CreateWiXSourceFiles()
     return false;
   }
 
-  featureDefinitions.AddAttribute("Title", cpackPackageName);
+  std::string featureTitle = cpackPackageName;
+  if (const char* title = GetOption("CPACK_WIX_ROOT_COMPONENT_DISPLAY_NAME")) {
+    featureTitle = title;
+  }
+  featureDefinitions.AddAttribute("Title", featureTitle);
+  if (const char* desc = GetOption("CPACK_WIX_ROOT_COMPONENT_DESCRIPTION")) {
+    featureDefinitions.AddAttribute("Description", desc);
+  }
   featureDefinitions.AddAttribute("Level", "1");
   this->Patch->ApplyFragment("#PRODUCTFEATURE", featureDefinitions);
 
@@ -559,6 +572,9 @@ bool cmCPackWIXGenerator::CreateWiXSourceFiles()
 
 std::string cmCPackWIXGenerator::GetProgramFilesFolderId() const
 {
+  if (cmSystemTools::IsOn(GetOption("CPACK_WIX_SKIP_PROGRAM_FOLDER"))) {
+    return "";
+  }
   if (GetArchitecture() == "x86") {
     return "ProgramFilesFolder";
   } else {
@@ -859,6 +875,8 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
     return;
   }
 
+  std::string lastComponentId;
+
   for (size_t i = 0; i < dir.GetNumberOfFiles(); ++i) {
     std::string fileName = dir.GetFile(static_cast<unsigned long>(i));
 
@@ -897,7 +915,10 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
       std::string componentId = fileDefinitions.EmitComponentFile(
         directoryId, id, fullPath, *(this->Patch), installedFile);
 
-      featureDefinitions.EmitComponentRef(componentId);
+      if (componentId != lastComponentId && !componentId.empty()) {
+        featureDefinitions.EmitComponentRef(componentId);
+      }
+      lastComponentId = componentId;
 
       for (size_t j = 0; j < packageExecutables.size(); ++j) {
         std::string const& executableName = packageExecutables[j++];
@@ -919,6 +940,7 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
       }
     }
   }
+  fileDefinitions.EmitEndComponent(lastComponentId);
 }
 
 bool cmCPackWIXGenerator::RequireOption(std::string const& name,
diff --git a/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx
index de64059..7030bee 100644
--- a/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx
+++ b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx
@@ -52,8 +52,12 @@ size_t cmWIXDirectoriesSourceWriter::BeginInstallationPrefixDirectory(
   std::string const& programFilesFolderId,
   std::string const& installRootString)
 {
-  BeginElement("Directory");
-  AddAttribute("Id", programFilesFolderId);
+  size_t offset = 1;
+  if(!programFilesFolderId.empty()) {
+    BeginElement("Directory");
+    AddAttribute("Id", programFilesFolderId);
+	offset = 0;
+  }
 
   std::vector<std::string> installRoot;
 
@@ -77,7 +81,7 @@ size_t cmWIXDirectoriesSourceWriter::BeginInstallationPrefixDirectory(
     AddAttribute("Name", installRoot[i]);
   }
 
-  return installRoot.size();
+  return installRoot.size() - offset;
 }
 
 void cmWIXDirectoriesSourceWriter::EndInstallationPrefixDirectory(size_t size)
diff --git a/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx
index 16dd0ab..1747b62 100644
--- a/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx
+++ b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx
@@ -86,6 +86,10 @@ void cmWIXFeaturesSourceWriter::EmitFeatureForComponent(
     AddAttribute("Display", "hidden");
   }
 
+  if (component.IsDisabledByDefault) {
+    AddAttribute("Level", "2");
+  }
+
   EndElement("Feature");
 }
 
diff --git a/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx b/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx
index 9a143cc..c6480c9 100644
--- a/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx
+++ b/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx
@@ -16,6 +16,9 @@
 
 #include <cmInstalledFile.h>
 
+#include <cmSystemTools.h>
+#include <cmUuid.h>
+
 #include <sys/types.h>
 // include sys/stat.h after sys/types.h
 #include <sys/stat.h>
@@ -23,6 +26,8 @@
 cmWIXFilesSourceWriter::cmWIXFilesSourceWriter(cmCPackLog* logger,
                                                std::string const& filename)
   : cmWIXSourceWriter(logger, filename)
+  , GenerateComponentGuids(false)
+  , BundleComponents(false)
 {
 }
 
@@ -118,40 +123,79 @@ std::string cmWIXFilesSourceWriter::EmitComponentCreateFolder(
   return componentId;
 }
 
+void cmWIXFilesSourceWriter::EmitEndComponent(std::string const& componentId)
+{
+  if (!this->BundleComponents || componentId.empty()) {
+    return;
+  }
+  if (this->Components.find(componentId) == this->Components.end()) {
+    return;
+  }
+  WriteNode(this->Components[componentId]);
+  this->Components.erase(componentId);
+}
+
 std::string cmWIXFilesSourceWriter::EmitComponentFile(
   std::string const& directoryId, std::string const& id,
   std::string const& filePath, cmWIXPatch& patch,
   cmInstalledFile const* installedFile)
 {
-  std::string componentId = std::string("CM_C") + id;
+  std::string componentId;
+  if (this->BundleComponents) {
+    componentId = "CM_C" + directoryId;
+  } else {
+    componentId = std::string("CM_C") + id;
+  }
   std::string fileId = std::string("CM_F") + id;
 
-  BeginElement("DirectoryRef");
-  AddAttribute("Id", directoryId);
+  std::string guid = "*";
+  if (this->GenerateComponentGuids) {
+    std::string md5 = cmSystemTools::ComputeStringMD5(componentId);
+    cmUuid uuid;
+    std::vector<unsigned char> ns;
+    guid = uuid.FromMd5(ns, md5);
+  }
 
-  BeginElement("Component");
-  AddAttribute("Id", componentId);
-  AddAttribute("Guid", "*");
+  Node component;
+  if (this->BundleComponents) {
+    component = this->Components[componentId];
+    component.Attributes["Directory"] = directoryId;
+  }
+  component.Name = "Component";
+  component.Attributes["Id"] = componentId;
+  component.Attributes["Guid"] = guid;
+
+  if (!this->BundleComponents) {
+    BeginElement("DirectoryRef");
+    AddAttribute("Id", directoryId);
+  }
 
   if (installedFile) {
     if (installedFile->GetPropertyAsBool("CPACK_NEVER_OVERWRITE")) {
-      AddAttribute("NeverOverwrite", "yes");
+      component.Attributes["NeverOverwrite"] = "yes";
     }
     if (installedFile->GetPropertyAsBool("CPACK_PERMANENT")) {
-      AddAttribute("Permanent", "yes");
+      component.Attributes["Permanent"] = "yes";
     }
   }
 
-  BeginElement("File");
-  AddAttribute("Id", fileId);
-  AddAttribute("Source", filePath);
-  AddAttribute("KeyPath", "yes");
+  Node file;
+  file.Name = "File";
+  file.Attributes["Id"] = fileId;
+  file.Attributes["Source"] = filePath;
+
+  if (installedFile) {
+    if (!this->BundleComponents ||
+        installedFile->GetPropertyAsBool("CPACK_WIX_KEYPATH")) {
+      file.Attributes["KeyPath"] = "yes";
+    }
+  }
 
   mode_t fileMode = 0;
   cmSystemTools::GetPermissions(filePath.c_str(), fileMode);
 
   if (!(fileMode & S_IWRITE)) {
-    AddAttribute("ReadOnly", "yes");
+    file.Attributes["ReadOnly"] = "yes";
   }
 
   if (installedFile) {
@@ -160,11 +204,61 @@ std::string cmWIXFilesSourceWriter::EmitComponentFile(
   }
 
   patch.ApplyFragment(fileId, *this);
-  EndElement("File");
+
+  component.Files.push_back(file);
+
+  if (installedFile) {
+    const cmInstalledFile::PropertyMapType& props =
+      installedFile->GetProperties();
+    const std::string propPrefix = "CPACK_WIX_PROPERTY_";
+    for (cmInstalledFile::PropertyMapType::const_iterator prop = props.begin();
+         prop != props.end(); ++prop) {
+      if (prop->first.substr(0, propPrefix.size()) == propPrefix) {
+        std::string tagName = prop->first.substr(propPrefix.size());
+        size_t pos = tagName.find('_');
+        if (pos != std::string::npos) {
+          tagName = tagName.substr(pos + 1);
+        }
+        Node n;
+        n.Name = tagName;
+        std::vector<std::string> p;
+        installedFile->GetPropertyAsList(prop->first, p);
+        for (std::vector<std::string>::const_iterator i = p.begin();
+             i != p.end(); ++i) {
+          std::vector<cmsys::String> v = cmSystemTools::SplitString(*i, '=');
+          n.Attributes[v[0]] = v[1];
+        }
+        component.Children.push_back(n);
+      }
+    }
+  }
 
   patch.ApplyFragment(componentId, *this);
-  EndElement("Component");
-  EndElement("DirectoryRef");
+  if (!this->BundleComponents) {
+    WriteNode(component);
+    EndElement("DirectoryRef");
+  } else {
+    this->Components[componentId] = component;
+  }
 
   return componentId;
 }
+
+void cmWIXFilesSourceWriter::WriteNode(Node const& n)
+{
+  BeginElement(n.Name);
+  for (std::map<std::string, std::string>::const_iterator i =
+         n.Attributes.begin();
+       i != n.Attributes.end(); ++i) {
+    AddAttribute(i->first, i->second);
+  }
+  for (std::list<Node>::const_iterator i = n.Files.begin(); i != n.Files.end();
+       ++i) {
+    WriteNode(*i);
+  }
+  for (std::list<Node>::const_iterator i = n.Children.begin();
+       i != n.Children.end(); ++i) {
+    WriteNode(*i);
+  }
+  EndElement(n.Name);
+}
diff --git a/Source/CPack/WiX/cmWIXFilesSourceWriter.h b/Source/CPack/WiX/cmWIXFilesSourceWriter.h
index c577e5b..a7f929d 100644
--- a/Source/CPack/WiX/cmWIXFilesSourceWriter.h
+++ b/Source/CPack/WiX/cmWIXFilesSourceWriter.h
@@ -43,10 +43,28 @@ public:
                                         std::string const& guid,
                                         cmInstalledFile const* installedFile);
 
+  void EmitEndComponent(std::string const& componentId);
+
   std::string EmitComponentFile(std::string const& directoryId,
                                 std::string const& id,
                                 std::string const& filePath, cmWIXPatch& patch,
                                 cmInstalledFile const* installedFile);
+
+  bool GenerateComponentGuids;
+  bool BundleComponents;
+
+private:
+
+  struct Node
+  {
+    std::string Name;
+    std::map<std::string, std::string> Attributes;
+    std::list<Node> Children;
+    std::list<Node> Files;
+  };
+  std::map<std::string, Node> Components;
+
+  void WriteNode(Node const& n);
 };
 
 #endif
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index fceec16..bf3ca10 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -1970,6 +1970,19 @@ cmInstalledFile const* cmake::GetInstalledFile(const std::string& name) const
   std::map<std::string, cmInstalledFile>::const_iterator i =
     this->InstalledFiles.find(name);
 
+  if (i == this->InstalledFiles.end()) {
+    std::list<std::string> prefixes;
+    prefixes.push_back("./");
+    prefixes.push_back(".\\");
+    for (std::list<std::string>::iterator p = prefixes.begin();
+         p != prefixes.end(); ++p) {
+      i = this->InstalledFiles.find(*p + name);
+      if (i != this->InstalledFiles.end()) {
+        break;
+      }
+    }
+  }
+
   if (i != this->InstalledFiles.end()) {
     cmInstalledFile const& file = i->second;
     return &file;
-- 
2.8.0.windows.1

