/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCPackPKGGenerator.h" #include #include "cmCPackComponentGroup.h" #include "cmCPackGenerator.h" #include "cmCPackLog.h" #include "cmSystemTools.h" #include "cmXMLWriter.h" cmCPackPKGGenerator::cmCPackPKGGenerator() { this->componentPackageMethod = ONE_PACKAGE; } cmCPackPKGGenerator::~cmCPackPKGGenerator() = default; bool cmCPackPKGGenerator::SupportsComponentInstallation() const { return true; } int cmCPackPKGGenerator::InitializeInternal() { cmCPackLogger(cmCPackLog::LOG_DEBUG, "cmCPackPKGGenerator::Initialize()" << std::endl); return this->Superclass::InitializeInternal(); } std::string cmCPackPKGGenerator::GetPackageName( const cmCPackComponent& component) { if (component.ArchiveFile.empty()) { std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); packagesDir += ".dummy"; std::ostringstream out; out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-" << component.Name << ".pkg"; return out.str(); } return component.ArchiveFile + ".pkg"; } void cmCPackPKGGenerator::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. std::ostringstream choiceOut; cmXMLWriter xout(choiceOut, 1); xout.StartElement("choices-outline"); // Emit the outline for the groups std::map::iterator groupIt; for (groupIt = this->ComponentGroups.begin(); groupIt != this->ComponentGroups.end(); ++groupIt) { if (groupIt->second.ParentGroup == nullptr) { CreateChoiceOutline(groupIt->second, xout); } } // Emit the outline for the non-grouped components std::map::iterator compIt; for (compIt = this->Components.begin(); compIt != this->Components.end(); ++compIt) { if (!compIt->second.Group) { xout.StartElement("line"); xout.Attribute("choice", compIt->first + "Choice"); xout.Content(""); // Avoid self-closing tag. xout.EndElement(); } } if (!this->PostFlightComponent.Name.empty()) { xout.StartElement("line"); xout.Attribute("choice", PostFlightComponent.Name + "Choice"); xout.Content(""); // Avoid self-closing tag. xout.EndElement(); } xout.EndElement(); // choices-outline> // Create the actual choices for (groupIt = this->ComponentGroups.begin(); groupIt != this->ComponentGroups.end(); ++groupIt) { CreateChoice(groupIt->second, xout); } for (compIt = this->Components.begin(); compIt != this->Components.end(); ++compIt) { CreateChoice(compIt->second, xout); } if (!this->PostFlightComponent.Name.empty()) { CreateChoice(PostFlightComponent, xout); } 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 cmCPackPKGGenerator::CreateChoiceOutline( const cmCPackComponentGroup& group, cmXMLWriter& xout) { xout.StartElement("line"); xout.Attribute("choice", group.Name + "Choice"); std::vector::const_iterator groupIt; for (groupIt = group.Subgroups.begin(); groupIt != group.Subgroups.end(); ++groupIt) { CreateChoiceOutline(**groupIt, xout); } std::vector::const_iterator compIt; for (compIt = group.Components.begin(); compIt != group.Components.end(); ++compIt) { xout.StartElement("line"); xout.Attribute("choice", (*compIt)->Name + "Choice"); xout.Content(""); // Avoid self-closing tag. xout.EndElement(); } xout.EndElement(); } void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group, cmXMLWriter& xout) { xout.StartElement("choice"); xout.Attribute("id", group.Name + "Choice"); xout.Attribute("title", group.DisplayName); xout.Attribute("start_selected", "true"); xout.Attribute("start_enabled", "true"); xout.Attribute("start_visible", "true"); if (!group.Description.empty()) { xout.Attribute("description", group.Description); } xout.EndElement(); } void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component, cmXMLWriter& xout) { std::string packageId = "com."; packageId += this->GetOption("CPACK_PACKAGE_VENDOR"); packageId += '.'; packageId += this->GetOption("CPACK_PACKAGE_NAME"); packageId += '.'; packageId += component.Name; xout.StartElement("choice"); xout.Attribute("id", component.Name + "Choice"); xout.Attribute("title", component.DisplayName); xout.Attribute( "start_selected", component.IsDisabledByDefault && !component.IsRequired ? "false" : "true"); xout.Attribute("start_enabled", component.IsRequired ? "false" : "true"); xout.Attribute("start_visible", component.IsHidden ? "false" : "true"); if (!component.Description.empty()) { xout.Attribute("description", 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. std::ostringstream selected("my.choice.selected", std::ios_base::ate); std::set visited; AddDependencyAttributes(component, visited, selected); visited.clear(); AddReverseDependencyAttributes(component, visited, selected); xout.Attribute("selected", selected.str()); } xout.StartElement("pkg-ref"); xout.Attribute("id", packageId); xout.EndElement(); // pkg-ref xout.EndElement(); // choice // 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; dirName += this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"); unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName); xout.StartElement("pkg-ref"); xout.Attribute("id", packageId); xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION")); xout.Attribute("installKBytes", installedSize); xout.Attribute("auth", "Admin"); xout.Attribute("onConclusion", "None"); if (component.IsDownloaded) { xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE")); xout.Content(this->GetPackageName(component)); } else { xout.Content("file:./"); xout.Content(cmSystemTools::EncodeURL(relativePackageLocation, /*escapeSlashes=*/false)); } xout.EndElement(); // pkg-ref } void cmCPackPKGGenerator::AddDependencyAttributes( const cmCPackComponent& component, std::set& visited, std::ostringstream& out) { if (visited.find(&component) != visited.end()) { return; } visited.insert(&component); std::vector::const_iterator dependIt; for (dependIt = component.Dependencies.begin(); dependIt != component.Dependencies.end(); ++dependIt) { out << " && choices['" << (*dependIt)->Name << "Choice'].selected"; AddDependencyAttributes(**dependIt, visited, out); } } void cmCPackPKGGenerator::AddReverseDependencyAttributes( const cmCPackComponent& component, std::set& visited, std::ostringstream& out) { if (visited.find(&component) != visited.end()) { return; } visited.insert(&component); std::vector::const_iterator dependIt; for (dependIt = component.ReverseDependencies.begin(); dependIt != component.ReverseDependencies.end(); ++dependIt) { out << " || choices['" << (*dependIt)->Name << "Choice'].selected"; AddReverseDependencyAttributes(**dependIt, visited, out); } } bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name, const std::string& dirName) { std::string uname = cmSystemTools::UpperCase(name); std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname; const char* inFileName = this->GetOption(cpackVar); if (!inFileName) { cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack option: " << cpackVar.c_str() << " not specified. It should point to " << (!name.empty() ? name : "") << ".rtf, " << name << ".html, or " << name << ".txt file" << std::endl); return false; } if (!cmSystemTools::FileExists(inFileName)) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find " << (!name.empty() ? name : "") << " 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", (name + ext).c_str()); cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: " << (inFileName ? inFileName : "(NULL)") << " to " << destFileName << std::endl); this->ConfigureFile(inFileName, destFileName.c_str()); return true; } bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name, const char* outName) { if (!outName) { outName = name.c_str(); } 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 << " to " << destFileName << std::endl); this->ConfigureFile(inFileName.c_str(), destFileName.c_str()); return true; } int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir, const std::string& script, const std::string& name) { std::string dst = resdir; dst += "/"; dst += name; cmSystemTools::CopyFileAlways(script, dst); cmSystemTools::SetPermissions(dst.c_str(), 0777); cmCPackLogger(cmCPackLog::LOG_VERBOSE, "copy script : " << script << "\ninto " << dst << std::endl); return 1; }