Merge topic 'wix-fragment-injection'

8632233 CPackWiX: allow customization of generated WiX sources
This commit is contained in:
Brad King 2013-12-19 10:13:47 -05:00 committed by CMake Topic Stage
commit eb20fab736
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
# 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
#
# Extra WiX source files

View File

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

View File

@ -218,6 +218,12 @@ bool cmCPackWIXGenerator::InitializeWiXConfiguration()
CollectExtensions("CPACK_WIX_EXTENSIONS", lightExtensions);
CollectExtensions("CPACK_WIX_LIGHT_EXTENSIONS", lightExtensions);
const char* patchFilePath = GetOption("CPACK_WIX_PATCH_FILE");
if(patchFilePath)
{
LoadPatchFragments(patchFilePath);
}
return true;
}
@ -529,6 +535,28 @@ bool cmCPackWIXGenerator::CreateWiXSourceFiles()
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;
}
@ -872,6 +900,7 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
packageExecutables,
shortcutMap);
ApplyPatchFragment(subDirectoryId, directoryDefinitions);
directoryDefinitions.EndElement("Directory");
}
else
@ -891,7 +920,10 @@ void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons(
fileDefinitions.AddAttribute("Source", fullPath);
fileDefinitions.AddAttribute("KeyPath", "yes");
ApplyPatchFragment(fileId, fileDefinitions);
fileDefinitions.EndElement("File");
ApplyPatchFragment(componentId, fileDefinitions);
fileDefinitions.EndElement("Component");
fileDefinitions.EndElement("DirectoryRef");
@ -1146,3 +1178,45 @@ void cmCPackWIXGenerator::CreateStartMenuFolder(
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
#define cmCPackWIXGenerator_h
#include "cmWIXPatchParser.h"
#include <CPack/cmCPackGenerator.h>
#include <string>
@ -160,12 +162,21 @@ private:
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;
id_map_t pathToIdMap;
ambiguity_map_t idAmbiguityCounter;
extension_set_t candleExtensions;
extension_set_t lightExtensions;
cmWIXPatchParser::fragment_map_t fragments;
};
#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"
)
set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_SOURCE_DIR}/patch.xml")
include(CPack)
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>