Files
UXP/python/mozbuild/mozpack/dmg.py
T

157 lines
6.7 KiB
Python

# 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 errno
import mozfile
import os
import fnmatch
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
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('/'):
source += '/'
subprocess.check_call(['rsync', '-a', '--copy-unsafe-links',
source, dest])
def set_folder_icon(dir):
'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])
def create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name):
'Given a prepared directory stagedir, produce a DMG at output_dmg.'
import buildconfig
if not is_linux:
# Running on OS X
hybrid = os.path.join(tmpdir, 'hybrid.dmg')
hdiutiloptions = ['create', '-fs', 'HFS+',
'-volname', volume_name,
'-srcfolder', stagedir,
'-ov', hybrid]
if buildconfig.substs['MOZ_MACBUNDLE_TYPE'] == 'hybrid':
hdiutiloptions = ['makehybrid', '-hfs',
'-hfs-volume-name', volume_name,
'-hfs-openfolder', stagedir,
'-ov', stagedir,
'-o', hybrid]
subprocess.check_call(['hdiutil'] + hdiutiloptions)
subprocess.check_call(['hdiutil', 'convert', '-format', 'UDBZ',
'-imagekey', 'bzip2-level=9',
'-ov', hybrid, '-o', output_dmg])
else:
uncompressed = os.path.join(tmpdir, 'uncompressed.dmg')
subprocess.check_call([
buildconfig.substs['GENISOIMAGE'],
'-V', volume_name,
'-D', '-R', '-apple', '-no-pad',
'-o', uncompressed,
stagedir
])
subprocess.check_call([
buildconfig.substs['DMG_TOOL'],
'dmg',
uncompressed,
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))
def create_dmg(source_directory, output_dmg, volume_name, extra_files):
'''
Create a DMG disk image at the path output_dmg from source_directory.
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')
with mozfile.TemporaryDirectory() as tmpdir:
import buildconfig
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, ' '))
# Set the folder attributes to use a custom icon
set_folder_icon(stagedir)
chmod(stagedir)
if not is_linux:
identity = buildconfig.substs['MOZ_MACBUNDLE_IDENTITY']
if identity != '':
dylibs = []
entitlements = []
appbundle = os.path.join(stagedir, buildconfig.substs['MOZ_MACBUNDLE_NAME'])
# If the -bin file is in Resources add it to the dylibs as well
resourcebin = os.path.join(appbundle, 'Contents/Resources/' + buildconfig.substs['MOZ_APP_NAME'] + '-bin')
if os.path.isfile(resourcebin):
dylibs.append(resourcebin)
# Create a list of dylibs in Contents/Resources that won't get signed by --deep
for root, dirnames, filenames in os.walk(os.path.join(appbundle,'Contents/Resources/')):
for filename in fnmatch.filter(filenames, '*.dylib'):
dylibs.append(os.path.join(root, filename))
# Select default entitlements based on whether debug is enabled
entitlement = os.path.abspath(os.path.join(os.getcwd(), '../../platform/security/mac/production.entitlements.xml'))
if buildconfig.substs['MOZ_DEBUG']:
entitlement = os.path.abspath(os.path.join(os.getcwd(), '../../platform/security/mac/developer.entitlements.xml'))
# Check to see if the entitlements are disabled or overrided by mozconfig
entitlementname = buildconfig.substs['MOZ_MACBUNDLE_ENTITLEMENT']
if entitlementname != '':
if os.path.isfile(entitlementname):
entitlement = entitlementname
if entitlementname != 'none':
if os.path.isfile(entitlement):
entitlements = ['--entitlements', entitlement]
# Call the codesign tool
subprocess.check_call(['codesign', '--deep', '--timestamp', '--options', 'runtime'] + entitlements + [ '-s', identity] + dylibs + [appbundle])
create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)