Bug 1347576 - Add a 'mach repackage' command, with OSX dmg support; r=chmanchester a=IanN DONTBUILD CLOSED TREE SEAMONKEY_2_49_ESR_RELBRANCH
authorMike Shal <mshal@mozilla.com>
Fri, 10 Mar 2017 18:36:23 -0500
branchSEAMONKEY_2_49_ESR_RELBRANCH
changeset 357564 2829cd1b5e25e13c2aaafd935bfcdf9649b66ad8
parent 357563 a5ec8063ad1cd577906e868608ae05b22026e26c
child 357565 09bfc5a069ed8e7dc8e32ac28e77d4bbe7258b90
push id7836
push userfrgrahl@gmx.net
push dateMon, 15 Jul 2019 16:17:02 +0000
treeherdermozilla-esr52@3a4afe7ecac5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschmanchester, IanN
bugs1347576
milestone52.9.1
Bug 1347576 - Add a 'mach repackage' command, with OSX dmg support; r=chmanchester a=IanN DONTBUILD CLOSED TREE SeaMonkey ESR52 release branch only. 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
@@ -1596,8 +1596,40 @@ class Vendor(MachCommandBase):
                 description='Vendor rust crates from crates.io into third_party/rust')
     @CommandArgument('--ignore-modified', action='store_true',
         help='Ignore modified files in current checkout',
         default=False)
     def vendor_rust(self, **kwargs):
         from mozbuild.vendor_rust import VendorRust
         vendor_command = self._spawn(VendorRust)
         vendor_command.vendor(**kwargs)
+
+@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