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
--- 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