Bug 1185666 - Move DMG unpack logic to a python script, support linux. r=mshal draft
authorJustin Wood <Callek@gmail.com>
Tue, 28 Mar 2017 21:46:19 -0400
changeset 553836 2eaad4d90870ea917f55f2161f93428e81eb3b40
parent 553835 6ee27fb3302ed17754775caad5a0145070af797a
child 553837 e8a685345f23d42e2b50271502f598d85d6810f6
push id51792
push userCallek@gmail.com
push dateThu, 30 Mar 2017 20:30:25 +0000
reviewersmshal
bugs1185666
milestone55.0a1
Bug 1185666 - Move DMG unpack logic to a python script, support linux. r=mshal MozReview-Commit-ID: inKT2BWof4
python/mozbuild/mozbuild/action/unpack_dmg.py
python/mozbuild/mozpack/dmg.py
toolkit/mozapps/installer/upload-files.mk
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/action/unpack_dmg.py
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import print_function
+
+from mozpack import dmg
+
+import argparse
+import sys
+
+
+def main(args):
+    parser = argparse.ArgumentParser(
+        description='Explode a DMG into its relevant files')
+
+    parser.add_argument('--dsstore', help='DSStore file from')
+    parser.add_argument('--background', help='Background file from')
+    parser.add_argument('--icon', help='Icon file from')
+
+    parser.add_argument('dmgfile', metavar='DMG_IN',
+                        help='DMG File to Unpack')
+    parser.add_argument('outpath', metavar='PATH_OUT',
+                        help='Location to put unpacked files')
+
+    options = parser.parse_args(args)
+
+    dmg.extract_dmg(dmgfile=options.dmgfile, output=options.outpath,
+                    dsstore=options.dsstore, background=options.background,
+                    icon=options.icon)
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozpack/dmg.py
+++ b/python/mozbuild/mozpack/dmg.py
@@ -4,16 +4,17 @@
 
 import buildconfig
 import errno
 import mozfile
 import os
 import platform
 import shutil
 import subprocess
+import sys
 
 from mozbuild.util import ensureParentDir
 
 is_linux = platform.system() == 'Linux'
 
 
 def mkdir(dir):
     if not os.path.isdir(dir):
@@ -26,18 +27,18 @@ def mkdir(dir):
 
 def chmod(dir):
     'Set permissions of DMG contents correctly'
     subprocess.check_call(['chmod', '-R', 'a+rX,a-st,u+w,go-w', dir])
 
 
 def rsync(source, dest):
     'rsync the contents of directory source into directory dest'
-    # Ensure a trailing slash so rsync copies the *contents* of source.
-    if not source.endswith('/'):
+    # Ensure a trailing slash on directories so rsync copies the *contents* of source.
+    if not source.endswith('/') and os.path.isdir(source):
         source += '/'
     subprocess.check_call(['rsync', '-a', '--copy-unsafe-links',
                            source, dest])
 
 
 def set_folder_icon(dir, tmpdir):
     'Set HFS attributes of dir to use a custom icon'
     if not is_linux:
@@ -151,8 +152,58 @@ def create_dmg(source_directory, output_
             mkdir(os.path.dirname(full_target))
             shutil.copyfile(source, full_target)
         generate_hfs_file(stagedir, tmpdir, volume_name)
         create_app_symlink(stagedir, tmpdir)
         # Set the folder attributes to use a custom icon
         set_folder_icon(stagedir, tmpdir)
         chmod(stagedir)
         create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
+
+
+def extract_dmg_contents(dmgfile, destdir):
+    import buildconfig
+    if is_linux:
+        with mozfile.TemporaryDirectory() as tmpdir:
+            hfs_file = os.path.join(tmpdir, 'firefox.hfs')
+            subprocess.check_call([
+                    buildconfig.substs['DMG_TOOL'],
+                    'extract',
+                    dmgfile,
+                    hfs_file
+                ],
+                # dmg is seriously chatty
+                stdout=open(os.devnull, 'wb'))
+            subprocess.check_call([
+                buildconfig.substs['HFS_TOOL'], hfs_file, 'extractall', '/', destdir])
+    else:
+        unpack_diskimage = os.path.join(buildconfig.topsrcdir, 'build', 'package',
+                                        'mac_osx', 'unpack-diskimage')
+        unpack_mountpoint = os.path.join(
+            '/tmp', '{}-unpack'.format(buildconfig.substs['MOZ_APP_NAME']))
+        subprocess.check_call([unpack_diskimage, dmgfile, unpack_mountpoint,
+                               destdir])
+
+
+def extract_dmg(dmgfile, output, dsstore=None, icon=None, background=None):
+    if platform.system() not in ('Darwin', 'Linux'):
+        raise Exception("Don't know how to extract a DMG on '%s'" % platform.system())
+
+    if is_linux:
+        check_tools('DMG_TOOL', 'MKFSHFS', 'HFS_TOOL')
+
+    with mozfile.TemporaryDirectory() as tmpdir:
+        extract_dmg_contents(dmgfile, tmpdir)
+        if os.path.islink(os.path.join(tmpdir, ' ')):
+            # Rsync will fail on the presence of this symlink
+            os.remove(os.path.join(tmpdir, ' '))
+        rsync(tmpdir, output)
+
+        if dsstore:
+            mkdir(os.path.dirname(dsstore))
+            rsync(os.path.join(tmpdir, '.DS_Store'), dsstore)
+        if background:
+            mkdir(os.path.dirname(background))
+            rsync(os.path.join(tmpdir, '.background', os.path.basename(background)),
+                  background)
+        if icon:
+            mkdir(os.path.dirname(icon))
+            rsync(os.path.join(tmpdir, '.VolumeIcon.icns'), icon)
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -205,40 +205,22 @@ endif
 
 ifeq ($(MOZ_PKG_FORMAT),DMG)
   PKG_SUFFIX	= .dmg
 
   _ABS_MOZSRCDIR = $(shell cd $(MOZILLA_DIR) && pwd)
   PKG_DMG_SOURCE = $(MOZ_PKG_DIR)
   INNER_MAKE_PACKAGE	= $(call py_action,make_dmg,'$(PKG_DMG_SOURCE)' '$(PACKAGE)')
   INNER_UNMAKE_PACKAGE	= \
-    set -ex; \
-    rm -rf $(ABS_DIST)/unpack.tmp; \
-    mkdir -p $(ABS_DIST)/unpack.tmp; \
-    $(_ABS_MOZSRCDIR)/build/package/mac_osx/unpack-diskimage $(UNPACKAGE) /tmp/$(MOZ_PKG_APPNAME)-unpack $(ABS_DIST)/unpack.tmp; \
-    rsync -a '$(ABS_DIST)/unpack.tmp/$(_APPNAME)' $(MOZ_PKG_DIR); \
-    if test -n '$(MOZ_PKG_MAC_DSSTORE)' ; then \
-      mkdir -p '$(dir $(MOZ_PKG_MAC_DSSTORE))'; \
-      rsync -a '$(ABS_DIST)/unpack.tmp/.DS_Store' '$(MOZ_PKG_MAC_DSSTORE)'; \
-    fi; \
-    if test -n '$(MOZ_PKG_MAC_BACKGROUND)' ; then \
-      mkdir -p '$(dir $(MOZ_PKG_MAC_BACKGROUND))'; \
-      rsync -a '$(ABS_DIST)/unpack.tmp/.background/$(notdir $(MOZ_PKG_MAC_BACKGROUND))' '$(MOZ_PKG_MAC_BACKGROUND)'; \
-    fi; \
-    if test -n '$(MOZ_PKG_MAC_ICON)' ; then \
-      mkdir -p '$(dir $(MOZ_PKG_MAC_ICON))'; \
-      rsync -a '$(ABS_DIST)/unpack.tmp/.VolumeIcon.icns' '$(MOZ_PKG_MAC_ICON)'; \
-    fi; \
-    rm -rf $(ABS_DIST)/unpack.tmp; \
-    if test -n '$(MOZ_PKG_MAC_RSRC)' ; then \
-      cp $(UNPACKAGE) $(MOZ_PKG_APPNAME).tmp.dmg && \
-      hdiutil unflatten $(MOZ_PKG_APPNAME).tmp.dmg && \
-      { /Developer/Tools/DeRez -skip plst -skip blkx $(MOZ_PKG_APPNAME).tmp.dmg > '$(MOZ_PKG_MAC_RSRC)' || { rm -f $(MOZ_PKG_APPNAME).tmp.dmg && false; }; } && \
-      rm -f $(MOZ_PKG_APPNAME).tmp.dmg; \
-    fi
+    $(call py_action,unpack_dmg, \
+        $(if $(MOZ_PKG_MAC_DSSTORE),--dsstore '$(MOZ_PKG_MAC_DSSTORE)') \
+        $(if $(MOZ_PKG_MAC_BACKGROUND),--background '$(MOZ_PKG_MAC_BACKGROUND)') \
+        $(if $(MOZ_PKG_MAC_ICON),--icon '$(MOZ_PKG_MAC_ICON)') \
+        '$(UNPACKAGE)' '$(MOZ_PKG_DIR)' \
+        );
 endif
 
 ifdef MOZ_INTERNAL_SIGNING_FORMAT
   MOZ_SIGN_PREPARED_PACKAGE_CMD=$(MOZ_SIGN_CMD) $(foreach f,$(MOZ_INTERNAL_SIGNING_FORMAT),-f $(f)) $(foreach i,$(SIGN_INCLUDES),-i $(i)) $(foreach x,$(SIGN_EXCLUDES),-x $(x))
   ifeq (WINNT,$(OS_ARCH))
     MOZ_SIGN_PREPARED_PACKAGE_CMD += --nsscmd '$(ABS_DIST)/bin/shlibsign$(BIN_SUFFIX) -v -i'
   endif
 endif