summaryrefslogtreecommitdiffstats
path: root/Source/CPack/cmCPackPackageMakerGenerator.cxx
diff options
context:
space:
mode:
authorDavid Cole <david.cole@kitware.com>2008-06-17 15:39:26 (GMT)
committerDavid Cole <david.cole@kitware.com>2008-06-17 15:39:26 (GMT)
commit1105a86c520d34a59b482b86b6f288c11ff45b81 (patch)
tree6814d23047d44fe8c2c3d18e40b98143aca186c4 /Source/CPack/cmCPackPackageMakerGenerator.cxx
parent64498a1287e13e9f7ad3488c1050318030b18f8d (diff)
downloadCMake-1105a86c520d34a59b482b86b6f288c11ff45b81.zip
CMake-1105a86c520d34a59b482b86b6f288c11ff45b81.tar.gz
CMake-1105a86c520d34a59b482b86b6f288c11ff45b81.tar.bz2
ENH: Add patch for feature request #6847 - CPack components for NSIS and PackageMaker installers. Thanks to Doug Gregor for all the hard work involved with implementing this patch! Also added new test CPackComponents that is conditionally executed only when NSIS or PackageMaker installer builders are available.
Diffstat (limited to 'Source/CPack/cmCPackPackageMakerGenerator.cxx')
-rw-r--r--Source/CPack/cmCPackPackageMakerGenerator.cxx518
1 files changed, 461 insertions, 57 deletions
diff --git a/Source/CPack/cmCPackPackageMakerGenerator.cxx b/Source/CPack/cmCPackPackageMakerGenerator.cxx
index a5884a9..e4a003d 100644
--- a/Source/CPack/cmCPackPackageMakerGenerator.cxx
+++ b/Source/CPack/cmCPackPackageMakerGenerator.cxx
@@ -22,6 +22,7 @@
#include "cmSystemTools.h"
#include "cmMakefile.h"
#include "cmGeneratedFileStream.h"
+#include "cmCPackComponentGroup.h"
#include "cmCPackLog.h"
#include <cmsys/SystemTools.hxx>
@@ -38,6 +39,13 @@ cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator()
{
}
+//----------------------------------------------------------------------
+bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const
+{
+ return true;
+}
+
+//----------------------------------------------------------------------
int cmCPackPackageMakerGenerator::CopyInstallScript(const char* resdir,
const char* script,
const char* name)
@@ -96,23 +104,59 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName,
// them executable
if(preflight)
{
- this->CopyInstallScript(resDir.c_str(),
- preflight,
- "preflight");
+ this->CopyInstallScript(resDir.c_str(),
+ preflight,
+ "preflight");
}
if(postflight)
{
- this->CopyInstallScript(resDir.c_str(),
- postflight,
- "postflight");
+ this->CopyInstallScript(resDir.c_str(),
+ postflight,
+ "postflight");
}
if(postupgrade)
{
- this->CopyInstallScript(resDir.c_str(),
- postupgrade,
- "postupgrade");
+ this->CopyInstallScript(resDir.c_str(),
+ postupgrade,
+ "postupgrade");
}
+ if (!this->Components.empty())
+ {
+ // Create the directory where component packages will be installed.
+ std::string basePackageDir = toplevel;
+ basePackageDir += "/packages";
+ if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str()))
+ {
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "Problem creating component packages directory: "
+ << basePackageDir.c_str() << std::endl);
+ return 0;
+ }
+
+ // Create packages for each component
+ std::map<std::string, cmCPackComponent>::iterator compIt;
+ for (compIt = this->Components.begin(); compIt != this->Components.end();
+ ++compIt)
+ {
+ std::string packageFile = basePackageDir;
+ packageFile += '/';
+ packageFile += GetPackageName(compIt->second);
+
+ std::string packageDir = toplevel;
+ packageDir += '/';
+ packageDir += compIt->first;
+ if (!this->GenerateComponentPackage(packageFile.c_str(),
+ packageDir.c_str(),
+ compIt->second))
+ {
+ return 0;
+ }
+ }
+ }
+ this->SetOption("CPACK_MODULE_VERSION_SUFFIX", "");
+
+ // Copy or create all of the resource files we need.
if ( !this->CopyCreateResourceFile("License")
|| !this->CopyCreateResourceFile("ReadMe")
|| !this->CopyCreateResourceFile("Welcome")
@@ -126,68 +170,58 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName,
std::string packageDirFileName
= this->GetOption("CPACK_TEMPORARY_DIRECTORY");
- packageDirFileName += ".pkg";
+ if (this->Components.empty())
+ {
+ packageDirFileName += ".pkg";
+ }
+ else
+ {
+ packageDirFileName += ".mpkg";
+ if (this->PackageMakerVersion == 3.0)
+ {
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "PackageMaker 3.0 cannot build component-based installations."
+ << std::endl << "Please use PackageMaker 2.5 instead." << std::endl);
+ }
+ }
- std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
- tmpFile += "/PackageMakerOutput.log";
cmOStringStream pkgCmd;
pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
- << "\" -build -p \"" << packageDirFileName << "\" -f \""
- << this->GetOption("CPACK_TEMPORARY_DIRECTORY")
- << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
- << "/Resources\" -i \""
- << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Info.plist\" -d \""
- << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Description.plist\"";
- if ( this->PackageMakerVersion > 2.0 )
+ << "\" -build -p \"" << packageDirFileName << "\"";
+ if (this->Components.empty())
{
- pkgCmd << " -v";
+ pkgCmd << " -f \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY");
}
- cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << pkgCmd.str().c_str()
- << std::endl);
- std::string output;
- int retVal = 1;
- //bool res = cmSystemTools::RunSingleCommand(pkgCmd.str().c_str(), &output,
- //&retVal, 0, this->GeneratorVerbose, 0);
- bool res = true;
- retVal = system(pkgCmd.str().c_str());
- cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running package maker"
- << std::endl);
- if ( !res || retVal )
+ else
{
- cmGeneratedFileStream ofs(tmpFile.c_str());
- ofs << "# Run command: " << pkgCmd.str().c_str() << std::endl
- << "# Output:" << std::endl
- << output.c_str() << std::endl;
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Problem running PackageMaker command: " << pkgCmd.str().c_str()
- << std::endl << "Please check " << tmpFile.c_str() << " for errors"
- << std::endl);
- return 0;
+ pkgCmd << " -mi \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY")
+ << "/packages/";
}
- // sometimes the pkgCmd finishes but the directory is not yet
- // created, so try 10 times to see if it shows up
- int tries = 10;
- while(tries > 0 &&
- !cmSystemTools::FileExists(packageDirFileName.c_str()))
+ pkgCmd << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
+ << "/Resources\" -i \""
+ << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Info.plist\" -d \""
+ << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Description.plist\"";
+ if ( this->PackageMakerVersion > 2.0 )
{
- cmSystemTools::Delay(500);
- tries--;
+ pkgCmd << " -v";
}
- if(!cmSystemTools::FileExists(packageDirFileName.c_str()))
+ if (!RunPackageMaker(pkgCmd.str().c_str(), packageDirFileName.c_str()))
+ return 0;
+
+ if (!this->Components.empty())
{
- cmCPackLogger(
- cmCPackLog::LOG_ERROR,
- "Problem running PackageMaker command: " << pkgCmd.str().c_str()
- << std::endl << "Package not created: " << packageDirFileName.c_str()
- << std::endl);
+ WriteDistributionFile(packageDirFileName.c_str());
}
- tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
+
+ std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
tmpFile += "/hdiutilOutput.log";
cmOStringStream dmgCmd;
dmgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM_DISK_IMAGE")
<< "\" create -ov -format UDZO -srcfolder \"" << packageDirFileName
<< "\" \"" << outFileName << "\"";
- res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output,
+ std::string output;
+ int retVal = 1;
+ bool res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output,
&retVal, 0, this->GeneratorVerbose, 0);
if ( !res || retVal )
{
@@ -339,8 +373,14 @@ bool cmCPackPackageMakerGenerator::CopyCreateResourceFile(const char* name)
return true;
}
-bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name)
+bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name,
+ const char* outName)
{
+ if (!outName)
+ {
+ outName = name;
+ }
+
std::string inFName = "CPack.";
inFName += name;
inFName += ".in";
@@ -354,10 +394,374 @@ bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name)
std::string destFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
destFileName += "/";
- destFileName += name;
+ destFileName += outName;
cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: "
<< inFileName.c_str() << " to " << destFileName.c_str() << std::endl);
this->ConfigureFile(inFileName.c_str(), destFileName.c_str());
return true;
}
+
+//----------------------------------------------------------------------
+bool cmCPackPackageMakerGenerator::RunPackageMaker(const char *command,
+ const char *packageFile)
+{
+ std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
+ tmpFile += "/PackageMakerOutput.log";
+
+ cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl);
+ std::string output;
+ int retVal = 1;
+ bool res = cmSystemTools::RunSingleCommand(command, &output, &retVal, 0,
+ this->GeneratorVerbose, 0);
+ cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running package maker"
+ << std::endl);
+ if ( !res || retVal )
+ {
+ cmGeneratedFileStream ofs(tmpFile.c_str());
+ ofs << "# Run command: " << command << std::endl
+ << "# Output:" << std::endl
+ << output.c_str() << std::endl;
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "Problem running PackageMaker command: " << command
+ << std::endl << "Please check " << tmpFile.c_str() << " for errors"
+ << std::endl);
+ return false;
+ }
+ // sometimes the command finishes but the directory is not yet
+ // created, so try 10 times to see if it shows up
+ int tries = 10;
+ while(tries > 0 &&
+ !cmSystemTools::FileExists(packageFile))
+ {
+ cmSystemTools::Delay(500);
+ tries--;
+ }
+ if(!cmSystemTools::FileExists(packageFile))
+ {
+ cmCPackLogger(
+ cmCPackLog::LOG_ERROR,
+ "Problem running PackageMaker command: " << command
+ << std::endl << "Package not created: " << packageFile
+ << std::endl);
+ return false;
+ }
+
+ return true;
+}
+
+//----------------------------------------------------------------------
+std::string
+cmCPackPackageMakerGenerator::GetPackageName(const cmCPackComponent& component)
+{
+ std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
+ packagesDir += ".dummy";
+ cmOStringStream out;
+ out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir)
+ << "-" << component.Name << ".pkg";
+ return out.str();
+}
+
+//----------------------------------------------------------------------
+bool
+cmCPackPackageMakerGenerator::
+GenerateComponentPackage(const char *packageFile,
+ const char *packageDir,
+ const cmCPackComponent& component)
+{
+ cmCPackLogger(cmCPackLog::LOG_OUTPUT,
+ "- Building component package: " << packageFile << std::endl);
+
+ // Create the description file for this component.
+ std::string descriptionFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
+ descriptionFile += '/' + component.Name + "-Description.plist";
+ std::ofstream out(descriptionFile.c_str());
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl
+ << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\""
+ << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << std::endl
+ << "<plist version=\"1.4\">" << std::endl
+ << "<dict>" << std::endl
+ << " <key>IFPkgDescriptionTitle</key>" << std::endl
+ << " <string>" << component.DisplayName << "</string>" << std::endl
+ << " <key>IFPkgDescriptionVersion</key>" << std::endl
+ << " <string>" << this->GetOption("CPACK_PACKAGE_VERSION")
+ << "</string>" << std::endl
+ << " <key>IFPkgDescriptionDescription</key>" << std::endl
+ << " <string>" + this->EscapeForXML(component.Description)
+ << "</string>" << std::endl
+ << "</dict>" << std::endl
+ << "</plist>" << std::endl;
+ out.close();
+
+ // Create the Info.plist file for this component
+ std::string moduleVersionSuffix = ".";
+ moduleVersionSuffix += component.Name;
+ this->SetOption("CPACK_MODULE_VERSION_SUFFIX", moduleVersionSuffix.c_str());
+ std::string infoFileName = component.Name;
+ infoFileName += "-Info.plist";
+ if (!this->CopyResourcePlistFile("Info.plist", infoFileName.c_str()))
+ {
+ return false;
+ }
+
+ // Run PackageMaker
+ cmOStringStream pkgCmd;
+ pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
+ << "\" -build -p \"" << packageFile << "\""
+ << " -f \"" << packageDir << "\""
+ << "-i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
+ << "/" << infoFileName << "\""
+ << "-d \"" << descriptionFile << "\"";
+ return RunPackageMaker(pkgCmd.str().c_str(), packageFile);
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::
+WriteDistributionFile(const char* metapackageFile)
+{
+ std::string distributionTemplate
+ = this->FindTemplate("CPack.distribution.dist.in");
+ if ( distributionTemplate.empty() )
+ {
+ cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find input file: "
+ << distributionTemplate << std::endl);
+ return;
+ }
+
+ std::string distributionFile = metapackageFile;
+ distributionFile += "/Contents/distribution.dist";
+
+ // Create the choice outline, which provides a tree-based view of
+ // the components in their groups.
+ cmOStringStream choiceOut;
+ choiceOut << "<choices-outline>" << std::endl;
+
+ // Emit the outline for the groups
+ std::map<std::string, cmCPackComponentGroup>::iterator groupIt;
+ for (groupIt = this->ComponentGroups.begin();
+ groupIt != this->ComponentGroups.end();
+ ++groupIt)
+ {
+ CreateChoiceOutline(groupIt->second, choiceOut);
+ }
+
+ // Emit the outline for the non-grouped components
+ std::map<std::string, cmCPackComponent>::iterator compIt;
+ for (compIt = this->Components.begin(); compIt != this->Components.end();
+ ++compIt)
+ {
+ if (!compIt->second.Group)
+ {
+ choiceOut << "<line choice=\"" << compIt->first << "Choice\"></line>"
+ << std::endl;
+ }
+ }
+ choiceOut << "</choices-outline>" << std::endl;
+
+ // Create the actual choices
+ for (groupIt = this->ComponentGroups.begin();
+ groupIt != this->ComponentGroups.end();
+ ++groupIt)
+ {
+ CreateChoice(groupIt->second, choiceOut);
+ }
+ for (compIt = this->Components.begin(); compIt != this->Components.end();
+ ++compIt)
+ {
+ CreateChoice(compIt->second, choiceOut);
+ }
+ this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str().c_str());
+
+ // Create the distribution.dist file in the metapackage to turn it
+ // into a distribution package.
+ this->ConfigureFile(distributionTemplate.c_str(),
+ distributionFile.c_str());
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::
+CreateChoiceOutline(const cmCPackComponentGroup& group, cmOStringStream& out)
+{
+ out << "<line choice=\"" << group.Name << "Choice\">" << std::endl;
+ std::vector<cmCPackComponent*>::const_iterator compIt;
+ for (compIt = group.Components.begin(); compIt != group.Components.end();
+ ++compIt)
+ {
+ out << " <line choice=\"" << (*compIt)->Name << "Choice\"></line>"
+ << std::endl;
+ }
+ out << "</line>" << std::endl;
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponentGroup& group,
+ cmOStringStream& out)
+{
+ out << "<choice id=\"" << group.Name << "Choice\" "
+ << "title=\"" << group.DisplayName << "\" "
+ << "start_selected=\"true\" "
+ << "start_enabled=\"true\" "
+ << "start_visible=\"true\" ";
+ if (!group.Description.empty())
+ {
+ out << "description=\"" << EscapeForXML(group.Description)
+ << "\"";
+ }
+ out << "></choice>" << std::endl;
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponent& component,
+ cmOStringStream& out)
+{
+ std::string packageId = "com.";
+ packageId += this->GetOption("CPACK_PACKAGE_VENDOR");
+ packageId += '.';
+ packageId += this->GetOption("CPACK_PACKAGE_NAME");
+ packageId += '.';
+ packageId += this->GetOption("CPACK_PACKAGE_VERSION");
+ packageId += '.';
+ packageId += component.Name;
+
+ out << "<choice id=\"" << component.Name << "Choice\" "
+ << "title=\"" << component.DisplayName << "\" "
+ << "start_selected=\""
+ << (component.IsDisabledByDefault && !component.IsRequired? "false" : "true")
+ << "\" "
+ << "start_enabled=\""
+ << (component.IsRequired? "false" : "true")
+ << "\" "
+ << "start_visible=\"" << (component.IsHidden? "false" : "true") << "\" ";
+ if (!component.Description.empty())
+ {
+ out << "description=\"" << EscapeForXML(component.Description)
+ << "\" ";
+ }
+ if (!component.Dependencies.empty() || !component.ReverseDependencies.empty())
+ {
+ // The "selected" expression is evaluated each time any choice is
+ // selected, for all choices *except* the one that the user
+ // selected. A component is marked selected if it has been
+ // selected (my.choice.selected in Javascript) and all of the
+ // components it depends on have been selected (transitively) or
+ // if any of the components that depend on it have been selected
+ // (transitively). Assume that we have components A, B, C, D, and
+ // E, where each component depends on the previous component (B
+ // depends on A, C depends on B, D depends on C, and E depends on
+ // D). The expression we build for the component C will be
+ // my.choice.selected && B && A || D || E
+ // This way, selecting C will automatically select everything it depends
+ // on (B and A), while selecting something that depends on C--either D
+ // or E--will automatically cause C to get selected.
+ out << "selected=\"my.choice.selected";
+ AddDependencyAttributes(component, out);
+ AddReverseDependencyAttributes(component, out);
+ out << "\"";
+ }
+ out << ">" << std::endl;
+ out << " <pkg-ref id=\"" << packageId << "\"></pkg-ref>" << std::endl;
+ out << "</choice>" << std::endl;
+
+ // Create a description of the package associated with this
+ // component.
+ std::string relativePackageLocation = "Contents/Packages/";
+ relativePackageLocation += GetPackageName(component);
+
+ // Determine the installed size of the package. To do so, we dig
+ // into the Info.plist file from the generated package to retrieve
+ // this size.
+ int installedSize = 0;
+ std::string infoPlistFile = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
+ infoPlistFile += ".mpkg/";
+ infoPlistFile += relativePackageLocation;
+ infoPlistFile += "/Contents/Info.plist";
+ bool foundFlagInstalledSize = false;
+ std::string line;
+ std::ifstream ifs(infoPlistFile.c_str());
+ while ( cmSystemTools::GetLineFromStream(ifs, line) )
+ {
+ if (foundFlagInstalledSize)
+ {
+ std::string::size_type pos = line.find("<integer>");
+ if (pos == std::string::npos)
+ {
+ cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot parse package size in "
+ << infoPlistFile << std::endl
+ << "String is \"" << line << "\"" << std::endl);
+ }
+ else
+ {
+ line.erase(0, pos + 9);
+ pos = line.find("</integer>");
+ if (pos == std::string::npos)
+ {
+ cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot parse package size in "
+ << infoPlistFile << std::endl);
+ }
+ else
+ {
+ line.erase(pos, std::string::npos);
+ installedSize = atoi(line.c_str());
+ }
+ }
+ foundFlagInstalledSize = false;
+ }
+ else
+ {
+ foundFlagInstalledSize
+ = line.find("IFPkgFlagInstalledSize") != std::string::npos;
+ }
+ }
+
+
+ out << "<pkg-ref id=\"" << packageId << "\" "
+ << "version=\"" << this->GetOption("CPACK_PACKAGE_VERSION") << "\" "
+ << "installKBytes=\"" << installedSize << "\" "
+ << "auth=\"Admin\" onConclusion=\"None\">"
+ << "file:./" << relativePackageLocation << "</pkg-ref>" << std::endl;
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::
+AddDependencyAttributes(const cmCPackComponent& component, cmOStringStream& out)
+{
+ std::vector<cmCPackComponent *>::const_iterator dependIt;
+ for (dependIt = component.Dependencies.begin();
+ dependIt != component.Dependencies.end();
+ ++dependIt)
+ {
+ out << " &amp;&amp; choices['" << (*dependIt)->Name << "Choice'].selected";
+ AddDependencyAttributes(**dependIt, out);
+ }
+}
+
+//----------------------------------------------------------------------
+void
+cmCPackPackageMakerGenerator::
+AddReverseDependencyAttributes(const cmCPackComponent& component,
+ cmOStringStream& out)
+{
+ std::vector<cmCPackComponent *>::const_iterator dependIt;
+ for (dependIt = component.ReverseDependencies.begin();
+ dependIt != component.ReverseDependencies.end();
+ ++dependIt)
+ {
+ out << " || choices['" << (*dependIt)->Name << "Choice'].selected";
+ AddReverseDependencyAttributes(**dependIt, out);
+ }
+}
+
+//----------------------------------------------------------------------
+std::string cmCPackPackageMakerGenerator::EscapeForXML(std::string str)
+{
+ cmSystemTools::ReplaceString(str, "&", "&amp;");
+ cmSystemTools::ReplaceString(str, "<", "&lt;");
+ cmSystemTools::ReplaceString(str, ">", "&gt;");
+ cmSystemTools::ReplaceString(str, "\"", "&quot;");
+ return str;
+}