| /* 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 <vector> |
| |
| #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("Internal/CPack/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 |
| for (auto const& group : this->ComponentGroups) { |
| if (group.second.ParentGroup == nullptr) { |
| CreateChoiceOutline(group.second, xout); |
| } |
| } |
| |
| // Emit the outline for the non-grouped components |
| for (auto const& comp : this->Components) { |
| if (!comp.second.Group) { |
| xout.StartElement("line"); |
| xout.Attribute("choice", comp.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 (auto const& group : this->ComponentGroups) { |
| CreateChoice(group.second, xout); |
| } |
| for (auto const& comp : this->Components) { |
| CreateChoice(comp.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, distributionFile); |
| } |
| |
| void cmCPackPKGGenerator::CreateChoiceOutline( |
| const cmCPackComponentGroup& group, cmXMLWriter& xout) |
| { |
| xout.StartElement("line"); |
| xout.Attribute("choice", group.Name + "Choice"); |
| for (cmCPackComponentGroup* subgroup : group.Subgroups) { |
| CreateChoiceOutline(*subgroup, xout); |
| } |
| |
| for (cmCPackComponent* comp : group.Components) { |
| xout.StartElement("line"); |
| xout.Attribute("choice", comp->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<const cmCPackComponent*> 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<const cmCPackComponent*>& visited, std::ostringstream& out) |
| { |
| if (visited.find(&component) != visited.end()) { |
| return; |
| } |
| visited.insert(&component); |
| |
| for (cmCPackComponent* depend : component.Dependencies) { |
| out << " && choices['" << depend->Name << "Choice'].selected"; |
| AddDependencyAttributes(*depend, visited, out); |
| } |
| } |
| |
| void cmCPackPKGGenerator::AddReverseDependencyAttributes( |
| const cmCPackComponent& component, |
| std::set<const cmCPackComponent*>& visited, std::ostringstream& out) |
| { |
| if (visited.find(&component) != visited.end()) { |
| return; |
| } |
| visited.insert(&component); |
| |
| for (cmCPackComponent* depend : component.ReverseDependencies) { |
| out << " || choices['" << depend->Name << "Choice'].selected"; |
| AddReverseDependencyAttributes(*depend, 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 : "<empty>") |
| << ".rtf, " << name << ".html, or " << name |
| << ".txt file" << std::endl); |
| return false; |
| } |
| if (!cmSystemTools::FileExists(inFileName)) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Cannot find " << (!name.empty() ? name : "<empty>") |
| << " 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); |
| return true; |
| } |
| |
| bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name, |
| const char* outName) |
| { |
| if (!outName) { |
| outName = name.c_str(); |
| } |
| |
| std::string inFName = "Internal/CPack/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, destFileName); |
| 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; |
| } |