Hi Brad,

the attached patch limits lines to 79 cols, and adds the necessary
documentation to the variables and release notes.

I added diagnostic pragmas to suppress the deprecation warnings as well
as a comment explaining the need for them. I've looked for a
replacement some more, but I still haven't been able to find anything.
I could try to hard-code it by writing a simple program that iterates
all possible language codes (it's "only" a 16-bit value, so 2^16
combinations). However, this feels even hackier to me than the current
state.

This patch is a single commit rebased on current master
(60cbd9b9da2059481e2f29fbb5859a5b0643d3d7)

Kind regards,
Simon

On Mi, 2015-11-04 at 10:46 -0500, Brad King wrote:
> On 11/03/2015 06:09 AM, Levermann, Simon wrote:
> > This adds support for multilingual SLAs which are displayed when
> > the user is trying to mount the DMG.
> 
> Thanks for working on this and for bringing the patch to this list.
> 
> Patches 2 and 3 look like fixups.  Please squash that all into one
> commit.  Also please keep C++ sources wrapped to 79 columns or less.
> 
> > Multiple languages can be added via the new variables
> > CPACK_DMG_SLA_DIR
> > and CPACK_DMG_SLA_LANGUAGES.
> 
> Please add Help/variable/*.rst files to document these.  Also
> please add a Help/release/dev/*.rst file to add a release note
> for the feature.
> 
> > For each language defined, CPack will search for a
> > language.menu.txt
> > and language.license.txt file in CPACK_SLA_DIR.
> 
> Good.  Please mention this in the above-requested documentation.
> 
> > This patch adds a library to the deprecated Carbon Framework to
> > CPackLib, since the functions/types required to acquire the region
> > code for the internal LPic data structure are only available in
> > this
> > old API. Apple does not seem to be offering a replacement API for
> > the
> > old ScriptManager region codes.
> 
> Hopefully an alternative can be found.  Meanwhile I get warnings
> during the build due to DEPRECATED_IN_MAC_OS_X_VERSION_10_6_AND_LATER
> appearing on the declarations of these APIs.  Please add pragmas
> or whatever is needed to suppress them, at least with Clang.  Call
> out the purpose for the suppression with a comment explaining why
> we need to use the deprecated APIs.
> 
> > Additional thought: One could add the language.menu.txt files for
> > some common languages to the repository.
> 
> Let's defer that until the actual feature is working.
> 
> Thanks,
> -Brad
> 
From 68bd81a62f7c4a11cb0bc46a64d6b0da0a8a6688 Mon Sep 17 00:00:00 2001
From: Simon Levermann <simon-git...@slevermann.de>
Date: Mon, 19 Oct 2015 11:13:55 +0200
Subject: [PATCH] Add support for multilingual SLAs

Multiple languages for SLAs and the SLA UI can be added via the CPack variables
CPACK_DMG_SLA_DIR and CPACK_DMG_SLA_LANGUAGES. For each language defined in the
languages variable, CPack will search for <language>.menu.txt and
<language>.license.txt in CPACK_DMG_SLA_DIR. If the sla directory variable is not
defined, the old behaviour using CPACK_RESOURCE_FILE_LICENSE is retained

Pass string by const& instead of copying

Remove superfluous assignment

Add deprecation pragma and comment

Break lines longer than 79 characters

Add variable documentation for new CPACK_DMG_ variables

Add release note for multilanguage-sla
---
 Help/release/dev/cpack-dmg-multilanguage-sla.txt |   6 +
 Help/variable/CPACK_DMG_SLA_DIR                  |   6 +
 Help/variable/CPACK_DMG_SLA_LANGUAGES            |   6 +
 Source/CMakeLists.txt                            |   4 +
 Source/CPack/cmCPackDragNDropGenerator.cxx       | 357 ++++++++++++++++++++---
 Source/CPack/cmCPackDragNDropGenerator.h         |  10 +
 6 files changed, 351 insertions(+), 38 deletions(-)
 create mode 100644 Help/release/dev/cpack-dmg-multilanguage-sla.txt
 create mode 100644 Help/variable/CPACK_DMG_SLA_DIR
 create mode 100644 Help/variable/CPACK_DMG_SLA_LANGUAGES

diff --git a/Help/release/dev/cpack-dmg-multilanguage-sla.txt b/Help/release/dev/cpack-dmg-multilanguage-sla.txt
new file mode 100644
index 0000000..cdf3162
--- /dev/null
+++ b/Help/release/dev/cpack-dmg-multilanguage-sla.txt
@@ -0,0 +1,6 @@
+cpack-dmg-multilanguage-sla
+--------------
+
+* This feature adds the capability to add multi-lingual SLAs to a DMG which
+  is presented to the user when they try to mount the DMG. See :variable:`CPACK_DMG_SLA_LANGUAGES`
+  and :variable:`CPACK_DMG_SLA_DIR` for usage information.
diff --git a/Help/variable/CPACK_DMG_SLA_DIR b/Help/variable/CPACK_DMG_SLA_DIR
new file mode 100644
index 0000000..7b38a23
--- /dev/null
+++ b/Help/variable/CPACK_DMG_SLA_DIR
@@ -0,0 +1,6 @@
+CPACK_DMG_SLA_DIR
+--------------------
+
+Directory where license and menu files for different languages are stored
+
+See :variable:`CPACK_DMG_SLA_LANGUAGES` for more information.
diff --git a/Help/variable/CPACK_DMG_SLA_LANGUAGES b/Help/variable/CPACK_DMG_SLA_LANGUAGES
new file mode 100644
index 0000000..77eef61
--- /dev/null
+++ b/Help/variable/CPACK_DMG_SLA_LANGUAGES
@@ -0,0 +1,6 @@
+CPACK_DMG_SLA_LANGUAGES
+--------------------
+
+Languages for which a license agreement is provided when mounting the generated DMG.
+
+For every language in this list, CPack will try to find a file language.menu.txt and language.license.txt in :variable:`CPACK_DMG_SLA_DIR`.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index fd71b0e..729f6ab 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -726,6 +726,10 @@ endif()
 # Build CPackLib
 add_library(CPackLib ${CPACK_SRCS})
 target_link_libraries(CPackLib CMakeLib)
+if(APPLE)
+  include_directories (${CMAKE_OSX_SYSROOT}/System/Library/Frameworks/Carbon.framework/Headers/)
+  target_link_libraries(CPackLib "-framework Carbon")
+endif()
 
 if(APPLE)
   add_executable(cmakexbuild cmakexbuild.cxx)
diff --git a/Source/CPack/cmCPackDragNDropGenerator.cxx b/Source/CPack/cmCPackDragNDropGenerator.cxx
index 4c400d9..993a571 100644
--- a/Source/CPack/cmCPackDragNDropGenerator.cxx
+++ b/Source/CPack/cmCPackDragNDropGenerator.cxx
@@ -18,6 +18,20 @@
 #include <cmsys/RegularExpression.hxx>
 #include <cmsys/FStream.hxx>
 
+#include <iomanip>
+
+#include <CoreFoundation/CFBase.h>
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CFLocale.h>
+
+// The carbon framework is deprecated, but the Region codes it supplies are
+// needed for the LPic data structure used for generating multi-lingual SLAs.
+// There does not seem to be a replacement API for these region codes.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#include <Carbon.h>
+#pragma clang diagnostic pop
+
 static const char* SLAHeader =
 "data 'LPic' (5000) {\n"
 "    $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n"
@@ -103,6 +117,70 @@ int cmCPackDragNDropGenerator::InitializeInternal()
     }
   this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path.c_str());
 
+  if(this->IsSet("CPACK_DMG_SLA_DIR"))
+  {
+    slaDirectory = this->GetOption("CPACK_DMG_SLA_DIR");
+    if(!slaDirectory.empty() && this->IsSet("CPACK_RESOURCE_FILE_LICENSE"))
+    {
+      std::string license_file =
+        this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
+      if(!license_file.empty() &&
+         (license_file.find("CPack.GenericLicense.txt") == std::string::npos))
+      {
+        cmCPackLogger(cmCPackLog::LOG_WARNING,
+          "Both CPACK_DMG_SLA_DIR and CPACK_RESOURCE_FILE_LICENSE specified, "
+          "defaulting to CPACK_DMG_SLA_DIR"
+          << std::endl);
+      }
+    }
+    if(!this->IsSet("CPACK_DMG_LANGUAGES"))
+    {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+        "CPACK_DMG_SLA_DIR set but no languages defined "
+        "(set CPACK_DMG_LANGUAGES)"
+        << std::endl);
+      return 0;
+    }
+    if(!cmSystemTools::FileExists(slaDirectory, false))
+    {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+        "CPACK_DMG_SLA_DIR does not exist"
+        << std::endl);
+      return 0;
+    }
+
+    std::vector<std::string> languages;
+    cmSystemTools::ExpandListArgument(this->GetOption("CPACK_DMG_LANGUAGES"),
+      languages);
+    if(languages.empty())
+    {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+      "CPACK_DMG_LANGUAGES set but empty"
+      << std::endl);
+    return 0;
+    }
+    for(size_t i = 0; i < languages.size(); ++i)
+    {
+      std::string license = slaDirectory + "/" + languages[i] +
+        ".license.txt";
+      if (!cmSystemTools::FileExists(license))
+      {
+        cmCPackLogger(cmCPackLog::LOG_ERROR,
+          "Missing license file " << languages[i] << ".license.txt"
+          << std::endl);
+        return 0;
+      }
+      std::string menu = slaDirectory + "/" + languages[i] + ".menu.txt";
+      if (!cmSystemTools::FileExists(menu))
+      {
+        cmCPackLogger(cmCPackLog::LOG_ERROR,
+          "Missing menu file " << languages[i] << ".menu.txt"
+          << std::endl);
+        return 0;
+      }
+    }
+  }
+
   return this->Superclass::InitializeInternal();
 }
 
@@ -246,9 +324,21 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
     this->GetOption("CPACK_DMG_DS_STORE")
     ? this->GetOption("CPACK_DMG_DS_STORE") : "";
 
+  const std::string cpack_dmg_languages =
+    this->GetOption("CPACK_DMG_LANGUAGES")
+      ? this->GetOption("CPACK_DMG_LANGUAGES") : "";
+
   // 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.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())
   {
     cpack_license_file = "";
   }
@@ -418,55 +508,123 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
       }
     }
 
-  if(!cpack_license_file.empty())
+  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 = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
     sla_r += "/sla.r";
 
-    cmsys::ifstream ifs;
-    ifs.open(cpack_license_file.c_str());
-    if(ifs.is_open())
+    std::vector<std::string> languages;
+    if(!oldStyle)
     {
-      cmGeneratedFileStream osf(sla_r.c_str());
-      osf << "#include <CoreServices/CoreServices.r>\n\n";
-      osf << SLAHeader;
-      osf << "\n";
-      osf << "data 'TEXT' (5002, \"English\") {\n";
-      while(ifs.good())
+      cmSystemTools::ExpandListArgument(cpack_dmg_languages, languages);
+    }
+
+    cmGeneratedFileStream ofs(sla_r.c_str());
+    ofs << "#include <CoreServices/CoreServices.r>\n\n";
+    if(oldStyle)
+    {
+      ofs << SLAHeader;
+      ofs << "\n";
+    }
+    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];
+       * }
+       */
+
+      // Create vector first for readability, then iterate to write to ofs
+      std::vector<std::uint16_t> header_data;
+      header_data.push_back(0);
+      header_data.push_back(languages.size());
+      for(size_t i = 0; i < languages.size(); ++i)
       {
-        std::string line;
-        std::getline(ifs, line);
-        // escape quotes
-        std::string::size_type pos = line.find('\"');
-        while(pos != std::string::npos)
+        CFStringRef language_cfstring = CFStringCreateWithCString(NULL,
+          languages[i].c_str(), kCFStringEncodingUTF8);
+        CFStringRef iso_language =
+          CFLocaleCreateCanonicalLanguageIdentifierFromString(NULL,
+          language_cfstring);
+        if (!iso_language)
         {
-          line.replace(pos, 1, "\\\"");
-          pos = line.find('\"', pos+2);
+          cmCPackLogger(cmCPackLog::LOG_ERROR,
+            languages[i] << " is not a recognized language"
+            << std::endl);
         }
-        // break up long lines to avoid Rez errors
-        std::vector<std::string> lines;
-        const size_t max_line_length = 512;
-        for(size_t i=0; i<line.size(); i+= max_line_length)
-          {
-          int line_length = max_line_length;
-          if(i+max_line_length > line.size())
-            line_length = line.size()-i;
-          lines.push_back(line.substr(i, line_length));
-          }
+        char *iso_language_cstr = (char *) malloc(65);
+        CFStringGetCString(iso_language, iso_language_cstr, 64,
+          kCFStringEncodingMacRoman);
+        LangCode lang = 0;
+        RegionCode region = 0;
+        OSStatus err = LocaleStringToLangAndRegionCodes(iso_language_cstr,
+          &lang, &region);
+        if (err != noErr)
+        {
+          cmCPackLogger(cmCPackLog::LOG_ERROR,
+            "No language/region code available for " << iso_language_cstr
+            << std::endl);
+          free(iso_language_cstr);
+          return 0;
+        }
+        free(iso_language_cstr);
+        header_data.push_back(region);
+        header_data.push_back(i);
+        header_data.push_back(0);
+      }
+      ofs << "data 'LPic' (5000) {\n";
+      ofs << std::hex << std::uppercase << std::setfill('0');
 
-        for(size_t i=0; i<lines.size(); i++)
-          {
-          osf << "        \"" << lines[i] << "\"\n";
-          }
-        osf << "        \"\\n\"\n";
+      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 << " ";
+        }
+      }
+      ofs << "};\n\n";
+      // Reset ofs options
+      ofs << std::dec << std::nouppercase << std::setfill(' ');
+    }
+
+    if(oldStyle)
+    {
+      WriteLicense(ofs, 0, "", cpack_license_file);
+    }
+    else
+    {
+      for(size_t i = 0; i < languages.size(); ++i)
+      {
+        WriteLicense(ofs, i + 5000, languages[i]);
       }
-      osf << "};\n";
-      osf << "\n";
-      osf << SLASTREnglish;
-      ifs.close();
-      osf.close();
     }
 
+    ofs.Close();
+
     // convert to UDCO
     std::string temp_udco = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
     temp_udco += "/temp-udco.dmg";
@@ -607,3 +765,126 @@ cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
 
   return GetComponentPackageFileName(package_file_name, componentName, false);
 }
+
+void
+cmCPackDragNDropGenerator::WriteLicense(cmGeneratedFileStream& outputStream,
+  int licenseNumber, std::string licenseLanguage, std::string licenseFile)
+{
+  if(!licenseFile.empty())
+  {
+    licenseNumber = 5002;
+    licenseLanguage = "English";
+  }
+
+  // License header
+  outputStream << "data 'TEXT' (" << licenseNumber << ", \""
+    << licenseLanguage << "\") {\n";
+  // License body
+  std::string actual_license = !licenseFile.empty() ? licenseFile :
+    (slaDirectory + "/" + licenseLanguage + ".license.txt");
+  cmsys::ifstream license_ifs;
+  license_ifs.open(actual_license);
+  if(license_ifs.is_open())
+  {
+    while(license_ifs.good())
+    {
+      std::string line;
+      std::getline(license_ifs, line);
+      if(!line.empty())
+      {
+        EscapeQuotes(line);
+        std::vector<std::string> lines;
+        BreakLongLine(line, lines);
+        for(size_t i = 0; i < lines.size(); ++i)
+        {
+          outputStream << "        \"" << lines[i] << "\"\n";
+        }
+      }
+      outputStream << "        \"\\n\"\n";
+    }
+    license_ifs.close();
+  }
+
+  // End of License
+  outputStream << "};\n\n";
+  if(!licenseFile.empty())
+  {
+    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");
+    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())
+        {
+          EscapeQuotes(line);
+          std::vector<std::string> lines;
+          BreakLongLine(line, lines);
+          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_ifs.close();
+    }
+
+    //End of menu
+    outputStream << "    }\n";
+    outputStream << "};\n";
+    outputStream << "\n";
+  }
+}
+
+void
+cmCPackDragNDropGenerator::BreakLongLine(const std::string& line,
+  std::vector<std::string>& lines)
+{
+  const size_t max_line_length = 512;
+  for(size_t i = 0; i < line.size(); i += max_line_length)
+  {
+    int line_length = max_line_length;
+    if(i + max_line_length > line.size())
+    {
+      line_length = line.size() - i;
+    }
+    lines.push_back(line.substr(i, line_length));
+  }
+}
+
+void
+cmCPackDragNDropGenerator::EscapeQuotes(std::string& line)
+{
+  std::string::size_type pos = line.find('\"');
+  while(pos != std::string::npos)
+  {
+    line.replace(pos, 1, "\\\"");
+    pos = line.find('\"', pos + 2);
+  }
+}
diff --git a/Source/CPack/cmCPackDragNDropGenerator.h b/Source/CPack/cmCPackDragNDropGenerator.h
index 1c84d49..da016f6 100644
--- a/Source/CPack/cmCPackDragNDropGenerator.h
+++ b/Source/CPack/cmCPackDragNDropGenerator.h
@@ -14,6 +14,7 @@
 #define cmCPackDragNDropGenerator_h
 
 #include "cmCPackGenerator.h"
+#include "cmGeneratedFileStream.h"
 
 /** \class cmCPackDragNDropGenerator
  * \brief A generator for OSX drag-n-drop installs
@@ -42,6 +43,15 @@ protected:
   int CreateDMG(const std::string& src_dir, const std::string& output_file);
 
   std::string InstallPrefix;
+
+private:
+  std::string slaDirectory;
+
+  void WriteLicense(cmGeneratedFileStream& outputStream, int licenseNumber,
+    std::string licenseLanguage, std::string licenseFile = "");
+  void BreakLongLine(const std::string& line,
+    std::vector<std::string>& lines);
+  void EscapeQuotes(std::string& line);
 };
 
 #endif
-- 
2.5.0

-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/cmake-developers

Reply via email to