in Source/CPack/cmCPackDragNDropGenerator.cxx [269:704]
int cmCPackDragNDropGenerator::CreateDMG(std::string const& src_dir,
std::string const& output_file)
{
// Get optional arguments ...
cmValue cpack_package_icon = this->GetOption("CPACK_PACKAGE_ICON");
std::string const cpack_dmg_volume_name =
this->GetOption("CPACK_DMG_VOLUME_NAME")
? *this->GetOption("CPACK_DMG_VOLUME_NAME")
: *this->GetOption("CPACK_PACKAGE_FILE_NAME");
std::string const cpack_dmg_format = this->GetOption("CPACK_DMG_FORMAT")
? *this->GetOption("CPACK_DMG_FORMAT")
: "UDZO";
std::string const cpack_dmg_filesystem =
this->GetOption("CPACK_DMG_FILESYSTEM")
? *this->GetOption("CPACK_DMG_FILESYSTEM")
: "HFS+";
// Get optional arguments ...
std::string cpack_license_file;
if (this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE")) {
cpack_license_file = *this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
}
cmValue cpack_dmg_background_image =
this->GetOption("CPACK_DMG_BACKGROUND_IMAGE");
cmValue cpack_dmg_ds_store = this->GetOption("CPACK_DMG_DS_STORE");
cmValue cpack_dmg_languages = this->GetOption("CPACK_DMG_SLA_LANGUAGES");
cmValue cpack_dmg_ds_store_setup_script =
this->GetOption("CPACK_DMG_DS_STORE_SETUP_SCRIPT");
bool const cpack_dmg_disable_applications_symlink =
this->IsOn("CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK");
// only put license on dmg if is user provided
if (!cpack_license_file.empty() &&
cpack_license_file.find("CPack.GenericLicense.txt") !=
std::string::npos) {
cpack_license_file = "";
}
// use sla_dir if both sla_dir and license_file are set
if (!cpack_license_file.empty() && !slaDirectory.empty() && !singleLicense) {
cpack_license_file = "";
}
// The staging directory contains everything that will end-up inside the
// final disk image ...
std::ostringstream staging;
staging << src_dir;
// Add a symlink to /Applications so users can drag-and-drop the bundle
// into it unless this behavior was disabled
if (!cpack_dmg_disable_applications_symlink) {
std::ostringstream application_link;
application_link << staging.str() << "/Applications";
cmSystemTools::CreateSymlink("/Applications", application_link.str());
}
// Optionally add a custom volume icon ...
if (!cpack_package_icon->empty()) {
std::ostringstream package_icon_source;
package_icon_source << cpack_package_icon;
std::ostringstream package_icon_destination;
package_icon_destination << staging.str() << "/.VolumeIcon.icns";
if (!this->CopyFile(package_icon_source, package_icon_destination)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error copying disk volume icon. "
"Check the value of CPACK_PACKAGE_ICON."
<< std::endl);
return 0;
}
}
// Optionally add a custom .DS_Store file
// (e.g. for setting background/layout) ...
if (!cpack_dmg_ds_store->empty()) {
std::ostringstream package_settings_source;
package_settings_source << cpack_dmg_ds_store;
std::ostringstream package_settings_destination;
package_settings_destination << staging.str() << "/.DS_Store";
if (!this->CopyFile(package_settings_source,
package_settings_destination)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error copying disk volume settings file. "
"Check the value of CPACK_DMG_DS_STORE."
<< std::endl);
return 0;
}
}
// Optionally add a custom background image ...
// Make sure the background file type is the same as the custom image
// and that the file is hidden so it doesn't show up.
if (!cpack_dmg_background_image->empty()) {
std::string const extension =
cmSystemTools::GetFilenameLastExtension(cpack_dmg_background_image);
std::ostringstream package_background_source;
package_background_source << cpack_dmg_background_image;
std::ostringstream package_background_destination;
package_background_destination << staging.str()
<< "/.background/background" << extension;
if (!this->CopyFile(package_background_source,
package_background_destination)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error copying disk volume background image. "
"Check the value of CPACK_DMG_BACKGROUND_IMAGE."
<< std::endl);
return 0;
}
}
bool remount_image =
!cpack_package_icon->empty() || !cpack_dmg_ds_store_setup_script->empty();
std::string temp_image_format = "UDZO";
// Create 1 MB dummy padding file in staging area when we need to remount
// image, so we have enough space for storing changes ...
if (remount_image) {
std::ostringstream dummy_padding;
dummy_padding << staging.str() << "/.dummy-padding-file";
if (!this->CreateEmptyFile(dummy_padding, 1048576)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error creating dummy padding file." << std::endl);
return 0;
}
temp_image_format = "UDRW";
}
// Create a temporary read-write disk image ...
std::string temp_image =
cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/temp.dmg");
std::string create_error;
auto temp_image_command =
cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
" create"
" -ov"
" -srcfolder \"",
staging.str(),
"\""
" -volname \"",
cpack_dmg_volume_name,
"\""
" -fs \"",
cpack_dmg_filesystem,
"\""
" -format ",
temp_image_format, " \"", temp_image, '"');
if (!this->RunCommand(temp_image_command, &create_error)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error generating temporary disk image." << std::endl
<< create_error
<< std::endl);
return 0;
}
if (remount_image) {
// Store that we have a failure so that we always unmount the image
// before we exit.
bool had_error = false;
auto attach_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
" attach"
" \"",
temp_image, '"');
std::string attach_output;
if (!this->RunCommand(attach_command, &attach_output)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error attaching temporary disk image." << std::endl);
return 0;
}
cmsys::RegularExpression mountpoint_regex(".*(/Volumes/[^\n]+)\n.*");
mountpoint_regex.find(attach_output.c_str());
std::string const temp_mount = mountpoint_regex.match(1);
std::string const temp_mount_name =
temp_mount.substr(cmStrLen("/Volumes/"));
// Remove dummy padding file so we have enough space on RW image ...
std::ostringstream dummy_padding;
dummy_padding << temp_mount << "/.dummy-padding-file";
if (!cmSystemTools::RemoveFile(dummy_padding.str())) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error removing dummy padding file." << std::endl);
had_error = true;
}
// Optionally set the custom icon flag for the image ...
if (!had_error && !cpack_package_icon->empty()) {
std::string error;
auto setfile_command = cmStrCat(this->GetOption("CPACK_COMMAND_SETFILE"),
" -a C"
" \"",
temp_mount, '"');
if (!this->RunCommand(setfile_command, &error)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error assigning custom icon to temporary disk image."
<< std::endl
<< error << std::endl);
had_error = true;
}
}
// Optionally we can execute a custom apple script to generate
// the .DS_Store for the volume folder ...
if (!had_error && !cpack_dmg_ds_store_setup_script->empty()) {
auto setup_script_command = cmStrCat("osascript"
" \"",
cpack_dmg_ds_store_setup_script,
"\""
" \"",
temp_mount_name, '"');
std::string error;
if (!this->RunCommand(setup_script_command, &error)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error executing custom script on disk image."
<< std::endl
<< error << std::endl);
had_error = true;
}
}
auto detach_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
" detach"
" \"",
temp_mount, '\"');
if (!this->RunCommand(detach_command)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error detaching temporary disk image." << std::endl);
return 0;
}
if (had_error) {
return 0;
}
}
// Create the final compressed read-only disk image ...
auto final_image_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
" convert \"", temp_image,
"\""
" -format ",
cpack_dmg_format,
" -imagekey"
" zlib-level=9"
" -o \"",
output_file, '"');
std::string convert_error;
if (!this->RunCommand(final_image_command, &convert_error)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error compressing disk image." << std::endl
<< convert_error
<< std::endl);
return 0;
}
if (!cpack_license_file.empty() || !slaDirectory.empty()) {
// Use old hardcoded style if sla_dir is not set
bool oldStyle = slaDirectory.empty();
std::string sla_xml =
cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.xml");
cmList languages;
if (!oldStyle) {
languages.assign(cpack_dmg_languages);
}
std::vector<uint16_t> header_data;
if (oldStyle) {
header_data = std::vector<uint16_t>(
DefaultLpic,
DefaultLpic + (sizeof(DefaultLpic) / sizeof(*DefaultLpic)));
} else {
/*
* LPic Layout
* (https://github.com/pypt/dmg-add-license/blob/master/main.c)
* as far as I can tell (no official documentation seems to exist):
* struct LPic {
* uint16_t default_language; // points to a resid, defaulting to 0,
* // which is the first set language
* uint16_t length;
* struct {
* uint16_t language_code;
* uint16_t resid;
* uint16_t encoding; // Encoding from TextCommon.h,
* // forcing MacRoman (0) for now. Might need to
* // allow overwrite per license by user later
* } item[1];
* }
*/
header_data.push_back(0);
header_data.push_back(languages.size());
// NOLINTNEXTLINE(modernize-loop-convert): `HAVE_CoreServices` needs `i`
for (cmList::size_type i = 0; i < languages.size(); ++i) {
auto const& language = languages[i];
CFStringRef language_cfstring = CFStringCreateWithCString(
nullptr, language.c_str(), kCFStringEncodingUTF8);
CFStringRef iso_language =
CFLocaleCreateCanonicalLanguageIdentifierFromString(
nullptr, language_cfstring);
if (!iso_language) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
language << " is not a recognized language"
<< std::endl);
}
char iso_language_cstr[65];
CFStringGetCString(iso_language, iso_language_cstr,
sizeof(iso_language_cstr) - 1,
kCFStringEncodingMacRoman);
LangCode lang = 0;
RegionCode region = 0;
#if HAVE_CoreServices
OSStatus err =
LocaleStringToLangAndRegionCodes(iso_language_cstr, &lang, ®ion);
if (err != noErr)
#endif
{
cmCPackLogger(cmCPackLog::LOG_ERROR,
"No language/region code available for "
<< iso_language_cstr << std::endl);
return 0;
}
#if HAVE_CoreServices
header_data.push_back(region);
header_data.push_back(i);
header_data.push_back(0);
#endif
}
}
RezDoc rez;
{
RezDict lpic = { {}, 5000, {} };
lpic.Data.reserve(header_data.size() * sizeof(header_data[0]));
for (uint16_t x : header_data) {
// LPic header is big-endian.
char* d = reinterpret_cast<char*>(&x);
#if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE
lpic.Data.push_back(d[1]);
lpic.Data.push_back(d[0]);
#else
lpic.Data.push_back(d[0]);
lpic.Data.push_back(d[1]);
#endif
}
rez.LPic.Entries.emplace_back(std::move(lpic));
}
bool have_write_license_error = false;
std::string error;
if (oldStyle) {
if (!this->WriteLicense(rez, 0, "", cpack_license_file, &error)) {
have_write_license_error = true;
}
} else {
for (size_t i = 0; i < languages.size() && !have_write_license_error;
++i) {
if (singleLicense) {
if (!this->WriteLicense(rez, i + 5000, languages[i],
cpack_license_file, &error)) {
have_write_license_error = true;
}
} else {
if (!this->WriteLicense(rez, i + 5000, languages[i], "", &error)) {
have_write_license_error = true;
}
}
}
}
if (have_write_license_error) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error writing license file to SLA." << std::endl
<< error
<< std::endl);
return 0;
}
this->WriteRezXML(sla_xml, rez);
// Create the final compressed read-only disk image ...
auto embed_sla_command = cmStrCat(this->GetOption("CPACK_COMMAND_HDIUTIL"),
" udifrez"
" -xml"
" \"",
sla_xml,
"\""
" FIXME_WHY_IS_THIS_ARGUMENT_NEEDED"
" \"",
output_file, '"');
std::string embed_error;
if (!this->RunCommand(embed_sla_command, &embed_error)) {
cmCPackLogger(cmCPackLog::LOG_ERROR,
"Error compressing disk image." << std::endl
<< embed_error
<< std::endl);
return 0;
}
}
return 1;
}