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 "cmCPackDragNDropGenerator.h"
10 #include <cm/string_view>
11 #include <cmext/string_view>
13 #include <CoreFoundation/CoreFoundation.h>
14 #include <cm3p/kwiml/abi.h>
16 #include "cmsys/Base64.h"
17 #include "cmsys/FStream.hxx"
18 #include "cmsys/RegularExpression.hxx"
20 #include "cmCPackConfigure.h"
21 #include "cmCPackGenerator.h"
22 #include "cmCPackLog.h"
23 #include "cmDuration.h"
24 #include "cmGeneratedFileStream.h"
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
29 #include "cmXMLWriter.h"
32 // For the old LocaleStringToLangAndRegionCodes() function, to convert
33 // to the old Script Manager RegionCode values needed for the 'LPic' data
34 // structure used for generating multi-lingual SLAs.
35 # include <CoreServices/CoreServices.h>
38 static const uint16_t DefaultLpic
[] = {
39 /* clang-format off */
40 0x0002, 0x0011, 0x0003, 0x0001, 0x0000, 0x0000, 0x0002, 0x0000,
41 0x0008, 0x0003, 0x0000, 0x0001, 0x0004, 0x0000, 0x0004, 0x0005,
42 0x0000, 0x000E, 0x0006, 0x0001, 0x0005, 0x0007, 0x0000, 0x0007,
43 0x0008, 0x0000, 0x0047, 0x0009, 0x0000, 0x0034, 0x000A, 0x0001,
44 0x0035, 0x000B, 0x0001, 0x0020, 0x000C, 0x0000, 0x0011, 0x000D,
45 0x0000, 0x005B, 0x0004, 0x0000, 0x0033, 0x000F, 0x0001, 0x000C,
46 0x0010, 0x0000, 0x000B, 0x000E, 0x0000
50 static const std::vector
<std::string
> DefaultMenu
= {
51 { "English", "Agree", "Disagree", "Print", "Save...",
52 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
53 "You agree to the License Agreement terms when "
54 "you click the \"Agree\" button.",
55 "Software License Agreement",
56 "This text cannot be saved. "
57 "This disk may be full or locked, or the file may be locked.",
58 "Unable to print. Make sure you have selected a printer." }
61 cmCPackDragNDropGenerator::cmCPackDragNDropGenerator()
62 : singleLicense(false)
64 // default to one package file for components
65 this->componentPackageMethod
= ONE_PACKAGE
;
68 cmCPackDragNDropGenerator::~cmCPackDragNDropGenerator() = default;
70 int cmCPackDragNDropGenerator::InitializeInternal()
72 // Starting with Xcode 4.3, look in "/Applications/Xcode.app" first:
74 std::vector
<std::string
> paths
;
75 paths
.emplace_back("/Applications/Xcode.app/Contents/Developer/Tools");
76 paths
.emplace_back("/Developer/Tools");
78 const std::string hdiutil_path
=
79 cmSystemTools::FindProgram("hdiutil", std::vector
<std::string
>(), false);
80 if (hdiutil_path
.empty()) {
81 cmCPackLogger(cmCPackLog::LOG_ERROR
,
82 "Cannot locate hdiutil command" << std::endl
);
85 this->SetOptionIfNotSet("CPACK_COMMAND_HDIUTIL", hdiutil_path
);
87 const std::string setfile_path
=
88 cmSystemTools::FindProgram("SetFile", paths
, false);
89 if (setfile_path
.empty()) {
90 cmCPackLogger(cmCPackLog::LOG_ERROR
,
91 "Cannot locate SetFile command" << std::endl
);
94 this->SetOptionIfNotSet("CPACK_COMMAND_SETFILE", setfile_path
);
96 const std::string rez_path
= cmSystemTools::FindProgram("Rez", paths
, false);
97 if (rez_path
.empty()) {
98 cmCPackLogger(cmCPackLog::LOG_ERROR
,
99 "Cannot locate Rez command" << std::endl
);
102 this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path
);
104 if (this->IsSet("CPACK_DMG_SLA_DIR")) {
105 slaDirectory
= this->GetOption("CPACK_DMG_SLA_DIR");
106 if (!slaDirectory
.empty() &&
107 this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE") &&
108 this->IsSet("CPACK_RESOURCE_FILE_LICENSE")) {
109 std::string license_file
=
110 this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
111 if (!license_file
.empty() &&
112 (license_file
.find("CPack.GenericLicense.txt") ==
113 std::string::npos
)) {
115 cmCPackLog::LOG_OUTPUT
,
116 "Both CPACK_DMG_SLA_DIR and CPACK_RESOURCE_FILE_LICENSE specified, "
117 "using CPACK_RESOURCE_FILE_LICENSE as a license for all languages."
119 singleLicense
= true;
122 if (!this->IsSet("CPACK_DMG_SLA_LANGUAGES")) {
123 cmCPackLogger(cmCPackLog::LOG_ERROR
,
124 "CPACK_DMG_SLA_DIR set but no languages defined "
125 "(set CPACK_DMG_SLA_LANGUAGES)"
129 if (!cmSystemTools::FileExists(slaDirectory
, false)) {
130 cmCPackLogger(cmCPackLog::LOG_ERROR
,
131 "CPACK_DMG_SLA_DIR does not exist" << std::endl
);
135 cmList languages
{ this->GetOption("CPACK_DMG_SLA_LANGUAGES") };
136 if (languages
.empty()) {
137 cmCPackLogger(cmCPackLog::LOG_ERROR
,
138 "CPACK_DMG_SLA_LANGUAGES set but empty" << std::endl
);
141 for (auto const& language
: languages
) {
142 std::string license
=
143 cmStrCat(slaDirectory
, '/', language
, ".license.txt");
144 std::string license_rtf
=
145 cmStrCat(slaDirectory
, '/', language
, ".license.rtf");
146 if (!singleLicense
) {
147 if (!cmSystemTools::FileExists(license
) &&
148 !cmSystemTools::FileExists(license_rtf
)) {
149 cmCPackLogger(cmCPackLog::LOG_ERROR
,
150 "Missing license file "
151 << language
<< ".license.txt"
152 << " / " << language
<< ".license.rtf" << std::endl
);
156 std::string menu
= cmStrCat(slaDirectory
, '/', language
, ".menu.txt");
157 if (!cmSystemTools::FileExists(menu
)) {
158 cmCPackLogger(cmCPackLog::LOG_ERROR
,
159 "Missing menu file " << language
<< ".menu.txt"
166 return this->Superclass::InitializeInternal();
169 const char* cmCPackDragNDropGenerator::GetOutputExtension()
174 int cmCPackDragNDropGenerator::PackageFiles()
176 // gather which directories to make dmg files for
177 // multiple directories occur if packaging components or groups separately
180 if (this->Components
.empty()) {
181 return this->CreateDMG(toplevel
, packageFileNames
[0]);
185 std::vector
<std::pair
<std::string
, std::string
>> package_files
;
187 std::map
<std::string
, cmCPackComponent
>::iterator compIt
;
188 for (compIt
= this->Components
.begin(); compIt
!= this->Components
.end();
190 std::string dirName
= GetComponentInstallDirNameSuffix(compIt
->first
);
191 std::string fileName
= GetComponentInstallSuffix(compIt
->first
);
192 package_files
.emplace_back(fileName
, dirName
);
194 std::sort(package_files
.begin(), package_files
.end());
195 package_files
.erase(std::unique(package_files
.begin(), package_files
.end()),
196 package_files
.end());
198 // loop to create dmg files
199 packageFileNames
.clear();
200 for (auto const& package_file
: package_files
) {
201 std::string full_package_name
= cmStrCat(toplevel
, '/');
202 if (package_file
.first
== "ALL_IN_ONE"_s
) {
203 full_package_name
+= this->GetOption("CPACK_PACKAGE_FILE_NAME");
205 full_package_name
+= package_file
.first
;
207 full_package_name
+= GetOutputExtension();
208 packageFileNames
.push_back(full_package_name
);
210 std::string src_dir
= cmStrCat(toplevel
, '/', package_file
.second
);
212 if (0 == this->CreateDMG(src_dir
, full_package_name
)) {
219 bool cmCPackDragNDropGenerator::CopyFile(std::ostringstream
& source
,
220 std::ostringstream
& target
)
222 if (!cmSystemTools::CopyFileIfDifferent(source
.str(), target
.str())) {
223 cmCPackLogger(cmCPackLog::LOG_ERROR
,
224 "Error copying " << source
.str() << " to " << target
.str()
233 bool cmCPackDragNDropGenerator::CreateEmptyFile(std::ostringstream
& target
,
236 cmsys::ofstream
fout(target
.str().c_str(), std::ios::out
| std::ios::binary
);
241 // Seek to desired size - 1 byte
242 fout
.seekp(size
- 1, std::ios::beg
);
244 // Write one byte to ensure file grows
245 fout
.write(&byte
, 1);
250 bool cmCPackDragNDropGenerator::RunCommand(std::string
const& command
,
255 bool result
= cmSystemTools::RunSingleCommand(
256 command
, output
, output
, &exit_code
, nullptr, this->GeneratorVerbose
,
259 if (!result
|| exit_code
) {
260 cmCPackLogger(cmCPackLog::LOG_ERROR
,
261 "Error executing: " << command
<< std::endl
);
269 int cmCPackDragNDropGenerator::CreateDMG(const std::string
& src_dir
,
270 const std::string
& output_file
)
272 // Get optional arguments ...
273 cmValue cpack_package_icon
= this->GetOption("CPACK_PACKAGE_ICON");
275 const std::string cpack_dmg_volume_name
=
276 this->GetOption("CPACK_DMG_VOLUME_NAME")
277 ? *this->GetOption("CPACK_DMG_VOLUME_NAME")
278 : *this->GetOption("CPACK_PACKAGE_FILE_NAME");
280 const std::string cpack_dmg_format
= this->GetOption("CPACK_DMG_FORMAT")
281 ? *this->GetOption("CPACK_DMG_FORMAT")
284 const std::string cpack_dmg_filesystem
=
285 this->GetOption("CPACK_DMG_FILESYSTEM")
286 ? *this->GetOption("CPACK_DMG_FILESYSTEM")
289 // Get optional arguments ...
290 std::string cpack_license_file
;
291 if (this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE")) {
292 cpack_license_file
= *this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
295 cmValue cpack_dmg_background_image
=
296 this->GetOption("CPACK_DMG_BACKGROUND_IMAGE");
298 cmValue cpack_dmg_ds_store
= this->GetOption("CPACK_DMG_DS_STORE");
300 cmValue cpack_dmg_languages
= this->GetOption("CPACK_DMG_SLA_LANGUAGES");
302 cmValue cpack_dmg_ds_store_setup_script
=
303 this->GetOption("CPACK_DMG_DS_STORE_SETUP_SCRIPT");
305 const bool cpack_dmg_disable_applications_symlink
=
306 this->IsOn("CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK");
308 // only put license on dmg if is user provided
309 if (!cpack_license_file
.empty() &&
310 cpack_license_file
.find("CPack.GenericLicense.txt") !=
312 cpack_license_file
= "";
315 // use sla_dir if both sla_dir and license_file are set
316 if (!cpack_license_file
.empty() && !slaDirectory
.empty() && !singleLicense
) {
317 cpack_license_file
= "";
320 // The staging directory contains everything that will end-up inside the
321 // final disk image ...
322 std::ostringstream staging
;
325 // Add a symlink to /Applications so users can drag-and-drop the bundle
326 // into it unless this behavior was disabled
327 if (!cpack_dmg_disable_applications_symlink
) {
328 std::ostringstream application_link
;
329 application_link
<< staging
.str() << "/Applications";
330 cmSystemTools::CreateSymlink("/Applications", application_link
.str());
333 // Optionally add a custom volume icon ...
334 if (!cpack_package_icon
->empty()) {
335 std::ostringstream package_icon_source
;
336 package_icon_source
<< cpack_package_icon
;
338 std::ostringstream package_icon_destination
;
339 package_icon_destination
<< staging
.str() << "/.VolumeIcon.icns";
341 if (!this->CopyFile(package_icon_source
, package_icon_destination
)) {
342 cmCPackLogger(cmCPackLog::LOG_ERROR
,
343 "Error copying disk volume icon. "
344 "Check the value of CPACK_PACKAGE_ICON."
351 // Optionally add a custom .DS_Store file
352 // (e.g. for setting background/layout) ...
353 if (!cpack_dmg_ds_store
->empty()) {
354 std::ostringstream package_settings_source
;
355 package_settings_source
<< cpack_dmg_ds_store
;
357 std::ostringstream package_settings_destination
;
358 package_settings_destination
<< staging
.str() << "/.DS_Store";
360 if (!this->CopyFile(package_settings_source
,
361 package_settings_destination
)) {
362 cmCPackLogger(cmCPackLog::LOG_ERROR
,
363 "Error copying disk volume settings file. "
364 "Check the value of CPACK_DMG_DS_STORE."
371 // Optionally add a custom background image ...
372 // Make sure the background file type is the same as the custom image
373 // and that the file is hidden so it doesn't show up.
374 if (!cpack_dmg_background_image
->empty()) {
375 const std::string extension
=
376 cmSystemTools::GetFilenameLastExtension(cpack_dmg_background_image
);
377 std::ostringstream package_background_source
;
378 package_background_source
<< cpack_dmg_background_image
;
380 std::ostringstream package_background_destination
;
381 package_background_destination
<< staging
.str()
382 << "/.background/background" << extension
;
384 if (!this->CopyFile(package_background_source
,
385 package_background_destination
)) {
386 cmCPackLogger(cmCPackLog::LOG_ERROR
,
387 "Error copying disk volume background image. "
388 "Check the value of CPACK_DMG_BACKGROUND_IMAGE."
396 !cpack_package_icon
->empty() || !cpack_dmg_ds_store_setup_script
->empty();
398 std::string temp_image_format
= "UDZO";
400 // Create 1 MB dummy padding file in staging area when we need to remount
401 // image, so we have enough space for storing changes ...
403 std::ostringstream dummy_padding
;
404 dummy_padding
<< staging
.str() << "/.dummy-padding-file";
405 if (!this->CreateEmptyFile(dummy_padding
, 1048576)) {
406 cmCPackLogger(cmCPackLog::LOG_ERROR
,
407 "Error creating dummy padding file." << std::endl
);
411 temp_image_format
= "UDRW";
414 // Create a temporary read-write disk image ...
415 std::string temp_image
=
416 cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/temp.dmg");
418 std::string create_error
;
419 auto temp_image_command
=
420 cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
427 cpack_dmg_volume_name
,
430 cpack_dmg_filesystem
,
433 temp_image_format
, " \"", temp_image
, '"');
435 if (!this->RunCommand(temp_image_command
, &create_error
)) {
436 cmCPackLogger(cmCPackLog::LOG_ERROR
,
437 "Error generating temporary disk image." << std::endl
445 // Store that we have a failure so that we always unmount the image
447 bool had_error
= false;
449 auto attach_command
= cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
454 std::string attach_output
;
455 if (!this->RunCommand(attach_command
, &attach_output
)) {
456 cmCPackLogger(cmCPackLog::LOG_ERROR
,
457 "Error attaching temporary disk image." << std::endl
);
462 cmsys::RegularExpression
mountpoint_regex(".*(/Volumes/[^\n]+)\n.*");
463 mountpoint_regex
.find(attach_output
.c_str());
464 std::string
const temp_mount
= mountpoint_regex
.match(1);
465 std::string
const temp_mount_name
=
466 temp_mount
.substr(cmStrLen("/Volumes/"));
468 // Remove dummy padding file so we have enough space on RW image ...
469 std::ostringstream dummy_padding
;
470 dummy_padding
<< temp_mount
<< "/.dummy-padding-file";
471 if (!cmSystemTools::RemoveFile(dummy_padding
.str())) {
472 cmCPackLogger(cmCPackLog::LOG_ERROR
,
473 "Error removing dummy padding file." << std::endl
);
478 // Optionally set the custom icon flag for the image ...
479 if (!had_error
&& !cpack_package_icon
->empty()) {
481 auto setfile_command
= cmStrCat(this->GetOption("CPACK_COMMAND_SETFILE"),
486 if (!this->RunCommand(setfile_command
, &error
)) {
487 cmCPackLogger(cmCPackLog::LOG_ERROR
,
488 "Error assigning custom icon to temporary disk image."
490 << error
<< std::endl
);
496 // Optionally we can execute a custom apple script to generate
497 // the .DS_Store for the volume folder ...
498 if (!had_error
&& !cpack_dmg_ds_store_setup_script
->empty()) {
499 auto setup_script_command
= cmStrCat("osascript"
501 cpack_dmg_ds_store_setup_script
,
504 temp_mount_name
, '"');
506 if (!this->RunCommand(setup_script_command
, &error
)) {
507 cmCPackLogger(cmCPackLog::LOG_ERROR
,
508 "Error executing custom script on disk image."
510 << error
<< std::endl
);
516 auto detach_command
= cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
521 if (!this->RunCommand(detach_command
)) {
522 cmCPackLogger(cmCPackLog::LOG_ERROR
,
523 "Error detaching temporary disk image." << std::endl
);
533 // Create the final compressed read-only disk image ...
534 auto final_image_command
= cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
535 " convert \"", temp_image
,
544 std::string convert_error
;
546 if (!this->RunCommand(final_image_command
, &convert_error
)) {
547 cmCPackLogger(cmCPackLog::LOG_ERROR
,
548 "Error compressing disk image." << std::endl
555 if (!cpack_license_file
.empty() || !slaDirectory
.empty()) {
556 // Use old hardcoded style if sla_dir is not set
557 bool oldStyle
= slaDirectory
.empty();
558 std::string sla_xml
=
559 cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.xml");
563 languages
.assign(cpack_dmg_languages
);
566 std::vector
<uint16_t> header_data
;
568 header_data
= std::vector
<uint16_t>(
570 DefaultLpic
+ (sizeof(DefaultLpic
) / sizeof(*DefaultLpic
)));
574 * (https://github.com/pypt/dmg-add-license/blob/master/main.c)
575 * as far as I can tell (no official documentation seems to exist):
577 * uint16_t default_language; // points to a resid, defaulting to 0,
578 * // which is the first set language
581 * uint16_t language_code;
583 * uint16_t encoding; // Encoding from TextCommon.h,
584 * // forcing MacRoman (0) for now. Might need to
585 * // allow overwrite per license by user later
590 header_data
.push_back(0);
591 header_data
.push_back(languages
.size());
592 // NOLINTNEXTLINE(modernize-loop-convert): `HAVE_CoreServices` needs `i`
593 for (cmList::size_type i
= 0; i
< languages
.size(); ++i
) {
594 auto const& language
= languages
[i
];
595 CFStringRef language_cfstring
= CFStringCreateWithCString(
596 nullptr, language
.c_str(), kCFStringEncodingUTF8
);
597 CFStringRef iso_language
=
598 CFLocaleCreateCanonicalLanguageIdentifierFromString(
599 nullptr, language_cfstring
);
601 cmCPackLogger(cmCPackLog::LOG_ERROR
,
602 language
<< " is not a recognized language"
605 char iso_language_cstr
[65];
606 CFStringGetCString(iso_language
, iso_language_cstr
,
607 sizeof(iso_language_cstr
) - 1,
608 kCFStringEncodingMacRoman
);
610 RegionCode region
= 0;
611 #if HAVE_CoreServices
613 LocaleStringToLangAndRegionCodes(iso_language_cstr
, &lang
, ®ion
);
617 cmCPackLogger(cmCPackLog::LOG_ERROR
,
618 "No language/region code available for "
619 << iso_language_cstr
<< std::endl
);
622 #if HAVE_CoreServices
623 header_data
.push_back(region
);
624 header_data
.push_back(i
);
625 header_data
.push_back(0);
633 RezDict lpic
= { {}, 5000, {} };
634 lpic
.Data
.reserve(header_data
.size() * sizeof(header_data
[0]));
635 for (uint16_t x
: header_data
) {
636 // LPic header is big-endian.
637 char* d
= reinterpret_cast<char*>(&x
);
638 #if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE
639 lpic
.Data
.push_back(d
[1]);
640 lpic
.Data
.push_back(d
[0]);
642 lpic
.Data
.push_back(d
[0]);
643 lpic
.Data
.push_back(d
[1]);
646 rez
.LPic
.Entries
.emplace_back(std::move(lpic
));
649 bool have_write_license_error
= false;
653 if (!this->WriteLicense(rez
, 0, "", cpack_license_file
, &error
)) {
654 have_write_license_error
= true;
657 for (size_t i
= 0; i
< languages
.size() && !have_write_license_error
;
660 if (!this->WriteLicense(rez
, i
+ 5000, languages
[i
],
661 cpack_license_file
, &error
)) {
662 have_write_license_error
= true;
665 if (!this->WriteLicense(rez
, i
+ 5000, languages
[i
], "", &error
)) {
666 have_write_license_error
= true;
672 if (have_write_license_error
) {
673 cmCPackLogger(cmCPackLog::LOG_ERROR
,
674 "Error writing license file to SLA." << std::endl
680 this->WriteRezXML(sla_xml
, rez
);
682 // Create the final compressed read-only disk image ...
683 auto embed_sla_command
= cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
689 " FIXME_WHY_IS_THIS_ARGUMENT_NEEDED"
692 std::string embed_error
;
693 if (!this->RunCommand(embed_sla_command
, &embed_error
)) {
694 cmCPackLogger(cmCPackLog::LOG_ERROR
,
695 "Error compressing disk image." << std::endl
706 bool cmCPackDragNDropGenerator::SupportsComponentInstallation() const
711 std::string
cmCPackDragNDropGenerator::GetComponentInstallSuffix(
712 const std::string
& componentName
)
714 // we want to group components together that go in the same dmg package
715 std::string package_file_name
= this->GetOption("CPACK_PACKAGE_FILE_NAME");
717 // we have 3 mutually exclusive modes to work in
718 // 1. all components in one package
719 // 2. each group goes in its own package with left over
720 // components in their own package
721 // 3. ignore groups - if grouping is defined, it is ignored
722 // and each component goes in its own package
724 if (this->componentPackageMethod
== ONE_PACKAGE
) {
728 if (this->componentPackageMethod
== ONE_PACKAGE_PER_GROUP
) {
729 // We have to find the name of the COMPONENT GROUP
730 // the current COMPONENT belongs to.
731 std::string groupVar
= cmStrCat(
732 "CPACK_COMPONENT_", cmSystemTools::UpperCase(componentName
), "_GROUP");
733 cmValue _groupName
= this->GetOption(groupVar
);
735 std::string groupName
= _groupName
;
738 GetComponentPackageFileName(package_file_name
, groupName
, true);
743 std::string componentFileName
= cmStrCat(
744 "CPACK_DMG_", cmSystemTools::UpperCase(componentName
), "_FILE_NAME");
745 if (this->IsSet(componentFileName
)) {
746 return this->GetOption(componentFileName
);
748 return GetComponentPackageFileName(package_file_name
, componentName
, false);
751 std::string
cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
752 const std::string
& componentName
)
754 return this->GetSanitizedDirOrFileName(
755 this->GetComponentInstallSuffix(componentName
));
758 void cmCPackDragNDropGenerator::WriteRezXML(std::string
const& file
,
761 cmGeneratedFileStream
fxml(file
);
762 cmXMLWriter
xml(fxml
);
764 xml
.StartElement("plist");
765 xml
.Attribute("version", "1.0");
766 xml
.StartElement("dict");
767 this->WriteRezArray(xml
, rez
.LPic
);
768 this->WriteRezArray(xml
, rez
.Menu
);
769 this->WriteRezArray(xml
, rez
.Text
);
770 this->WriteRezArray(xml
, rez
.RTF
);
771 xml
.EndElement(); // dict
772 xml
.EndElement(); // plist
777 void cmCPackDragNDropGenerator::WriteRezArray(cmXMLWriter
& xml
,
778 RezArray
const& array
)
780 if (array
.Entries
.empty()) {
783 xml
.StartElement("key");
784 xml
.Content(array
.Key
);
785 xml
.EndElement(); // key
786 xml
.StartElement("array");
787 for (RezDict
const& dict
: array
.Entries
) {
788 this->WriteRezDict(xml
, dict
);
790 xml
.EndElement(); // array
793 void cmCPackDragNDropGenerator::WriteRezDict(cmXMLWriter
& xml
,
796 std::vector
<char> base64buf(dict
.Data
.size() * 3 / 2 + 5);
798 cmsysBase64_Encode(dict
.Data
.data(), dict
.Data
.size(),
799 reinterpret_cast<unsigned char*>(base64buf
.data()), 0);
800 std::string
base64data(base64buf
.data(), base64len
);
801 /* clang-format off */
802 xml
.StartElement("dict");
803 xml
.StartElement("key"); xml
.Content("Attributes"); xml
.EndElement();
804 xml
.StartElement("string"); xml
.Content("0x0000"); xml
.EndElement();
805 xml
.StartElement("key"); xml
.Content("Data"); xml
.EndElement();
806 xml
.StartElement("data"); xml
.Content(base64data
); xml
.EndElement();
807 xml
.StartElement("key"); xml
.Content("ID"); xml
.EndElement();
808 xml
.StartElement("string"); xml
.Content(dict
.ID
); xml
.EndElement();
809 xml
.StartElement("key"); xml
.Content("Name"); xml
.EndElement();
810 xml
.StartElement("string"); xml
.Content(dict
.Name
); xml
.EndElement();
811 xml
.EndElement(); // dict
812 /* clang-format on */
815 bool cmCPackDragNDropGenerator::WriteLicense(RezDoc
& rez
, size_t licenseNumber
,
816 std::string licenseLanguage
,
817 const std::string
& licenseFile
,
820 if (!licenseFile
.empty() && !singleLicense
) {
821 licenseNumber
= 5002;
822 licenseLanguage
= "English";
826 RezArray
* licenseArray
= &rez
.Text
;
827 std::string actual_license
;
828 if (!licenseFile
.empty()) {
829 if (cmHasLiteralSuffix(licenseFile
, ".rtf")) {
830 licenseArray
= &rez
.RTF
;
832 actual_license
= licenseFile
;
834 std::string license_wo_ext
=
835 cmStrCat(slaDirectory
, '/', licenseLanguage
, ".license");
836 if (cmSystemTools::FileExists(cmStrCat(license_wo_ext
, ".txt"))) {
837 actual_license
= cmStrCat(license_wo_ext
, ".txt");
839 licenseArray
= &rez
.RTF
;
840 actual_license
= cmStrCat(license_wo_ext
, ".rtf");
846 RezDict license
= { licenseLanguage
, licenseNumber
, {} };
847 std::vector
<std::string
> lines
;
848 if (!this->ReadFile(actual_license
, lines
, error
)) {
851 this->EncodeLicense(license
, lines
);
852 licenseArray
->Entries
.emplace_back(std::move(license
));
857 RezDict menu
= { licenseLanguage
, licenseNumber
, {} };
858 if (!licenseFile
.empty() && !singleLicense
) {
859 this->EncodeMenu(menu
, DefaultMenu
);
861 std::vector
<std::string
> lines
;
862 std::string actual_menu
=
863 cmStrCat(slaDirectory
, '/', licenseLanguage
, ".menu.txt");
864 if (!this->ReadFile(actual_menu
, lines
, error
)) {
867 this->EncodeMenu(menu
, lines
);
869 rez
.Menu
.Entries
.emplace_back(std::move(menu
));
875 void cmCPackDragNDropGenerator::EncodeLicense(
876 RezDict
& dict
, std::vector
<std::string
> const& lines
)
878 // License text uses CR newlines.
879 for (std::string
const& l
: lines
) {
880 dict
.Data
.insert(dict
.Data
.end(), l
.begin(), l
.end());
881 dict
.Data
.push_back('\r');
883 dict
.Data
.push_back('\r');
886 void cmCPackDragNDropGenerator::EncodeMenu(
887 RezDict
& dict
, std::vector
<std::string
> const& lines
)
889 // Menu resources start with a big-endian uint16_t for number of lines:
891 uint16_t numLines
= static_cast<uint16_t>(lines
.size());
892 char* d
= reinterpret_cast<char*>(&numLines
);
893 #if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE
894 dict
.Data
.push_back(d
[1]);
895 dict
.Data
.push_back(d
[0]);
897 dict
.Data
.push_back(d
[0]);
898 dict
.Data
.push_back(d
[1]);
901 // Each line starts with a uint8_t length, plus the bytes themselves:
902 for (std::string
const& l
: lines
) {
903 dict
.Data
.push_back(static_cast<unsigned char>(l
.length()));
904 dict
.Data
.insert(dict
.Data
.end(), l
.begin(), l
.end());
908 bool cmCPackDragNDropGenerator::ReadFile(std::string
const& file
,
909 std::vector
<std::string
>& lines
,
912 cmsys::ifstream
ifs(file
);
914 while (std::getline(ifs
, line
)) {
915 if (!this->BreakLongLine(line
, lines
, error
)) {
922 bool cmCPackDragNDropGenerator::BreakLongLine(const std::string
& line
,
923 std::vector
<std::string
>& lines
,
926 const size_t max_line_length
= 255;
927 size_t line_length
= max_line_length
;
928 for (size_t i
= 0; i
< line
.size(); i
+= line_length
) {
929 line_length
= max_line_length
;
930 if (i
+ line_length
> line
.size()) {
931 line_length
= line
.size() - i
;
933 while (line_length
> 0 && line
[i
+ line_length
- 1] != ' ') {
934 line_length
= line_length
- 1;
938 if (line_length
== 0) {
939 *error
= "Please make sure there are no words "
940 "(or character sequences not broken up by spaces or newlines) "
941 "in your license file which are more than 255 characters long.";
944 lines
.push_back(line
.substr(i
, line_length
));