diff options
author | Brad King <brad.king@kitware.com> | 2020-07-03 15:11:20 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2020-07-07 12:08:21 (GMT) |
commit | 1ace607329a679b79f60a1a939dccbc97fb25766 (patch) | |
tree | 34534cde000201167eb68030176c855b84b2bb82 /Source/CPack | |
parent | 98e0cbd8eb6a09b35542cf321755cefcbb942d10 (diff) | |
download | CMake-1ace607329a679b79f60a1a939dccbc97fb25766.zip CMake-1ace607329a679b79f60a1a939dccbc97fb25766.tar.gz CMake-1ace607329a679b79f60a1a939dccbc97fb25766.tar.bz2 |
CPack/DragNDrop: Re-implement SLA attachment to avoid deprecated tools
The `Rez` tool has been deprecated since Xcode 6. The `hdiutil flatten`
and `hdiutil unflatten` tools have been deprecated since macOS 10.15 and
are removed in macOS 11. Instead use `hdiutil udifrez` to attach the
SLA resources to disk images. This tool accepts XML input files, so
convert our resource file generation to produce that format.
Fixes: #20889
Diffstat (limited to 'Source/CPack')
-rw-r--r-- | Source/CPack/cmCPackDragNDropGenerator.cxx | 448 | ||||
-rw-r--r-- | Source/CPack/cmCPackDragNDropGenerator.h | 33 |
2 files changed, 248 insertions, 233 deletions
diff --git a/Source/CPack/cmCPackDragNDropGenerator.cxx b/Source/CPack/cmCPackDragNDropGenerator.cxx index fe7abf4..cefb906 100644 --- a/Source/CPack/cmCPackDragNDropGenerator.cxx +++ b/Source/CPack/cmCPackDragNDropGenerator.cxx @@ -8,7 +8,9 @@ #include <map> #include <CoreFoundation/CoreFoundation.h> +#include <cm3p/kwiml/abi.h> +#include "cmsys/Base64.h" #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" @@ -18,6 +20,7 @@ #include "cmGeneratedFileStream.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" +#include "cmXMLWriter.h" #ifdef HAVE_CoreServices // For the old LocaleStringToLangAndRegionCodes() function, to convert @@ -26,36 +29,28 @@ # include <CoreServices/CoreServices.h> #endif -static const char* SLAHeader = - "data 'LPic' (5000) {\n" - " $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n" - " $\"0008 0003 0000 0001 0004 0000 0004 0005\"\n" - " $\"0000 000E 0006 0001 0005 0007 0000 0007\"\n" - " $\"0008 0000 0047 0009 0000 0034 000A 0001\"\n" - " $\"0035 000B 0001 0020 000C 0000 0011 000D\"\n" - " $\"0000 005B 0004 0000 0033 000F 0001 000C\"\n" - " $\"0010 0000 000B 000E 0000\"\n" - "};\n" - "\n"; - -static const char* SLASTREnglish = - "resource 'STR#' (5002, \"English\") {\n" - " {\n" - " \"English\",\n" - " \"Agree\",\n" - " \"Disagree\",\n" - " \"Print\",\n" - " \"Save...\",\n" - " \"You agree to the License Agreement terms when you click \"\n" - " \"the \\\"Agree\\\" button.\",\n" - " \"Software License Agreement\",\n" - " \"This text cannot be saved. This disk may be full or locked, " - "or the \"\n" - " \"file may be locked.\",\n" - " \"Unable to print. Make sure you have selected a printer.\"\n" - " }\n" - "};\n" - "\n"; +static const uint16_t DefaultLpic[] = { + /* clang-format off */ + 0x0002, 0x0011, 0x0003, 0x0001, 0x0000, 0x0000, 0x0002, 0x0000, + 0x0008, 0x0003, 0x0000, 0x0001, 0x0004, 0x0000, 0x0004, 0x0005, + 0x0000, 0x000E, 0x0006, 0x0001, 0x0005, 0x0007, 0x0000, 0x0007, + 0x0008, 0x0000, 0x0047, 0x0009, 0x0000, 0x0034, 0x000A, 0x0001, + 0x0035, 0x000B, 0x0001, 0x0020, 0x000C, 0x0000, 0x0011, 0x000D, + 0x0000, 0x005B, 0x0004, 0x0000, 0x0033, 0x000F, 0x0001, 0x000C, + 0x0010, 0x0000, 0x000B, 0x000E, 0x0000 + /* clang-format on */ +}; + +static const std::vector<std::string> DefaultMenu = { + { "English", "Agree", "Disagree", "Print", "Save...", + // NOLINTNEXTLINE(bugprone-suspicious-missing-comma) + "You agree to the License Agreement terms when " + "you click the \"Agree\" button.", + "Software License Agreement", + "This text cannot be saved. " + "This disk may be full or locked, or the file may be locked.", + "Unable to print. Make sure you have selected a printer." } +}; cmCPackDragNDropGenerator::cmCPackDragNDropGenerator() : singleLicense(false) @@ -523,22 +518,43 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir, } } + // Create the final compressed read-only disk image ... + std::ostringstream final_image_command; + final_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); + final_image_command << " convert \"" << temp_image << "\""; + final_image_command << " -format "; + final_image_command << cpack_dmg_format; + final_image_command << " -imagekey"; + final_image_command << " zlib-level=9"; + final_image_command << " -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_r = - cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.r"); + std::string sla_xml = + cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.xml"); std::vector<std::string> languages; if (!oldStyle) { cmExpandList(cpack_dmg_languages, languages); } - cmGeneratedFileStream ofs(sla_r); - ofs << "#include <CoreServices/CoreServices.r>\n\n"; + std::vector<uint16_t> header_data; if (oldStyle) { - ofs << SLAHeader; - ofs << "\n"; + header_data = std::vector<uint16_t>( + DefaultLpic, + DefaultLpic + (sizeof(DefaultLpic) / sizeof(*DefaultLpic))); } else { /* * LPic Layout @@ -558,8 +574,6 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir, * } */ - // Create vector first for readability, then iterate to write to ofs - std::vector<uint16_t> header_data; header_data.push_back(0); header_data.push_back(languages.size()); for (size_t i = 0; i < languages.size(); ++i) { @@ -596,52 +610,50 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir, header_data.push_back(0); #endif } - ofs << "data 'LPic' (5000) {\n"; - ofs << std::hex << std::uppercase << std::setfill('0'); - - for (size_t i = 0; i < header_data.size(); ++i) { - if (i % 8 == 0) { - ofs << " $\""; - } - - ofs << std::setw(4) << header_data[i]; + } - if (i % 8 == 7 || i == header_data.size() - 1) { - ofs << "\"\n"; - } else { - ofs << " "; - } + 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 } - ofs << "};\n\n"; - // Reset ofs options - ofs << std::dec << std::nouppercase << std::setfill(' '); + rez.LPic.Entries.emplace_back(std::move(lpic)); } bool have_write_license_error = false; std::string error; if (oldStyle) { - if (!this->WriteLicense(ofs, 0, "", cpack_license_file, &error)) { + 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(ofs, i + 5000, languages[i], + if (!this->WriteLicense(rez, i + 5000, languages[i], cpack_license_file, &error)) { have_write_license_error = true; } } else { - if (!this->WriteLicense(ofs, i + 5000, languages[i], "", &error)) { + if (!this->WriteLicense(rez, i + 5000, languages[i], "", &error)) { have_write_license_error = true; } } } } - ofs.Close(); - if (have_write_license_error) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Error writing license file to SLA." << std::endl @@ -650,96 +662,27 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir, return 0; } - if (temp_image_format != "UDZO") { - temp_image_format = "UDZO"; - // convert to UDZO to enable unflatten/flatten - std::string temp_udzo = cmStrCat( - this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/temp-udzo.dmg"); + this->WriteRezXML(sla_xml, rez); - std::ostringstream udco_image_command; - udco_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); - udco_image_command << " convert \"" << temp_image << "\""; - udco_image_command << " -format UDZO"; - udco_image_command << " -ov -o \"" << temp_udzo << "\""; - - if (!this->RunCommand(udco_image_command, &error)) { - cmCPackLogger(cmCPackLog::LOG_ERROR, - "Error converting to UDCO dmg for adding SLA." - << std::endl - << error << std::endl); - return 0; - } - temp_image = temp_udzo; - } - - // unflatten dmg - std::ostringstream unflatten_command; - unflatten_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); - unflatten_command << " unflatten "; - unflatten_command << "\"" << temp_image << "\""; - - if (!this->RunCommand(unflatten_command, &error)) { - cmCPackLogger(cmCPackLog::LOG_ERROR, - "Error unflattening dmg for adding SLA." << std::endl - << error - << std::endl); - return 0; - } - - // Rez the SLA + // Create the final compressed read-only disk image ... std::ostringstream embed_sla_command; - embed_sla_command << this->GetOption("CPACK_COMMAND_REZ"); - const char* sysroot = this->GetOption("CPACK_OSX_SYSROOT"); - if (sysroot && sysroot[0] != '\0') { - embed_sla_command << " -isysroot \"" << sysroot << "\""; - } - embed_sla_command << " \"" << sla_r << "\""; - embed_sla_command << " -a -o "; - embed_sla_command << "\"" << temp_image << "\""; - - if (!this->RunCommand(embed_sla_command, &error)) { + embed_sla_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); + embed_sla_command << " udifrez"; + embed_sla_command << " -xml"; + embed_sla_command << " \"" << sla_xml << "\""; + embed_sla_command << " FIXME_WHY_IS_THIS_ARGUMENT_NEEDED"; + embed_sla_command << " \"" << output_file << "\""; + std::string embed_error; + if (!this->RunCommand(embed_sla_command, &embed_error)) { cmCPackLogger(cmCPackLog::LOG_ERROR, - "Error adding SLA." << std::endl - << error << std::endl); - return 0; - } - - // flatten dmg - std::ostringstream flatten_command; - flatten_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); - flatten_command << " flatten "; - flatten_command << "\"" << temp_image << "\""; + "Error compressing disk image." << std::endl + << embed_error + << std::endl); - if (!this->RunCommand(flatten_command, &error)) { - cmCPackLogger(cmCPackLog::LOG_ERROR, - "Error flattening dmg for adding SLA." << std::endl - << error - << std::endl); return 0; } } - // Create the final compressed read-only disk image ... - std::ostringstream final_image_command; - final_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL"); - final_image_command << " convert \"" << temp_image << "\""; - final_image_command << " -format "; - final_image_command << cpack_dmg_format; - final_image_command << " -imagekey"; - final_image_command << " zlib-level=9"; - final_image_command << " -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; - } - return 1; } @@ -788,10 +731,67 @@ std::string cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix( return GetComponentPackageFileName(package_file_name, componentName, false); } -bool cmCPackDragNDropGenerator::WriteLicense( - cmGeneratedFileStream& outputStream, int licenseNumber, - std::string licenseLanguage, const std::string& licenseFile, - std::string* error) +void cmCPackDragNDropGenerator::WriteRezXML(std::string const& file, + RezDoc const& rez) +{ + cmGeneratedFileStream fxml(file); + cmXMLWriter xml(fxml); + xml.StartDocument(); + xml.StartElement("plist"); + xml.Attribute("version", "1.0"); + xml.StartElement("dict"); + this->WriteRezArray(xml, rez.LPic); + this->WriteRezArray(xml, rez.Menu); + this->WriteRezArray(xml, rez.Text); + this->WriteRezArray(xml, rez.RTF); + xml.EndElement(); // dict + xml.EndElement(); // plist + xml.EndDocument(); + fxml.Close(); +} + +void cmCPackDragNDropGenerator::WriteRezArray(cmXMLWriter& xml, + RezArray const& array) +{ + if (array.Entries.empty()) { + return; + } + xml.StartElement("key"); + xml.Content(array.Key); + xml.EndElement(); // key + xml.StartElement("array"); + for (RezDict const& dict : array.Entries) { + this->WriteRezDict(xml, dict); + } + xml.EndElement(); // array +} + +void cmCPackDragNDropGenerator::WriteRezDict(cmXMLWriter& xml, + RezDict const& dict) +{ + std::vector<char> base64buf(dict.Data.size() * 3 / 2 + 5); + size_t base64len = + cmsysBase64_Encode(dict.Data.data(), dict.Data.size(), + reinterpret_cast<unsigned char*>(base64buf.data()), 0); + std::string base64data(base64buf.data(), base64len); + /* clang-format off */ + xml.StartElement("dict"); + xml.StartElement("key"); xml.Content("Attributes"); xml.EndElement(); + xml.StartElement("string"); xml.Content("0x0000"); xml.EndElement(); + xml.StartElement("key"); xml.Content("Data"); xml.EndElement(); + xml.StartElement("data"); xml.Content(base64data); xml.EndElement(); + xml.StartElement("key"); xml.Content("ID"); xml.EndElement(); + xml.StartElement("string"); xml.Content(dict.ID); xml.EndElement(); + xml.StartElement("key"); xml.Content("Name"); xml.EndElement(); + xml.StartElement("string"); xml.Content(dict.Name); xml.EndElement(); + xml.EndElement(); // dict + /* clang-format on */ +} + +bool cmCPackDragNDropGenerator::WriteLicense(RezDoc& rez, size_t licenseNumber, + std::string licenseLanguage, + const std::string& licenseFile, + std::string* error) { if (!licenseFile.empty() && !singleLicense) { licenseNumber = 5002; @@ -799,11 +799,11 @@ bool cmCPackDragNDropGenerator::WriteLicense( } // License file - std::string license_format = "TEXT"; + RezArray* licenseArray = &rez.Text; std::string actual_license; if (!licenseFile.empty()) { if (cmHasLiteralSuffix(licenseFile, ".rtf")) { - license_format = "RTF "; + licenseArray = &rez.RTF; } actual_license = licenseFile; } else { @@ -812,85 +812,86 @@ bool cmCPackDragNDropGenerator::WriteLicense( if (cmSystemTools::FileExists(license_wo_ext + ".txt")) { actual_license = license_wo_ext + ".txt"; } else { - license_format = "RTF "; + licenseArray = &rez.RTF; actual_license = license_wo_ext + ".rtf"; } } - // License header - outputStream << "data '" << license_format << "' (" << licenseNumber - << ", \"" << licenseLanguage << "\") {\n"; // License body - cmsys::ifstream license_ifs; - license_ifs.open(actual_license.c_str()); - if (license_ifs.is_open()) { - while (license_ifs.good()) { - std::string line; - std::getline(license_ifs, line); - if (!line.empty()) { - EscapeQuotesAndBackslashes(line); - std::vector<std::string> lines; - if (!this->BreakLongLine(line, lines, error)) { - return false; - } - for (auto const& l : lines) { - outputStream << " \"" << l << "\"\n"; - } - } - outputStream << " \"\\n\"\n"; + { + RezDict license = { licenseLanguage, licenseNumber, {} }; + std::vector<std::string> lines; + if (!this->ReadFile(actual_license, lines, error)) { + return false; } - license_ifs.close(); + this->EncodeLicense(license, lines); + licenseArray->Entries.emplace_back(std::move(license)); } - // End of License - outputStream << "};\n\n"; - if (!licenseFile.empty() && !singleLicense) { - outputStream << SLASTREnglish; - } else { - // Menu header - outputStream << "resource 'STR#' (" << licenseNumber << ", \"" - << licenseLanguage << "\") {\n"; - outputStream << " {\n"; - - // Menu body - cmsys::ifstream menu_ifs; - menu_ifs.open( - (slaDirectory + "/" + licenseLanguage + ".menu.txt").c_str()); - if (menu_ifs.is_open()) { - size_t lines_written = 0; - while (menu_ifs.good()) { - // Lines written from original file, not from broken up lines - std::string line; - std::getline(menu_ifs, line); - if (!line.empty()) { - EscapeQuotesAndBackslashes(line); - std::vector<std::string> lines; - if (!this->BreakLongLine(line, lines, error)) { - return false; - } - for (size_t i = 0; i < lines.size(); ++i) { - std::string comma; - // We need a comma after every complete string, - // but not on the very last line - if (lines_written != 8 && i == lines.size() - 1) { - comma = ","; - } else { - comma = ""; - } - outputStream << " \"" << lines[i] << "\"" << comma << "\n"; - } - ++lines_written; - } + // Menu body + { + RezDict menu = { licenseLanguage, licenseNumber, {} }; + if (!licenseFile.empty() && !singleLicense) { + this->EncodeMenu(menu, DefaultMenu); + } else { + std::vector<std::string> lines; + std::string actual_menu = + slaDirectory + "/" + licenseLanguage + ".menu.txt"; + if (!this->ReadFile(actual_menu, lines, error)) { + return false; } - menu_ifs.close(); + this->EncodeMenu(menu, lines); } + rez.Menu.Entries.emplace_back(std::move(menu)); + } + + return true; +} + +void cmCPackDragNDropGenerator::EncodeLicense( + RezDict& dict, std::vector<std::string> const& lines) +{ + // License text uses CR newlines. + for (std::string const& l : lines) { + dict.Data.insert(dict.Data.end(), l.begin(), l.end()); + dict.Data.push_back('\r'); + } + dict.Data.push_back('\r'); +} - // End of menu - outputStream << " }\n"; - outputStream << "};\n"; - outputStream << "\n"; +void cmCPackDragNDropGenerator::EncodeMenu( + RezDict& dict, std::vector<std::string> const& lines) +{ + // Menu resources start with a big-endian uint16_t for number of lines: + { + uint16_t numLines = static_cast<uint16_t>(lines.size()); + char* d = reinterpret_cast<char*>(&numLines); +#if KWIML_ABI_ENDIAN_ID == KWIML_ABI_ENDIAN_ID_LITTLE + dict.Data.push_back(d[1]); + dict.Data.push_back(d[0]); +#else + dict.Data.push_back(d[0]); + dict.Data.push_back(d[1]); +#endif } + // Each line starts with a uint8_t length, plus the bytes themselves: + for (std::string const& l : lines) { + dict.Data.push_back(static_cast<unsigned char>(l.length())); + dict.Data.insert(dict.Data.end(), l.begin(), l.end()); + } +} +bool cmCPackDragNDropGenerator::ReadFile(std::string const& file, + std::vector<std::string>& lines, + std::string* error) +{ + cmsys::ifstream ifs(file); + std::string line; + while (std::getline(ifs, line)) { + if (!this->BreakLongLine(line, lines, error)) { + return false; + } + } return true; } @@ -898,7 +899,7 @@ bool cmCPackDragNDropGenerator::BreakLongLine(const std::string& line, std::vector<std::string>& lines, std::string* error) { - const size_t max_line_length = 512; + const size_t max_line_length = 255; size_t line_length = max_line_length; for (size_t i = 0; i < line.size(); i += line_length) { line_length = max_line_length; @@ -913,25 +914,10 @@ bool cmCPackDragNDropGenerator::BreakLongLine(const std::string& line, if (line_length == 0) { *error = "Please make sure there are no words " "(or character sequences not broken up by spaces or newlines) " - "in your license file which are more than 512 characters long."; + "in your license file which are more than 255 characters long."; return false; } lines.push_back(line.substr(i, line_length)); } return true; } - -void cmCPackDragNDropGenerator::EscapeQuotesAndBackslashes(std::string& line) -{ - std::string::size_type backslash_pos = line.find('\\'); - while (backslash_pos != std::string::npos) { - line.replace(backslash_pos, 1, "\\\\"); - backslash_pos = line.find('\\', backslash_pos + 2); - } - - std::string::size_type quote_pos = line.find('\"'); - while (quote_pos != std::string::npos) { - line.replace(quote_pos, 1, "\\\""); - quote_pos = line.find('\"', quote_pos + 2); - } -} diff --git a/Source/CPack/cmCPackDragNDropGenerator.h b/Source/CPack/cmCPackDragNDropGenerator.h index f8c86c0..dbd190c 100644 --- a/Source/CPack/cmCPackDragNDropGenerator.h +++ b/Source/CPack/cmCPackDragNDropGenerator.h @@ -14,6 +14,7 @@ #include "cmCPackGenerator.h" class cmGeneratedFileStream; +class cmXMLWriter; /** \class cmCPackDragNDropGenerator * \brief A generator for OSX drag-n-drop installs @@ -45,12 +46,40 @@ private: std::string slaDirectory; bool singleLicense; - bool WriteLicense(cmGeneratedFileStream& outputStream, int licenseNumber, + struct RezDict + { + std::string Name; + size_t ID; + std::vector<unsigned char> Data; + }; + + struct RezArray + { + std::string Key; + std::vector<RezDict> Entries; + }; + + struct RezDoc + { + RezArray LPic = { "LPic", {} }; + RezArray Menu = { "STR#", {} }; + RezArray Text = { "TEXT", {} }; + RezArray RTF = { "RTF ", {} }; + }; + + void WriteRezXML(std::string const& file, RezDoc const& rez); + void WriteRezArray(cmXMLWriter& xml, RezArray const& array); + void WriteRezDict(cmXMLWriter& xml, RezDict const& dict); + + bool WriteLicense(RezDoc& rez, size_t licenseNumber, std::string licenseLanguage, const std::string& licenseFile, std::string* error); + void EncodeLicense(RezDict& dict, std::vector<std::string> const& lines); + void EncodeMenu(RezDict& dict, std::vector<std::string> const& lines); + bool ReadFile(std::string const& file, std::vector<std::string>& lines, + std::string* error); bool BreakLongLine(const std::string& line, std::vector<std::string>& lines, std::string* error); - void EscapeQuotesAndBackslashes(std::string& line); }; #endif |