cmCPackPKGGenerator.cxx 16 KB

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