Browse Source

Merge topic 'better_looking_mac_package'

4bca9401 Improve appearance of CMake .dmg package on OS X
c4b9ee18 CPack/DragNDrop: Update documentation to include new variables
167a4655 CPack/DragNDrop: Optionally run an AppleScript when making a package
9c1dfbfd CPack/DragNDrop: Place the background image file in a hidden folder
47302038 CPack/DragNDrop: Use source file extension for background image
Brad King 10 years ago
parent
commit
9feb24e514

+ 7 - 0
CMakeCPackOptions.cmake.in

@@ -183,6 +183,13 @@ if("${CPACK_GENERATOR}" STREQUAL "PackageMaker")
   endif()
 endif()
 
+if("${CPACK_GENERATOR}" STREQUAL "DragNDrop")
+  set(CPACK_DMG_BACKGROUND_IMAGE
+      "@CMake_SOURCE_DIR@/Packaging/CMakeDMGBackground.tif")
+  set(CPACK_DMG_DS_STORE_SETUP_SCRIPT
+      "@CMake_SOURCE_DIR@/Packaging/CMakeDMGSetup.scpt")
+endif()
+
 if("${CPACK_GENERATOR}" STREQUAL "WIX")
   # Reset CPACK_PACKAGE_VERSION to deal with WiX restriction.
   # But the file names still use the full CMake_VERSION value:

+ 8 - 0
Help/release/dev/better-looking-mac-packages.rst

@@ -0,0 +1,8 @@
+better-looking-mac-packages
+---------------------------
+
+* The :module:`CPackDMG` module learned new variable to specify AppleScript
+  file run to customize appearance of ``DragNDrop`` installer folder,
+  including background image setting using supplied PNG or multi-resolution
+  TIFF file.  See the :variable:`CPACK_DMG_DS_STORE_SETUP_SCRIPT` and
+  :variable:`CPACK_DMG_BACKGROUND_IMAGE` variables.

+ 15 - 5
Modules/CPackDMG.cmake

@@ -26,15 +26,25 @@
 #  Path to a custom DS_Store file. This .DS_Store file e.g. can be used to
 #  specify the Finder window position/geometry and layout (such as hidden
 #  toolbars, placement of the icons etc.). This file has to be generated by
-#  the Finder (either manually or through OSA-script) using a normal folder
+#  the Finder (either manually or through AppleScript) using a normal folder
 #  from which the .DS_Store file can then be extracted.
 #
+# .. variable:: CPACK_DMG_DS_STORE_SETUP_SCRIPT
+#
+#  Path to a custom AppleScript file.  This AppleScript is used to generate
+#  a .DS_Store file which specifies the Finder window position/geometry and
+#  layout (such as hidden toolbars, placement of the icons etc.).
+#  By specifying a custom AppleScript there is no need to use
+#  CPACK_DMG_DS_STORE, as the .DS_Store that is generated by the AppleScript
+#  will be packaged.
+#
 # .. variable:: CPACK_DMG_BACKGROUND_IMAGE
 #
-#  Path to a background image file. This file will be used as the background
-#  for the Finder Window when the disk image is opened.  By default no
-#  background image is set. The background image is applied after applying the
-#  custom .DS_Store file.
+#  Path to an image file to be used as the background.  This file will be
+#  copied to .background/background.<ext>, where ext is the original image file
+#  extension.  The background image is installed into the image before
+#  CPACK_DMG_DS_STORE_SETUP_SCRIPT is executed or CPACK_DMG_DS_STORE is
+#  installed.  By default no background image is set.
 #
 # .. variable:: CPACK_DMG_SLA_DIR
 #

BIN
Packaging/CMakeDMGBackground.tif


+ 42 - 0
Packaging/CMakeDMGSetup.scpt

@@ -0,0 +1,42 @@
+on run argv
+  set image_name to item 1 of argv
+
+  tell application "Finder"
+  tell disk image_name
+
+    -- open the image the first time and save a DS_Store with just
+    -- background and icon setup
+    open
+      set current view of container window to icon view
+      set theViewOptions to the icon view options of container window
+      set background picture of theViewOptions to file ".background:background.tif"
+      set arrangement of theViewOptions to not arranged
+      set icon size of theViewOptions to 128
+      delay 1
+    close
+
+    -- next setup the position of the app and Applications symlink
+    -- plus hide all the window decoration
+    open
+      update without registering applications
+      tell container window
+        set sidebar width to 0
+        set statusbar visible to false
+        set toolbar visible to false
+        set the bounds to { 400, 100, 900, 465 }
+        set position of item "CMake.app" to { 133, 200 }
+        set position of item "Applications" to { 378, 200 }
+      end tell
+      update without registering applications
+      delay 1
+    close
+
+    -- one last open and close so you can see everything looks correct
+    open
+      delay 5
+    close
+
+  end tell
+  delay 1
+end tell
+end run

+ 99 - 21
Source/CPack/cmCPackDragNDropGenerator.cxx

@@ -270,6 +270,28 @@ bool cmCPackDragNDropGenerator::CopyFile(std::ostringstream& source,
   return true;
 }
 
+//----------------------------------------------------------------------
+bool cmCPackDragNDropGenerator::CreateEmptyFile(std::ostringstream& target,
+                                                size_t size)
+{
+  cmsys::ofstream fout(target.str().c_str(),
+                       std::ios::out | std::ios::binary);
+  if(!fout)
+    {
+    return false;
+    }
+  else
+    {
+    // Seek to desired size - 1 byte
+    fout.seekp(size - 1, std::ios_base::beg);
+    char byte = 0;
+    // Write one byte to ensure file grows
+    fout.write(&byte, 1);
+    }
+
+  return true;
+}
+
 //----------------------------------------------------------------------
 bool cmCPackDragNDropGenerator::RunCommand(std::ostringstream& command,
   std::string* output)
@@ -331,6 +353,10 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
     this->GetOption("CPACK_DMG_LANGUAGES")
       ? this->GetOption("CPACK_DMG_LANGUAGES") : "";
 
+  const std::string cpack_dmg_ds_store_setup_script =
+    this->GetOption("CPACK_DMG_DS_STORE_SETUP_SCRIPT")
+    ? this->GetOption("CPACK_DMG_DS_STORE_SETUP_SCRIPT") : "";
+
   // only put license on dmg if is user provided
   if(!cpack_license_file.empty() &&
       cpack_license_file.find("CPack.GenericLicense.txt") != std::string::npos)
@@ -399,13 +425,18 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
     }
 
   // 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())
     {
+    const std::string 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.png";
+    package_background_destination << staging.str()
+                                   << "/.background/background" << extension;
 
     if(!this->CopyFile(package_background_source,
         package_background_destination))
@@ -417,18 +448,22 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
 
       return 0;
       }
+    }
 
-    std::ostringstream temp_background_hiding_command;
-    temp_background_hiding_command << this->GetOption("CPACK_COMMAND_SETFILE");
-    temp_background_hiding_command << " -a V \"";
-    temp_background_hiding_command << package_background_destination.str();
-    temp_background_hiding_command << "\"";
+  bool remount_image = !cpack_package_icon.empty() ||
+                       !cpack_dmg_ds_store_setup_script.empty();
 
-    if(!this->RunCommand(temp_background_hiding_command))
+  // 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 setting attributes on disk volume background image."
-          << std::endl);
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+        "Error creating dummy padding file."
+        << std::endl);
 
       return 0;
       }
@@ -457,10 +492,11 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
     return 0;
     }
 
-  // Optionally set the custom icon flag for the image ...
-  if(!cpack_package_icon.empty())
+  if(remount_image)
     {
-    std::ostringstream temp_mount;
+    // Store that we have a failure so that we always unmount the image
+    // before we exit.
+    bool had_error = false;
 
     std::ostringstream attach_command;
     attach_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
@@ -479,20 +515,57 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
 
     cmsys::RegularExpression mountpoint_regex(".*(/Volumes/[^\n]+)\n.*");
     mountpoint_regex.find(attach_output.c_str());
+    std::ostringstream temp_mount;
     temp_mount << mountpoint_regex.match(1);
 
-    std::ostringstream setfile_command;
-    setfile_command << this->GetOption("CPACK_COMMAND_SETFILE");
-    setfile_command << " -a C";
-    setfile_command << " \"" << temp_mount.str() << "\"";
-
-    if(!this->RunCommand(setfile_command))
+    // Remove dummy padding file so we have enough space on RW image ...
+    std::ostringstream dummy_padding;
+    dummy_padding << temp_mount.str() << "/.dummy-padding-file";
+    if(!cmSystemTools::RemoveFile(dummy_padding.str()))
       {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
-        "Error assigning custom icon to temporary disk image."
+        "Error removing dummy padding file."
         << std::endl);
 
-      return 0;
+      had_error = true;
+      }
+
+    // Optionally set the custom icon flag for the image ...
+    if(!had_error && !cpack_package_icon.empty())
+      {
+      std::ostringstream setfile_command;
+      setfile_command << this->GetOption("CPACK_COMMAND_SETFILE");
+      setfile_command << " -a C";
+      setfile_command << " \"" << temp_mount.str() << "\"";
+
+      if(!this->RunCommand(setfile_command))
+        {
+        cmCPackLogger(cmCPackLog::LOG_ERROR,
+          "Error assigning custom icon to temporary disk image."
+          << 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())
+      {
+      std::ostringstream setup_script_command;
+      setup_script_command << "osascript"
+                           << " \"" << cpack_dmg_ds_store_setup_script << "\""
+                           << " \"" << cpack_dmg_volume_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;
+        }
       }
 
     std::ostringstream detach_command;
@@ -508,6 +581,11 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
 
       return 0;
       }
+
+    if(had_error)
+      {
+      return 0;
+      }
     }
 
   if(!cpack_license_file.empty() || !slaDirectory.empty())

+ 1 - 0
Source/CPack/cmCPackDragNDropGenerator.h

@@ -36,6 +36,7 @@ protected:
 
 
   bool CopyFile(std::ostringstream& source, std::ostringstream& target);
+  bool CreateEmptyFile(std::ostringstream& target, size_t size);
   bool RunCommand(std::ostringstream& command, std::string* output = 0);
 
   std::string