cmCPackPKGGenerator.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCPackPKGGenerator.h"
  4. #include <vector>
  5. #include "cmCPackComponentGroup.h"
  6. #include "cmCPackGenerator.h"
  7. #include "cmCPackLog.h"
  8. #include "cmStringAlgorithms.h"
  9. #include "cmSystemTools.h"
  10. #include "cmXMLWriter.h"
  11. cmCPackPKGGenerator::cmCPackPKGGenerator()
  12. {
  13. this->componentPackageMethod = ONE_PACKAGE;
  14. }
  15. cmCPackPKGGenerator::~cmCPackPKGGenerator() = default;
  16. bool cmCPackPKGGenerator::SupportsComponentInstallation() const
  17. {
  18. return true;
  19. }
  20. int cmCPackPKGGenerator::InitializeInternal()
  21. {
  22. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  23. "cmCPackPKGGenerator::Initialize()" << std::endl);
  24. return this->Superclass::InitializeInternal();
  25. }
  26. std::string cmCPackPKGGenerator::GetPackageName(
  27. const cmCPackComponent& component)
  28. {
  29. if (component.ArchiveFile.empty()) {
  30. std::string packagesDir =
  31. cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
  32. std::ostringstream out;
  33. out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-"
  34. << component.Name << ".pkg";
  35. return out.str();
  36. }
  37. return component.ArchiveFile + ".pkg";
  38. }
  39. void cmCPackPKGGenerator::CreateBackground(const char* themeName,
  40. const char* metapackageFile,
  41. cm::string_view genName,
  42. cmXMLWriter& xout)
  43. {
  44. std::string paramSuffix =
  45. (themeName == nullptr) ? "" : cmSystemTools::UpperCase(themeName);
  46. std::string opt = (themeName == nullptr)
  47. ? cmStrCat("CPACK_", genName, "_BACKGROUND")
  48. : cmStrCat("CPACK_", genName, "_BACKGROUND_", paramSuffix);
  49. const char* bgFileName = this->GetOption(opt);
  50. if (bgFileName == nullptr) {
  51. return;
  52. }
  53. std::string bgFilePath = cmStrCat(metapackageFile, "/Contents/", bgFileName);
  54. if (!cmSystemTools::FileExists(bgFilePath)) {
  55. cmCPackLogger(cmCPackLog::LOG_ERROR,
  56. "Background image doesn't exist in the resource directory: "
  57. << bgFileName << std::endl);
  58. return;
  59. }
  60. if (themeName == nullptr) {
  61. xout.StartElement("background");
  62. } else {
  63. xout.StartElement(cmStrCat("background-", themeName));
  64. }
  65. xout.Attribute("file", bgFileName);
  66. const char* param = this->GetOption(cmStrCat(opt, "_ALIGNMENT"));
  67. if (param != nullptr) {
  68. xout.Attribute("alignment", param);
  69. }
  70. param = this->GetOption(cmStrCat(opt, "_SCALING"));
  71. if (param != nullptr) {
  72. xout.Attribute("scaling", param);
  73. }
  74. // Apple docs say that you must provide either mime-type or uti
  75. // attribute for the background, but I've seen examples that
  76. // doesn't have them, so don't make them mandatory.
  77. param = this->GetOption(cmStrCat(opt, "_MIME_TYPE"));
  78. if (param != nullptr) {
  79. xout.Attribute("mime-type", param);
  80. }
  81. param = this->GetOption(cmStrCat(opt, "_UTI"));
  82. if (param != nullptr) {
  83. xout.Attribute("uti", param);
  84. }
  85. xout.EndElement();
  86. }
  87. void cmCPackPKGGenerator::WriteDistributionFile(const char* metapackageFile,
  88. const char* genName)
  89. {
  90. std::string distributionTemplate =
  91. this->FindTemplate("Internal/CPack/CPack.distribution.dist.in");
  92. if (distributionTemplate.empty()) {
  93. cmCPackLogger(cmCPackLog::LOG_ERROR,
  94. "Cannot find input file: " << distributionTemplate
  95. << std::endl);
  96. return;
  97. }
  98. std::string distributionFile =
  99. cmStrCat(metapackageFile, "/Contents/distribution.dist");
  100. // Create the choice outline, which provides a tree-based view of
  101. // the components in their groups.
  102. std::ostringstream choiceOut;
  103. cmXMLWriter xout(choiceOut, 1);
  104. xout.StartElement("choices-outline");
  105. // Emit the outline for the groups
  106. for (auto const& group : this->ComponentGroups) {
  107. if (group.second.ParentGroup == nullptr) {
  108. CreateChoiceOutline(group.second, xout);
  109. }
  110. }
  111. // Emit the outline for the non-grouped components
  112. for (auto const& comp : this->Components) {
  113. if (!comp.second.Group) {
  114. xout.StartElement("line");
  115. xout.Attribute("choice", comp.first + "Choice");
  116. xout.Content(""); // Avoid self-closing tag.
  117. xout.EndElement();
  118. }
  119. }
  120. if (!this->PostFlightComponent.Name.empty()) {
  121. xout.StartElement("line");
  122. xout.Attribute("choice", PostFlightComponent.Name + "Choice");
  123. xout.Content(""); // Avoid self-closing tag.
  124. xout.EndElement();
  125. }
  126. xout.EndElement(); // choices-outline>
  127. // Create the actual choices
  128. for (auto const& group : this->ComponentGroups) {
  129. CreateChoice(group.second, xout);
  130. }
  131. for (auto const& comp : this->Components) {
  132. CreateChoice(comp.second, xout);
  133. }
  134. if (!this->PostFlightComponent.Name.empty()) {
  135. CreateChoice(PostFlightComponent, xout);
  136. }
  137. // default background
  138. this->CreateBackground(nullptr, metapackageFile, genName, xout);
  139. // Dark Aqua
  140. this->CreateBackground("darkAqua", metapackageFile, genName, xout);
  141. this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str().c_str());
  142. // Create the distribution.dist file in the metapackage to turn it
  143. // into a distribution package.
  144. this->ConfigureFile(distributionTemplate, distributionFile);
  145. }
  146. void cmCPackPKGGenerator::CreateChoiceOutline(
  147. const cmCPackComponentGroup& group, cmXMLWriter& xout)
  148. {
  149. xout.StartElement("line");
  150. xout.Attribute("choice", group.Name + "Choice");
  151. for (cmCPackComponentGroup* subgroup : group.Subgroups) {
  152. CreateChoiceOutline(*subgroup, xout);
  153. }
  154. for (cmCPackComponent* comp : group.Components) {
  155. xout.StartElement("line");
  156. xout.Attribute("choice", comp->Name + "Choice");
  157. xout.Content(""); // Avoid self-closing tag.
  158. xout.EndElement();
  159. }
  160. xout.EndElement();
  161. }
  162. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group,
  163. cmXMLWriter& xout)
  164. {
  165. xout.StartElement("choice");
  166. xout.Attribute("id", group.Name + "Choice");
  167. xout.Attribute("title", group.DisplayName);
  168. xout.Attribute("start_selected", "true");
  169. xout.Attribute("start_enabled", "true");
  170. xout.Attribute("start_visible", "true");
  171. if (!group.Description.empty()) {
  172. xout.Attribute("description", group.Description);
  173. }
  174. xout.EndElement();
  175. }
  176. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component,
  177. cmXMLWriter& xout)
  178. {
  179. std::string packageId =
  180. cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"), '.',
  181. this->GetOption("CPACK_PACKAGE_NAME"), '.', component.Name);
  182. xout.StartElement("choice");
  183. xout.Attribute("id", component.Name + "Choice");
  184. xout.Attribute("title", component.DisplayName);
  185. xout.Attribute(
  186. "start_selected",
  187. component.IsDisabledByDefault && !component.IsRequired ? "false" : "true");
  188. xout.Attribute("start_enabled", component.IsRequired ? "false" : "true");
  189. xout.Attribute("start_visible", component.IsHidden ? "false" : "true");
  190. if (!component.Description.empty()) {
  191. xout.Attribute("description", component.Description);
  192. }
  193. if (!component.Dependencies.empty() ||
  194. !component.ReverseDependencies.empty()) {
  195. // The "selected" expression is evaluated each time any choice is
  196. // selected, for all choices *except* the one that the user
  197. // selected. A component is marked selected if it has been
  198. // selected (my.choice.selected in Javascript) and all of the
  199. // components it depends on have been selected (transitively) or
  200. // if any of the components that depend on it have been selected
  201. // (transitively). Assume that we have components A, B, C, D, and
  202. // E, where each component depends on the previous component (B
  203. // depends on A, C depends on B, D depends on C, and E depends on
  204. // D). The expression we build for the component C will be
  205. // my.choice.selected && B && A || D || E
  206. // This way, selecting C will automatically select everything it depends
  207. // on (B and A), while selecting something that depends on C--either D
  208. // or E--will automatically cause C to get selected.
  209. std::ostringstream selected("my.choice.selected", std::ios_base::ate);
  210. std::set<const cmCPackComponent*> visited;
  211. AddDependencyAttributes(component, visited, selected);
  212. visited.clear();
  213. AddReverseDependencyAttributes(component, visited, selected);
  214. xout.Attribute("selected", selected.str());
  215. }
  216. xout.StartElement("pkg-ref");
  217. xout.Attribute("id", packageId);
  218. xout.EndElement(); // pkg-ref
  219. xout.EndElement(); // choice
  220. // Create a description of the package associated with this
  221. // component.
  222. std::string relativePackageLocation =
  223. cmStrCat("Contents/Packages/", this->GetPackageName(component));
  224. // Determine the installed size of the package.
  225. std::string dirName =
  226. cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component.Name,
  227. this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"));
  228. unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName);
  229. xout.StartElement("pkg-ref");
  230. xout.Attribute("id", packageId);
  231. xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION"));
  232. xout.Attribute("installKBytes", installedSize);
  233. xout.Attribute("auth", "Admin");
  234. xout.Attribute("onConclusion", "None");
  235. if (component.IsDownloaded) {
  236. xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE"));
  237. xout.Content(this->GetPackageName(component));
  238. } else {
  239. xout.Content("file:./");
  240. xout.Content(cmSystemTools::EncodeURL(relativePackageLocation,
  241. /*escapeSlashes=*/false));
  242. }
  243. xout.EndElement(); // pkg-ref
  244. }
  245. void cmCPackPKGGenerator::AddDependencyAttributes(
  246. const cmCPackComponent& component,
  247. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  248. {
  249. if (visited.find(&component) != visited.end()) {
  250. return;
  251. }
  252. visited.insert(&component);
  253. for (cmCPackComponent* depend : component.Dependencies) {
  254. out << " && choices['" << depend->Name << "Choice'].selected";
  255. AddDependencyAttributes(*depend, visited, out);
  256. }
  257. }
  258. void cmCPackPKGGenerator::AddReverseDependencyAttributes(
  259. const cmCPackComponent& component,
  260. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  261. {
  262. if (visited.find(&component) != visited.end()) {
  263. return;
  264. }
  265. visited.insert(&component);
  266. for (cmCPackComponent* depend : component.ReverseDependencies) {
  267. out << " || choices['" << depend->Name << "Choice'].selected";
  268. AddReverseDependencyAttributes(*depend, visited, out);
  269. }
  270. }
  271. bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name,
  272. const std::string& dirName)
  273. {
  274. std::string uname = cmSystemTools::UpperCase(name);
  275. std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
  276. const char* inFileName = this->GetOption(cpackVar);
  277. if (!inFileName) {
  278. cmCPackLogger(cmCPackLog::LOG_ERROR,
  279. "CPack option: " << cpackVar.c_str()
  280. << " not specified. It should point to "
  281. << (!name.empty() ? name : "<empty>")
  282. << ".rtf, " << name << ".html, or " << name
  283. << ".txt file" << std::endl);
  284. return false;
  285. }
  286. if (!cmSystemTools::FileExists(inFileName)) {
  287. cmCPackLogger(cmCPackLog::LOG_ERROR,
  288. "Cannot find " << (!name.empty() ? name : "<empty>")
  289. << " resource file: " << inFileName
  290. << std::endl);
  291. return false;
  292. }
  293. std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
  294. if (ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt") {
  295. cmCPackLogger(
  296. cmCPackLog::LOG_ERROR,
  297. "Bad file extension specified: "
  298. << ext
  299. << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
  300. << std::endl);
  301. return false;
  302. }
  303. std::string destFileName = cmStrCat(dirName, '/', name, ext);
  304. // Set this so that distribution.dist gets the right name (without
  305. // the path).
  306. this->SetOption("CPACK_RESOURCE_FILE_" + uname + "_NOPATH",
  307. (name + ext).c_str());
  308. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  309. "Configure file: " << (inFileName ? inFileName : "(NULL)")
  310. << " to " << destFileName << std::endl);
  311. this->ConfigureFile(inFileName, destFileName);
  312. return true;
  313. }
  314. bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name,
  315. const char* outName)
  316. {
  317. if (!outName) {
  318. outName = name.c_str();
  319. }
  320. std::string inFName = cmStrCat("Internal/CPack/CPack.", name, ".in");
  321. std::string inFileName = this->FindTemplate(inFName.c_str());
  322. if (inFileName.empty()) {
  323. cmCPackLogger(cmCPackLog::LOG_ERROR,
  324. "Cannot find input file: " << inFName << std::endl);
  325. return false;
  326. }
  327. std::string destFileName =
  328. cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), '/', outName);
  329. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  330. "Configure file: " << inFileName << " to " << destFileName
  331. << std::endl);
  332. this->ConfigureFile(inFileName, destFileName);
  333. return true;
  334. }
  335. int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir,
  336. const std::string& script,
  337. const std::string& name)
  338. {
  339. std::string dst = cmStrCat(resdir, '/', name);
  340. cmSystemTools::CopyFileAlways(script, dst);
  341. cmSystemTools::SetPermissions(dst.c_str(), 0777);
  342. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  343. "copy script : " << script << "\ninto " << dst << std::endl);
  344. return 1;
  345. }