cmCPackPKGGenerator.cxx 14 KB

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