cmCPackPKGGenerator.cxx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. 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", 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", PostFlightComponent.Name + "Choice");
  143. xChoiceOut.Content(""); // Avoid self-closing tag.
  144. xChoiceOut.EndElement();
  145. }
  146. xChoiceOut.EndElement(); // choices-outline>
  147. // Create the actual choices
  148. for (auto const& group : this->ComponentGroups) {
  149. CreateChoice(group.second, xChoiceOut);
  150. }
  151. for (auto const& comp : this->Components) {
  152. CreateChoice(comp.second, xChoiceOut);
  153. }
  154. if (!this->PostFlightComponent.Name.empty()) {
  155. CreateChoice(PostFlightComponent, xChoiceOut);
  156. }
  157. // default background. These are not strictly part of the choices, but they
  158. // must be included in CPACK_PACKAGEMAKER_CHOICES to preserve backward
  159. // compatibility.
  160. this->CreateBackground(nullptr, metapackageFile, genName, xChoiceOut);
  161. // Dark Aqua
  162. this->CreateBackground("darkAqua", metapackageFile, genName, xChoiceOut);
  163. // Provide the content for substitution into the template. Support both the
  164. // old and new variables.
  165. this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str());
  166. this->SetOption("CPACK_APPLE_PKG_INSTALLER_CONTENT",
  167. cmStrCat(xContents.str(), " ", choiceOut.str()));
  168. // Create the distribution.dist file in the metapackage to turn it
  169. // into a distribution package.
  170. this->ConfigureFile(distributionTemplate, distributionFile);
  171. }
  172. void cmCPackPKGGenerator::CreateChoiceOutline(
  173. const cmCPackComponentGroup& group, cmXMLWriter& xout)
  174. {
  175. xout.StartElement("line");
  176. xout.Attribute("choice", group.Name + "Choice");
  177. for (cmCPackComponentGroup* subgroup : group.Subgroups) {
  178. CreateChoiceOutline(*subgroup, xout);
  179. }
  180. for (cmCPackComponent* comp : group.Components) {
  181. xout.StartElement("line");
  182. xout.Attribute("choice", comp->Name + "Choice");
  183. xout.Content(""); // Avoid self-closing tag.
  184. xout.EndElement();
  185. }
  186. xout.EndElement();
  187. }
  188. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group,
  189. cmXMLWriter& xout)
  190. {
  191. xout.StartElement("choice");
  192. xout.Attribute("id", group.Name + "Choice");
  193. xout.Attribute("title", group.DisplayName);
  194. xout.Attribute("start_selected", "true");
  195. xout.Attribute("start_enabled", "true");
  196. xout.Attribute("start_visible", "true");
  197. if (!group.Description.empty()) {
  198. xout.Attribute("description", group.Description);
  199. }
  200. xout.EndElement();
  201. }
  202. void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component,
  203. cmXMLWriter& xout)
  204. {
  205. std::string packageId;
  206. if (cmValue i = this->GetOption("CPACK_PRODUCTBUILD_IDENTIFIER")) {
  207. packageId = cmStrCat(i, '.', component.Name);
  208. } else {
  209. packageId =
  210. cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"), '.',
  211. this->GetOption("CPACK_PACKAGE_NAME"), '.', component.Name);
  212. }
  213. xout.StartElement("choice");
  214. xout.Attribute("id", component.Name + "Choice");
  215. xout.Attribute("title", component.DisplayName);
  216. xout.Attribute(
  217. "start_selected",
  218. component.IsDisabledByDefault && !component.IsRequired ? "false" : "true");
  219. xout.Attribute("start_enabled", component.IsRequired ? "false" : "true");
  220. xout.Attribute("start_visible", component.IsHidden ? "false" : "true");
  221. if (!component.Description.empty()) {
  222. xout.Attribute("description", component.Description);
  223. }
  224. if (!component.Dependencies.empty() ||
  225. !component.ReverseDependencies.empty()) {
  226. // The "selected" expression is evaluated each time any choice is
  227. // selected, for all choices *except* the one that the user
  228. // selected. A component is marked selected if it has been
  229. // selected (my.choice.selected in Javascript) and all of the
  230. // components it depends on have been selected (transitively) or
  231. // if any of the components that depend on it have been selected
  232. // (transitively). Assume that we have components A, B, C, D, and
  233. // E, where each component depends on the previous component (B
  234. // depends on A, C depends on B, D depends on C, and E depends on
  235. // D). The expression we build for the component C will be
  236. // my.choice.selected && B && A || D || E
  237. // This way, selecting C will automatically select everything it depends
  238. // on (B and A), while selecting something that depends on C--either D
  239. // or E--will automatically cause C to get selected.
  240. std::ostringstream selected("my.choice.selected", std::ios_base::ate);
  241. std::set<const cmCPackComponent*> visited;
  242. AddDependencyAttributes(component, visited, selected);
  243. visited.clear();
  244. AddReverseDependencyAttributes(component, visited, selected);
  245. xout.Attribute("selected", selected.str());
  246. }
  247. xout.StartElement("pkg-ref");
  248. xout.Attribute("id", packageId);
  249. xout.EndElement(); // pkg-ref
  250. xout.EndElement(); // choice
  251. // Create a description of the package associated with this
  252. // component.
  253. std::string relativePackageLocation =
  254. cmStrCat("Contents/Packages/", this->GetPackageName(component));
  255. // Determine the installed size of the package.
  256. std::string dirName =
  257. cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component.Name,
  258. this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"));
  259. unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName);
  260. xout.StartElement("pkg-ref");
  261. xout.Attribute("id", packageId);
  262. xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION"));
  263. xout.Attribute("installKBytes", installedSize);
  264. // The auth attribute is deprecated in favor of the domains element
  265. if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
  266. xout.Attribute("auth", "Admin");
  267. }
  268. xout.Attribute("onConclusion", "None");
  269. if (component.IsDownloaded) {
  270. xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE"));
  271. xout.Content(this->GetPackageName(component));
  272. } else {
  273. xout.Content("file:./");
  274. xout.Content(cmSystemTools::EncodeURL(relativePackageLocation,
  275. /*escapeSlashes=*/false));
  276. }
  277. xout.EndElement(); // pkg-ref
  278. }
  279. void cmCPackPKGGenerator::CreateDomains(cmXMLWriter& xout)
  280. {
  281. if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
  282. return;
  283. }
  284. xout.StartElement("domains");
  285. // Product can be installed at the root of any volume by default
  286. // unless specified
  287. cmValue param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ANYWHERE");
  288. xout.Attribute("enable_anywhere",
  289. (param && cmIsOff(param)) ? "false" : "true");
  290. // Product cannot be installed into the current user's home directory
  291. // by default unless specified
  292. param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_USER");
  293. xout.Attribute("enable_currentUserHome",
  294. (param && cmIsOn(param)) ? "true" : "false");
  295. // Product can be installed into the root directory by default
  296. // unless specified
  297. param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ROOT");
  298. xout.Attribute("enable_localSystem",
  299. (param && cmIsOff(param)) ? "false" : "true");
  300. xout.EndElement();
  301. }
  302. void cmCPackPKGGenerator::AddDependencyAttributes(
  303. const cmCPackComponent& component,
  304. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  305. {
  306. if (visited.find(&component) != visited.end()) {
  307. return;
  308. }
  309. visited.insert(&component);
  310. for (cmCPackComponent* depend : component.Dependencies) {
  311. out << " && choices['" << depend->Name << "Choice'].selected";
  312. AddDependencyAttributes(*depend, visited, out);
  313. }
  314. }
  315. void cmCPackPKGGenerator::AddReverseDependencyAttributes(
  316. const cmCPackComponent& component,
  317. std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
  318. {
  319. if (visited.find(&component) != visited.end()) {
  320. return;
  321. }
  322. visited.insert(&component);
  323. for (cmCPackComponent* depend : component.ReverseDependencies) {
  324. out << " || choices['" << depend->Name << "Choice'].selected";
  325. AddReverseDependencyAttributes(*depend, visited, out);
  326. }
  327. }
  328. bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name,
  329. const std::string& dirName)
  330. {
  331. std::string uname = cmSystemTools::UpperCase(name);
  332. std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
  333. cmValue inFileName = this->GetOption(cpackVar);
  334. if (!inFileName) {
  335. cmCPackLogger(cmCPackLog::LOG_ERROR,
  336. "CPack option: " << cpackVar.c_str()
  337. << " not specified. It should point to "
  338. << (!name.empty() ? name : "<empty>")
  339. << ".rtf, " << name << ".html, or " << name
  340. << ".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("CPACK_RESOURCE_FILE_" + uname + "_NOPATH", (name + ext));
  364. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  365. "Configure file: " << (inFileName ? *inFileName : "(NULL)")
  366. << " to " << destFileName << std::endl);
  367. this->ConfigureFile(inFileName, destFileName);
  368. return true;
  369. }
  370. bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name,
  371. const char* outName)
  372. {
  373. if (!outName) {
  374. outName = name.c_str();
  375. }
  376. std::string inFName = cmStrCat("CPack.", name, ".in");
  377. std::string inFileName = this->FindTemplate(inFName.c_str());
  378. if (inFileName.empty()) {
  379. cmCPackLogger(cmCPackLog::LOG_ERROR,
  380. "Cannot find input file: " << inFName << std::endl);
  381. return false;
  382. }
  383. std::string destFileName =
  384. cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), '/', outName);
  385. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  386. "Configure file: " << inFileName << " to " << destFileName
  387. << std::endl);
  388. this->ConfigureFile(inFileName, destFileName);
  389. return true;
  390. }
  391. int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir,
  392. const std::string& script,
  393. const std::string& name)
  394. {
  395. std::string dst = cmStrCat(resdir, '/', name);
  396. cmSystemTools::CopyFileAlways(script, dst);
  397. cmSystemTools::SetPermissions(dst.c_str(), 0777);
  398. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  399. "copy script : " << script << "\ninto " << dst << std::endl);
  400. return 1;
  401. }