diff options
Diffstat (limited to 'Source/CPack/cmCPackPackageMakerGenerator.cxx')
-rw-r--r-- | Source/CPack/cmCPackPackageMakerGenerator.cxx | 518 |
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 << " && 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, "&", "&"); + cmSystemTools::ReplaceString(str, "<", "<"); + cmSystemTools::ReplaceString(str, ">", ">"); + cmSystemTools::ReplaceString(str, "\"", """); + return str; +} |