CPackWiX: allow customization of generated WiX sources

Added a new variable CPACK_WIX_PATCH_FILE that users can point at an
XML patch file. Fragments defined within the patch file will be inserted
at supported insertion points (currently Component, File and Directory).
This commit is contained in:
Nils Gladitz 2013-12-16 22:30:11 +01:00
parent b4fdbba55a
commit 8632233a2f
8 changed files with 366 additions and 0 deletions

View File

@ -116,6 +116,57 @@
# If this variable is not set, the default MSI template included with CMake # If this variable is not set, the default MSI template included with CMake
# will be used. # will be used.
# #
# .. variable:: CPACK_WIX_PATCH_FILE
#
# Optional XML file with fragments to be inserted into generated WiX sources
#
# This optional variable can be used to specify an XML file that the
# WiX generator will use to inject fragments into its generated
# source files.
#
# Patch files understood by the CPack WiX generator
# roughly follow this RELAX NG compact schema:
#
# .. code-block:: none
#
# start = CPackWiXPatch
#
# CPackWiXPatch = element CPackWiXPatch { CPackWiXFragment* }
#
# CPackWiXFragment = element CPackWiXFragment
# {
# attribute Id { string },
# fragmentContent*
# }
#
# fragmentContent = element * - CPackWiXFragment
# {
# (attribute * { text } | text | fragmentContent)*
# }
#
# Currently fragments can be injected into most
# Component, File and Directory elements.
#
# The following example illustrates how this works.
#
# Given that the WiX generator creates the following XML element:
#
# .. code-block:: xml
#
# <Component Id="CM_CP_applications.bin.my_libapp.exe" Guid="*"/>
#
# The following XML patch file may be used to inject an Environment element
# into it:
#
# .. code-block:: xml
#
# <CPackWiXPatch>
# <CPackWiXFragment Id="CM_CP_applications.bin.my_libapp.exe">
# <Environment Id="MyEnvironment" Action="set"
# Name="MyVariableName" Value="MyVariableValue"/>
# </CPackWiXFragment>
# </CPackWiXPatch>
#
# .. variable:: CPACK_WIX_EXTRA_SOURCES # .. variable:: CPACK_WIX_EXTRA_SOURCES
# #
# Extra WiX source files # Extra WiX source files

View File

@ -517,6 +517,7 @@ if(WIN32)
CPack/WiX/cmCPackWIXGenerator.cxx CPack/WiX/cmCPackWIXGenerator.cxx
CPack/WiX/cmWIXSourceWriter.cxx CPack/WiX/cmWIXSourceWriter.cxx
CPack/WiX/cmWIXRichTextFormatWriter.cxx CPack/WiX/cmWIXRichTextFormatWriter.cxx
CPack/WiX/cmWIXPatchParser.cxx
) )
endif() endif()

View File

@ -218,6 +218,12 @@ bool cmCPackWIXGenerator::InitializeWiXConfiguration()
CollectExtensions("CPACK_WIX_EXTENSIONS", lightExtensions); CollectExtensions("CPACK_WIX_EXTENSIONS", lightExtensions);
CollectExtensions("CPACK_WIX_LIGHT_EXTENSIONS", lightExtensions); CollectExtensions("CPACK_WIX_LIGHT_EXTENSIONS", lightExtensions);
const char* patchFilePath = GetOption("CPACK_WIX_PATCH_FILE");
if(patchFilePath)
{
LoadPatchFragments(patchFilePath);
}
return true; return true;
} }
@ -529,6 +535,28 @@ bool cmCPackWIXGenerator::CreateWiXSourceFiles()
wixSources.push_back(mainSourceFilePath); wixSources.push_back(mainSourceFilePath);
std::string fragmentList;
for(cmWIXPatchParser::fragment_map_t::const_iterator
i = fragments.begin(); i != fragments.end(); ++i)
{
if(!fragmentList.empty())
{
fragmentList += ", ";
}
fragmentList += "'";
fragmentList += i->first;
fragmentList += "'";
}
if(fragmentList.size())
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Some XML patch fragments did not have matching IDs: " <<
fragmentList << std::endl);
return false;
}
return true; return true;
} }
@ -872,6 +900,7 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
packageExecutables, packageExecutables,
shortcutMap); shortcutMap);
ApplyPatchFragment(subDirectoryId, directoryDefinitions);
directoryDefinitions.EndElement("Directory"); directoryDefinitions.EndElement("Directory");
} }
else else
@ -891,7 +920,10 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
fileDefinitions.AddAttribute("Source", fullPath); fileDefinitions.AddAttribute("Source", fullPath);
fileDefinitions.AddAttribute("KeyPath", "yes"); fileDefinitions.AddAttribute("KeyPath", "yes");
ApplyPatchFragment(fileId, fileDefinitions);
fileDefinitions.EndElement("File"); fileDefinitions.EndElement("File");
ApplyPatchFragment(componentId, fileDefinitions);
fileDefinitions.EndElement("Component"); fileDefinitions.EndElement("Component");
fileDefinitions.EndElement("DirectoryRef"); fileDefinitions.EndElement("DirectoryRef");
@ -1146,3 +1178,45 @@ void cmCPackWIXGenerator::CreateStartMenuFolder(
directoryDefinitions.EndElement("Directory"); directoryDefinitions.EndElement("Directory");
} }
void cmCPackWIXGenerator::LoadPatchFragments(const std::string& patchFilePath)
{
cmWIXPatchParser parser(fragments, Logger);
parser.ParseFile(patchFilePath.c_str());
}
void cmCPackWIXGenerator::ApplyPatchFragment(
const std::string& id, cmWIXSourceWriter& writer)
{
cmWIXPatchParser::fragment_map_t::iterator i = fragments.find(id);
if(i == fragments.end()) return;
const cmWIXPatchElement& fragment = i->second;
for(cmWIXPatchElement::child_list_t::const_iterator
j = fragment.children.begin(); j != fragment.children.end(); ++j)
{
ApplyPatchElement(**j, writer);
}
fragments.erase(i);
}
void cmCPackWIXGenerator::ApplyPatchElement(
const cmWIXPatchElement& element, cmWIXSourceWriter& writer)
{
writer.BeginElement(element.name);
for(cmWIXPatchElement::attributes_t::const_iterator
i = element.attributes.begin(); i != element.attributes.end(); ++i)
{
writer.AddAttribute(i->first, i->second);
}
for(cmWIXPatchElement::child_list_t::const_iterator
i = element.children.begin(); i != element.children.end(); ++i)
{
ApplyPatchElement(**i, writer);
}
writer.EndElement(element.name);
}

View File

@ -13,6 +13,8 @@
#ifndef cmCPackWIXGenerator_h #ifndef cmCPackWIXGenerator_h
#define cmCPackWIXGenerator_h #define cmCPackWIXGenerator_h
#include "cmWIXPatchParser.h"
#include <CPack/cmCPackGenerator.h> #include <CPack/cmCPackGenerator.h>
#include <string> #include <string>
@ -160,12 +162,21 @@ private:
void CreateStartMenuFolder(cmWIXSourceWriter& directoryDefinitions); void CreateStartMenuFolder(cmWIXSourceWriter& directoryDefinitions);
void LoadPatchFragments(const std::string& patchFilePath);
void ApplyPatchFragment(const std::string& id, cmWIXSourceWriter& writer);
void ApplyPatchElement(const cmWIXPatchElement& element,
cmWIXSourceWriter& writer);
std::vector<std::string> wixSources; std::vector<std::string> wixSources;
id_map_t pathToIdMap; id_map_t pathToIdMap;
ambiguity_map_t idAmbiguityCounter; ambiguity_map_t idAmbiguityCounter;
extension_set_t candleExtensions; extension_set_t candleExtensions;
extension_set_t lightExtensions; extension_set_t lightExtensions;
cmWIXPatchParser::fragment_map_t fragments;
}; };
#endif #endif

View File

@ -0,0 +1,145 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2013 Kitware, Inc.
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmWIXPatchParser.h"
#include <CPack/cmCPackGenerator.h>
#include <cm_expat.h>
cmWIXPatchElement::~cmWIXPatchElement()
{
for(child_list_t::iterator i = children.begin(); i != children.end(); ++i)
{
delete *i;
}
}
cmWIXPatchParser::cmWIXPatchParser(
fragment_map_t& fragments, cmCPackLog* logger):
Logger(logger),
state(BEGIN_DOCUMENT),
valid(true),
fragments(fragments)
{
}
void cmWIXPatchParser::StartElement(const char *name, const char **atts)
{
std::string name_str = name;
if(state == BEGIN_DOCUMENT)
{
if(name_str == "CPackWiXPatch")
{
state = BEGIN_FRAGMENTS;
}
else
{
ReportValidationError("Expected root element 'CPackWiXPatch'");
}
}
else if(state == BEGIN_FRAGMENTS)
{
if(name_str == "CPackWiXFragment")
{
state = INSIDE_FRAGMENT;
StartFragment(atts);
}
else
{
ReportValidationError("Expected 'CPackWixFragment' element");
}
}
else if(state == INSIDE_FRAGMENT)
{
cmWIXPatchElement &parent = *elementStack.back();
parent.children.resize(parent.children.size() + 1);
cmWIXPatchElement*& currentElement = parent.children.back();
currentElement = new cmWIXPatchElement;
currentElement->name = name;
for(size_t i = 0; atts[i]; i += 2)
{
std::string key = atts[i];
std::string value = atts[i+1];
currentElement->attributes[key] = value;
}
elementStack.push_back(currentElement);
}
}
void cmWIXPatchParser::StartFragment(const char **attributes)
{
for(size_t i = 0; attributes[i]; i += 2)
{
std::string key = attributes[i];
std::string value = attributes[i+1];
if(key == "Id")
{
if(fragments.find(value) != fragments.end())
{
std::stringstream tmp;
tmp << "Invalid reuse of 'CPackWixFragment' 'Id': " << value;
ReportValidationError(tmp.str());
}
elementStack.push_back(&fragments[value]);
}
else
{
ReportValidationError(
"The only allowed 'CPackWixFragment' attribute is 'Id'");
}
}
}
void cmWIXPatchParser::EndElement(const char *name)
{
std::string name_str = name;
if(state == INSIDE_FRAGMENT)
{
if(name_str == "CPackWiXFragment")
{
state = BEGIN_FRAGMENTS;
elementStack.clear();
}
else
{
elementStack.pop_back();
}
}
}
void cmWIXPatchParser::ReportError(int line, int column, const char* msg)
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error while processing XML patch file at " << line << ":" << column <<
": "<< msg << std::endl);
valid = false;
}
void cmWIXPatchParser::ReportValidationError(const std::string& message)
{
ReportError(XML_GetCurrentLineNumber(Parser),
XML_GetCurrentColumnNumber(Parser),
message.c_str());
}
bool cmWIXPatchParser::IsValid() const
{
return valid;
}

View File

@ -0,0 +1,75 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2013 Kitware, Inc.
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmCPackWIXPatchParser_h
#define cmCPackWIXPatchParser_h
#include <cmXMLParser.h>
#include <CPack/cmCPackLog.h>
#include <map>
#include <list>
struct cmWIXPatchElement
{
~cmWIXPatchElement();
typedef std::list<cmWIXPatchElement*> child_list_t;
typedef std::map<std::string, std::string> attributes_t;
std::string name;
child_list_t children;
attributes_t attributes;
};
/** \class cmWIXPatchParser
* \brief Helper class that parses XML patch files (CPACK_WIX_PATCH_FILE)
*/
class cmWIXPatchParser : public cmXMLParser
{
public:
typedef std::map<std::string, cmWIXPatchElement> fragment_map_t;
cmWIXPatchParser(fragment_map_t& fragments, cmCPackLog* logger);
private:
virtual void StartElement(const char *name, const char **atts);
void StartFragment(const char **attributes);
virtual void EndElement(const char *name);
virtual void ReportError(int line, int column, const char* msg);
void ReportValidationError(const std::string& message);
bool IsValid() const;
cmCPackLog* Logger;
enum ParserState
{
BEGIN_DOCUMENT,
BEGIN_FRAGMENTS,
INSIDE_FRAGMENT
};
ParserState state;
bool valid;
fragment_map_t& fragments;
std::list<cmWIXPatchElement*> elementStack;
};
#endif

View File

@ -49,6 +49,8 @@ set(CPACK_PACKAGE_EXECUTABLES
"my-other-app" "Second CPack WiX Test" "my-other-app" "Second CPack WiX Test"
) )
set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_SOURCE_DIR}/patch.xml")
include(CPack) include(CPack)
cpack_add_install_type(Full DISPLAY_NAME "Everything") cpack_add_install_type(Full DISPLAY_NAME "Everything")

View File

@ -0,0 +1,7 @@
<CPackWiXPatch>
<CPackWiXFragment Id="CM_CP_applications.bin.my_libapp.exe">
<Environment Id="MyEnvironment" Action="set"
Name="CPackWiXGeneratorTest"
Value="CPackWiXGeneratorTest"/>
</CPackWiXFragment>
</CPackWiXPatch>