diff options
Diffstat (limited to 'Source/CPack/WiX')
20 files changed, 2943 insertions, 0 deletions
diff --git a/Source/CPack/WiX/cmCPackWIXGenerator.cxx b/Source/CPack/WiX/cmCPackWIXGenerator.cxx new file mode 100644 index 0000000..2bccf2e --- /dev/null +++ b/Source/CPack/WiX/cmCPackWIXGenerator.cxx @@ -0,0 +1,1144 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCPackWIXGenerator.h" + +#include <CPack/cmCPackComponentGroup.h> +#include <CPack/cmCPackLog.h> +#include <algorithm> +#include <cmCryptoHash.h> +#include <cmGeneratedFileStream.h> +#include <cmInstalledFile.h> +#include <cmSystemTools.h> +#include <cmUuid.h> + +#include "cmWIXDirectoriesSourceWriter.h" +#include "cmWIXFeaturesSourceWriter.h" +#include "cmWIXFilesSourceWriter.h" +#include "cmWIXRichTextFormatWriter.h" +#include "cmWIXSourceWriter.h" + +#include <cmsys/Directory.hxx> +#include <cmsys/Encoding.hxx> +#include <cmsys/FStream.hxx> +#include <cmsys/SystemTools.hxx> + +#include <rpc.h> // for GUID generation + +cmCPackWIXGenerator::cmCPackWIXGenerator() + : Patch(0) + , ComponentGuidType(cmWIXSourceWriter::WIX_GENERATED_GUID) +{ +} + +cmCPackWIXGenerator::~cmCPackWIXGenerator() +{ + if (this->Patch) { + delete this->Patch; + } +} + +int cmCPackWIXGenerator::InitializeInternal() +{ + componentPackageMethod = ONE_PACKAGE; + this->Patch = new cmWIXPatch(this->Logger); + + return this->Superclass::InitializeInternal(); +} + +bool cmCPackWIXGenerator::RunWiXCommand(std::string const& command) +{ + std::string logFileName = this->CPackTopLevel + "/wix.log"; + + cmCPackLogger(cmCPackLog::LOG_DEBUG, "Running WiX command: " << command + << std::endl); + + std::string output; + + int returnValue = 0; + bool status = cmSystemTools::RunSingleCommand(command.c_str(), &output, + &output, &returnValue, 0, + cmSystemTools::OUTPUT_NONE); + + cmsys::ofstream logFile(logFileName.c_str(), std::ios::app); + logFile << command << std::endl; + logFile << output; + logFile.close(); + + if (!status || returnValue) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running WiX candle. " + "Please check '" + << logFileName << "' for errors." << std::endl); + + return false; + } + + return true; +} + +bool cmCPackWIXGenerator::RunCandleCommand(std::string const& sourceFile, + std::string const& objectFile) +{ + std::string executable; + if (!RequireOption("CPACK_WIX_CANDLE_EXECUTABLE", executable)) { + return false; + } + + std::ostringstream command; + command << QuotePath(executable); + command << " -nologo"; + command << " -arch " << GetArchitecture(); + command << " -out " << QuotePath(objectFile); + + for (extension_set_t::const_iterator i = CandleExtensions.begin(); + i != CandleExtensions.end(); ++i) { + command << " -ext " << QuotePath(*i); + } + + AddCustomFlags("CPACK_WIX_CANDLE_EXTRA_FLAGS", command); + + command << " " << QuotePath(sourceFile); + + return RunWiXCommand(command.str()); +} + +bool cmCPackWIXGenerator::RunLightCommand(std::string const& objectFiles) +{ + std::string executable; + if (!RequireOption("CPACK_WIX_LIGHT_EXECUTABLE", executable)) { + return false; + } + + std::ostringstream command; + command << QuotePath(executable); + command << " -nologo"; + command << " -out " << QuotePath(packageFileNames.at(0)); + + for (extension_set_t::const_iterator i = this->LightExtensions.begin(); + i != this->LightExtensions.end(); ++i) { + command << " -ext " << QuotePath(*i); + } + + const char* const cultures = GetOption("CPACK_WIX_CULTURES"); + if (cultures) { + command << " -cultures:" << cultures; + } + + AddCustomFlags("CPACK_WIX_LIGHT_EXTRA_FLAGS", command); + + command << " " << objectFiles; + + return RunWiXCommand(command.str()); +} + +int cmCPackWIXGenerator::PackageFiles() +{ + if (!PackageFilesImpl() || cmSystemTools::GetErrorOccuredFlag()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Fatal WiX Generator Error" + << std::endl); + return false; + } + + return true; +} + +bool cmCPackWIXGenerator::InitializeWiXConfiguration() +{ + if (!ReadListFile("CPackWIX.cmake")) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Error while executing CPackWIX.cmake" + << std::endl); + return false; + } + + if (GetOption("CPACK_WIX_PRODUCT_GUID") == 0) { + std::string guid = GenerateGUID(); + SetOption("CPACK_WIX_PRODUCT_GUID", guid.c_str()); + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "CPACK_WIX_PRODUCT_GUID implicitly set to " << guid << " . " + << std::endl); + } + + if (GetOption("CPACK_WIX_UPGRADE_GUID") == 0) { + std::string guid = GenerateGUID(); + SetOption("CPACK_WIX_UPGRADE_GUID", guid.c_str()); + + cmCPackLogger( + cmCPackLog::LOG_WARNING, "CPACK_WIX_UPGRADE_GUID implicitly set to " + << guid << " . " + "Please refer to the documentation on how and why " + "you might want to set this explicitly." + << std::endl); + } + + if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY", this->CPackTopLevel)) { + return false; + } + + if (GetOption("CPACK_WIX_LICENSE_RTF") == 0) { + std::string licenseFilename = this->CPackTopLevel + "/License.rtf"; + SetOption("CPACK_WIX_LICENSE_RTF", licenseFilename.c_str()); + + if (!CreateLicenseFile()) { + return false; + } + } + + if (GetOption("CPACK_PACKAGE_VENDOR") == 0) { + std::string defaultVendor = "Humanity"; + SetOption("CPACK_PACKAGE_VENDOR", defaultVendor.c_str()); + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "CPACK_PACKAGE_VENDOR implicitly set to " + << defaultVendor << " . " << std::endl); + } + + if (GetOption("CPACK_WIX_UI_REF") == 0) { + std::string defaultRef = "WixUI_InstallDir"; + + if (!this->Components.empty()) { + defaultRef = "WixUI_FeatureTree"; + } + + SetOption("CPACK_WIX_UI_REF", defaultRef.c_str()); + } + + const char* packageContact = GetOption("CPACK_PACKAGE_CONTACT"); + if (packageContact != 0 && GetOption("CPACK_WIX_PROPERTY_ARPCONTACT") == 0) { + SetOption("CPACK_WIX_PROPERTY_ARPCONTACT", packageContact); + } + + CollectExtensions("CPACK_WIX_EXTENSIONS", this->CandleExtensions); + CollectExtensions("CPACK_WIX_CANDLE_EXTENSIONS", this->CandleExtensions); + + this->LightExtensions.insert("WixUIExtension"); + CollectExtensions("CPACK_WIX_EXTENSIONS", this->LightExtensions); + CollectExtensions("CPACK_WIX_LIGHT_EXTENSIONS", this->LightExtensions); + + const char* patchFilePath = GetOption("CPACK_WIX_PATCH_FILE"); + if (patchFilePath) { + std::vector<std::string> patchFilePaths; + cmSystemTools::ExpandListArgument(patchFilePath, patchFilePaths); + + for (size_t i = 0; i < patchFilePaths.size(); ++i) { + if (!this->Patch->LoadFragments(patchFilePaths[i])) { + return false; + } + } + } + + // if install folder is supposed to be set absolutely, the default + // component guid "*" cannot be used + if (cmSystemTools::IsOn(GetOption("CPACK_WIX_SKIP_PROGRAM_FOLDER"))) { + this->ComponentGuidType = cmWIXSourceWriter::CMAKE_GENERATED_GUID; + } + + return true; +} + +bool cmCPackWIXGenerator::PackageFilesImpl() +{ + if (!InitializeWiXConfiguration()) { + return false; + } + + CreateWiXVariablesIncludeFile(); + CreateWiXPropertiesIncludeFile(); + CreateWiXProductFragmentIncludeFile(); + + if (!CreateWiXSourceFiles()) { + return false; + } + + AppendUserSuppliedExtraSources(); + + std::set<std::string> usedBaseNames; + + std::ostringstream objectFiles; + for (size_t i = 0; i < this->WixSources.size(); ++i) { + std::string const& sourceFilename = this->WixSources[i]; + + std::string baseName = + cmSystemTools::GetFilenameWithoutLastExtension(sourceFilename); + + unsigned int counter = 0; + std::string uniqueBaseName = baseName; + + while (usedBaseNames.find(uniqueBaseName) != usedBaseNames.end()) { + std::ostringstream tmp; + tmp << baseName << ++counter; + uniqueBaseName = tmp.str(); + } + + usedBaseNames.insert(uniqueBaseName); + + std::string objectFilename = + this->CPackTopLevel + "/" + uniqueBaseName + ".wixobj"; + + if (!RunCandleCommand(sourceFilename, objectFilename)) { + return false; + } + + objectFiles << " " << QuotePath(objectFilename); + } + + AppendUserSuppliedExtraObjects(objectFiles); + + return RunLightCommand(objectFiles.str()); +} + +void cmCPackWIXGenerator::AppendUserSuppliedExtraSources() +{ + const char* cpackWixExtraSources = GetOption("CPACK_WIX_EXTRA_SOURCES"); + if (!cpackWixExtraSources) + return; + + cmSystemTools::ExpandListArgument(cpackWixExtraSources, this->WixSources); +} + +void cmCPackWIXGenerator::AppendUserSuppliedExtraObjects(std::ostream& stream) +{ + const char* cpackWixExtraObjects = GetOption("CPACK_WIX_EXTRA_OBJECTS"); + if (!cpackWixExtraObjects) + return; + + std::vector<std::string> expandedExtraObjects; + + cmSystemTools::ExpandListArgument(cpackWixExtraObjects, + expandedExtraObjects); + + for (size_t i = 0; i < expandedExtraObjects.size(); ++i) { + stream << " " << QuotePath(expandedExtraObjects[i]); + } +} + +void cmCPackWIXGenerator::CreateWiXVariablesIncludeFile() +{ + std::string includeFilename = this->CPackTopLevel + "/cpack_variables.wxi"; + + cmWIXSourceWriter includeFile(this->Logger, includeFilename, + this->ComponentGuidType, + cmWIXSourceWriter::INCLUDE_ELEMENT_ROOT); + + CopyDefinition(includeFile, "CPACK_WIX_PRODUCT_GUID"); + CopyDefinition(includeFile, "CPACK_WIX_UPGRADE_GUID"); + CopyDefinition(includeFile, "CPACK_PACKAGE_VENDOR"); + CopyDefinition(includeFile, "CPACK_PACKAGE_NAME"); + CopyDefinition(includeFile, "CPACK_PACKAGE_VERSION"); + CopyDefinition(includeFile, "CPACK_WIX_LICENSE_RTF"); + CopyDefinition(includeFile, "CPACK_WIX_PRODUCT_ICON"); + CopyDefinition(includeFile, "CPACK_WIX_UI_BANNER"); + CopyDefinition(includeFile, "CPACK_WIX_UI_DIALOG"); + SetOptionIfNotSet("CPACK_WIX_PROGRAM_MENU_FOLDER", + GetOption("CPACK_PACKAGE_NAME")); + CopyDefinition(includeFile, "CPACK_WIX_PROGRAM_MENU_FOLDER"); + CopyDefinition(includeFile, "CPACK_WIX_UI_REF"); +} + +void cmCPackWIXGenerator::CreateWiXPropertiesIncludeFile() +{ + std::string includeFilename = this->CPackTopLevel + "/properties.wxi"; + + cmWIXSourceWriter includeFile(this->Logger, includeFilename, + this->ComponentGuidType, + cmWIXSourceWriter::INCLUDE_ELEMENT_ROOT); + + std::string prefix = "CPACK_WIX_PROPERTY_"; + std::vector<std::string> options = GetOptions(); + + for (size_t i = 0; i < options.size(); ++i) { + std::string const& name = options[i]; + + if (name.length() > prefix.length() && + name.substr(0, prefix.length()) == prefix) { + std::string id = name.substr(prefix.length()); + std::string value = GetOption(name.c_str()); + + includeFile.BeginElement("Property"); + includeFile.AddAttribute("Id", id); + includeFile.AddAttribute("Value", value); + includeFile.EndElement("Property"); + } + } + + if (GetOption("CPACK_WIX_PROPERTY_ARPINSTALLLOCATION") == 0) { + includeFile.BeginElement("Property"); + includeFile.AddAttribute("Id", "INSTALL_ROOT"); + includeFile.AddAttribute("Secure", "yes"); + + includeFile.BeginElement("RegistrySearch"); + includeFile.AddAttribute("Id", "FindInstallLocation"); + includeFile.AddAttribute("Root", "HKLM"); + includeFile.AddAttribute( + "Key", "Software\\Microsoft\\Windows\\" + "CurrentVersion\\Uninstall\\[WIX_UPGRADE_DETECTED]"); + includeFile.AddAttribute("Name", "InstallLocation"); + includeFile.AddAttribute("Type", "raw"); + includeFile.EndElement("RegistrySearch"); + includeFile.EndElement("Property"); + + includeFile.BeginElement("SetProperty"); + includeFile.AddAttribute("Id", "ARPINSTALLLOCATION"); + includeFile.AddAttribute("Value", "[INSTALL_ROOT]"); + includeFile.AddAttribute("After", "CostFinalize"); + includeFile.EndElement("SetProperty"); + } +} + +void cmCPackWIXGenerator::CreateWiXProductFragmentIncludeFile() +{ + std::string includeFilename = this->CPackTopLevel + "/product_fragment.wxi"; + + cmWIXSourceWriter includeFile(this->Logger, includeFilename, + this->ComponentGuidType, + cmWIXSourceWriter::INCLUDE_ELEMENT_ROOT); + + this->Patch->ApplyFragment("#PRODUCT", includeFile); +} + +void cmCPackWIXGenerator::CopyDefinition(cmWIXSourceWriter& source, + std::string const& name) +{ + const char* value = GetOption(name.c_str()); + if (value) { + AddDefinition(source, name, value); + } +} + +void cmCPackWIXGenerator::AddDefinition(cmWIXSourceWriter& source, + std::string const& name, + std::string const& value) +{ + std::ostringstream tmp; + tmp << name << "=\"" << value << '"'; + + source.AddProcessingInstruction("define", tmp.str()); +} + +bool cmCPackWIXGenerator::CreateWiXSourceFiles() +{ + // if install folder is supposed to be set absolutely, the default + // component guid "*" cannot be used + std::string directoryDefinitionsFilename = + this->CPackTopLevel + "/directories.wxs"; + + this->WixSources.push_back(directoryDefinitionsFilename); + + cmWIXDirectoriesSourceWriter directoryDefinitions( + this->Logger, directoryDefinitionsFilename, this->ComponentGuidType); + directoryDefinitions.BeginElement("Fragment"); + + std::string installRoot; + if (!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY", installRoot)) { + return false; + } + + directoryDefinitions.BeginElement("Directory"); + directoryDefinitions.AddAttribute("Id", "TARGETDIR"); + directoryDefinitions.AddAttribute("Name", "SourceDir"); + + size_t installRootSize = + directoryDefinitions.BeginInstallationPrefixDirectory( + GetProgramFilesFolderId(), installRoot); + + std::string fileDefinitionsFilename = this->CPackTopLevel + "/files.wxs"; + + this->WixSources.push_back(fileDefinitionsFilename); + + cmWIXFilesSourceWriter fileDefinitions(this->Logger, fileDefinitionsFilename, + this->ComponentGuidType); + + fileDefinitions.BeginElement("Fragment"); + + std::string featureDefinitionsFilename = + this->CPackTopLevel + "/features.wxs"; + + this->WixSources.push_back(featureDefinitionsFilename); + + cmWIXFeaturesSourceWriter featureDefinitions( + this->Logger, featureDefinitionsFilename, this->ComponentGuidType); + + featureDefinitions.BeginElement("Fragment"); + + featureDefinitions.BeginElement("Feature"); + featureDefinitions.AddAttribute("Id", "ProductFeature"); + featureDefinitions.AddAttribute("Display", "expand"); + featureDefinitions.AddAttribute("Absent", "disallow"); + featureDefinitions.AddAttribute("ConfigurableDirectory", "INSTALL_ROOT"); + + std::string cpackPackageName; + if (!RequireOption("CPACK_PACKAGE_NAME", cpackPackageName)) { + return false; + } + + std::string featureTitle = cpackPackageName; + if (const char* title = GetOption("CPACK_WIX_ROOT_FEATURE_TITLE")) { + featureTitle = title; + } + featureDefinitions.AddAttribute("Title", featureTitle); + if (const char* desc = GetOption("CPACK_WIX_ROOT_FEATURE_DESCRIPTION")) { + featureDefinitions.AddAttribute("Description", desc); + } + featureDefinitions.AddAttribute("Level", "1"); + this->Patch->ApplyFragment("#PRODUCTFEATURE", featureDefinitions); + + const char* package = GetOption("CPACK_WIX_CMAKE_PACKAGE_REGISTRY"); + if (package) { + featureDefinitions.CreateCMakePackageRegistryEntry( + package, GetOption("CPACK_WIX_UPGRADE_GUID")); + } + + if (!CreateFeatureHierarchy(featureDefinitions)) { + return false; + } + + featureDefinitions.EndElement("Feature"); + + std::set<cmWIXShortcuts::Type> emittedShortcutTypes; + + cmWIXShortcuts globalShortcuts; + if (Components.empty()) { + AddComponentsToFeature(toplevel, "ProductFeature", directoryDefinitions, + fileDefinitions, featureDefinitions, + globalShortcuts); + + globalShortcuts.AddShortcutTypes(emittedShortcutTypes); + } else { + for (std::map<std::string, cmCPackComponent>::const_iterator i = + this->Components.begin(); + i != this->Components.end(); ++i) { + cmCPackComponent const& component = i->second; + + std::string componentPath = toplevel; + componentPath += "/"; + componentPath += component.Name; + + std::string componentFeatureId = "CM_C_" + component.Name; + + cmWIXShortcuts featureShortcuts; + AddComponentsToFeature(componentPath, componentFeatureId, + directoryDefinitions, fileDefinitions, + featureDefinitions, featureShortcuts); + + featureShortcuts.AddShortcutTypes(emittedShortcutTypes); + + if (!CreateShortcuts(component.Name, componentFeatureId, + featureShortcuts, false, fileDefinitions, + featureDefinitions)) { + return false; + } + } + } + + bool emitUninstallShortcut = + emittedShortcutTypes.find(cmWIXShortcuts::START_MENU) != + emittedShortcutTypes.end(); + + if (!CreateShortcuts(std::string(), "ProductFeature", globalShortcuts, + emitUninstallShortcut, fileDefinitions, + featureDefinitions)) { + return false; + } + + featureDefinitions.EndElement("Fragment"); + fileDefinitions.EndElement("Fragment"); + + directoryDefinitions.EndInstallationPrefixDirectory(installRootSize); + + if (emittedShortcutTypes.find(cmWIXShortcuts::START_MENU) != + emittedShortcutTypes.end()) { + directoryDefinitions.EmitStartMenuFolder( + GetOption("CPACK_WIX_PROGRAM_MENU_FOLDER")); + } + + if (emittedShortcutTypes.find(cmWIXShortcuts::DESKTOP) != + emittedShortcutTypes.end()) { + directoryDefinitions.EmitDesktopFolder(); + } + + if (emittedShortcutTypes.find(cmWIXShortcuts::STARTUP) != + emittedShortcutTypes.end()) { + directoryDefinitions.EmitStartupFolder(); + } + + directoryDefinitions.EndElement("Directory"); + directoryDefinitions.EndElement("Fragment"); + + if (!GenerateMainSourceFileFromTemplate()) { + return false; + } + + return this->Patch->CheckForUnappliedFragments(); +} + +std::string cmCPackWIXGenerator::GetProgramFilesFolderId() const +{ + if (cmSystemTools::IsOn(GetOption("CPACK_WIX_SKIP_PROGRAM_FOLDER"))) { + return ""; + } + if (GetArchitecture() == "x86") { + return "ProgramFilesFolder"; + } else { + return "ProgramFiles64Folder"; + } +} + +bool cmCPackWIXGenerator::GenerateMainSourceFileFromTemplate() +{ + std::string wixTemplate = FindTemplate("WIX.template.in"); + if (GetOption("CPACK_WIX_TEMPLATE") != 0) { + wixTemplate = GetOption("CPACK_WIX_TEMPLATE"); + } + + if (wixTemplate.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Could not find CPack WiX template file WIX.template.in" + << std::endl); + return false; + } + + std::string mainSourceFilePath = this->CPackTopLevel + "/main.wxs"; + + if (!ConfigureFile(wixTemplate.c_str(), mainSourceFilePath.c_str())) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed creating '" + << mainSourceFilePath << "'' from template." << std::endl); + + return false; + } + + this->WixSources.push_back(mainSourceFilePath); + + return true; +} + +bool cmCPackWIXGenerator::CreateFeatureHierarchy( + cmWIXFeaturesSourceWriter& featureDefinitions) +{ + for (std::map<std::string, cmCPackComponentGroup>::const_iterator i = + ComponentGroups.begin(); + i != ComponentGroups.end(); ++i) { + cmCPackComponentGroup const& group = i->second; + if (group.ParentGroup == 0) { + featureDefinitions.EmitFeatureForComponentGroup(group, *this->Patch); + } + } + + for (std::map<std::string, cmCPackComponent>::const_iterator i = + this->Components.begin(); + i != this->Components.end(); ++i) { + cmCPackComponent const& component = i->second; + + if (!component.Group) { + featureDefinitions.EmitFeatureForComponent(component, *this->Patch); + } + } + + return true; +} + +bool cmCPackWIXGenerator::AddComponentsToFeature( + std::string const& rootPath, std::string const& featureId, + cmWIXDirectoriesSourceWriter& directoryDefinitions, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions, cmWIXShortcuts& shortcuts) +{ + featureDefinitions.BeginElement("FeatureRef"); + featureDefinitions.AddAttribute("Id", featureId); + + std::vector<std::string> cpackPackageExecutablesList; + const char* cpackPackageExecutables = GetOption("CPACK_PACKAGE_EXECUTABLES"); + if (cpackPackageExecutables) { + cmSystemTools::ExpandListArgument(cpackPackageExecutables, + cpackPackageExecutablesList); + if (cpackPackageExecutablesList.size() % 2 != 0) { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and " + "<text label>." + << std::endl); + return false; + } + } + + std::vector<std::string> cpackPackageDesktopLinksList; + const char* cpackPackageDesktopLinks = + GetOption("CPACK_CREATE_DESKTOP_LINKS"); + if (cpackPackageDesktopLinks) { + cmSystemTools::ExpandListArgument(cpackPackageDesktopLinks, + cpackPackageDesktopLinksList); + } + + AddDirectoryAndFileDefinitons(rootPath, "INSTALL_ROOT", directoryDefinitions, + fileDefinitions, featureDefinitions, + cpackPackageExecutablesList, + cpackPackageDesktopLinksList, shortcuts); + + featureDefinitions.EndElement("FeatureRef"); + + return true; +} + +bool cmCPackWIXGenerator::CreateShortcuts( + std::string const& cpackComponentName, std::string const& featureId, + cmWIXShortcuts const& shortcuts, bool emitUninstallShortcut, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions) +{ + if (!shortcuts.empty(cmWIXShortcuts::START_MENU)) { + if (!this->CreateShortcutsOfSpecificType( + cmWIXShortcuts::START_MENU, cpackComponentName, featureId, "", + shortcuts, emitUninstallShortcut, fileDefinitions, + featureDefinitions)) { + return false; + } + } + + if (!shortcuts.empty(cmWIXShortcuts::DESKTOP)) { + if (!this->CreateShortcutsOfSpecificType( + cmWIXShortcuts::DESKTOP, cpackComponentName, featureId, "DESKTOP", + shortcuts, false, fileDefinitions, featureDefinitions)) { + return false; + } + } + + if (!shortcuts.empty(cmWIXShortcuts::STARTUP)) { + if (!this->CreateShortcutsOfSpecificType( + cmWIXShortcuts::STARTUP, cpackComponentName, featureId, "STARTUP", + shortcuts, false, fileDefinitions, featureDefinitions)) { + return false; + } + } + + return true; +} + +bool cmCPackWIXGenerator::CreateShortcutsOfSpecificType( + cmWIXShortcuts::Type type, std::string const& cpackComponentName, + std::string const& featureId, std::string const& idPrefix, + cmWIXShortcuts const& shortcuts, bool emitUninstallShortcut, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions) +{ + std::string directoryId; + switch (type) { + case cmWIXShortcuts::START_MENU: + directoryId = "PROGRAM_MENU_FOLDER"; + break; + case cmWIXShortcuts::DESKTOP: + directoryId = "DesktopFolder"; + break; + case cmWIXShortcuts::STARTUP: + directoryId = "StartupFolder"; + break; + default: + return false; + } + + featureDefinitions.BeginElement("FeatureRef"); + featureDefinitions.AddAttribute("Id", featureId); + + std::string cpackVendor; + if (!RequireOption("CPACK_PACKAGE_VENDOR", cpackVendor)) { + return false; + } + + std::string cpackPackageName; + if (!RequireOption("CPACK_PACKAGE_NAME", cpackPackageName)) { + return false; + } + + std::string idSuffix; + if (!cpackComponentName.empty()) { + idSuffix += "_"; + idSuffix += cpackComponentName; + } + + std::string componentId = "CM_SHORTCUT"; + if (idPrefix.size()) { + componentId += "_" + idPrefix; + } + + componentId += idSuffix; + + fileDefinitions.BeginElement("DirectoryRef"); + fileDefinitions.AddAttribute("Id", directoryId); + + fileDefinitions.BeginElement("Component"); + fileDefinitions.AddAttribute("Id", componentId); + fileDefinitions.AddAttribute( + "Guid", fileDefinitions.CreateGuidFromComponentId(componentId)); + + this->Patch->ApplyFragment(componentId, fileDefinitions); + + std::string registryKey = + std::string("Software\\") + cpackVendor + "\\" + cpackPackageName; + + shortcuts.EmitShortcuts(type, registryKey, cpackComponentName, + fileDefinitions); + + if (type == cmWIXShortcuts::START_MENU) { + fileDefinitions.EmitRemoveFolder("CM_REMOVE_PROGRAM_MENU_FOLDER" + + idSuffix); + } + + if (emitUninstallShortcut) { + fileDefinitions.EmitUninstallShortcut(cpackPackageName); + } + + fileDefinitions.EndElement("Component"); + fileDefinitions.EndElement("DirectoryRef"); + + featureDefinitions.EmitComponentRef(componentId); + featureDefinitions.EndElement("FeatureRef"); + + return true; +} + +bool cmCPackWIXGenerator::CreateLicenseFile() +{ + std::string licenseSourceFilename; + if (!RequireOption("CPACK_RESOURCE_FILE_LICENSE", licenseSourceFilename)) { + return false; + } + + std::string licenseDestinationFilename; + if (!RequireOption("CPACK_WIX_LICENSE_RTF", licenseDestinationFilename)) { + return false; + } + + std::string extension = GetRightmostExtension(licenseSourceFilename); + + if (extension == ".rtf") { + cmSystemTools::CopyAFile(licenseSourceFilename.c_str(), + licenseDestinationFilename.c_str()); + } else if (extension == ".txt") { + cmWIXRichTextFormatWriter rtfWriter(licenseDestinationFilename); + + cmsys::ifstream licenseSource(licenseSourceFilename.c_str()); + + std::string line; + while (std::getline(licenseSource, line)) { + rtfWriter.AddText(line); + rtfWriter.AddText("\n"); + } + } else { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "unsupported WiX License file extension '" + << extension << "'" << std::endl); + + return false; + } + + return true; +} + +void cmCPackWIXGenerator::AddDirectoryAndFileDefinitons( + std::string const& topdir, std::string const& directoryId, + cmWIXDirectoriesSourceWriter& directoryDefinitions, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions, + std::vector<std::string> const& packageExecutables, + std::vector<std::string> const& desktopExecutables, + cmWIXShortcuts& shortcuts) +{ + cmsys::Directory dir; + dir.Load(topdir.c_str()); + + std::string relativeDirectoryPath = + cmSystemTools::RelativePath(toplevel.c_str(), topdir.c_str()); + + if (relativeDirectoryPath.empty()) { + relativeDirectoryPath = "."; + } + + cmInstalledFile const* directoryInstalledFile = this->GetInstalledFile( + this->RelativePathWithoutComponentPrefix(relativeDirectoryPath)); + + bool emptyDirectory = dir.GetNumberOfFiles() == 2; + bool createDirectory = false; + + if (emptyDirectory) { + createDirectory = true; + } + + if (directoryInstalledFile) { + if (directoryInstalledFile->HasProperty("CPACK_WIX_ACL")) { + createDirectory = true; + } + } + + if (createDirectory) { + std::string componentId = fileDefinitions.EmitComponentCreateFolder( + directoryId, GenerateGUID(), directoryInstalledFile); + featureDefinitions.EmitComponentRef(componentId); + } + + if (emptyDirectory) { + return; + } + + for (size_t i = 0; i < dir.GetNumberOfFiles(); ++i) { + std::string fileName = dir.GetFile(static_cast<unsigned long>(i)); + + if (fileName == "." || fileName == "..") { + continue; + } + + std::string fullPath = topdir + "/" + fileName; + + std::string relativePath = + cmSystemTools::RelativePath(toplevel.c_str(), fullPath.c_str()); + + std::string id = PathToId(relativePath); + + if (cmSystemTools::FileIsDirectory(fullPath.c_str())) { + std::string subDirectoryId = std::string("CM_D") + id; + + directoryDefinitions.BeginElement("Directory"); + directoryDefinitions.AddAttribute("Id", subDirectoryId); + directoryDefinitions.AddAttribute("Name", fileName); + + AddDirectoryAndFileDefinitons( + fullPath, subDirectoryId, directoryDefinitions, fileDefinitions, + featureDefinitions, packageExecutables, desktopExecutables, shortcuts); + + this->Patch->ApplyFragment(subDirectoryId, directoryDefinitions); + directoryDefinitions.EndElement("Directory"); + } else { + cmInstalledFile const* installedFile = this->GetInstalledFile( + this->RelativePathWithoutComponentPrefix(relativePath)); + + if (installedFile) { + shortcuts.CreateFromProperties(id, directoryId, *installedFile); + } + + std::string componentId = fileDefinitions.EmitComponentFile( + directoryId, id, fullPath, *(this->Patch), installedFile); + + featureDefinitions.EmitComponentRef(componentId); + + for (size_t j = 0; j < packageExecutables.size(); ++j) { + std::string const& executableName = packageExecutables[j++]; + std::string const& textLabel = packageExecutables[j]; + + if (cmSystemTools::LowerCase(fileName) == + cmSystemTools::LowerCase(executableName) + ".exe") { + cmWIXShortcut shortcut; + shortcut.label = textLabel; + shortcut.workingDirectoryId = directoryId; + shortcuts.insert(cmWIXShortcuts::START_MENU, id, shortcut); + + if (!desktopExecutables.empty() && + std::find(desktopExecutables.begin(), desktopExecutables.end(), + executableName) != desktopExecutables.end()) { + shortcuts.insert(cmWIXShortcuts::DESKTOP, id, shortcut); + } + } + } + } + } +} + +bool cmCPackWIXGenerator::RequireOption(std::string const& name, + std::string& value) const +{ + const char* tmp = GetOption(name.c_str()); + if (tmp) { + value = tmp; + + return true; + } else { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Required variable " + << name << " not set" << std::endl); + + return false; + } +} + +std::string cmCPackWIXGenerator::GetArchitecture() const +{ + std::string void_p_size; + RequireOption("CPACK_WIX_SIZEOF_VOID_P", void_p_size); + + if (void_p_size == "8") { + return "x64"; + } else { + return "x86"; + } +} + +std::string cmCPackWIXGenerator::GenerateGUID() +{ + UUID guid; + UuidCreate(&guid); + + unsigned short* tmp = 0; + UuidToStringW(&guid, &tmp); + + std::string result = + cmsys::Encoding::ToNarrow(reinterpret_cast<wchar_t*>(tmp)); + RpcStringFreeW(&tmp); + + return cmSystemTools::UpperCase(result); +} + +std::string cmCPackWIXGenerator::QuotePath(std::string const& path) +{ + return std::string("\"") + path + '"'; +} + +std::string cmCPackWIXGenerator::GetRightmostExtension( + std::string const& filename) +{ + std::string extension; + + std::string::size_type i = filename.rfind("."); + if (i != std::string::npos) { + extension = filename.substr(i); + } + + return cmSystemTools::LowerCase(extension); +} + +std::string cmCPackWIXGenerator::PathToId(std::string const& path) +{ + id_map_t::const_iterator i = PathToIdMap.find(path); + if (i != PathToIdMap.end()) + return i->second; + + std::string id = CreateNewIdForPath(path); + return id; +} + +std::string cmCPackWIXGenerator::CreateNewIdForPath(std::string const& path) +{ + std::vector<std::string> components; + cmSystemTools::SplitPath(path.c_str(), components, false); + + size_t replacementCount = 0; + + std::string identifier; + std::string currentComponent; + + for (size_t i = 1; i < components.size(); ++i) { + if (i != 1) + identifier += '.'; + + currentComponent = + NormalizeComponentForId(components[i], replacementCount); + + identifier += currentComponent; + } + + std::string idPrefix = "P"; + size_t replacementPercent = replacementCount * 100 / identifier.size(); + if (replacementPercent > 33 || identifier.size() > 60) { + identifier = CreateHashedId(path, currentComponent); + idPrefix = "H"; + } + + std::ostringstream result; + result << idPrefix << "_" << identifier; + + size_t ambiguityCount = ++IdAmbiguityCounter[identifier]; + + if (ambiguityCount > 999) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Error while trying to generate a unique Id for '" + << path << "'" << std::endl); + + return std::string(); + } else if (ambiguityCount > 1) { + result << "_" << ambiguityCount; + } + + std::string resultString = result.str(); + + PathToIdMap[path] = resultString; + + return resultString; +} + +std::string cmCPackWIXGenerator::CreateHashedId( + std::string const& path, std::string const& normalizedFilename) +{ + cmCryptoHash sha1(cmCryptoHash::AlgoSHA1); + std::string const hash = sha1.HashString(path); + + std::string identifier; + identifier += hash.substr(0, 7) + "_"; + + const size_t maxFileNameLength = 52; + if (normalizedFilename.length() > maxFileNameLength) { + identifier += normalizedFilename.substr(0, maxFileNameLength - 3); + identifier += "..."; + } else { + identifier += normalizedFilename; + } + + return identifier; +} + +std::string cmCPackWIXGenerator::NormalizeComponentForId( + std::string const& component, size_t& replacementCount) +{ + std::string result; + result.resize(component.size()); + + for (size_t i = 0; i < component.size(); ++i) { + char c = component[i]; + if (IsLegalIdCharacter(c)) { + result[i] = c; + } else { + result[i] = '_'; + ++replacementCount; + } + } + + return result; +} + +bool cmCPackWIXGenerator::IsLegalIdCharacter(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '_' || c == '.'; +} + +void cmCPackWIXGenerator::CollectExtensions(std::string const& variableName, + extension_set_t& extensions) +{ + const char* variableContent = GetOption(variableName.c_str()); + if (!variableContent) + return; + + std::vector<std::string> list; + cmSystemTools::ExpandListArgument(variableContent, list); + extensions.insert(list.begin(), list.end()); +} + +void cmCPackWIXGenerator::AddCustomFlags(std::string const& variableName, + std::ostream& stream) +{ + const char* variableContent = GetOption(variableName.c_str()); + if (!variableContent) + return; + + std::vector<std::string> list; + cmSystemTools::ExpandListArgument(variableContent, list); + + for (std::vector<std::string>::const_iterator i = list.begin(); + i != list.end(); ++i) { + stream << " " << QuotePath(*i); + } +} + +std::string cmCPackWIXGenerator::RelativePathWithoutComponentPrefix( + std::string const& path) +{ + if (this->Components.empty()) { + return path; + } + + std::string::size_type pos = path.find('/'); + + return path.substr(pos + 1); +} diff --git a/Source/CPack/WiX/cmCPackWIXGenerator.h b/Source/CPack/WiX/cmCPackWIXGenerator.h new file mode 100644 index 0000000..fc0994c --- /dev/null +++ b/Source/CPack/WiX/cmCPackWIXGenerator.h @@ -0,0 +1,159 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmCPackWIXGenerator_h +#define cmCPackWIXGenerator_h + +#include <CPack/cmCPackGenerator.h> + +#include "cmWIXPatch.h" +#include "cmWIXShortcut.h" + +#include <map> +#include <string> + +class cmWIXSourceWriter; +class cmWIXDirectoriesSourceWriter; +class cmWIXFilesSourceWriter; +class cmWIXFeaturesSourceWriter; + +/** \class cmCPackWIXGenerator + * \brief A generator for WIX files + */ +class cmCPackWIXGenerator : public cmCPackGenerator +{ +public: + cmCPackTypeMacro(cmCPackWIXGenerator, cmCPackGenerator); + + cmCPackWIXGenerator(); + ~cmCPackWIXGenerator(); + +protected: + virtual int InitializeInternal(); + + virtual int PackageFiles(); + + virtual const char* GetOutputExtension() { return ".msi"; } + + virtual enum CPackSetDestdirSupport SupportsSetDestdir() const + { + return SETDESTDIR_UNSUPPORTED; + } + + virtual bool SupportsAbsoluteDestination() const { return false; } + + virtual bool SupportsComponentInstallation() const { return true; } + +private: + typedef std::map<std::string, std::string> id_map_t; + typedef std::map<std::string, size_t> ambiguity_map_t; + typedef std::set<std::string> extension_set_t; + + bool InitializeWiXConfiguration(); + + bool PackageFilesImpl(); + + void CreateWiXVariablesIncludeFile(); + + void CreateWiXPropertiesIncludeFile(); + + void CreateWiXProductFragmentIncludeFile(); + + void CopyDefinition(cmWIXSourceWriter& source, std::string const& name); + + void AddDefinition(cmWIXSourceWriter& source, std::string const& name, + std::string const& value); + + bool CreateWiXSourceFiles(); + + std::string GetProgramFilesFolderId() const; + + bool GenerateMainSourceFileFromTemplate(); + + bool CreateFeatureHierarchy(cmWIXFeaturesSourceWriter& featureDefinitions); + + bool AddComponentsToFeature( + std::string const& rootPath, std::string const& featureId, + cmWIXDirectoriesSourceWriter& directoryDefinitions, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions, cmWIXShortcuts& shortcuts); + + bool CreateShortcuts(std::string const& cpackComponentName, + std::string const& featureId, + cmWIXShortcuts const& shortcuts, + bool emitUninstallShortcut, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions); + + bool CreateShortcutsOfSpecificType( + cmWIXShortcuts::Type type, std::string const& cpackComponentName, + std::string const& featureId, std::string const& idPrefix, + cmWIXShortcuts const& shortcuts, bool emitUninstallShortcut, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions); + + void AppendUserSuppliedExtraSources(); + + void AppendUserSuppliedExtraObjects(std::ostream& stream); + + bool CreateLicenseFile(); + + bool RunWiXCommand(std::string const& command); + + bool RunCandleCommand(std::string const& sourceFile, + std::string const& objectFile); + + bool RunLightCommand(std::string const& objectFiles); + + void AddDirectoryAndFileDefinitons( + std::string const& topdir, std::string const& directoryId, + cmWIXDirectoriesSourceWriter& directoryDefinitions, + cmWIXFilesSourceWriter& fileDefinitions, + cmWIXFeaturesSourceWriter& featureDefinitions, + std::vector<std::string> const& packageExecutables, + std::vector<std::string> const& desktopExecutables, + cmWIXShortcuts& shortcuts); + + bool RequireOption(std::string const& name, std::string& value) const; + + std::string GetArchitecture() const; + + static std::string GenerateGUID(); + + static std::string QuotePath(std::string const& path); + + static std::string GetRightmostExtension(std::string const& filename); + + std::string PathToId(std::string const& path); + + std::string CreateNewIdForPath(std::string const& path); + + static std::string CreateHashedId(std::string const& path, + std::string const& normalizedFilename); + + std::string NormalizeComponentForId(std::string const& component, + size_t& replacementCount); + + static bool IsLegalIdCharacter(char c); + + void CollectExtensions(std::string const& variableName, + extension_set_t& extensions); + + void AddCustomFlags(std::string const& variableName, std::ostream& stream); + + std::string RelativePathWithoutComponentPrefix(std::string const& path); + + std::vector<std::string> WixSources; + id_map_t PathToIdMap; + ambiguity_map_t IdAmbiguityCounter; + + extension_set_t CandleExtensions; + extension_set_t LightExtensions; + + std::string CPackTopLevel; + + cmWIXPatch* Patch; + + cmWIXSourceWriter::GuidType ComponentGuidType; +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXAccessControlList.cxx b/Source/CPack/WiX/cmWIXAccessControlList.cxx new file mode 100644 index 0000000..9f9b39c --- /dev/null +++ b/Source/CPack/WiX/cmWIXAccessControlList.cxx @@ -0,0 +1,126 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXAccessControlList.h" + +#include <CPack/cmCPackGenerator.h> + +#include <cmSystemTools.h> + +cmWIXAccessControlList::cmWIXAccessControlList( + cmCPackLog* logger, cmInstalledFile const& installedFile, + cmWIXSourceWriter& sourceWriter) + : Logger(logger) + , InstalledFile(installedFile) + , SourceWriter(sourceWriter) +{ +} + +bool cmWIXAccessControlList::Apply() +{ + std::vector<std::string> entries; + this->InstalledFile.GetPropertyAsList("CPACK_WIX_ACL", entries); + + for (size_t i = 0; i < entries.size(); ++i) { + this->CreatePermissionElement(entries[i]); + } + + return true; +} + +void cmWIXAccessControlList::CreatePermissionElement(std::string const& entry) +{ + std::string::size_type pos = entry.find('='); + if (pos == std::string::npos) { + this->ReportError(entry, "Did not find mandatory '='"); + return; + } + + std::string user_and_domain = entry.substr(0, pos); + std::string permission_string = entry.substr(pos + 1); + + pos = user_and_domain.find('@'); + std::string user; + std::string domain; + if (pos != std::string::npos) { + user = user_and_domain.substr(0, pos); + domain = user_and_domain.substr(pos + 1); + } else { + user = user_and_domain; + } + + std::vector<std::string> permissions = + cmSystemTools::tokenize(permission_string, ","); + + this->SourceWriter.BeginElement("Permission"); + this->SourceWriter.AddAttribute("User", user); + if (!domain.empty()) { + this->SourceWriter.AddAttribute("Domain", domain); + } + for (size_t i = 0; i < permissions.size(); ++i) { + this->EmitBooleanAttribute(entry, + cmSystemTools::TrimWhitespace(permissions[i])); + } + this->SourceWriter.EndElement("Permission"); +} + +void cmWIXAccessControlList::ReportError(std::string const& entry, + std::string const& message) +{ + cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed processing ACL entry '" + << entry << "': " << message << std::endl); +} + +bool cmWIXAccessControlList::IsBooleanAttribute(std::string const& name) +{ + static const char* validAttributes[] = { + /* clang-format needs this comment to break after the opening brace */ + "Append", + "ChangePermission", + "CreateChild", + "CreateFile", + "CreateLink", + "CreateSubkeys", + "Delete", + "DeleteChild", + "EnumerateSubkeys", + "Execute", + "FileAllRights", + "GenericAll", + "GenericExecute", + "GenericRead", + "GenericWrite", + "Notify", + "Read", + "ReadAttributes", + "ReadExtendedAttributes", + "ReadPermission", + "SpecificRightsAll", + "Synchronize", + "TakeOwnership", + "Traverse", + "Write", + "WriteAttributes", + "WriteExtendedAttributes", + 0 + }; + + size_t i = 0; + while (validAttributes[i]) { + if (name == validAttributes[i++]) + return true; + } + + return false; +} + +void cmWIXAccessControlList::EmitBooleanAttribute(std::string const& entry, + std::string const& name) +{ + if (!this->IsBooleanAttribute(name)) { + std::ostringstream message; + message << "Unknown boolean attribute '" << name << "'"; + this->ReportError(entry, message.str()); + } + + this->SourceWriter.AddAttribute(name, "yes"); +} diff --git a/Source/CPack/WiX/cmWIXAccessControlList.h b/Source/CPack/WiX/cmWIXAccessControlList.h new file mode 100644 index 0000000..0a25ee5 --- /dev/null +++ b/Source/CPack/WiX/cmWIXAccessControlList.h @@ -0,0 +1,34 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXAccessControlList_h +#define cmWIXAccessControlList_h + +#include "cmWIXSourceWriter.h" + +#include <CPack/cmCPackLog.h> +#include <cmInstalledFile.h> + +class cmWIXAccessControlList +{ +public: + cmWIXAccessControlList(cmCPackLog* logger, + cmInstalledFile const& installedFile, + cmWIXSourceWriter& sourceWriter); + + bool Apply(); + +private: + void CreatePermissionElement(std::string const& entry); + + void ReportError(std::string const& entry, std::string const& message); + + bool IsBooleanAttribute(std::string const& name); + + void EmitBooleanAttribute(std::string const& entry, std::string const& name); + + cmCPackLog* Logger; + cmInstalledFile const& InstalledFile; + cmWIXSourceWriter& SourceWriter; +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx new file mode 100644 index 0000000..975dffb --- /dev/null +++ b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.cxx @@ -0,0 +1,82 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXDirectoriesSourceWriter.h" + +cmWIXDirectoriesSourceWriter::cmWIXDirectoriesSourceWriter( + cmCPackLog* logger, std::string const& filename, GuidType componentGuidType) + : cmWIXSourceWriter(logger, filename, componentGuidType) +{ +} + +void cmWIXDirectoriesSourceWriter::EmitStartMenuFolder( + std::string const& startMenuFolder) +{ + BeginElement("Directory"); + AddAttribute("Id", "ProgramMenuFolder"); + + BeginElement("Directory"); + AddAttribute("Id", "PROGRAM_MENU_FOLDER"); + AddAttribute("Name", startMenuFolder); + EndElement("Directory"); + + EndElement("Directory"); +} + +void cmWIXDirectoriesSourceWriter::EmitDesktopFolder() +{ + BeginElement("Directory"); + AddAttribute("Id", "DesktopFolder"); + AddAttribute("Name", "Desktop"); + EndElement("Directory"); +} + +void cmWIXDirectoriesSourceWriter::EmitStartupFolder() +{ + BeginElement("Directory"); + AddAttribute("Id", "StartupFolder"); + AddAttribute("Name", "Startup"); + EndElement("Directory"); +} + +size_t cmWIXDirectoriesSourceWriter::BeginInstallationPrefixDirectory( + std::string const& programFilesFolderId, + std::string const& installRootString) +{ + size_t offset = 1; + if (!programFilesFolderId.empty()) { + BeginElement("Directory"); + AddAttribute("Id", programFilesFolderId); + offset = 0; + } + + std::vector<std::string> installRoot; + + cmSystemTools::SplitPath(installRootString.c_str(), installRoot); + + if (!installRoot.empty() && installRoot.back().empty()) { + installRoot.pop_back(); + } + + for (size_t i = 1; i < installRoot.size(); ++i) { + BeginElement("Directory"); + + if (i == installRoot.size() - 1) { + AddAttribute("Id", "INSTALL_ROOT"); + } else { + std::ostringstream tmp; + tmp << "INSTALL_PREFIX_" << i; + AddAttribute("Id", tmp.str()); + } + + AddAttribute("Name", installRoot[i]); + } + + return installRoot.size() - offset; +} + +void cmWIXDirectoriesSourceWriter::EndInstallationPrefixDirectory(size_t size) +{ + for (size_t i = 0; i < size; ++i) { + EndElement("Directory"); + } +} diff --git a/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.h b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.h new file mode 100644 index 0000000..062efe3 --- /dev/null +++ b/Source/CPack/WiX/cmWIXDirectoriesSourceWriter.h @@ -0,0 +1,34 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXDirectoriesSourceWriter_h +#define cmWIXDirectoriesSourceWriter_h + +#include "cmWIXSourceWriter.h" + +#include <CPack/cmCPackGenerator.h> + +#include <string> + +/** \class cmWIXDirectoriesSourceWriter + * \brief Helper class to generate directories.wxs + */ +class cmWIXDirectoriesSourceWriter : public cmWIXSourceWriter +{ +public: + cmWIXDirectoriesSourceWriter(cmCPackLog* logger, std::string const& filename, + GuidType componentGuidType); + + void EmitStartMenuFolder(std::string const& startMenuFolder); + + void EmitDesktopFolder(); + + void EmitStartupFolder(); + + size_t BeginInstallationPrefixDirectory( + std::string const& programFilesFolderId, + std::string const& installRootString); + + void EndInstallationPrefixDirectory(size_t size); +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx new file mode 100644 index 0000000..79a9fdd --- /dev/null +++ b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.cxx @@ -0,0 +1,95 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXFeaturesSourceWriter.h" + +cmWIXFeaturesSourceWriter::cmWIXFeaturesSourceWriter( + cmCPackLog* logger, std::string const& filename, GuidType componentGuidType) + : cmWIXSourceWriter(logger, filename, componentGuidType) +{ +} + +void cmWIXFeaturesSourceWriter::CreateCMakePackageRegistryEntry( + std::string const& package, std::string const& upgradeGuid) +{ + BeginElement("Component"); + AddAttribute("Id", "CM_PACKAGE_REGISTRY"); + AddAttribute("Directory", "TARGETDIR"); + AddAttribute("Guid", CreateGuidFromComponentId("CM_PACKAGE_REGISTRY")); + + std::string registryKey = + std::string("Software\\Kitware\\CMake\\Packages\\") + package; + + BeginElement("RegistryValue"); + AddAttribute("Root", "HKLM"); + AddAttribute("Key", registryKey); + AddAttribute("Name", upgradeGuid); + AddAttribute("Type", "string"); + AddAttribute("Value", "[INSTALL_ROOT]"); + AddAttribute("KeyPath", "yes"); + EndElement("RegistryValue"); + + EndElement("Component"); +} + +void cmWIXFeaturesSourceWriter::EmitFeatureForComponentGroup( + cmCPackComponentGroup const& group, cmWIXPatch& patch) +{ + BeginElement("Feature"); + AddAttribute("Id", "CM_G_" + group.Name); + + if (group.IsExpandedByDefault) { + AddAttribute("Display", "expand"); + } + + AddAttributeUnlessEmpty("Title", group.DisplayName); + AddAttributeUnlessEmpty("Description", group.Description); + + for (std::vector<cmCPackComponentGroup*>::const_iterator i = + group.Subgroups.begin(); + i != group.Subgroups.end(); ++i) { + EmitFeatureForComponentGroup(**i, patch); + } + + for (std::vector<cmCPackComponent*>::const_iterator i = + group.Components.begin(); + i != group.Components.end(); ++i) { + EmitFeatureForComponent(**i, patch); + } + + patch.ApplyFragment("CM_G_" + group.Name, *this); + + EndElement("Feature"); +} + +void cmWIXFeaturesSourceWriter::EmitFeatureForComponent( + cmCPackComponent const& component, cmWIXPatch& patch) +{ + BeginElement("Feature"); + AddAttribute("Id", "CM_C_" + component.Name); + + AddAttributeUnlessEmpty("Title", component.DisplayName); + AddAttributeUnlessEmpty("Description", component.Description); + + if (component.IsRequired) { + AddAttribute("Absent", "disallow"); + } + + if (component.IsHidden) { + AddAttribute("Display", "hidden"); + } + + if (component.IsDisabledByDefault) { + AddAttribute("Level", "2"); + } + + patch.ApplyFragment("CM_C_" + component.Name, *this); + + EndElement("Feature"); +} + +void cmWIXFeaturesSourceWriter::EmitComponentRef(std::string const& id) +{ + BeginElement("ComponentRef"); + AddAttribute("Id", id); + EndElement("ComponentRef"); +} diff --git a/Source/CPack/WiX/cmWIXFeaturesSourceWriter.h b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.h new file mode 100644 index 0000000..368b250 --- /dev/null +++ b/Source/CPack/WiX/cmWIXFeaturesSourceWriter.h @@ -0,0 +1,32 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXFeaturesSourceWriter_h +#define cmWIXFeaturesSourceWriter_h + +#include "cmWIXPatch.h" +#include "cmWIXSourceWriter.h" + +#include <CPack/cmCPackGenerator.h> + +/** \class cmWIXFeaturesSourceWriter + * \brief Helper class to generate features.wxs + */ +class cmWIXFeaturesSourceWriter : public cmWIXSourceWriter +{ +public: + cmWIXFeaturesSourceWriter(cmCPackLog* logger, std::string const& filename, + GuidType componentGuidType); + + void CreateCMakePackageRegistryEntry(std::string const& package, + std::string const& upgradeGuid); + + void EmitFeatureForComponentGroup(const cmCPackComponentGroup& group, + cmWIXPatch& patch); + + void EmitFeatureForComponent(const cmCPackComponent& component, + cmWIXPatch& patch); + + void EmitComponentRef(std::string const& id); +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx b/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx new file mode 100644 index 0000000..a471d26 --- /dev/null +++ b/Source/CPack/WiX/cmWIXFilesSourceWriter.cxx @@ -0,0 +1,166 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXFilesSourceWriter.h" + +#include "cmWIXAccessControlList.h" + +#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> + +cmWIXFilesSourceWriter::cmWIXFilesSourceWriter(cmCPackLog* logger, + std::string const& filename, + GuidType componentGuidType) + : cmWIXSourceWriter(logger, filename, componentGuidType) +{ +} + +void cmWIXFilesSourceWriter::EmitShortcut(std::string const& id, + cmWIXShortcut const& shortcut, + std::string const& shortcutPrefix, + size_t shortcutIndex) +{ + std::ostringstream shortcutId; + shortcutId << shortcutPrefix << id; + + if (shortcutIndex > 0) { + shortcutId << "_" << shortcutIndex; + } + + std::string fileId = std::string("CM_F") + id; + + BeginElement("Shortcut"); + AddAttribute("Id", shortcutId.str()); + AddAttribute("Name", shortcut.label); + std::string target = "[#" + fileId + "]"; + AddAttribute("Target", target); + AddAttribute("WorkingDirectory", shortcut.workingDirectoryId); + EndElement("Shortcut"); +} + +void cmWIXFilesSourceWriter::EmitRemoveFolder(std::string const& id) +{ + BeginElement("RemoveFolder"); + AddAttribute("Id", id); + AddAttribute("On", "uninstall"); + EndElement("RemoveFolder"); +} + +void cmWIXFilesSourceWriter::EmitInstallRegistryValue( + std::string const& registryKey, std::string const& cpackComponentName, + std::string const& suffix) +{ + std::string valueName; + if (!cpackComponentName.empty()) { + valueName = cpackComponentName + "_"; + } + + valueName += "installed"; + valueName += suffix; + + BeginElement("RegistryValue"); + AddAttribute("Root", "HKCU"); + AddAttribute("Key", registryKey); + AddAttribute("Name", valueName); + AddAttribute("Type", "integer"); + AddAttribute("Value", "1"); + AddAttribute("KeyPath", "yes"); + EndElement("RegistryValue"); +} + +void cmWIXFilesSourceWriter::EmitUninstallShortcut( + std::string const& packageName) +{ + BeginElement("Shortcut"); + AddAttribute("Id", "UNINSTALL"); + AddAttribute("Name", "Uninstall " + packageName); + AddAttribute("Description", "Uninstalls " + packageName); + AddAttribute("Target", "[SystemFolder]msiexec.exe"); + AddAttribute("Arguments", "/x [ProductCode]"); + EndElement("Shortcut"); +} + +std::string cmWIXFilesSourceWriter::EmitComponentCreateFolder( + std::string const& directoryId, std::string const& guid, + cmInstalledFile const* installedFile) +{ + std::string componentId = std::string("CM_C_EMPTY_") + directoryId; + + BeginElement("DirectoryRef"); + AddAttribute("Id", directoryId); + + BeginElement("Component"); + AddAttribute("Id", componentId); + AddAttribute("Guid", guid); + + BeginElement("CreateFolder"); + + if (installedFile) { + cmWIXAccessControlList acl(Logger, *installedFile, *this); + acl.Apply(); + } + + EndElement("CreateFolder"); + EndElement("Component"); + EndElement("DirectoryRef"); + + return 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 fileId = std::string("CM_F") + id; + + std::string guid = CreateGuidFromComponentId(componentId); + + BeginElement("DirectoryRef"); + AddAttribute("Id", directoryId); + + BeginElement("Component"); + AddAttribute("Id", componentId); + AddAttribute("Guid", guid); + + if (installedFile) { + if (installedFile->GetPropertyAsBool("CPACK_NEVER_OVERWRITE")) { + AddAttribute("NeverOverwrite", "yes"); + } + if (installedFile->GetPropertyAsBool("CPACK_PERMANENT")) { + AddAttribute("Permanent", "yes"); + } + } + + BeginElement("File"); + AddAttribute("Id", fileId); + AddAttribute("Source", filePath); + AddAttribute("KeyPath", "yes"); + + mode_t fileMode = 0; + cmSystemTools::GetPermissions(filePath.c_str(), fileMode); + + if (!(fileMode & S_IWRITE)) { + AddAttribute("ReadOnly", "yes"); + } + + if (installedFile) { + cmWIXAccessControlList acl(Logger, *installedFile, *this); + acl.Apply(); + } + + patch.ApplyFragment(fileId, *this); + EndElement("File"); + + patch.ApplyFragment(componentId, *this); + EndElement("Component"); + EndElement("DirectoryRef"); + + return componentId; +} diff --git a/Source/CPack/WiX/cmWIXFilesSourceWriter.h b/Source/CPack/WiX/cmWIXFilesSourceWriter.h new file mode 100644 index 0000000..d7a642d --- /dev/null +++ b/Source/CPack/WiX/cmWIXFilesSourceWriter.h @@ -0,0 +1,43 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXFilesSourceWriter_h +#define cmWIXFilesSourceWriter_h + +#include "cmWIXSourceWriter.h" + +#include "cmWIXPatch.h" +#include "cmWIXShortcut.h" + +#include <CPack/cmCPackGenerator.h> + +/** \class cmWIXFilesSourceWriter + * \brief Helper class to generate files.wxs + */ +class cmWIXFilesSourceWriter : public cmWIXSourceWriter +{ +public: + cmWIXFilesSourceWriter(cmCPackLog* logger, std::string const& filename, + GuidType componentGuidType); + + void EmitShortcut(std::string const& id, cmWIXShortcut const& shortcut, + std::string const& shortcutPrefix, size_t shortcutIndex); + + void EmitRemoveFolder(std::string const& id); + + void EmitInstallRegistryValue(std::string const& registryKey, + std::string const& cpackComponentName, + std::string const& suffix); + + void EmitUninstallShortcut(std::string const& packageName); + + std::string EmitComponentCreateFolder(std::string const& directoryId, + std::string const& guid, + cmInstalledFile const* installedFile); + + std::string EmitComponentFile(std::string const& directoryId, + std::string const& id, + std::string const& filePath, cmWIXPatch& patch, + cmInstalledFile const* installedFile); +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXPatch.cxx b/Source/CPack/WiX/cmWIXPatch.cxx new file mode 100644 index 0000000..7c48653 --- /dev/null +++ b/Source/CPack/WiX/cmWIXPatch.cxx @@ -0,0 +1,95 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXPatch.h" + +#include <CPack/cmCPackGenerator.h> + +cmWIXPatch::cmWIXPatch(cmCPackLog* logger) + : Logger(logger) +{ +} + +bool cmWIXPatch::LoadFragments(std::string const& patchFilePath) +{ + cmWIXPatchParser parser(Fragments, Logger); + if (!parser.ParseFile(patchFilePath.c_str())) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed parsing XML patch file: '" + << patchFilePath << "'" << std::endl); + return false; + } + + return true; +} + +void cmWIXPatch::ApplyFragment(std::string const& id, + cmWIXSourceWriter& writer) +{ + cmWIXPatchParser::fragment_map_t::iterator i = Fragments.find(id); + if (i == Fragments.end()) + return; + + const cmWIXPatchElement& fragment = i->second; + + this->ApplyElementChildren(fragment, writer); + + Fragments.erase(i); +} + +void cmWIXPatch::ApplyElementChildren(const cmWIXPatchElement& element, + cmWIXSourceWriter& writer) +{ + for (cmWIXPatchElement::child_list_t::const_iterator j = + element.children.begin(); + j != element.children.end(); ++j) { + cmWIXPatchNode* node = *j; + + switch (node->type()) { + case cmWIXPatchNode::ELEMENT: + ApplyElement(dynamic_cast<const cmWIXPatchElement&>(*node), writer); + break; + case cmWIXPatchNode::TEXT: + writer.AddTextNode(dynamic_cast<const cmWIXPatchText&>(*node).text); + break; + } + } +} + +void cmWIXPatch::ApplyElement(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); + } + + this->ApplyElementChildren(element, writer); + + writer.EndElement(element.name); +} + +bool cmWIXPatch::CheckForUnappliedFragments() +{ + 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.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Some XML patch fragments did not have matching IDs: " + << fragmentList << std::endl); + return false; + } + + return true; +} diff --git a/Source/CPack/WiX/cmWIXPatch.h b/Source/CPack/WiX/cmWIXPatch.h new file mode 100644 index 0000000..a4c9e71 --- /dev/null +++ b/Source/CPack/WiX/cmWIXPatch.h @@ -0,0 +1,37 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXPatch_h +#define cmWIXPatch_h + +#include "cmWIXPatchParser.h" +#include "cmWIXSourceWriter.h" + +#include <string> + +/** \class cmWIXPatch + * \brief Class that maintains and applies patch fragments + */ +class cmWIXPatch +{ +public: + cmWIXPatch(cmCPackLog* logger); + + bool LoadFragments(std::string const& patchFilePath); + + void ApplyFragment(std::string const& id, cmWIXSourceWriter& writer); + + bool CheckForUnappliedFragments(); + +private: + void ApplyElementChildren(const cmWIXPatchElement& element, + cmWIXSourceWriter& writer); + + void ApplyElement(const cmWIXPatchElement& element, + cmWIXSourceWriter& writer); + + cmCPackLog* Logger; + + cmWIXPatchParser::fragment_map_t Fragments; +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXPatchParser.cxx b/Source/CPack/WiX/cmWIXPatchParser.cxx new file mode 100644 index 0000000..47f98d1 --- /dev/null +++ b/Source/CPack/WiX/cmWIXPatchParser.cxx @@ -0,0 +1,146 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXPatchParser.h" + +#include <CPack/cmCPackGenerator.h> + +#include <cm_expat.h> + +cmWIXPatchNode::Type cmWIXPatchText::type() +{ + return cmWIXPatchNode::TEXT; +} + +cmWIXPatchNode::Type cmWIXPatchElement::type() +{ + return cmWIXPatchNode::ELEMENT; +} + +cmWIXPatchNode::~cmWIXPatchNode() +{ +} + +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 std::string& name, const char** atts) +{ + if (State == BEGIN_DOCUMENT) { + if (name == "CPackWiXPatch") { + State = BEGIN_FRAGMENTS; + } else { + ReportValidationError("Expected root element 'CPackWiXPatch'"); + } + } else if (State == BEGIN_FRAGMENTS) { + if (name == "CPackWiXFragment") { + State = INSIDE_FRAGMENT; + StartFragment(atts); + } else { + ReportValidationError("Expected 'CPackWixFragment' element"); + } + } else if (State == INSIDE_FRAGMENT) { + cmWIXPatchElement& parent = *ElementStack.back(); + + cmWIXPatchElement* element = new cmWIXPatchElement; + parent.children.push_back(element); + + element->name = name; + + for (size_t i = 0; atts[i]; i += 2) { + std::string key = atts[i]; + std::string value = atts[i + 1]; + + element->attributes[key] = value; + } + + ElementStack.push_back(element); + } +} + +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::ostringstream 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 std::string& name) +{ + if (State == INSIDE_FRAGMENT) { + if (name == "CPackWiXFragment") { + State = BEGIN_FRAGMENTS; + ElementStack.clear(); + } else { + ElementStack.pop_back(); + } + } +} + +void cmWIXPatchParser::CharacterDataHandler(const char* data, int length) +{ + const char* whitespace = "\x20\x09\x0d\x0a"; + + if (State == INSIDE_FRAGMENT) { + cmWIXPatchElement& parent = *ElementStack.back(); + + std::string text(data, length); + + std::string::size_type first = text.find_first_not_of(whitespace); + std::string::size_type last = text.find_last_not_of(whitespace); + + if (first != std::string::npos && last != std::string::npos) { + cmWIXPatchText* text_node = new cmWIXPatchText; + text_node->text = text.substr(first, last - first + 1); + + parent.children.push_back(text_node); + } + } +} + +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(std::string const& message) +{ + ReportError( + XML_GetCurrentLineNumber(static_cast<XML_Parser>(this->Parser)), + XML_GetCurrentColumnNumber(static_cast<XML_Parser>(this->Parser)), + message.c_str()); +} + +bool cmWIXPatchParser::IsValid() const +{ + return Valid; +} diff --git a/Source/CPack/WiX/cmWIXPatchParser.h b/Source/CPack/WiX/cmWIXPatchParser.h new file mode 100644 index 0000000..a2f0a3c --- /dev/null +++ b/Source/CPack/WiX/cmWIXPatchParser.h @@ -0,0 +1,90 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmCPackWIXPatchParser_h +#define cmCPackWIXPatchParser_h + +#include <CPack/cmCPackLog.h> + +#include <cmXMLParser.h> + +#include <list> +#include <map> + +struct cmWIXPatchNode +{ + enum Type + { + TEXT, + ELEMENT + }; + + virtual ~cmWIXPatchNode(); + + virtual Type type() = 0; +}; + +struct cmWIXPatchText : public cmWIXPatchNode +{ + virtual Type type(); + + std::string text; +}; + +struct cmWIXPatchElement : cmWIXPatchNode +{ + virtual Type type(); + + ~cmWIXPatchElement(); + + typedef std::list<cmWIXPatchNode*> 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 std::string& name, const char** atts); + + void StartFragment(const char** attributes); + + virtual void EndElement(const std::string& name); + + virtual void CharacterDataHandler(const char* data, int length); + + virtual void ReportError(int line, int column, const char* msg); + + void ReportValidationError(std::string const& 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 diff --git a/Source/CPack/WiX/cmWIXRichTextFormatWriter.cxx b/Source/CPack/WiX/cmWIXRichTextFormatWriter.cxx new file mode 100644 index 0000000..d819347 --- /dev/null +++ b/Source/CPack/WiX/cmWIXRichTextFormatWriter.cxx @@ -0,0 +1,186 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXRichTextFormatWriter.h" + +#include <cmVersion.h> + +cmWIXRichTextFormatWriter::cmWIXRichTextFormatWriter( + std::string const& filename) + : File(filename.c_str(), std::ios::binary) +{ + StartGroup(); + WriteHeader(); + WriteDocumentPrefix(); +} + +cmWIXRichTextFormatWriter::~cmWIXRichTextFormatWriter() +{ + EndGroup(); + + /* I haven't seen this in the RTF spec but + * wordpad terminates its RTF like this */ + File << "\r\n"; + File.put(0); +} + +void cmWIXRichTextFormatWriter::AddText(std::string const& text) +{ + typedef unsigned char rtf_byte_t; + + for (size_t i = 0; i < text.size(); ++i) { + rtf_byte_t c = rtf_byte_t(text[i]); + + switch (c) { + case '\\': + File << "\\\\"; + break; + case '{': + File << "\\{"; + break; + case '}': + File << "\\}"; + break; + case '\n': + File << "\\par\r\n"; + break; + case '\r': + continue; + default: { + if (c <= 0x7F) { + File << c; + } else { + if (c <= 0xC0) { + EmitInvalidCodepoint(c); + } else if (c < 0xE0 && i + 1 < text.size()) { + EmitUnicodeCodepoint((text[i + 1] & 0x3F) | ((c & 0x1F) << 6)); + i += 1; + } else if (c < 0xF0 && i + 2 < text.size()) { + EmitUnicodeCodepoint((text[i + 2] & 0x3F) | + ((text[i + 1] & 0x3F) << 6) | + ((c & 0xF) << 12)); + i += 2; + } else if (c < 0xF8 && i + 3 < text.size()) { + EmitUnicodeCodepoint( + (text[i + 3] & 0x3F) | ((text[i + 2] & 0x3F) << 6) | + ((text[i + 1] & 0x3F) << 12) | ((c & 0x7) << 18)); + i += 3; + } else { + EmitInvalidCodepoint(c); + } + } + } break; + } + } +} + +void cmWIXRichTextFormatWriter::WriteHeader() +{ + ControlWord("rtf1"); + ControlWord("ansi"); + ControlWord("ansicpg1252"); + ControlWord("deff0"); + ControlWord("deflang1031"); + + WriteFontTable(); + WriteColorTable(); + WriteGenerator(); +} + +void cmWIXRichTextFormatWriter::WriteFontTable() +{ + StartGroup(); + ControlWord("fonttbl"); + + StartGroup(); + ControlWord("f0"); + ControlWord("fswiss"); + ControlWord("fcharset0 Arial;"); + EndGroup(); + + EndGroup(); +} + +void cmWIXRichTextFormatWriter::WriteColorTable() +{ + StartGroup(); + ControlWord("colortbl ;"); + ControlWord("red255"); + ControlWord("green0"); + ControlWord("blue0;"); + ControlWord("red0"); + ControlWord("green255"); + ControlWord("blue0;"); + ControlWord("red0"); + ControlWord("green0"); + ControlWord("blue255;"); + EndGroup(); +} + +void cmWIXRichTextFormatWriter::WriteGenerator() +{ + StartGroup(); + NewControlWord("generator"); + File << " CPack WiX Generator (" << cmVersion::GetCMakeVersion() << ");"; + EndGroup(); +} + +void cmWIXRichTextFormatWriter::WriteDocumentPrefix() +{ + ControlWord("viewkind4"); + ControlWord("uc1"); + ControlWord("pard"); + ControlWord("f0"); + ControlWord("fs20"); +} + +void cmWIXRichTextFormatWriter::ControlWord(std::string const& keyword) +{ + File << "\\" << keyword; +} + +void cmWIXRichTextFormatWriter::NewControlWord(std::string const& keyword) +{ + File << "\\*\\" << keyword; +} + +void cmWIXRichTextFormatWriter::StartGroup() +{ + File.put('{'); +} + +void cmWIXRichTextFormatWriter::EndGroup() +{ + File.put('}'); +} + +void cmWIXRichTextFormatWriter::EmitUnicodeCodepoint(int c) +{ + // Do not emit byte order mark (BOM) + if (c == 0xFEFF) { + return; + } else if (c <= 0xFFFF) { + EmitUnicodeSurrogate(c); + } else { + c -= 0x10000; + EmitUnicodeSurrogate(((c >> 10) & 0x3FF) + 0xD800); + EmitUnicodeSurrogate((c & 0x3FF) + 0xDC00); + } +} + +void cmWIXRichTextFormatWriter::EmitUnicodeSurrogate(int c) +{ + ControlWord("u"); + if (c <= 32767) { + File << c; + } else { + File << (c - 65536); + } + File << "?"; +} + +void cmWIXRichTextFormatWriter::EmitInvalidCodepoint(int c) +{ + ControlWord("cf1 "); + File << "[INVALID-BYTE-" << int(c) << "]"; + ControlWord("cf0 "); +} diff --git a/Source/CPack/WiX/cmWIXRichTextFormatWriter.h b/Source/CPack/WiX/cmWIXRichTextFormatWriter.h new file mode 100644 index 0000000..a3c8394 --- /dev/null +++ b/Source/CPack/WiX/cmWIXRichTextFormatWriter.h @@ -0,0 +1,45 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXRichTextFormatWriter_h +#define cmWIXRichTextFormatWriter_h + +#include <cmConfigure.h> + +#include <cmsys/FStream.hxx> +#include <string> + +/** \class cmWIXRichtTextFormatWriter + * \brief Helper class to generate Rich Text Format (RTF) documents + * from plain text (e.g. for license and welcome text) + */ +class cmWIXRichTextFormatWriter +{ +public: + cmWIXRichTextFormatWriter(std::string const& filename); + ~cmWIXRichTextFormatWriter(); + + void AddText(std::string const& text); + +private: + void WriteHeader(); + void WriteFontTable(); + void WriteColorTable(); + void WriteGenerator(); + + void WriteDocumentPrefix(); + + void ControlWord(std::string const& keyword); + void NewControlWord(std::string const& keyword); + + void StartGroup(); + void EndGroup(); + + void EmitUnicodeCodepoint(int c); + void EmitUnicodeSurrogate(int c); + + void EmitInvalidCodepoint(int c); + + cmsys::ofstream File; +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXShortcut.cxx b/Source/CPack/WiX/cmWIXShortcut.cxx new file mode 100644 index 0000000..e5dea94 --- /dev/null +++ b/Source/CPack/WiX/cmWIXShortcut.cxx @@ -0,0 +1,105 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXShortcut.h" + +#include "cmWIXFilesSourceWriter.h" + +void cmWIXShortcuts::insert(Type type, std::string const& id, + cmWIXShortcut const& shortcut) +{ + this->Shortcuts[type][id].push_back(shortcut); +} + +bool cmWIXShortcuts::empty(Type type) const +{ + return this->Shortcuts.find(type) == this->Shortcuts.end(); +} + +bool cmWIXShortcuts::EmitShortcuts( + Type type, std::string const& registryKey, + std::string const& cpackComponentName, + cmWIXFilesSourceWriter& fileDefinitions) const +{ + shortcut_type_map_t::const_iterator i = this->Shortcuts.find(type); + + if (i == this->Shortcuts.end()) { + return false; + } + + shortcut_id_map_t const& id_map = i->second; + + std::string shortcutPrefix; + std::string registrySuffix; + + switch (type) { + case START_MENU: + shortcutPrefix = "CM_S"; + break; + case DESKTOP: + shortcutPrefix = "CM_DS"; + registrySuffix = "_desktop"; + break; + case STARTUP: + shortcutPrefix = "CM_SS"; + registrySuffix = "_startup"; + break; + default: + return false; + } + + for (shortcut_id_map_t::const_iterator j = id_map.begin(); j != id_map.end(); + ++j) { + std::string const& id = j->first; + shortcut_list_t const& shortcutList = j->second; + + for (size_t shortcutListIndex = 0; shortcutListIndex < shortcutList.size(); + ++shortcutListIndex) { + cmWIXShortcut const& shortcut = shortcutList[shortcutListIndex]; + fileDefinitions.EmitShortcut(id, shortcut, shortcutPrefix, + shortcutListIndex); + } + } + + fileDefinitions.EmitInstallRegistryValue(registryKey, cpackComponentName, + registrySuffix); + + return true; +} + +void cmWIXShortcuts::AddShortcutTypes(std::set<Type>& types) +{ + for (shortcut_type_map_t::const_iterator i = this->Shortcuts.begin(); + i != this->Shortcuts.end(); ++i) { + types.insert(i->first); + } +} + +void cmWIXShortcuts::CreateFromProperties(std::string const& id, + std::string const& directoryId, + cmInstalledFile const& installedFile) +{ + CreateFromProperty("CPACK_START_MENU_SHORTCUTS", START_MENU, id, directoryId, + installedFile); + + CreateFromProperty("CPACK_DESKTOP_SHORTCUTS", DESKTOP, id, directoryId, + installedFile); + + CreateFromProperty("CPACK_STARTUP_SHORTCUTS", STARTUP, id, directoryId, + installedFile); +} + +void cmWIXShortcuts::CreateFromProperty(std::string const& propertyName, + Type type, std::string const& id, + std::string const& directoryId, + cmInstalledFile const& installedFile) +{ + std::vector<std::string> list; + installedFile.GetPropertyAsList(propertyName, list); + + for (size_t i = 0; i < list.size(); ++i) { + cmWIXShortcut shortcut; + shortcut.label = list[i]; + shortcut.workingDirectoryId = directoryId; + insert(type, id, shortcut); + } +} diff --git a/Source/CPack/WiX/cmWIXShortcut.h b/Source/CPack/WiX/cmWIXShortcut.h new file mode 100644 index 0000000..cba3b34 --- /dev/null +++ b/Source/CPack/WiX/cmWIXShortcut.h @@ -0,0 +1,60 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXShortcut_h +#define cmWIXShortcut_h + +#include <cmInstalledFile.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +class cmWIXFilesSourceWriter; + +struct cmWIXShortcut +{ + std::string label; + std::string workingDirectoryId; +}; + +class cmWIXShortcuts +{ +public: + enum Type + { + START_MENU, + DESKTOP, + STARTUP + }; + + typedef std::vector<cmWIXShortcut> shortcut_list_t; + typedef std::map<std::string, shortcut_list_t> shortcut_id_map_t; + + void insert(Type type, std::string const& id, cmWIXShortcut const& shortcut); + + bool empty(Type type) const; + + bool EmitShortcuts(Type type, std::string const& registryKey, + std::string const& cpackComponentName, + cmWIXFilesSourceWriter& fileDefinitions) const; + + void AddShortcutTypes(std::set<Type>& types); + + void CreateFromProperties(std::string const& id, + std::string const& directoryId, + cmInstalledFile const& installedFile); + +private: + typedef std::map<Type, shortcut_id_map_t> shortcut_type_map_t; + + void CreateFromProperty(std::string const& propertyName, Type type, + std::string const& id, + std::string const& directoryId, + cmInstalledFile const& installedFile); + + shortcut_type_map_t Shortcuts; + shortcut_id_map_t EmptyIdMap; +}; + +#endif diff --git a/Source/CPack/WiX/cmWIXSourceWriter.cxx b/Source/CPack/WiX/cmWIXSourceWriter.cxx new file mode 100644 index 0000000..b434334 --- /dev/null +++ b/Source/CPack/WiX/cmWIXSourceWriter.cxx @@ -0,0 +1,184 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmWIXSourceWriter.h" + +#include <CPack/cmCPackGenerator.h> + +#include <cmUuid.h> + +#include <windows.h> + +cmWIXSourceWriter::cmWIXSourceWriter(cmCPackLog* logger, + std::string const& filename, + GuidType componentGuidType, + RootElementType rootElementType) + : Logger(logger) + , File(filename.c_str()) + , State(DEFAULT) + , SourceFilename(filename) + , ComponentGuidType(componentGuidType) +{ + WriteXMLDeclaration(); + + if (rootElementType == INCLUDE_ELEMENT_ROOT) { + BeginElement("Include"); + } else { + BeginElement("Wix"); + } + + AddAttribute("xmlns", "http://schemas.microsoft.com/wix/2006/wi"); +} + +cmWIXSourceWriter::~cmWIXSourceWriter() +{ + if (Elements.size() > 1) { + cmCPackLogger(cmCPackLog::LOG_ERROR, Elements.size() - 1 + << " WiX elements were still open when closing '" + << SourceFilename << "'" << std::endl); + return; + } + + EndElement(Elements.back()); +} + +void cmWIXSourceWriter::BeginElement(std::string const& name) +{ + if (State == BEGIN) { + File << ">"; + } + + File << "\n"; + Indent(Elements.size()); + File << "<" << name; + + Elements.push_back(name); + State = BEGIN; +} + +void cmWIXSourceWriter::EndElement(std::string const& name) +{ + if (Elements.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "can not end WiX element with no open elements in '" + << SourceFilename << "'" << std::endl); + return; + } + + if (Elements.back() != name) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "WiX element <" + << Elements.back() << "> can not be closed by </" << name + << "> in '" << SourceFilename << "'" << std::endl); + return; + } + + if (State == DEFAULT) { + File << "\n"; + Indent(Elements.size() - 1); + File << "</" << Elements.back() << ">"; + } else { + File << "/>"; + } + + Elements.pop_back(); + State = DEFAULT; +} + +void cmWIXSourceWriter::AddTextNode(std::string const& text) +{ + if (State == BEGIN) { + File << ">"; + } + + if (Elements.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "can not add text without open WiX element in '" + << SourceFilename << "'" << std::endl); + return; + } + + File << this->EscapeAttributeValue(text); + State = DEFAULT; +} + +void cmWIXSourceWriter::AddProcessingInstruction(std::string const& target, + std::string const& content) +{ + if (State == BEGIN) { + File << ">"; + } + + File << "\n"; + Indent(Elements.size()); + File << "<?" << target << " " << content << "?>"; + + State = DEFAULT; +} + +void cmWIXSourceWriter::AddAttribute(std::string const& key, + std::string const& value) +{ + File << " " << key << "=\"" << EscapeAttributeValue(value) << '"'; +} + +void cmWIXSourceWriter::AddAttributeUnlessEmpty(std::string const& key, + std::string const& value) +{ + if (!value.empty()) { + AddAttribute(key, value); + } +} + +std::string cmWIXSourceWriter::CreateGuidFromComponentId( + std::string const& componentId) +{ + std::string guid = "*"; + if (this->ComponentGuidType == CMAKE_GENERATED_GUID) { + std::string md5 = cmSystemTools::ComputeStringMD5(componentId); + cmUuid uuid; + std::vector<unsigned char> ns; + guid = uuid.FromMd5(ns, md5); + } + return guid; +} + +void cmWIXSourceWriter::WriteXMLDeclaration() +{ + File << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl; +} + +void cmWIXSourceWriter::Indent(size_t count) +{ + for (size_t i = 0; i < count; ++i) { + File << " "; + } +} + +std::string cmWIXSourceWriter::EscapeAttributeValue(std::string const& value) +{ + std::string result; + result.reserve(value.size()); + + char c = 0; + for (size_t i = 0; i < value.size(); ++i) { + c = value[i]; + switch (c) { + case '<': + result += "<"; + break; + case '>': + result += ">"; + break; + case '&': + result += "&"; + break; + case '"': + result += """; + break; + default: + result += c; + break; + } + } + + return result; +} diff --git a/Source/CPack/WiX/cmWIXSourceWriter.h b/Source/CPack/WiX/cmWIXSourceWriter.h new file mode 100644 index 0000000..45aefe5 --- /dev/null +++ b/Source/CPack/WiX/cmWIXSourceWriter.h @@ -0,0 +1,80 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmWIXSourceWriter_h +#define cmWIXSourceWriter_h + +#include <CPack/cmCPackLog.h> + +#include <cmsys/FStream.hxx> + +#include <string> +#include <vector> + +/** \class cmWIXSourceWriter + * \brief Helper class to generate XML WiX source files + */ +class cmWIXSourceWriter +{ +public: + enum GuidType + { + WIX_GENERATED_GUID, + CMAKE_GENERATED_GUID + }; + + enum RootElementType + { + WIX_ELEMENT_ROOT, + INCLUDE_ELEMENT_ROOT + }; + + cmWIXSourceWriter(cmCPackLog* logger, std::string const& filename, + GuidType componentGuidType, + RootElementType rootElementType = WIX_ELEMENT_ROOT); + + ~cmWIXSourceWriter(); + + void BeginElement(std::string const& name); + + void EndElement(std::string const& name); + + void AddTextNode(std::string const& text); + + void AddProcessingInstruction(std::string const& target, + std::string const& content); + + void AddAttribute(std::string const& key, std::string const& value); + + void AddAttributeUnlessEmpty(std::string const& key, + std::string const& value); + + std::string CreateGuidFromComponentId(std::string const& componentId); + +protected: + cmCPackLog* Logger; + +private: + enum State + { + DEFAULT, + BEGIN + }; + + void WriteXMLDeclaration(); + + void Indent(size_t count); + + static std::string EscapeAttributeValue(std::string const& value); + + cmsys::ofstream File; + + State State; + + std::vector<std::string> Elements; + + std::string SourceFilename; + + GuidType ComponentGuidType; +}; + +#endif |