Bug 1197325 -- Set volume icon for DMG in Linux->Mac cross compiles. r=ted draft
authorJustin Wood <Callek@gmail.com>
Mon, 30 Jan 2017 17:32:32 -0500
changeset 480683 18d4e2fa3af4a387bf5e08f987d44a0a658ac1d3
parent 480682 bc20b8c35b8d62b2230f52a076c619fc674047e1
child 480684 5afb3583c4426d3903b00e879a248a19cb02bd31
push id44628
push userCallek@gmail.com
push dateWed, 08 Feb 2017 20:19:05 +0000
reviewersted
bugs1197325
milestone54.0a1
Bug 1197325 -- Set volume icon for DMG in Linux->Mac cross compiles. r=ted MozReview-Commit-ID: C4LFZB6msmL
browser/config/tooltool-manifests/macosx64/cross-releng.manifest
build/macosx/cross-mozconfig.common
moz.configure
python/mozbuild/mozpack/dmg.py
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -50,10 +50,18 @@
 },
 {
 "version": "rustc 1.14.0 (e8a012324 2016-12-16) repack",
 "size": 152573516,
 "digest": "eef2f10bf57005d11c34b9b49eb76d0f09d026295055039fea89952a3be51580abdab29366278ed4bfa393b33c5cee4d51d3af4221e9e7d7d833d0fc1347597c",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
+},
+{
+"size": 281576,
+"visibility": "public",
+"digest": "71616564533d138fb12f08e761c2638d054814fdf9c9439638ec57b201e100445c364d73d8d7a4f0e3b784898d5fe6264e8242863fc5ac40163f1791468bbc46",
+"algorithm": "sha512",
+"filename": "hfsplus-tools.tar.xz",
+"unpack": true
 }
 ]
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -27,17 +27,19 @@ FLAGS="-target x86_64-apple-darwin10 -ml
 export CC="$topsrcdir/clang/bin/clang $FLAGS"
 export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
 export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
 export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
 export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
 export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10-
 export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
 export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage
+export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
 export DMG_TOOL=$topsrcdir/dmg/dmg
+export HFS_TOOL=$topsrcdir/dmg/hfsplus
 
 export HOST_CC="$topsrcdir/clang/bin/clang"
 export HOST_CXX="$topsrcdir/clang/bin/clang++"
 export HOST_CPP="$topsrcdir/clang/bin/clang -E"
 export HOST_CFLAGS="-g"
 export HOST_CXXFLAGS="-g"
 export HOST_LDFLAGS="-g"
 
--- a/moz.configure
+++ b/moz.configure
@@ -249,24 +249,30 @@ check_prog('DOXYGEN', ('doxygen',), allo
 check_prog('XARGS', ('xargs',))
 
 @depends(target)
 def extra_programs(target):
     if target.kernel == 'Darwin':
         return namespace(
             DSYMUTIL=('dsymutil', 'llvm-dsymutil'),
             GENISOIMAGE=('genisoimage',),
+            MKFSHFS=('newfs_hfs', 'mkfs.hfsplus'),
+            HFS_TOOL=('hfsplus',)
         )
     if target.os == 'GNU' and target.kernel == 'Linux':
         return namespace(RPMBUILD=('rpmbuild',))
 
 check_prog('DSYMUTIL', delayed_getattr(extra_programs, 'DSYMUTIL'),
            allow_missing=True)
 check_prog('GENISOIMAGE', delayed_getattr(extra_programs, 'GENISOIMAGE'),
            allow_missing=True)
+check_prog('MKFSHFS', delayed_getattr(extra_programs, 'MKFSHFS'),
+           allow_missing=True)
+check_prog('HFS_TOOL', delayed_getattr(extra_programs, 'HFS_TOOL'),
+           allow_missing=True)
 check_prog('RPMBUILD', delayed_getattr(extra_programs, 'RPMBUILD'),
            allow_missing=True)
 
 option('--enable-system-hunspell',
        help="Use system hunspell (located with pkgconfig)")
 
 @depends('--enable-system-hunspell', compile_environment)
 def check_for_hunspell(value, compile_env):
--- a/python/mozbuild/mozpack/dmg.py
+++ b/python/mozbuild/mozpack/dmg.py
@@ -1,21 +1,23 @@
 # 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/.
 
+import buildconfig
 import errno
 import mozfile
 import os
 import platform
 import shutil
 import subprocess
 
 is_linux = platform.system() == 'Linux'
 
+
 def mkdir(dir):
     if not os.path.isdir(dir):
         try:
             os.makedirs(dir)
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
 
@@ -29,60 +31,91 @@ 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('/'):
         source += '/'
     subprocess.check_call(['rsync', '-a', '--copy-unsafe-links',
                            source, dest])
 
 
-def set_folder_icon(dir):
+def set_folder_icon(dir, tmpdir):
     'Set HFS attributes of dir to use a custom icon'
     if not is_linux:
-        #TODO: bug 1197325 - figure out how to support this on Linux
         subprocess.check_call(['SetFile', '-a', 'C', dir])
+    else:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        subprocess.check_call([
+            buildconfig.substs['HFS_TOOL'],  hfs, 'attr', '/', 'C'])
+
+
+def generate_hfs_file(stagedir, tmpdir, volume_name):
+    '''
+    When cross compiling, we zero fill an hfs file, that we will turn into
+    a DMG. To do so we test the size of the staged dir, and add some slight
+    padding to that.
+    '''
+    if is_linux:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        output = subprocess.check_output(['du', '-s', stagedir])
+        size = (int(output.split()[0]) / 1000)  # Get in MB
+        size = int(size * 1.02)  # Bump the used size slightly larger.
+        # Setup a proper file sized out with zero's
+        subprocess.check_call(['dd', 'if=/dev/zero', 'of={}'.format(hfs),
+                               'bs=1M', 'count={}'.format(size)])
+        subprocess.check_call([
+            buildconfig.substs['MKFSHFS'], '-v', volume_name,
+            hfs])
+
+
+def create_app_symlink(stagedir, tmpdir):
+    '''
+    Make a symlink to /Applications. The symlink name is a space
+    so we don't have to localize it. The Applications folder icon
+    will be shown in Finder, which should be clear enough for users.
+    '''
+    if is_linux:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        subprocess.check_call([
+            buildconfig.substs['HFS_TOOL'], hfs, 'symlink',
+            '/ ', '/Applications'])
+    else:
+        os.symlink('/Applications', os.path.join(stagedir, ' '))
 
 
 def create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name):
     'Given a prepared directory stagedir, produce a DMG at output_dmg.'
     if not is_linux:
         # Running on OS X
         hybrid = os.path.join(tmpdir, 'hybrid.dmg')
         subprocess.check_call(['hdiutil', 'makehybrid', '-hfs',
                                '-hfs-volume-name', volume_name,
                                '-hfs-openfolder', stagedir,
                                '-ov', stagedir,
                                '-o', hybrid])
         subprocess.check_call(['hdiutil', 'convert', '-format', 'UDBZ',
                                '-imagekey', 'bzip2-level=9',
                                '-ov', hybrid, '-o', output_dmg])
     else:
-        import buildconfig
-        uncompressed = os.path.join(tmpdir, 'uncompressed.dmg')
+        hfs = os.path.join(tmpdir, 'staged.hfs')
         subprocess.check_call([
-            buildconfig.substs['GENISOIMAGE'],
-            '-V', volume_name,
-            '-D', '-R', '-apple', '-no-pad',
-            '-o', uncompressed,
-            stagedir
-        ])
+            buildconfig.substs['HFS_TOOL'], hfs, 'addall', stagedir])
         subprocess.check_call([
             buildconfig.substs['DMG_TOOL'],
-            'dmg',
-            uncompressed,
+            'build',
+            hfs,
             output_dmg
         ],
                               # dmg is seriously chatty
                               stdout=open(os.devnull, 'wb'))
 
+
 def check_tools(*tools):
     '''
     Check that each tool named in tools exists in SUBSTS and is executable.
     '''
-    import buildconfig
     for tool in tools:
         path = buildconfig.substs[tool]
         if not path:
             raise Exception('Required tool "%s" not found' % tool)
         if not os.path.isfile(path):
             raise Exception('Required tool "%s" not found at path "%s"' % (tool, path))
         if not os.access(path, os.X_OK):
             raise Exception('Required tool "%s" at path "%s" is not executable' % (tool, path))
@@ -95,27 +128,25 @@ def create_dmg(source_directory, output_
     Use volume_name as the disk image volume name, and
     use extra_files as a list of tuples of (filename, relative path) to copy
     into the disk image.
     '''
     if platform.system() not in ('Darwin', 'Linux'):
         raise Exception("Don't know how to build a DMG on '%s'" % platform.system())
 
     if is_linux:
-        check_tools('DMG_TOOL', 'GENISOIMAGE')
+        check_tools('DMG_TOOL', 'GENISOIMAGE', 'MKFSHFS', 'HFS_TOOL')
     with mozfile.TemporaryDirectory() as tmpdir:
         stagedir = os.path.join(tmpdir, 'stage')
         os.mkdir(stagedir)
         # Copy the app bundle over using rsync
         rsync(source_directory, stagedir)
         # Copy extra files
         for source, target in extra_files:
             full_target = os.path.join(stagedir, target)
             mkdir(os.path.dirname(full_target))
             shutil.copyfile(source, full_target)
-        # Make a symlink to /Applications. The symlink name is a space
-        # so we don't have to localize it. The Applications folder icon
-        # will be shown in Finder, which should be clear enough for users.
-        os.symlink('/Applications', os.path.join(stagedir, ' '))
+        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)
+        set_folder_icon(stagedir, tmpdir)
         chmod(stagedir)
         create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)