CMake/Source/CPack/cmCPackPackageMakerGenerator.cxx
David Cole 05a76d53c0 CPack: Fix retry logic when calls to hdiutil fail
The long-standing sporadic failures of CPack tests on the Mac dashboards
are caused by an occasional problem running hdiutil. To compensate for
this, a retry loop was added in the code in a previous commit: a9fa71a4
... but the logic for breaking out of the retry loop was flawed, breaking
out of the loop (and not retrying) when the hdiutil command returns an
error instead of when it returns success.

This commit fixes the flawed logic, bumps up the number of retries from
4 to 10, and adds a half-second delay in between retries.

The delay is specifically added in case a virus checker or spotlight indexer
is temporarily causing the hdiutil failure by hanging onto a newly created
file longer than hdiutil expects it to.

As with all sporadically occurring issues, we'll never know if this is
really fixed all the way. But I'll be happy even if we can only get it to
happen just a bit less often.
2012-03-09 11:39:01 -05:00

942 lines
32 KiB
C++

/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmCPackPackageMakerGenerator.h"
#include "cmake.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmSystemTools.h"
#include "cmMakefile.h"
#include "cmGeneratedFileStream.h"
#include "cmCPackComponentGroup.h"
#include "cmCPackLog.h"
#include <cmsys/SystemTools.hxx>
#include <cmsys/Glob.hxx>
//----------------------------------------------------------------------
cmCPackPackageMakerGenerator::cmCPackPackageMakerGenerator()
{
this->PackageMakerVersion = 0.0;
this->PackageCompatibilityVersion = 10.4;
}
//----------------------------------------------------------------------
cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator()
{
}
//----------------------------------------------------------------------
bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const
{
return this->PackageCompatibilityVersion >= 10.4;
}
//----------------------------------------------------------------------
int cmCPackPackageMakerGenerator::CopyInstallScript(const char* resdir,
const char* script,
const char* name)
{
std::string dst = resdir;
dst += "/";
dst += name;
cmSystemTools::CopyFileAlways(script, dst.c_str());
cmSystemTools::SetPermissions(dst.c_str(),0777);
cmCPackLogger(cmCPackLog::LOG_VERBOSE,
"copy script : " << script << "\ninto " << dst.c_str() <<
std::endl);
return 1;
}
//----------------------------------------------------------------------
int cmCPackPackageMakerGenerator::PackageFiles()
{
// TODO: Use toplevel
// It is used! Is this an obsolete comment?
std::string resDir; // Where this package's resources will go.
std::string packageDirFileName
= this->GetOption("CPACK_TEMPORARY_DIRECTORY");
if (this->Components.empty())
{
packageDirFileName += ".pkg";
resDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
resDir += "/Resources";
}
else
{
packageDirFileName += ".mpkg";
if ( !cmsys::SystemTools::MakeDirectory(packageDirFileName.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"unable to create package directory "
<< packageDirFileName << std::endl);
return 0;
}
resDir = packageDirFileName;
resDir += "/Contents";
if ( !cmsys::SystemTools::MakeDirectory(resDir.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"unable to create package subdirectory " << resDir
<< std::endl);
return 0;
}
resDir += "/Resources";
if ( !cmsys::SystemTools::MakeDirectory(resDir.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"unable to create package subdirectory " << resDir
<< std::endl);
return 0;
}
resDir += "/en.lproj";
}
// Create directory structure
std::string preflightDirName = resDir + "/PreFlight";
std::string postflightDirName = resDir + "/PostFlight";
const char* preflight = this->GetOption("CPACK_PREFLIGHT_SCRIPT");
const char* postflight = this->GetOption("CPACK_POSTFLIGHT_SCRIPT");
const char* postupgrade = this->GetOption("CPACK_POSTUPGRADE_SCRIPT");
// if preflight or postflight scripts not there create directories
// of the same name, I think this makes it work
if(!preflight)
{
if ( !cmsys::SystemTools::MakeDirectory(preflightDirName.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Problem creating installer directory: "
<< preflightDirName.c_str() << std::endl);
return 0;
}
}
if(!postflight)
{
if ( !cmsys::SystemTools::MakeDirectory(postflightDirName.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Problem creating installer directory: "
<< postflightDirName.c_str() << std::endl);
return 0;
}
}
// if preflight, postflight, or postupgrade are set
// then copy them into the resource directory and make
// them executable
if(preflight)
{
this->CopyInstallScript(resDir.c_str(),
preflight,
"preflight");
}
if(postflight)
{
this->CopyInstallScript(resDir.c_str(),
postflight,
"postflight");
}
if(postupgrade)
{
this->CopyInstallScript(resDir.c_str(),
postupgrade,
"postupgrade");
}
if (!this->Components.empty())
{
// Create the directory where component packages will be built.
std::string basePackageDir = packageDirFileName;
basePackageDir += "/Contents/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 the directory where downloaded component packages will
// be placed.
const char* userUploadDirectory =
this->GetOption("CPACK_UPLOAD_DIRECTORY");
std::string uploadDirectory;
if (userUploadDirectory && *userUploadDirectory)
{
uploadDirectory = userUploadDirectory;
}
else
{
uploadDirectory= this->GetOption("CPACK_PACKAGE_DIRECTORY");
uploadDirectory += "/CPackUploads";
}
// Create packages for each component
bool warnedAboutDownloadCompatibility = false;
std::map<std::string, cmCPackComponent>::iterator compIt;
for (compIt = this->Components.begin(); compIt != this->Components.end();
++compIt)
{
std::string packageFile;
if (compIt->second.IsDownloaded)
{
if (this->PackageCompatibilityVersion >= 10.5 &&
this->PackageMakerVersion >= 3.0)
{
// Build this package within the upload directory.
packageFile = uploadDirectory;
if(!cmSystemTools::FileExists(uploadDirectory.c_str()))
{
if (!cmSystemTools::MakeDirectory(uploadDirectory.c_str()))
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Unable to create package upload directory "
<< uploadDirectory << std::endl);
return 0;
}
}
}
else if (!warnedAboutDownloadCompatibility)
{
if (this->PackageCompatibilityVersion < 10.5)
{
cmCPackLogger(
cmCPackLog::LOG_WARNING,
"CPack warning: please set CPACK_OSX_PACKAGE_VERSION to 10.5 "
"or greater enable downloaded packages. CPack will build a "
"non-downloaded package."
<< std::endl);
}
if (this->PackageMakerVersion < 3)
{
cmCPackLogger(cmCPackLog::LOG_WARNING,
"CPack warning: unable to build downloaded "
"packages with PackageMaker versions prior "
"to 3.0. CPack will build a non-downloaded package."
<< std::endl);
}
warnedAboutDownloadCompatibility = true;
}
}
if (packageFile.empty())
{
// Build this package within the overall distribution
// metapackage.
packageFile = basePackageDir;
// We're not downloading this component, even if the user
// requested it.
compIt->second.IsDownloaded = false;
}
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", resDir.c_str())
|| !this->CopyCreateResourceFile("ReadMe", resDir.c_str())
|| !this->CopyCreateResourceFile("Welcome", resDir.c_str())
|| !this->CopyResourcePlistFile("Info.plist")
|| !this->CopyResourcePlistFile("Description.plist") )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem copying the resource files"
<< std::endl);
return 0;
}
if (this->Components.empty())
{
// Use PackageMaker to build the package.
cmOStringStream pkgCmd;
pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
<< "\" -build -p \"" << packageDirFileName << "\"";
if (this->Components.empty())
{
pkgCmd << " -f \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY");
}
else
{
pkgCmd << " -mi \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY")
<< "/packages/";
}
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 )
{
pkgCmd << " -v";
}
if (!RunPackageMaker(pkgCmd.str().c_str(), packageDirFileName.c_str()))
return 0;
}
else
{
// We have built the package in place. Generate the
// distribution.dist file to describe it for the installer.
WriteDistributionFile(packageDirFileName.c_str());
}
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
<< "\" \"" << packageFileNames[0] << "\"";
std::string output;
int retVal = 1;
int numTries = 10;
bool res = false;
while(numTries > 0)
{
res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output,
&retVal, 0, this->GeneratorVerbose,
0);
if ( res && !retVal )
{
numTries = -1;
break;
}
cmSystemTools::Delay(500);
numTries--;
}
if ( !res || retVal )
{
cmGeneratedFileStream ofs(tmpFile.c_str());
ofs << "# Run command: " << dmgCmd.str().c_str() << std::endl
<< "# Output:" << std::endl
<< output.c_str() << std::endl;
cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running hdiutil command: "
<< dmgCmd.str().c_str() << std::endl
<< "Please check " << tmpFile.c_str() << " for errors" << std::endl);
return 0;
}
return 1;
}
//----------------------------------------------------------------------
int cmCPackPackageMakerGenerator::InitializeInternal()
{
cmCPackLogger(cmCPackLog::LOG_DEBUG,
"cmCPackPackageMakerGenerator::Initialize()" << std::endl);
this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr");
std::vector<std::string> path;
std::string pkgPath
= "/Developer/Applications/Utilities/PackageMaker.app/Contents";
std::string versionFile = pkgPath + "/version.plist";
if ( !cmSystemTools::FileExists(versionFile.c_str()) )
{
pkgPath = "/Developer/Applications/PackageMaker.app/Contents";
std::string newVersionFile = pkgPath + "/version.plist";
if ( !cmSystemTools::FileExists(newVersionFile.c_str()) )
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Cannot find PackageMaker compiler version file: "
<< versionFile.c_str() << " or " << newVersionFile.c_str()
<< std::endl);
return 0;
}
versionFile = newVersionFile;
}
std::ifstream ifs(versionFile.c_str());
if ( !ifs )
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Cannot open PackageMaker compiler version file" << std::endl);
return 0;
}
// Check the PackageMaker version
cmsys::RegularExpression rexKey("<key>CFBundleShortVersionString</key>");
cmsys::RegularExpression rexVersion("<string>([0-9]+.[0-9.]+)</string>");
std::string line;
bool foundKey = false;
while ( cmSystemTools::GetLineFromStream(ifs, line) )
{
if ( rexKey.find(line) )
{
foundKey = true;
break;
}
}
if ( !foundKey )
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Cannot find CFBundleShortVersionString in the PackageMaker compiler "
"version file" << std::endl);
return 0;
}
if ( !cmSystemTools::GetLineFromStream(ifs, line) ||
!rexVersion.find(line) )
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Problem reading the PackageMaker compiler version file: "
<< versionFile.c_str() << std::endl);
return 0;
}
this->PackageMakerVersion = atof(rexVersion.match(1).c_str());
if ( this->PackageMakerVersion < 1.0 )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Require PackageMaker 1.0 or higher"
<< std::endl);
return 0;
}
cmCPackLogger(cmCPackLog::LOG_DEBUG, "PackageMaker version is: "
<< this->PackageMakerVersion << std::endl);
// Determine the package compatibility version. If it wasn't
// specified by the user, we define it based on which features the
// user requested.
const char *packageCompat = this->GetOption("CPACK_OSX_PACKAGE_VERSION");
if (packageCompat && *packageCompat)
{
this->PackageCompatibilityVersion = atof(packageCompat);
}
else if (this->GetOption("CPACK_DOWNLOAD_SITE"))
{
this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.5");
this->PackageCompatibilityVersion = 10.5;
}
else if (this->GetOption("CPACK_COMPONENTS_ALL"))
{
this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.4");
this->PackageCompatibilityVersion = 10.4;
}
else
{
this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.3");
this->PackageCompatibilityVersion = 10.3;
}
pkgPath += "/MacOS";
path.push_back(pkgPath);
pkgPath = cmSystemTools::FindProgram("PackageMaker", path, false);
if ( pkgPath.empty() )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find PackageMaker compiler"
<< std::endl);
return 0;
}
this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", pkgPath.c_str());
pkgPath = cmSystemTools::FindProgram("hdiutil", path, false);
if ( pkgPath.empty() )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find hdiutil compiler"
<< std::endl);
return 0;
}
this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM_DISK_IMAGE",
pkgPath.c_str());
return this->Superclass::InitializeInternal();
}
//----------------------------------------------------------------------
bool cmCPackPackageMakerGenerator::CopyCreateResourceFile(const char* name,
const char* dirName)
{
std::string uname = cmSystemTools::UpperCase(name);
std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
const char* inFileName = this->GetOption(cpackVar.c_str());
if ( !inFileName )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack option: " << cpackVar.c_str()
<< " not specified. It should point to "
<< (name ? name : "(NULL)")
<< ".rtf, " << name
<< ".html, or " << name << ".txt file" << std::endl);
return false;
}
if ( !cmSystemTools::FileExists(inFileName) )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find "
<< (name ? name : "(NULL)")
<< " resource file: " << inFileName << std::endl);
return false;
}
std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
if ( ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt" )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Bad file extension specified: "
<< ext << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
<< std::endl);
return false;
}
std::string destFileName = dirName;
destFileName += '/';
destFileName += name + ext;
// Set this so that distribution.dist gets the right name (without
// the path).
this->SetOption(("CPACK_RESOURCE_FILE_" + uname + "_NOPATH").c_str(),
(name + ext).c_str());
cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: "
<< (inFileName ? inFileName : "(NULL)")
<< " to " << destFileName.c_str() << std::endl);
this->ConfigureFile(inFileName, destFileName.c_str());
return true;
}
bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name,
const char* outName)
{
if (!outName)
{
outName = name;
}
std::string inFName = "CPack.";
inFName += name;
inFName += ".in";
std::string inFileName = this->FindTemplate(inFName.c_str());
if ( inFileName.empty() )
{
cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find input file: "
<< inFName << std::endl);
return false;
}
std::string destFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
destFileName += "/";
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)
{
if (component.ArchiveFile.empty())
{
std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
packagesDir += ".dummy";
cmOStringStream out;
out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir)
<< "-" << component.Name << ".pkg";
return out.str();
}
else
{
return component.ArchiveFile + ".pkg";
}
}
//----------------------------------------------------------------------
bool
cmCPackPackageMakerGenerator::
GenerateComponentPackage(const char *packageFile,
const char *packageDir,
const cmCPackComponent& component)
{
cmCPackLogger(cmCPackLog::LOG_OUTPUT,
"- Building component package: " <<
packageFile << std::endl);
// The command that will be used to run PackageMaker
cmOStringStream pkgCmd;
if (this->PackageCompatibilityVersion < 10.5 ||
this->PackageMakerVersion < 3.0)
{
// Create Description.plist and Info.plist files for normal Mac OS
// X packages, which work on Mac OS X 10.3 and newer.
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;
}
pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
<< "\" -build -p \"" << packageFile << "\""
<< " -f \"" << packageDir << "\""
<< " -i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
<< "/" << infoFileName << "\""
<< " -d \"" << descriptionFile << "\"";
}
else
{
// Create a "flat" package on Mac OS X 10.5 and newer. Flat
// packages are stored in a single file, rather than a directory
// like normal packages, and can be downloaded by the installer
// on-the-fly in Mac OS X 10.5 or newer. Thus, we need to create
// flat packages when the packages will be downloaded on the fly.
std::string pkgId = "com.";
pkgId += this->GetOption("CPACK_PACKAGE_VENDOR");
pkgId += '.';
pkgId += this->GetOption("CPACK_PACKAGE_NAME");
pkgId += '.';
pkgId += component.Name;
pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
<< "\" --root \"" << packageDir << "\""
<< " --id " << pkgId
<< " --target " << this->GetOption("CPACK_OSX_PACKAGE_VERSION")
<< " --out \"" << packageFile << "\"";
}
// Run PackageMaker
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)
{
if (groupIt->second.ParentGroup == 0)
{
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<cmCPackComponentGroup*>::const_iterator groupIt;
for (groupIt = group.Subgroups.begin(); groupIt != group.Subgroups.end();
++groupIt)
{
CreateChoiceOutline(**groupIt, out);
}
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 += 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";
std::set<const cmCPackComponent *> visited;
AddDependencyAttributes(component, visited, out);
visited.clear();
AddReverseDependencyAttributes(component, visited, 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 += this->GetPackageName(component);
// Determine the installed size of the package.
std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
dirName += '/';
dirName += component.Name;
unsigned long installedSize
= component.GetInstalledSizeInKbytes(dirName.c_str());
out << "<pkg-ref id=\"" << packageId << "\" "
<< "version=\"" << this->GetOption("CPACK_PACKAGE_VERSION") << "\" "
<< "installKBytes=\"" << installedSize << "\" "
<< "auth=\"Admin\" onConclusion=\"None\">";
if (component.IsDownloaded)
{
out << this->GetOption("CPACK_DOWNLOAD_SITE")
<< this->GetPackageName(component);
}
else
{
out << "file:./" << relativePackageLocation;
}
out << "</pkg-ref>" << std::endl;
}
//----------------------------------------------------------------------
void
cmCPackPackageMakerGenerator::
AddDependencyAttributes(const cmCPackComponent& component,
std::set<const cmCPackComponent *>& visited,
cmOStringStream& out)
{
if (visited.find(&component) != visited.end())
{
return;
}
visited.insert(&component);
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, visited, out);
}
}
//----------------------------------------------------------------------
void
cmCPackPackageMakerGenerator::
AddReverseDependencyAttributes(const cmCPackComponent& component,
std::set<const cmCPackComponent *>& visited,
cmOStringStream& out)
{
if (visited.find(&component) != visited.end())
{
return;
}
visited.insert(&component);
std::vector<cmCPackComponent *>::const_iterator dependIt;
for (dependIt = component.ReverseDependencies.begin();
dependIt != component.ReverseDependencies.end();
++dependIt)
{
out << " || choices['" << (*dependIt)->Name << "Choice'].selected";
AddReverseDependencyAttributes(**dependIt, visited, 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;
}