Bug 1347576 - Add a 'mach repackage' command, with OSX dmg support; r?chmanchester draft
authorMike Shal <mshal@mozilla.com>
Fri, 10 Mar 2017 18:36:23 -0500
changeset 553698 c436857cd3e68775caa966309d5eee31bc8cd8b6
parent 553679 03d602fd723ad6ff4588c04855884ffa1dee9410
child 622148 8d1fb6e9aad1be3c36c4a31e3e8765288e24aaa3
push id51727
push userbmo:mshal@mozilla.com
push dateThu, 30 Mar 2017 13:56:41 +0000
reviewerschmanchester
bugs1347576
milestone55.0a1
Bug 1347576 - Add a 'mach repackage' command, with OSX dmg support; r?chmanchester This is the initial support of 'mach repackage', which can take an existing tarball and create a DMG on either an OSX host or on a Linux host with cross-OSX tools. Configure is needed in order to find the tools necessary to create the DMG. On a Linux cross-compiled environment with tooltool, this can be as simple as: export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs export DMG_TOOL=$topsrcdir/dmg/dmg export HFS_TOOL=$topsrcdir/dmg/hfsplus ac_add_options --disable-compile-environment MozReview-Commit-ID: 6t2rlXpwUvu
python/mozbuild/mozbuild/mach_commands.py
python/mozbuild/mozbuild/repackage.py
python/mozbuild/mozpack/dmg.py
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1618,8 +1618,40 @@ class WebRTCGTestCommands(GTestCommands)
             b'GTEST_FILTER': gtest_filter
         }
 
         return self.run_process(args=args,
                                 append_env=gtest_env,
                                 cwd=cwd,
                                 ensure_exit_code=False,
                                 pass_thru=True)
+
+@CommandProvider
+class Repackage(MachCommandBase):
+    '''Repackages artifacts into different formats.
+
+    This is generally used after packages are signed by the signing
+    scriptworkers in order to bundle things up into shippable formats, such as a
+    .dmg on OSX or an installer exe on Windows.
+    '''
+    @Command('repackage', category='misc',
+             description='Repackage artifacts into different formats.')
+    @CommandArgument('--input', '-i', type=str, required=True,
+        help='Input filename')
+    @CommandArgument('--output', '-o', type=str, required=True,
+        help='Output filename')
+    def repackage(self, input, output):
+        if not os.path.exists(input):
+            print('Input file does not exist: %s' % input)
+            return 1
+
+        if not os.path.exists(os.path.join(self.topobjdir, 'config.status')):
+            print('config.status not found.  Please run |mach configure| '
+                  'prior to |mach repackage|.')
+            return 1
+
+        if output.endswith('.dmg'):
+            from mozbuild.repackage import repackage_dmg
+            repackage_dmg(input, output)
+        else:
+            print("Repackaging into output '%s' is not yet supported." % output)
+            return 1
+        return 0
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/repackage.py
@@ -0,0 +1,49 @@
+# 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 os
+import tempfile
+import tarfile
+import shutil
+import ConfigParser
+import mozpack.path as mozpath
+from mozpack.dmg import create_dmg
+
+def repackage_dmg(infile, output):
+
+    if not tarfile.is_tarfile(infile):
+        raise Exception("Input file %s is not a valid tarfile." % infile)
+
+    tmpdir = tempfile.mkdtemp()
+    try:
+        with tarfile.open(infile) as tar:
+            tar.extractall(path=tmpdir)
+
+        # Remove the /Applications symlink. If we don't, an rsync command in
+        # create_dmg() will break, and create_dmg() re-creates the symlink anyway.
+        try:
+            os.remove(mozpath.join(tmpdir, ' '))
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+
+        # Grab the volume name
+        volume_name = None
+        for root, dirs, files in os.walk(tmpdir):
+            if 'application.ini' in files:
+                parser = ConfigParser.ConfigParser()
+                parser.read(mozpath.join(root, 'application.ini'))
+                volume_name = parser.get('App', 'CodeName')
+                break
+
+        if volume_name is None:
+            raise Exception("Input package does not contain an application.ini file")
+
+        # The extra_files argument is empty [] because they are already a part
+        # of the original dmg produced by the build, and they remain in the
+        # tarball generated by the signing task.
+        create_dmg(tmpdir, output, volume_name, [])
+
+    finally:
+        shutil.rmtree(tmpdir)
--- a/python/mozbuild/mozpack/dmg.py
+++ b/python/mozbuild/mozpack/dmg.py
@@ -5,16 +5,18 @@
 import buildconfig
 import errno
 import mozfile
 import os
 import platform
 import shutil
 import subprocess
 
+from mozbuild.util import ensureParentDir
+
 is_linux = platform.system() == 'Linux'
 
 
 def mkdir(dir):
     if not os.path.isdir(dir):
         try:
             os.makedirs(dir)
         except OSError as e:
@@ -89,16 +91,20 @@ def create_dmg_from_staged(stagedir, out
                                '-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:
+        # The dmg tool doesn't create the destination directories, and silently
+        # returns success if the parent directory doesn't exist.
+        ensureParentDir(output_dmg)
+
         hfs = os.path.join(tmpdir, 'staged.hfs')
         subprocess.check_call([
             buildconfig.substs['HFS_TOOL'], hfs, 'addall', stagedir])
         subprocess.check_call([
             buildconfig.substs['DMG_TOOL'],
             'build',
             hfs,
             output_dmg