cmCPackDragNDropGenerator.cxx 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmCPackDragNDropGenerator.h"
  4. #include <algorithm>
  5. #include <cstdlib>
  6. #include <iomanip>
  7. #include <map>
  8. #include <cm/string_view>
  9. #include <cmext/string_view>
  10. #include <CoreFoundation/CFLocale.h>
  11. #include <CoreFoundation/CFString.h>
  12. #include <cm3p/kwiml/abi.h>
  13. #include "cmsys/Base64.h"
  14. #include "cmsys/FStream.hxx"
  15. #include "cmsys/RegularExpression.hxx"
  16. #include "cmCPackConfigure.h"
  17. #include "cmCPackGenerator.h"
  18. #include "cmCPackLog.h"
  19. #include "cmDuration.h"
  20. #include "cmGeneratedFileStream.h"
  21. #include "cmList.h"
  22. #include "cmStringAlgorithms.h"
  23. #include "cmSystemTools.h"
  24. #include "cmValue.h"
  25. #include "cmXMLWriter.h"
  26. #if HAVE_CoreServices
  27. // For the old LocaleStringToLangAndRegionCodes() function, to convert
  28. // to the old Script Manager RegionCode values needed for the 'LPic' data
  29. // structure used for generating multi-lingual SLAs.
  30. # include <CoreServices/CoreServices.h>
  31. #endif
  32. static uint16_t const DefaultLpic[] = {
  33. /* clang-format off */
  34. 0x0002, 0x0011, 0x0003, 0x0001, 0x0000, 0x0000, 0x0002, 0x0000,
  35. 0x0008, 0x0003, 0x0000, 0x0001, 0x0004, 0x0000, 0x0004, 0x0005,
  36. 0x0000, 0x000E, 0x0006, 0x0001, 0x0005, 0x0007, 0x0000, 0x0007,
  37. 0x0008, 0x0000, 0x0047, 0x0009, 0x0000, 0x0034, 0x000A, 0x0001,
  38. 0x0035, 0x000B, 0x0001, 0x0020, 0x000C, 0x0000, 0x0011, 0x000D,
  39. 0x0000, 0x005B, 0x0004, 0x0000, 0x0033, 0x000F, 0x0001, 0x000C,
  40. 0x0010, 0x0000, 0x000B, 0x000E, 0x0000
  41. /* clang-format on */
  42. };
  43. static std::vector<std::string> const DefaultMenu = {
  44. { "English", "Agree", "Disagree", "Print", "Save...",
  45. // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
  46. "You agree to the License Agreement terms when "
  47. "you click the \"Agree\" button.",
  48. "Software License Agreement",
  49. "This text cannot be saved. "
  50. "This disk may be full or locked, or the file may be locked.",
  51. "Unable to print. Make sure you have selected a printer." }
  52. };
  53. cmCPackDragNDropGenerator::cmCPackDragNDropGenerator()
  54. : singleLicense(false)
  55. {
  56. // default to one package file for components
  57. this->componentPackageMethod = ONE_PACKAGE;
  58. }
  59. cmCPackDragNDropGenerator::~cmCPackDragNDropGenerator() = default;
  60. int cmCPackDragNDropGenerator::InitializeInternal()
  61. {
  62. // Starting with Xcode 4.3, look in "/Applications/Xcode.app" first:
  63. //
  64. std::vector<std::string> paths;
  65. paths.emplace_back("/Applications/Xcode.app/Contents/Developer/Tools");
  66. paths.emplace_back("/Developer/Tools");
  67. std::string const hdiutil_path = cmSystemTools::FindProgram("hdiutil");
  68. if (hdiutil_path.empty()) {
  69. cmCPackLogger(cmCPackLog::LOG_ERROR,
  70. "Cannot locate hdiutil command" << std::endl);
  71. return 0;
  72. }
  73. this->SetOptionIfNotSet("CPACK_COMMAND_HDIUTIL", hdiutil_path);
  74. std::string const setfile_path =
  75. cmSystemTools::FindProgram("SetFile", paths);
  76. if (setfile_path.empty()) {
  77. cmCPackLogger(cmCPackLog::LOG_ERROR,
  78. "Cannot locate SetFile command" << std::endl);
  79. return 0;
  80. }
  81. this->SetOptionIfNotSet("CPACK_COMMAND_SETFILE", setfile_path);
  82. std::string const rez_path = cmSystemTools::FindProgram("Rez", paths);
  83. if (rez_path.empty()) {
  84. cmCPackLogger(cmCPackLog::LOG_ERROR,
  85. "Cannot locate Rez command" << std::endl);
  86. return 0;
  87. }
  88. this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path);
  89. if (cmValue v = this->GetOptionIfSet("CPACK_DMG_SLA_DIR")) {
  90. slaDirectory = *v;
  91. if (!slaDirectory.empty() &&
  92. this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE") &&
  93. (v = this->GetOptionIfSet("CPACK_RESOURCE_FILE_LICENSE"))) {
  94. std::string license_file = *v;
  95. if (!license_file.empty() &&
  96. (license_file.find("CPack.GenericLicense.txt") ==
  97. std::string::npos)) {
  98. cmCPackLogger(
  99. cmCPackLog::LOG_OUTPUT,
  100. "Both CPACK_DMG_SLA_DIR and CPACK_RESOURCE_FILE_LICENSE specified, "
  101. "using CPACK_RESOURCE_FILE_LICENSE as a license for all languages."
  102. << std::endl);
  103. singleLicense = true;
  104. }
  105. }
  106. cmValue lang = this->GetOptionIfSet("CPACK_DMG_SLA_LANGUAGES");
  107. if (!lang) {
  108. cmCPackLogger(cmCPackLog::LOG_ERROR,
  109. "CPACK_DMG_SLA_DIR set but no languages defined "
  110. "(set CPACK_DMG_SLA_LANGUAGES)"
  111. << std::endl);
  112. return 0;
  113. }
  114. if (!cmSystemTools::FileExists(slaDirectory, false)) {
  115. cmCPackLogger(cmCPackLog::LOG_ERROR,
  116. "CPACK_DMG_SLA_DIR does not exist" << std::endl);
  117. return 0;
  118. }
  119. cmList languages{ *lang };
  120. if (languages.empty()) {
  121. cmCPackLogger(cmCPackLog::LOG_ERROR,
  122. "CPACK_DMG_SLA_LANGUAGES set but empty" << std::endl);
  123. return 0;
  124. }
  125. for (auto const& language : languages) {
  126. std::string license =
  127. cmStrCat(slaDirectory, '/', language, ".license.txt");
  128. std::string license_rtf =
  129. cmStrCat(slaDirectory, '/', language, ".license.rtf");
  130. if (!singleLicense) {
  131. if (!cmSystemTools::FileExists(license) &&
  132. !cmSystemTools::FileExists(license_rtf)) {
  133. cmCPackLogger(cmCPackLog::LOG_ERROR,
  134. "Missing license file "
  135. << language << ".license.txt"
  136. << " / " << language << ".license.rtf" << std::endl);
  137. return 0;
  138. }
  139. }
  140. std::string menu = cmStrCat(slaDirectory, '/', language, ".menu.txt");
  141. if (!cmSystemTools::FileExists(menu)) {
  142. cmCPackLogger(cmCPackLog::LOG_ERROR,
  143. "Missing menu file " << language << ".menu.txt"
  144. << std::endl);
  145. return 0;
  146. }
  147. }
  148. }
  149. return this->Superclass::InitializeInternal();
  150. }
  151. char const* cmCPackDragNDropGenerator::GetOutputExtension()
  152. {
  153. return ".dmg";
  154. }
  155. int cmCPackDragNDropGenerator::PackageFiles()
  156. {
  157. // gather which directories to make dmg files for
  158. // multiple directories occur if packaging components or groups separately
  159. // monolith
  160. if (this->Components.empty()) {
  161. return this->CreateDMG(toplevel, packageFileNames[0]);
  162. }
  163. // component install
  164. std::vector<std::pair<std::string, std::string>> package_files;
  165. std::map<std::string, cmCPackComponent>::iterator compIt;
  166. for (compIt = this->Components.begin(); compIt != this->Components.end();
  167. ++compIt) {
  168. std::string dirName = GetComponentInstallDirNameSuffix(compIt->first);
  169. std::string fileName = GetComponentInstallSuffix(compIt->first);
  170. package_files.emplace_back(fileName, dirName);
  171. }
  172. std::sort(package_files.begin(), package_files.end());
  173. package_files.erase(std::unique(package_files.begin(), package_files.end()),
  174. package_files.end());
  175. // loop to create dmg files
  176. packageFileNames.clear();
  177. for (auto const& package_file : package_files) {
  178. std::string full_package_name = cmStrCat(toplevel, '/');
  179. if (package_file.first == "ALL_IN_ONE"_s) {
  180. full_package_name += this->GetOption("CPACK_PACKAGE_FILE_NAME");
  181. } else {
  182. full_package_name += package_file.first;
  183. }
  184. full_package_name += GetOutputExtension();
  185. packageFileNames.push_back(full_package_name);
  186. std::string src_dir = cmStrCat(toplevel, '/', package_file.second);
  187. if (0 == this->CreateDMG(src_dir, full_package_name)) {
  188. return 0;
  189. }
  190. }
  191. return 1;
  192. }
  193. bool cmCPackDragNDropGenerator::CopyFile(std::ostringstream& source,
  194. std::ostringstream& target)
  195. {
  196. if (!cmSystemTools::CopyFileIfDifferent(source.str(), target.str())) {
  197. cmCPackLogger(cmCPackLog::LOG_ERROR,
  198. "Error copying " << source.str() << " to " << target.str()
  199. << std::endl);
  200. return false;
  201. }
  202. return true;
  203. }
  204. bool cmCPackDragNDropGenerator::CreateEmptyFile(std::ostringstream& target,
  205. size_t size)
  206. {
  207. cmsys::ofstream fout(target.str().c_str(), std::ios::out | std::ios::binary);
  208. if (!fout) {
  209. return false;
  210. }
  211. // Seek to desired size - 1 byte
  212. fout.seekp(size - 1, std::ios::beg);
  213. char byte = 0;
  214. // Write one byte to ensure file grows
  215. fout.write(&byte, 1);
  216. return true;
  217. }
  218. bool cmCPackDragNDropGenerator::RunCommand(std::string const& command,
  219. std::string* output)
  220. {
  221. int exit_code = 1;
  222. bool result = cmSystemTools::RunSingleCommand(
  223. command, output, output, &exit_code, nullptr, this->GeneratorVerbose,
  224. cmDuration::zero());
  225. if (!result || exit_code) {
  226. cmCPackLogger(cmCPackLog::LOG_ERROR,
  227. "Error executing: " << command << std::endl);
  228. return false;
  229. }
  230. return true;
  231. }
  232. int cmCPackDragNDropGenerator::CreateDMG(std::string const& src_dir,
  233. std::string const& output_file)
  234. {
  235. // Get optional arguments ...
  236. cmValue cpack_package_icon = this->GetOption("CPACK_PACKAGE_ICON");
  237. std::string const cpack_dmg_volume_name =
  238. this->GetOption("CPACK_DMG_VOLUME_NAME")
  239. ? *this->GetOption("CPACK_DMG_VOLUME_NAME")
  240. : *this->GetOption("CPACK_PACKAGE_FILE_NAME");
  241. std::string const cpack_dmg_format = this->GetOption("CPACK_DMG_FORMAT")
  242. ? *this->GetOption("CPACK_DMG_FORMAT")
  243. : "UDZO";
  244. std::string const cpack_dmg_filesystem =
  245. this->GetOption("CPACK_DMG_FILESYSTEM")
  246. ? *this->GetOption("CPACK_DMG_FILESYSTEM")
  247. : "HFS+";
  248. // Get optional arguments ...
  249. std::string cpack_license_file;
  250. if (this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE")) {
  251. cpack_license_file = *this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
  252. }
  253. cmValue cpack_dmg_background_image =
  254. this->GetOption("CPACK_DMG_BACKGROUND_IMAGE");
  255. cmValue cpack_dmg_ds_store = this->GetOption("CPACK_DMG_DS_STORE");
  256. cmValue cpack_dmg_languages = this->GetOption("CPACK_DMG_SLA_LANGUAGES");
  257. cmValue cpack_dmg_ds_store_setup_script =
  258. this->GetOption("CPACK_DMG_DS_STORE_SETUP_SCRIPT");
  259. bool const cpack_dmg_disable_applications_symlink =
  260. this->IsOn("CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK");
  261. // only put license on dmg if is user provided
  262. if (!cpack_license_file.empty() &&
  263. cpack_license_file.find("CPack.GenericLicense.txt") !=
  264. std::string::npos) {
  265. cpack_license_file = "";
  266. }
  267. // use sla_dir if both sla_dir and license_file are set
  268. if (!cpack_license_file.empty() && !slaDirectory.empty() && !singleLicense) {
  269. cpack_license_file = "";
  270. }
  271. // The staging directory contains everything that will end-up inside the
  272. // final disk image ...
  273. std::ostringstream staging;
  274. staging << src_dir;
  275. // Add a symlink to /Applications so users can drag-and-drop the bundle
  276. // into it unless this behavior was disabled
  277. if (!cpack_dmg_disable_applications_symlink) {
  278. std::ostringstream application_link;
  279. application_link << staging.str() << "/Applications";
  280. cmSystemTools::CreateSymlink("/Applications", application_link.str());
  281. }
  282. // Optionally add a custom volume icon ...
  283. if (!cpack_package_icon->empty()) {
  284. std::ostringstream package_icon_source;
  285. package_icon_source << cpack_package_icon;
  286. std::ostringstream package_icon_destination;
  287. package_icon_destination << staging.str() << "/.VolumeIcon.icns";
  288. if (!this->CopyFile(package_icon_source, package_icon_destination)) {
  289. cmCPackLogger(cmCPackLog::LOG_ERROR,
  290. "Error copying disk volume icon. "
  291. "Check the value of CPACK_PACKAGE_ICON."
  292. << std::endl);
  293. return 0;
  294. }
  295. }
  296. // Optionally add a custom .DS_Store file
  297. // (e.g. for setting background/layout) ...
  298. if (!cpack_dmg_ds_store->empty()) {
  299. std::ostringstream package_settings_source;
  300. package_settings_source << cpack_dmg_ds_store;
  301. std::ostringstream package_settings_destination;
  302. package_settings_destination << staging.str() << "/.DS_Store";
  303. if (!this->CopyFile(package_settings_source,
  304. package_settings_destination)) {
  305. cmCPackLogger(cmCPackLog::LOG_ERROR,
  306. "Error copying disk volume settings file. "
  307. "Check the value of CPACK_DMG_DS_STORE."
  308. << std::endl);
  309. return 0;
  310. }
  311. }
  312. // Optionally add a custom background image ...
  313. // Make sure the background file type is the same as the custom image
  314. // and that the file is hidden so it doesn't show up.
  315. if (!cpack_dmg_background_image->empty()) {
  316. std::string const extension =
  317. cmSystemTools::GetFilenameLastExtension(cpack_dmg_background_image);
  318. std::ostringstream package_background_source;
  319. package_background_source << cpack_dmg_background_image;
  320. std::ostringstream package_background_destination;
  321. package_background_destination << staging.str()
  322. << "/.background/background" << extension;
  323. if (!this->CopyFile(package_background_source,
  324. package_background_destination)) {
  325. cmCPackLogger(cmCPackLog::LOG_ERROR,
  326. "Error copying disk volume background image. "
  327. "Check the value of CPACK_DMG_BACKGROUND_IMAGE."
  328. << std::endl);
  329. return 0;
  330. }
  331. }
  332. bool remount_image =
  333. !cpack_package_icon->empty() || !cpack_dmg_ds_store_setup_script->empty();
  334. std::string temp_image_format = "UDZO";
  335. // Create 1 MB dummy padding file in staging area when we need to remount
  336. // image, so we have enough space for storing changes ...
  337. if (remount_image) {
  338. std::ostringstream dummy_padding;
  339. dummy_padding << staging.str() << "/.dummy-padding-file";
  340. if (!this->CreateEmptyFile(dummy_padding, 1048576)) {
  341. cmCPackLogger(cmCPackLog::LOG_ERROR,
  342. "Error creating dummy padding file." << std::endl);
  343. return 0;
  344. }
  345. temp_image_format = "UDRW";
  346. }
  347. // Create a temporary read-write disk image ...
  348. std::string temp_image =
  349. cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/temp.dmg");
  350. std::string create_error;
  351. auto temp_image_command =
  352. cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
  353. " create"
  354. " -ov"
  355. " -srcfolder \"",
  356. staging.str(),
  357. "\""
  358. " -volname \"",
  359. cpack_dmg_volume_name,
  360. "\""
  361. " -fs \"",
  362. cpack_dmg_filesystem,
  363. "\""
  364. " -format ",
  365. temp_image_format, " \"", temp_image, '"');
  366. if (!this->RunCommand(temp_image_command, &create_error)) {
  367. cmCPackLogger(cmCPackLog::LOG_ERROR,
  368. "Error generating temporary disk image." << std::endl
  369. << create_error
  370. << std::endl);
  371. return 0;
  372. }
  373. if (remount_image) {
  374. // Store that we have a failure so that we always unmount the image
  375. // before we exit.
  376. bool had_error = false;
  377. auto attach_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
  378. " attach"
  379. " \"",
  380. temp_image, '"');
  381. std::string attach_output;
  382. if (!this->RunCommand(attach_command, &attach_output)) {
  383. cmCPackLogger(cmCPackLog::LOG_ERROR,
  384. "Error attaching temporary disk image." << std::endl);
  385. return 0;
  386. }
  387. cmsys::RegularExpression mountpoint_regex(".*(/Volumes/[^\n]+)\n.*");
  388. mountpoint_regex.find(attach_output.c_str());
  389. std::string const temp_mount = mountpoint_regex.match(1);
  390. std::string const temp_mount_name =
  391. temp_mount.substr(cmStrLen("/Volumes/"));
  392. // Remove dummy padding file so we have enough space on RW image ...
  393. std::ostringstream dummy_padding;
  394. dummy_padding << temp_mount << "/.dummy-padding-file";
  395. if (!cmSystemTools::RemoveFile(dummy_padding.str())) {
  396. cmCPackLogger(cmCPackLog::LOG_ERROR,
  397. "Error removing dummy padding file." << std::endl);
  398. had_error = true;
  399. }
  400. // Optionally set the custom icon flag for the image ...
  401. if (!had_error && !cpack_package_icon->empty()) {
  402. std::string error;
  403. auto setfile_command = cmStrCat(this->GetOption("CPACK_COMMAND_SETFILE"),
  404. " -a C"
  405. " \"",
  406. temp_mount, '"');
  407. if (!this->RunCommand(setfile_command, &error)) {
  408. cmCPackLogger(cmCPackLog::LOG_ERROR,
  409. "Error assigning custom icon to temporary disk image."
  410. << std::endl
  411. << error << std::endl);
  412. had_error = true;
  413. }
  414. }
  415. // Optionally we can execute a custom apple script to generate
  416. // the .DS_Store for the volume folder ...
  417. if (!had_error && !cpack_dmg_ds_store_setup_script->empty()) {
  418. auto setup_script_command = cmStrCat("osascript"
  419. " \"",
  420. cpack_dmg_ds_store_setup_script,
  421. "\""
  422. " \"",
  423. temp_mount_name, '"');
  424. std::string error;
  425. if (!this->RunCommand(setup_script_command, &error)) {
  426. cmCPackLogger(cmCPackLog::LOG_ERROR,
  427. "Error executing custom script on disk image."
  428. << std::endl
  429. << error << std::endl);
  430. had_error = true;
  431. }
  432. }
  433. auto detach_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
  434. " detach"
  435. " \"",
  436. temp_mount, '\"');
  437. if (!this->RunCommand(detach_command)) {
  438. cmCPackLogger(cmCPackLog::LOG_ERROR,
  439. "Error detaching temporary disk image." << std::endl);
  440. return 0;
  441. }
  442. if (had_error) {
  443. return 0;
  444. }
  445. }
  446. // Create the final compressed read-only disk image ...
  447. auto final_image_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
  448. " convert \"", temp_image,
  449. "\""
  450. " -format ",
  451. cpack_dmg_format,
  452. " -imagekey"
  453. " zlib-level=9"
  454. " -o \"",
  455. output_file, '"');
  456. std::string convert_error;
  457. if (!this->RunCommand(final_image_command, &convert_error)) {
  458. cmCPackLogger(cmCPackLog::LOG_ERROR,
  459. "Error compressing disk image." << std::endl
  460. << convert_error
  461. << std::endl);
  462. return 0;
  463. }
  464. if (!cpack_license_file.empty() || !slaDirectory.empty()) {
  465. // Use old hardcoded style if sla_dir is not set
  466. bool oldStyle = slaDirectory.empty();
  467. std::string sla_xml =
  468. cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.xml");
  469. cmList languages;
  470. if (!oldStyle) {
  471. languages.assign(cpack_dmg_languages);
  472. }
  473. std::vector<uint16_t> header_data;
  474. if (oldStyle) {
  475. header_data = std::vector<uint16_t>(
  476. DefaultLpic,
  477. DefaultLpic + (sizeof(DefaultLpic) / sizeof(*DefaultLpic)));
  478. } else {
  479. /*
  480. * LPic Layout
  481. * (https://github.com/pypt/dmg-add-license/blob/master/main.c)
  482. * as far as I can tell (no official documentation seems to exist):
  483. * struct LPic {
  484. * uint16_t default_language; // points to a resid, defaulting to 0,
  485. * // which is the first set language
  486. * uint16_t length;
  487. * struct {
  488. * uint16_t language_code;
  489. * uint16_t resid;
  490. * uint16_t encoding; // Encoding from TextCommon.h,
  491. * // forcing MacRoman (0) for now. Might need to
  492. * // allow overwrite per license by user later
  493. * } item[1];
  494. * }
  495. */
  496. header_data.push_back(0);
  497. header_data.push_back(languages.size());
  498. // NOLINTNEXTLINE(modernize-loop-convert): `HAVE_CoreServices` needs `i`
  499. for (cmList::size_type i = 0; i < languages.size(); ++i) {
  500. auto const& language = languages[i];
  501. CFStringRef language_cfstring = CFStringCreateWithCString(
  502. nullptr, language.c_str(), kCFStringEncodingUTF8);
  503. CFStringRef iso_language =
  504. CFLocaleCreateCanonicalLanguageIdentifierFromString(
  505. nullptr, language_cfstring);
  506. if (!iso_language) {
  507. cmCPackLogger(cmCPackLog::LOG_ERROR,
  508. language << " is not a recognized language"
  509. << std::endl);
  510. }
  511. char iso_language_cstr[65];
  512. CFStringGetCString(iso_language, iso_language_cstr,
  513. sizeof(iso_language_cstr) - 1,
  514. kCFStringEncodingMacRoman);
  515. LangCode lang = 0;
  516. RegionCode region = 0;
  517. #if HAVE_CoreServices
  518. OSStatus err =
  519. LocaleStringToLangAndRegionCodes(iso_language_cstr, &lang, &region);
  520. if (err != noErr)
  521. #endif
  522. {
  523. cmCPackLogger(cmCPackLog::LOG_ERROR,
  524. "No language/region code available for "
  525. << iso_language_cstr << std::endl);
  526. return 0;
  527. }
  528. #if HAVE_CoreServices
  529. header_data.push_back(region);
  530. header_data.push_back(i);
  531. header_data.push_back(0);
  532. #endif
  533. }
  534. }
  535. RezDoc rez;
  536. {
  537. RezDict lpic = { {}, 5000, {} };
  538. lpic.Data.reserve(header_data.size() * sizeof(header_data[0]));
  539. for (uint16_t x : header_data) {
  540. // LPic header is big-endian.
  541. char* d = reinterpret_cast<char*>(&x);
  542. #if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE
  543. lpic.Data.push_back(d[1]);
  544. lpic.Data.push_back(d[0]);
  545. #else
  546. lpic.Data.push_back(d[0]);
  547. lpic.Data.push_back(d[1]);
  548. #endif
  549. }
  550. rez.LPic.Entries.emplace_back(std::move(lpic));
  551. }
  552. bool have_write_license_error = false;
  553. std::string error;
  554. if (oldStyle) {
  555. if (!this->WriteLicense(rez, 0, "", cpack_license_file, &error)) {
  556. have_write_license_error = true;
  557. }
  558. } else {
  559. for (size_t i = 0; i < languages.size() && !have_write_license_error;
  560. ++i) {
  561. if (singleLicense) {
  562. if (!this->WriteLicense(rez, i + 5000, languages[i],
  563. cpack_license_file, &error)) {
  564. have_write_license_error = true;
  565. }
  566. } else {
  567. if (!this->WriteLicense(rez, i + 5000, languages[i], "", &error)) {
  568. have_write_license_error = true;
  569. }
  570. }
  571. }
  572. }
  573. if (have_write_license_error) {
  574. cmCPackLogger(cmCPackLog::LOG_ERROR,
  575. "Error writing license file to SLA." << std::endl
  576. << error
  577. << std::endl);
  578. return 0;
  579. }
  580. this->WriteRezXML(sla_xml, rez);
  581. // Create the final compressed read-only disk image ...
  582. auto embed_sla_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
  583. " udifrez"
  584. " -xml"
  585. " \"",
  586. sla_xml,
  587. "\""
  588. " FIXME_WHY_IS_THIS_ARGUMENT_NEEDED"
  589. " \"",
  590. output_file, '"');
  591. std::string embed_error;
  592. if (!this->RunCommand(embed_sla_command, &embed_error)) {
  593. cmCPackLogger(cmCPackLog::LOG_ERROR,
  594. "Error compressing disk image." << std::endl
  595. << embed_error
  596. << std::endl);
  597. return 0;
  598. }
  599. }
  600. return 1;
  601. }
  602. bool cmCPackDragNDropGenerator::SupportsComponentInstallation() const
  603. {
  604. return true;
  605. }
  606. std::string cmCPackDragNDropGenerator::GetComponentInstallSuffix(
  607. std::string const& componentName)
  608. {
  609. // we want to group components together that go in the same dmg package
  610. std::string package_file_name = this->GetOption("CPACK_PACKAGE_FILE_NAME");
  611. // we have 3 mutually exclusive modes to work in
  612. // 1. all components in one package
  613. // 2. each group goes in its own package with left over
  614. // components in their own package
  615. // 3. ignore groups - if grouping is defined, it is ignored
  616. // and each component goes in its own package
  617. if (this->componentPackageMethod == ONE_PACKAGE) {
  618. return "ALL_IN_ONE";
  619. }
  620. if (this->componentPackageMethod == ONE_PACKAGE_PER_GROUP) {
  621. // We have to find the name of the COMPONENT GROUP
  622. // the current COMPONENT belongs to.
  623. std::string groupVar = cmStrCat(
  624. "CPACK_COMPONENT_", cmSystemTools::UpperCase(componentName), "_GROUP");
  625. cmValue _groupName = this->GetOption(groupVar);
  626. if (_groupName) {
  627. std::string groupName = _groupName;
  628. groupName =
  629. GetComponentPackageFileName(package_file_name, groupName, true);
  630. return groupName;
  631. }
  632. }
  633. std::string componentFileName = cmStrCat(
  634. "CPACK_DMG_", cmSystemTools::UpperCase(componentName), "_FILE_NAME");
  635. if (cmValue v = this->GetOptionIfSet(componentFileName)) {
  636. return *v;
  637. }
  638. return GetComponentPackageFileName(package_file_name, componentName, false);
  639. }
  640. std::string cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
  641. std::string const& componentName)
  642. {
  643. return this->GetSanitizedDirOrFileName(
  644. this->GetComponentInstallSuffix(componentName));
  645. }
  646. void cmCPackDragNDropGenerator::WriteRezXML(std::string const& file,
  647. RezDoc const& rez)
  648. {
  649. cmGeneratedFileStream fxml(file);
  650. cmXMLWriter xml(fxml);
  651. xml.StartDocument();
  652. xml.StartElement("plist");
  653. xml.Attribute("version", "1.0");
  654. xml.StartElement("dict");
  655. this->WriteRezArray(xml, rez.LPic);
  656. this->WriteRezArray(xml, rez.Menu);
  657. this->WriteRezArray(xml, rez.Text);
  658. this->WriteRezArray(xml, rez.RTF);
  659. xml.EndElement(); // dict
  660. xml.EndElement(); // plist
  661. xml.EndDocument();
  662. fxml.Close();
  663. }
  664. void cmCPackDragNDropGenerator::WriteRezArray(cmXMLWriter& xml,
  665. RezArray const& array)
  666. {
  667. if (array.Entries.empty()) {
  668. return;
  669. }
  670. xml.StartElement("key");
  671. xml.Content(array.Key);
  672. xml.EndElement(); // key
  673. xml.StartElement("array");
  674. for (RezDict const& dict : array.Entries) {
  675. this->WriteRezDict(xml, dict);
  676. }
  677. xml.EndElement(); // array
  678. }
  679. void cmCPackDragNDropGenerator::WriteRezDict(cmXMLWriter& xml,
  680. RezDict const& dict)
  681. {
  682. std::vector<char> base64buf(dict.Data.size() * 3 / 2 + 5);
  683. size_t base64len =
  684. cmsysBase64_Encode(dict.Data.data(), dict.Data.size(),
  685. reinterpret_cast<unsigned char*>(base64buf.data()), 0);
  686. std::string base64data(base64buf.data(), base64len);
  687. /* clang-format off */
  688. xml.StartElement("dict");
  689. xml.StartElement("key"); xml.Content("Attributes"); xml.EndElement();
  690. xml.StartElement("string"); xml.Content("0x0000"); xml.EndElement();
  691. xml.StartElement("key"); xml.Content("Data"); xml.EndElement();
  692. xml.StartElement("data"); xml.Content(base64data); xml.EndElement();
  693. xml.StartElement("key"); xml.Content("ID"); xml.EndElement();
  694. xml.StartElement("string"); xml.Content(dict.ID); xml.EndElement();
  695. xml.StartElement("key"); xml.Content("Name"); xml.EndElement();
  696. xml.StartElement("string"); xml.Content(dict.Name); xml.EndElement();
  697. xml.EndElement(); // dict
  698. /* clang-format on */
  699. }
  700. bool cmCPackDragNDropGenerator::WriteLicense(RezDoc& rez, size_t licenseNumber,
  701. std::string licenseLanguage,
  702. std::string const& licenseFile,
  703. std::string* error)
  704. {
  705. if (!licenseFile.empty() && !singleLicense) {
  706. licenseNumber = 5002;
  707. licenseLanguage = "English";
  708. }
  709. // License file
  710. RezArray* licenseArray = &rez.Text;
  711. std::string actual_license;
  712. if (!licenseFile.empty()) {
  713. if (cmHasLiteralSuffix(licenseFile, ".rtf")) {
  714. licenseArray = &rez.RTF;
  715. }
  716. actual_license = licenseFile;
  717. } else {
  718. std::string license_wo_ext =
  719. cmStrCat(slaDirectory, '/', licenseLanguage, ".license");
  720. if (cmSystemTools::FileExists(cmStrCat(license_wo_ext, ".txt"))) {
  721. actual_license = cmStrCat(license_wo_ext, ".txt");
  722. } else {
  723. licenseArray = &rez.RTF;
  724. actual_license = cmStrCat(license_wo_ext, ".rtf");
  725. }
  726. }
  727. // License body
  728. {
  729. RezDict license = { licenseLanguage, licenseNumber, {} };
  730. std::vector<std::string> lines;
  731. if (!this->ReadFile(actual_license, lines, error)) {
  732. return false;
  733. }
  734. this->EncodeLicense(license, lines);
  735. licenseArray->Entries.emplace_back(std::move(license));
  736. }
  737. // Menu body
  738. {
  739. RezDict menu = { licenseLanguage, licenseNumber, {} };
  740. if (!licenseFile.empty() && !singleLicense) {
  741. this->EncodeMenu(menu, DefaultMenu);
  742. } else {
  743. std::vector<std::string> lines;
  744. std::string actual_menu =
  745. cmStrCat(slaDirectory, '/', licenseLanguage, ".menu.txt");
  746. if (!this->ReadFile(actual_menu, lines, error)) {
  747. return false;
  748. }
  749. this->EncodeMenu(menu, lines);
  750. }
  751. rez.Menu.Entries.emplace_back(std::move(menu));
  752. }
  753. return true;
  754. }
  755. void cmCPackDragNDropGenerator::EncodeLicense(
  756. RezDict& dict, std::vector<std::string> const& lines)
  757. {
  758. // License text uses CR newlines.
  759. for (std::string const& l : lines) {
  760. dict.Data.insert(dict.Data.end(), l.begin(), l.end());
  761. dict.Data.push_back('\r');
  762. }
  763. dict.Data.push_back('\r');
  764. }
  765. void cmCPackDragNDropGenerator::EncodeMenu(
  766. RezDict& dict, std::vector<std::string> const& lines)
  767. {
  768. // Menu resources start with a big-endian uint16_t for number of lines:
  769. {
  770. uint16_t numLines = static_cast<uint16_t>(lines.size());
  771. char* d = reinterpret_cast<char*>(&numLines);
  772. #if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE
  773. dict.Data.push_back(d[1]);
  774. dict.Data.push_back(d[0]);
  775. #else
  776. dict.Data.push_back(d[0]);
  777. dict.Data.push_back(d[1]);
  778. #endif
  779. }
  780. // Each line starts with a uint8_t length, plus the bytes themselves:
  781. for (std::string const& l : lines) {
  782. dict.Data.push_back(static_cast<unsigned char>(l.length()));
  783. dict.Data.insert(dict.Data.end(), l.begin(), l.end());
  784. }
  785. }
  786. bool cmCPackDragNDropGenerator::ReadFile(std::string const& file,
  787. std::vector<std::string>& lines,
  788. std::string* error)
  789. {
  790. cmsys::ifstream ifs(file);
  791. std::string line;
  792. while (std::getline(ifs, line)) {
  793. if (!this->BreakLongLine(line, lines, error)) {
  794. return false;
  795. }
  796. }
  797. return true;
  798. }
  799. bool cmCPackDragNDropGenerator::BreakLongLine(std::string const& line,
  800. std::vector<std::string>& lines,
  801. std::string* error)
  802. {
  803. size_t const max_line_length = 255;
  804. size_t line_length = max_line_length;
  805. for (size_t i = 0; i < line.size(); i += line_length) {
  806. line_length = max_line_length;
  807. if (i + line_length > line.size()) {
  808. line_length = line.size() - i;
  809. } else {
  810. while (line_length > 0 && line[i + line_length - 1] != ' ') {
  811. line_length = line_length - 1;
  812. }
  813. }
  814. if (line_length == 0) {
  815. *error = "Please make sure there are no words "
  816. "(or character sequences not broken up by spaces or newlines) "
  817. "in your license file which are more than 255 characters long.";
  818. return false;
  819. }
  820. lines.push_back(line.substr(i, line_length));
  821. }
  822. return true;
  823. }