cmCPackPKGGenerator.cxx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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. return cmStrCat(
  34. cmSystemTools::GetFilenameWithoutLastExtension(packagesDir), "-",
  35. component.Name, ".pkg");
  36. }
  37. return cmStrCat(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. cmValue bgFileName = this->GetOption(opt);
  50. if (!bgFileName) {
  51. return;
  52. }
  53. std::string bgFilePath =
  54. 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. std::ostringstream xContents;
  102. cmXMLWriter xout(xContents, 1);
  103. // Installer-wide options and domains. These need to be separate from the
  104. // choices and background elements added further below so that we can
  105. // preserve backward compatibility.
  106. xout.StartElement("options");
  107. xout.Attribute("allow-external-scripts", "no");
  108. xout.Attribute("customize", "allow");
  109. if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
  110. xout.Attribute("rootVolumeOnly", "false");
  111. }
  112. xout.EndElement();
  113. this->CreateDomains(xout);
  114. // In order to preserve backward compatibility, all elements added below
  115. // here need to be made available in a variable named
  116. // CPACK_PACKAGEMAKER_CHOICES. The above elements are new and only appear
  117. // in the CPACK_APPLE_PKG_INSTALLER_CONTENT variable, which is a superset
  118. // of what CPACK_PACKAGEMAKER_CHOICES used to provide. The renaming reflects
  119. // the fact that CMake has deprecated the PackageMaker generator.
  120. // Create the choice outline, which provides a tree-based view of
  121. // the components in their groups.
  122. std::ostringstream choiceOut;
  123. cmXMLWriter xChoiceOut(choiceOut, 1);
  124. xChoiceOut.StartElement("choices-outline");
  125. // Emit the outline for the groups
  126. for (auto const& group : this->ComponentGroups) {
  127. if (group.second.ParentGroup == nullptr) {
  128. CreateChoiceOutline(group.second, xChoiceOut);
  129. }
  130. }
  131. // Emit the outline for the non-grouped components
  132. for (auto const& comp : this->Components) {
  133. if (!comp.second.Group) {
  134. xChoiceOut.StartElement("line");
  135. xChoiceOut.Attribute("choice", cmStrCat(comp.first, "Choice"));
  136. xChoiceOut.Content(""); // Avoid self-closing tag.
  137. xChoiceOut.EndElement();
  138. }
  139. }
  140. if (!this->PostFlightComponent.Name.empty()) {
  141. xChoiceOut.StartElement("line");
  142. xChoiceOut.Attribute("choice",
  143. cmStrCat(PostFlightComponent.Name, "Choice"));
  144. xChoiceOut.Content(""); // Avoid self-closing tag.
  145. xChoiceOut.EndElement();
  146. }
  147. xChoiceOut.EndElement(); // choices-outline>
  148. // Create the actual choices
  149. for (auto const& group : this->ComponentGroups) {
  150. CreateChoice(group.second, xChoiceOut);
  151. }
  152. for (auto const& comp : this->Components) {
  153. CreateChoice(comp.second, xChoiceOut);
  154. }
  155. if (!this->PostFlightComponent.Name.empty()) {
  156. CreateChoice(PostFlightComponent, xChoiceOut);
  157. }
  158. // default background. These are not strictly part of the choices, but they
  159. // must be included in CPACK_PACKAGEMAKER_CHOICES to preserve backward
  160. // compatibility.
  161. this->CreateBackground(nullptr, metapackageFile, genName, xChoiceOut);
  162. // Dark Aqua
  163. this->CreateBackground("darkAqua", metapackageFile, genName, xChoiceOut);
  164. // Provide the content for substitution into the template. Support both the
  165. // old and new variables.
  166. this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str());
  167. this->SetOption("CPACK_APPLE_PKG_INSTALLER_CONTENT",
  168. cmStrCat(xContents.str(), " ", choiceOut.str()));
  169. // Create the distribution.dist file in the metapackage to turn it
  170. // into a distribution package.
  171. this->ConfigureFile(distributionTemplate, distributionFile);
  172. }
  173. void cmCPackPKGGenerator::CreateChoiceOutline(
  174. const cmCPackComponentGroup& group, cmXMLWriter& xout)
  175. {
  176. xout.StartElement("line");
  177. xout.Attribute("choice", cmStrCat(group.Name, "Choice"));
  178. for (cmCPackComponentGroup* subgroup : group.Subgroups) {
  179. CreateChoiceOutline(*subgroup, xout);
  180. }
  181. for (cmCPackComponent* comp : group.Components) {
  182. xout.StartElement("line");
  183. xout.Attribute("choice", cmStrCat(comp->Name, "Choice"));
  184. xout.Content(""); // Avoid self-closing tag.
  185. xout.EndElement();
  186. }
  187. xout.EndElement();
  188. }
  189. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group,
  190. cmXMLWriter& xout)
  191. {
  192. xout.StartElement("choice");
  193. xout.Attribute("id", cmStrCat(group.Name, "Choice"));
  194. xout.Attribute("title", group.DisplayName);
  195. xout.Attribute("start_selected", "true");
  196. xout.Attribute("start_enabled", "true");
  197. xout.Attribute("start_visible", "true");
  198. if (!group.Description.empty()) {
  199. xout.Attribute("description", group.Description);
  200. }
  201. xout.EndElement();
  202. }
  203. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component,
  204. cmXMLWriter& xout)
  205. {
  206. std::string packageId;
  207. if (cmValue i = this->GetOption("CPACK_PRODUCTBUILD_IDENTIFIER")) {
  208. packageId = cmStrCat(i, '.', component.Name);
  209. } else {
  210. packageId =
  211. cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"), '.',
  212. this->GetOption("CPACK_PACKAGE_NAME"), '.', component.Name);
  213. }
  214. xout.StartElement("choice");
  215. xout.Attribute("id", cmStrCat(component.Name, "Choice"));
  216. xout.Attribute("title", component.DisplayName);
  217. xout.Attribute(
  218. "start_selected",
  219. component.IsDisabledByDefault && !component.IsRequired ? "false" : "true");
  220. xout.Attribute("start_enabled", component.IsRequired ? "false" : "true");
  221. xout.Attribute("start_visible", component.IsHidden ? "false" : "true");
  222. if (!component.Description.empty()) {
  223. xout.Attribute("description", component.Description);
  224. }
  225. if (!component.Dependencies.empty() ||
  226. !component.ReverseDependencies.empty()) {
  227. // The "selected" expression is evaluated each time any choice is
  228. // selected, for all choices *except* the one that the user
  229. // selected. A component is marked selected if it has been
  230. // selected (my.choice.selected in Javascript) and all of the
  231. // components it depends on have been selected (transitively) or
  232. // if any of the components that depend on it have been selected
  233. // (transitively). Assume that we have components A, B, C, D, and
  234. // E, where each component depends on the previous component (B
  235. // depends on A, C depends on B, D depends on C, and E depends on
  236. // D). The expression we build for the component C will be
  237. // my.choice.selected && B && A || D || E
  238. // This way, selecting C will automatically select everything it depends
  239. // on (B and A), while selecting something that depends on C--either D
  240. // or E--will automatically cause C to get selected.
  241. std::ostringstream selected("my.choice.selected", std::ios_base::ate);
  242. std::set<const cmCPackComponent*> visited;
  243. AddDependencyAttributes(component, visited, selected);
  244. visited.clear();
  245. AddReverseDependencyAttributes(component, visited, selected);
  246. xout.Attribute("selected", selected.str());
  247. }
  248. xout.StartElement("pkg-ref");
  249. xout.Attribute("id", packageId);
  250. xout.EndElement(); // pkg-ref
  251. xout.EndElement(); // choice
  252. // Create a description of the package associated with this
  253. // component.
  254. std::string relativePackageLocation =
  255. cmStrCat("Contents/Packages/", this->GetPackageName(component));
  256. // Determine the installed size of the package.
  257. std::string dirName =
  258. cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component.Name,
  259. this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"));
  260. unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName);
  261. xout.StartElement("pkg-ref");
  262. xout.Attribute("id", packageId);
  263. xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION"));
  264. xout.Attribute("installKBytes", installedSize);
  265. // The auth attribute is deprecated in favor of the domains element
  266. if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
  267. xout.Attribute("auth", "Admin");
  268. }
  269. xout.Attribute("onConclusion", "None");
  270. if (component.IsDownloaded) {
  271. xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE"));
  272. xout.Content(this->GetPackageName(component));
  273. } else {
  274. xout.Content("file:./");
  275. xout.Content(cmSystemTools::EncodeURL(relativePackageLocation,
  276. /*escapeSlashes=*/false));
  277. }
  278. xout.EndElement(); // pkg-ref
  279. }
  280. void cmCPackPKGGenerator::CreateDomains(cmXMLWriter& xout)
  281. {
  282. if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
  283. return;
  284. }
  285. xout.StartElement("domains");
  286. // Product can be installed at the root of any volume by default
  287. // unless specified
  288. cmValue param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ANYWHERE");
  289. xout.Attribute("enable_anywhere",
  290. (param && cmIsOff(param)) ? "false" : "true");
  291. // Product cannot be installed into the current user's home directory
  292. // by default unless specified
  293. param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_USER");
  294. xout.Attribute("enable_currentUserHome",
  295. (param && cmIsOn(param)) ? "true" : "false");
  296. // Product can be installed into the root directory by default
  297. // unless specified
  298. param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ROOT");
  299. xout.Attribute("enable_localSystem",
  300. (param && cmIsOff(param)) ? "false" : "true");
  301. xout.EndElement();
  302. }
  303. void cmCPackPKGGenerator::AddDependencyAttributes(
  304. const cmCPackComponent& component,
  305. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  306. {
  307. if (visited.find(&component) != visited.end()) {
  308. return;
  309. }
  310. visited.insert(&component);
  311. for (cmCPackComponent* depend : component.Dependencies) {
  312. out << " && choices['" << depend->Name << "Choice'].selected";
  313. AddDependencyAttributes(*depend, visited, out);
  314. }
  315. }
  316. void cmCPackPKGGenerator::AddReverseDependencyAttributes(
  317. const cmCPackComponent& component,
  318. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  319. {
  320. if (visited.find(&component) != visited.end()) {
  321. return;
  322. }
  323. visited.insert(&component);
  324. for (cmCPackComponent* depend : component.ReverseDependencies) {
  325. out << " || choices['" << depend->Name << "Choice'].selected";
  326. AddReverseDependencyAttributes(*depend, visited, out);
  327. }
  328. }
  329. bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name,
  330. const std::string& dirName)
  331. {
  332. std::string uname = cmSystemTools::UpperCase(name);
  333. std::string cpackVar = cmStrCat("CPACK_RESOURCE_FILE_", uname);
  334. cmValue inFileName = this->GetOption(cpackVar);
  335. if (!inFileName) {
  336. cmCPackLogger(cmCPackLog::LOG_ERROR,
  337. "CPack option: "
  338. << cpackVar << " not specified. It should point to "
  339. << (!name.empty() ? name : "<empty>") << ".rtf, " << name
  340. << ".html, or " << name << ".txt file" << std::endl);
  341. return false;
  342. }
  343. if (!cmSystemTools::FileExists(inFileName)) {
  344. cmCPackLogger(cmCPackLog::LOG_ERROR,
  345. "Cannot find " << (!name.empty() ? name : "<empty>")
  346. << " resource file: " << inFileName
  347. << std::endl);
  348. return false;
  349. }
  350. std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
  351. if (ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt") {
  352. cmCPackLogger(
  353. cmCPackLog::LOG_ERROR,
  354. "Bad file extension specified: "
  355. << ext
  356. << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
  357. << std::endl);
  358. return false;
  359. }
  360. std::string destFileName = cmStrCat(dirName, '/', name, ext);
  361. // Set this so that distribution.dist gets the right name (without
  362. // the path).
  363. this->SetOption(cmStrCat("CPACK_RESOURCE_FILE_", uname, "_NOPATH"),
  364. cmStrCat(name, ext));
  365. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  366. "Configure file: " << (inFileName ? *inFileName : "(NULL)")
  367. << " to " << destFileName << std::endl);
  368. this->ConfigureFile(inFileName, destFileName);
  369. return true;
  370. }
  371. bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name,
  372. const char* outName)
  373. {
  374. if (!outName) {
  375. outName = name.c_str();
  376. }
  377. std::string inFName = cmStrCat("CPack.", name, ".in");
  378. std::string inFileName = this->FindTemplate(inFName.c_str());
  379. if (inFileName.empty()) {
  380. cmCPackLogger(cmCPackLog::LOG_ERROR,
  381. "Cannot find input file: " << inFName << std::endl);
  382. return false;
  383. }
  384. std::string destFileName =
  385. cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), '/', outName);
  386. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  387. "Configure file: " << inFileName << " to " << destFileName
  388. << std::endl);
  389. this->ConfigureFile(inFileName, destFileName);
  390. return true;
  391. }
  392. int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir,
  393. const std::string& script,
  394. const std::string& name)
  395. {
  396. std::string dst = cmStrCat(resdir, '/', name);
  397. cmSystemTools::CopyFileAlways(script, dst);
  398. cmSystemTools::SetPermissions(dst, 0777);
  399. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  400. "copy script : " << script << "\ninto " << dst << std::endl);
  401. return 1;
  402. }