gecko-dev/python/mozbuild/mozpack/dmg.py

235 строки
7.9 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/.
from __future__ import absolute_import, print_function, unicode_literals
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:
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 on directories so rsync copies the *contents* of source.
if not source.endswith("/") and os.path.isdir(source):
source += "/"
subprocess.check_call(["rsync", "-a", "--copy-unsafe-links", source, dest])
def set_folder_icon(dir, tmpdir):
"Set HFS attributes of dir to use a custom icon"
if not is_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:
# 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],
# 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.
"""
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", "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)
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, tmpdir)
chmod(stagedir)
create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
def extract_dmg_contents(dmgfile, destdir):
import buildconfig
if is_linux:
with mozfile.TemporaryDirectory() as tmpdir:
hfs_file = os.path.join(tmpdir, "firefox.hfs")
subprocess.check_call(
[buildconfig.substs["DMG_TOOL"], "extract", dmgfile, hfs_file],
# dmg is seriously chatty
stdout=open(os.devnull, "wb"),
)
subprocess.check_call(
[buildconfig.substs["HFS_TOOL"], hfs_file, "extractall", "/", destdir]
)
else:
unpack_diskimage = os.path.join(
buildconfig.topsrcdir, "build", "package", "mac_osx", "unpack-diskimage"
)
unpack_mountpoint = os.path.join(
"/tmp", "{}-unpack".format(buildconfig.substs["MOZ_APP_NAME"])
)
subprocess.check_call([unpack_diskimage, dmgfile, unpack_mountpoint, destdir])
def extract_dmg(dmgfile, output, dsstore=None, icon=None, background=None):
if platform.system() not in ("Darwin", "Linux"):
raise Exception("Don't know how to extract a DMG on '%s'" % platform.system())
if is_linux:
check_tools("DMG_TOOL", "MKFSHFS", "HFS_TOOL")
with mozfile.TemporaryDirectory() as tmpdir:
extract_dmg_contents(dmgfile, tmpdir)
if os.path.islink(os.path.join(tmpdir, " ")):
# Rsync will fail on the presence of this symlink
os.remove(os.path.join(tmpdir, " "))
rsync(tmpdir, output)
if dsstore:
mkdir(os.path.dirname(dsstore))
rsync(os.path.join(tmpdir, ".DS_Store"), dsstore)
if background:
mkdir(os.path.dirname(background))
rsync(
os.path.join(tmpdir, ".background", os.path.basename(background)),
background,
)
if icon:
mkdir(os.path.dirname(icon))
rsync(os.path.join(tmpdir, ".VolumeIcon.icns"), icon)