mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 09:53:47 +01:00
3d41521569
By generating the symlink earlier in the macdeploy process, we can unify the logic in the deploy script.
599 lines
24 KiB
Python
Executable File
599 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import sys, re, os, platform, shutil, stat, subprocess, os.path
|
|
from argparse import ArgumentParser
|
|
from ds_store import DSStore
|
|
from mac_alias import Alias
|
|
from pathlib import Path
|
|
from subprocess import PIPE, run
|
|
from typing import List, Optional
|
|
|
|
# This is ported from the original macdeployqt with modifications
|
|
|
|
class FrameworkInfo(object):
|
|
def __init__(self):
|
|
self.frameworkDirectory = ""
|
|
self.frameworkName = ""
|
|
self.frameworkPath = ""
|
|
self.binaryDirectory = ""
|
|
self.binaryName = ""
|
|
self.binaryPath = ""
|
|
self.version = ""
|
|
self.installName = ""
|
|
self.deployedInstallName = ""
|
|
self.sourceFilePath = ""
|
|
self.destinationDirectory = ""
|
|
self.sourceResourcesDirectory = ""
|
|
self.sourceVersionContentsDirectory = ""
|
|
self.sourceContentsDirectory = ""
|
|
self.destinationResourcesDirectory = ""
|
|
self.destinationVersionContentsDirectory = ""
|
|
|
|
def __eq__(self, other):
|
|
if self.__class__ == other.__class__:
|
|
return self.__dict__ == other.__dict__
|
|
else:
|
|
return False
|
|
|
|
def __str__(self):
|
|
return f""" Framework name: {self.frameworkName}
|
|
Framework directory: {self.frameworkDirectory}
|
|
Framework path: {self.frameworkPath}
|
|
Binary name: {self.binaryName}
|
|
Binary directory: {self.binaryDirectory}
|
|
Binary path: {self.binaryPath}
|
|
Version: {self.version}
|
|
Install name: {self.installName}
|
|
Deployed install name: {self.deployedInstallName}
|
|
Source file Path: {self.sourceFilePath}
|
|
Deployed Directory (relative to bundle): {self.destinationDirectory}
|
|
"""
|
|
|
|
def isDylib(self):
|
|
return self.frameworkName.endswith(".dylib")
|
|
|
|
def isQtFramework(self):
|
|
if self.isDylib():
|
|
return self.frameworkName.startswith("libQt")
|
|
else:
|
|
return self.frameworkName.startswith("Qt")
|
|
|
|
reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
|
|
bundleFrameworkDirectory = "Contents/Frameworks"
|
|
bundleBinaryDirectory = "Contents/MacOS"
|
|
|
|
@classmethod
|
|
def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']:
|
|
# Note: line must be trimmed
|
|
if line == "":
|
|
return None
|
|
|
|
# Don't deploy system libraries
|
|
if line.startswith("/System/Library/") or line.startswith("@executable_path") or line.startswith("/usr/lib/"):
|
|
return None
|
|
|
|
m = cls.reOLine.match(line)
|
|
if m is None:
|
|
raise RuntimeError(f"otool line could not be parsed: {line}")
|
|
|
|
path = m.group(1)
|
|
|
|
info = cls()
|
|
info.sourceFilePath = path
|
|
info.installName = path
|
|
|
|
if path.endswith(".dylib"):
|
|
dirname, filename = os.path.split(path)
|
|
info.frameworkName = filename
|
|
info.frameworkDirectory = dirname
|
|
info.frameworkPath = path
|
|
|
|
info.binaryDirectory = dirname
|
|
info.binaryName = filename
|
|
info.binaryPath = path
|
|
info.version = "-"
|
|
|
|
info.installName = path
|
|
info.deployedInstallName = f"@executable_path/../Frameworks/{info.binaryName}"
|
|
info.sourceFilePath = path
|
|
info.destinationDirectory = cls.bundleFrameworkDirectory
|
|
else:
|
|
parts = path.split("/")
|
|
i = 0
|
|
# Search for the .framework directory
|
|
for part in parts:
|
|
if part.endswith(".framework"):
|
|
break
|
|
i += 1
|
|
if i == len(parts):
|
|
raise RuntimeError(f"Could not find .framework or .dylib in otool line: {line}")
|
|
|
|
info.frameworkName = parts[i]
|
|
info.frameworkDirectory = "/".join(parts[:i])
|
|
info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
|
|
|
|
info.binaryName = parts[i+3]
|
|
info.binaryDirectory = "/".join(parts[i+1:i+3])
|
|
info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
|
|
info.version = parts[i+2]
|
|
|
|
info.deployedInstallName = f"@executable_path/../Frameworks/{os.path.join(info.frameworkName, info.binaryPath)}"
|
|
info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
|
|
|
|
info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
|
|
info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents")
|
|
info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents")
|
|
info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
|
|
info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents")
|
|
|
|
return info
|
|
|
|
class ApplicationBundleInfo(object):
|
|
def __init__(self, path: str):
|
|
self.path = path
|
|
# for backwards compatibility reasons, this must remain as Bitcoin-Qt
|
|
self.binaryPath = os.path.join(path, "Contents", "MacOS", "Bitcoin-Qt")
|
|
if not os.path.exists(self.binaryPath):
|
|
raise RuntimeError(f"Could not find bundle binary for {path}")
|
|
self.resourcesPath = os.path.join(path, "Contents", "Resources")
|
|
self.pluginPath = os.path.join(path, "Contents", "PlugIns")
|
|
|
|
class DeploymentInfo(object):
|
|
def __init__(self):
|
|
self.qtPath = None
|
|
self.pluginPath = None
|
|
self.deployedFrameworks = []
|
|
|
|
def detectQtPath(self, frameworkDirectory: str):
|
|
parentDir = os.path.dirname(frameworkDirectory)
|
|
if os.path.exists(os.path.join(parentDir, "translations")):
|
|
# Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
|
|
self.qtPath = parentDir
|
|
else:
|
|
self.qtPath = os.getenv("QTDIR", None)
|
|
|
|
if self.qtPath is not None:
|
|
pluginPath = os.path.join(self.qtPath, "plugins")
|
|
if os.path.exists(pluginPath):
|
|
self.pluginPath = pluginPath
|
|
|
|
def usesFramework(self, name: str) -> bool:
|
|
for framework in self.deployedFrameworks:
|
|
if framework.endswith(".framework"):
|
|
if framework.startswith(f"{name}."):
|
|
return True
|
|
elif framework.endswith(".dylib"):
|
|
if framework.startswith(f"lib{name}."):
|
|
return True
|
|
return False
|
|
|
|
def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]:
|
|
if verbose:
|
|
print(f"Inspecting with otool: {binaryPath}")
|
|
otoolbin=os.getenv("OTOOL", "otool")
|
|
otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
|
if otool.returncode != 0:
|
|
sys.stderr.write(otool.stderr)
|
|
sys.stderr.flush()
|
|
raise RuntimeError(f"otool failed with return code {otool.returncode}")
|
|
|
|
otoolLines = otool.stdout.split("\n")
|
|
otoolLines.pop(0) # First line is the inspected binary
|
|
if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
|
|
otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
|
|
|
|
libraries = []
|
|
for line in otoolLines:
|
|
line = line.replace("@loader_path", os.path.dirname(binaryPath))
|
|
info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
|
|
if info is not None:
|
|
if verbose:
|
|
print("Found framework:")
|
|
print(info)
|
|
libraries.append(info)
|
|
|
|
return libraries
|
|
|
|
def runInstallNameTool(action: str, *args):
|
|
installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool")
|
|
run([installnametoolbin, "-"+action] + list(args), check=True)
|
|
|
|
def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int):
|
|
if verbose:
|
|
print("Using install_name_tool:")
|
|
print(" in", binaryPath)
|
|
print(" change reference", oldName)
|
|
print(" to", newName)
|
|
runInstallNameTool("change", oldName, newName, binaryPath)
|
|
|
|
def changeIdentification(id: str, binaryPath: str, verbose: int):
|
|
if verbose:
|
|
print("Using install_name_tool:")
|
|
print(" change identification in", binaryPath)
|
|
print(" to", id)
|
|
runInstallNameTool("id", id, binaryPath)
|
|
|
|
def runStrip(binaryPath: str, verbose: int):
|
|
stripbin=os.getenv("STRIP", "strip")
|
|
if verbose:
|
|
print("Using strip:")
|
|
print(" stripped", binaryPath)
|
|
run([stripbin, "-x", binaryPath], check=True)
|
|
|
|
def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[str]:
|
|
if framework.sourceFilePath.startswith("Qt"):
|
|
#standard place for Nokia Qt installer's frameworks
|
|
fromPath = f"/Library/Frameworks/{framework.sourceFilePath}"
|
|
else:
|
|
fromPath = framework.sourceFilePath
|
|
toDir = os.path.join(path, framework.destinationDirectory)
|
|
toPath = os.path.join(toDir, framework.binaryName)
|
|
|
|
if framework.isDylib():
|
|
if not os.path.exists(fromPath):
|
|
raise RuntimeError(f"No file at {fromPath}")
|
|
|
|
if os.path.exists(toPath):
|
|
return None # Already there
|
|
|
|
if not os.path.exists(toDir):
|
|
os.makedirs(toDir)
|
|
|
|
shutil.copy2(fromPath, toPath)
|
|
if verbose:
|
|
print("Copied:", fromPath)
|
|
print(" to:", toPath)
|
|
else:
|
|
to_dir = os.path.join(path, "Contents", "Frameworks", framework.frameworkName)
|
|
if os.path.exists(to_dir):
|
|
return None # Already there
|
|
|
|
from_dir = framework.frameworkPath
|
|
if not os.path.exists(from_dir):
|
|
raise RuntimeError(f"No directory at {from_dir}")
|
|
|
|
shutil.copytree(from_dir, to_dir, symlinks=True)
|
|
if verbose:
|
|
print("Copied:", from_dir)
|
|
print(" to:", to_dir)
|
|
|
|
headers_link = os.path.join(to_dir, "Headers")
|
|
if os.path.exists(headers_link):
|
|
os.unlink(headers_link)
|
|
|
|
headers_dir = os.path.join(to_dir, framework.binaryDirectory, "Headers")
|
|
if os.path.exists(headers_dir):
|
|
shutil.rmtree(headers_dir)
|
|
|
|
permissions = os.stat(toPath)
|
|
if not permissions.st_mode & stat.S_IWRITE:
|
|
os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
|
|
|
|
return toPath
|
|
|
|
def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo:
|
|
if deploymentInfo is None:
|
|
deploymentInfo = DeploymentInfo()
|
|
|
|
while len(frameworks) > 0:
|
|
framework = frameworks.pop(0)
|
|
deploymentInfo.deployedFrameworks.append(framework.frameworkName)
|
|
|
|
print("Processing", framework.frameworkName, "...")
|
|
|
|
# Get the Qt path from one of the Qt frameworks
|
|
if deploymentInfo.qtPath is None and framework.isQtFramework():
|
|
deploymentInfo.detectQtPath(framework.frameworkDirectory)
|
|
|
|
if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
|
|
print(framework.frameworkName, "already deployed, skipping.")
|
|
continue
|
|
|
|
# install_name_tool the new id into the binary
|
|
changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
|
|
|
|
# Copy framework to app bundle.
|
|
deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
|
|
# Skip the rest if already was deployed.
|
|
if deployedBinaryPath is None:
|
|
continue
|
|
|
|
if strip:
|
|
runStrip(deployedBinaryPath, verbose)
|
|
|
|
# install_name_tool it a new id.
|
|
changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
|
|
# Check for framework dependencies
|
|
dependencies = getFrameworks(deployedBinaryPath, verbose)
|
|
|
|
for dependency in dependencies:
|
|
changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
|
|
|
|
# Deploy framework if necessary.
|
|
if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
|
|
frameworks.append(dependency)
|
|
|
|
return deploymentInfo
|
|
|
|
def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo:
|
|
frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
|
|
if len(frameworks) == 0:
|
|
print(f"Warning: Could not find any external frameworks to deploy in {applicationBundle.path}.")
|
|
return DeploymentInfo()
|
|
else:
|
|
return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
|
|
|
|
def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: DeploymentInfo, strip: bool, verbose: int):
|
|
plugins = []
|
|
if deploymentInfo.pluginPath is None:
|
|
return
|
|
for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
|
|
pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
|
|
|
|
if pluginDirectory not in ['styles', 'platforms']:
|
|
continue
|
|
|
|
for pluginName in filenames:
|
|
pluginPath = os.path.join(pluginDirectory, pluginName)
|
|
|
|
if pluginName.split('.')[0] not in ['libqminimal', 'libqcocoa', 'libqmacstyle']:
|
|
continue
|
|
|
|
plugins.append((pluginDirectory, pluginName))
|
|
|
|
for pluginDirectory, pluginName in plugins:
|
|
print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
|
|
|
|
sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
|
|
destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
|
|
if not os.path.exists(destinationDirectory):
|
|
os.makedirs(destinationDirectory)
|
|
|
|
destinationPath = os.path.join(destinationDirectory, pluginName)
|
|
shutil.copy2(sourcePath, destinationPath)
|
|
if verbose:
|
|
print("Copied:", sourcePath)
|
|
print(" to:", destinationPath)
|
|
|
|
if strip:
|
|
runStrip(destinationPath, verbose)
|
|
|
|
dependencies = getFrameworks(destinationPath, verbose)
|
|
|
|
for dependency in dependencies:
|
|
changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
|
|
|
|
# Deploy framework if necessary.
|
|
if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
|
|
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
|
|
|
|
ap = ArgumentParser(description="""Improved version of macdeployqt.
|
|
|
|
Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
|
|
Note, that the "dist" folder will be deleted before deploying on each run.
|
|
|
|
Optionally, Qt translation files (.qm) can be added to the bundle.""")
|
|
|
|
ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
|
|
ap.add_argument("appname", nargs=1, metavar="appname", help="name of the app being deployed")
|
|
ap.add_argument("-verbose", nargs="?", const=True, help="Output additional debugging information")
|
|
ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
|
|
ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
|
|
ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image")
|
|
ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translations. Base translations will automatically be added to the bundle's resources.")
|
|
|
|
config = ap.parse_args()
|
|
|
|
verbose = config.verbose
|
|
|
|
# ------------------------------------------------
|
|
|
|
app_bundle = config.app_bundle[0]
|
|
appname = config.appname[0]
|
|
|
|
if not os.path.exists(app_bundle):
|
|
sys.stderr.write(f"Error: Could not find app bundle \"{app_bundle}\"\n")
|
|
sys.exit(1)
|
|
|
|
# ------------------------------------------------
|
|
|
|
if os.path.exists("dist"):
|
|
print("+ Removing existing dist folder +")
|
|
shutil.rmtree("dist")
|
|
|
|
if os.path.exists(appname + ".dmg"):
|
|
print("+ Removing existing DMG +")
|
|
os.unlink(appname + ".dmg")
|
|
|
|
if os.path.exists(appname + ".temp.dmg"):
|
|
os.unlink(appname + ".temp.dmg")
|
|
|
|
# ------------------------------------------------
|
|
|
|
target = os.path.join("dist", "Bitcoin-Qt.app")
|
|
|
|
print("+ Copying source bundle +")
|
|
if verbose:
|
|
print(app_bundle, "->", target)
|
|
|
|
os.mkdir("dist")
|
|
shutil.copytree(app_bundle, target, symlinks=True)
|
|
|
|
applicationBundle = ApplicationBundleInfo(target)
|
|
|
|
# ------------------------------------------------
|
|
|
|
print("+ Deploying frameworks +")
|
|
|
|
try:
|
|
deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
|
|
if deploymentInfo.qtPath is None:
|
|
deploymentInfo.qtPath = os.getenv("QTDIR", None)
|
|
if deploymentInfo.qtPath is None:
|
|
sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
|
|
config.plugins = False
|
|
except RuntimeError as e:
|
|
sys.stderr.write(f"Error: {str(e)}\n")
|
|
sys.exit(1)
|
|
|
|
# ------------------------------------------------
|
|
|
|
if config.plugins:
|
|
print("+ Deploying plugins +")
|
|
|
|
try:
|
|
deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
|
|
except RuntimeError as e:
|
|
sys.stderr.write(f"Error: {str(e)}\n")
|
|
sys.exit(1)
|
|
|
|
# ------------------------------------------------
|
|
|
|
if config.translations_dir:
|
|
if not Path(config.translations_dir[0]).exists():
|
|
sys.stderr.write(f"Error: Could not find translation dir \"{config.translations_dir[0]}\"\n")
|
|
sys.exit(1)
|
|
|
|
print("+ Adding Qt translations +")
|
|
|
|
translations = Path(config.translations_dir[0])
|
|
|
|
regex = re.compile('qt_[a-z]*(.qm|_[A-Z]*.qm)')
|
|
|
|
lang_files = [x for x in translations.iterdir() if regex.match(x.name)]
|
|
|
|
for file in lang_files:
|
|
if verbose:
|
|
print(file.as_posix(), "->", os.path.join(applicationBundle.resourcesPath, file.name))
|
|
shutil.copy2(file.as_posix(), os.path.join(applicationBundle.resourcesPath, file.name))
|
|
|
|
# ------------------------------------------------
|
|
|
|
print("+ Installing qt.conf +")
|
|
|
|
qt_conf="""[Paths]
|
|
Translations=Resources
|
|
Plugins=PlugIns
|
|
"""
|
|
|
|
with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f:
|
|
f.write(qt_conf.encode())
|
|
|
|
# ------------------------------------------------
|
|
|
|
print("+ Generating .DS_Store +")
|
|
|
|
output_file = os.path.join("dist", ".DS_Store")
|
|
|
|
ds = DSStore.open(output_file, 'w+')
|
|
|
|
ds['.']['bwsp'] = {
|
|
'WindowBounds': '{{300, 280}, {500, 343}}',
|
|
'PreviewPaneVisibility': False,
|
|
}
|
|
|
|
icvp = {
|
|
'gridOffsetX': 0.0,
|
|
'textSize': 12.0,
|
|
'viewOptionsVersion': 1,
|
|
'backgroundImageAlias': b'\x00\x00\x00\x00\x02\x1e\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd1\x94\\\xb0H+\x00\x05\x00\x00\x00\x98\x0fbackground.tiff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\xd19\xb0\xf8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\r\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b.background\x00\x00\x10\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x11\x00\x08\x00\x00\xd19\xb0\xf8\x00\x00\x00\x01\x00\x04\x00\x00\x00\x98\x00\x0e\x00 \x00\x0f\x00b\x00a\x00c\x00k\x00g\x00r\x00o\x00u\x00n\x00d\x00.\x00t\x00i\x00f\x00f\x00\x0f\x00\x02\x00\x00\x00\x12\x00\x1c/.background/background.tiff\x00\x14\x01\x06\x00\x00\x00\x00\x01\x06\x00\x02\x00\x00\x0cMacintosh HD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\x97\xab\xc3H+\x00\x00\x01\x88[\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02u\xab\x8d\xd1\x94\\\xb0devrddsk\xff\xff\xff\xff\x00\x00\t \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07bitcoin\x00\x00\x10\x00\x08\x00\x00\xce\x97\xab\xc3\x00\x00\x00\x11\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x01\x00\x14\x01\x88[\x88\x00\x16\xa9\t\x00\x08\xfaR\x00\x08\xfaQ\x00\x02d\x8e\x00\x0e\x00\x02\x00\x00\x00\x0f\x00\x1a\x00\x0c\x00M\x00a\x00c\x00i\x00n\x00t\x00o\x00s\x00h\x00 \x00H\x00D\x00\x13\x00\x01/\x00\x00\x15\x00\x02\x00\x14\xff\xff\x00\x00\xff\xff\x00\x00',
|
|
'backgroundColorBlue': 1.0,
|
|
'iconSize': 96.0,
|
|
'backgroundColorGreen': 1.0,
|
|
'arrangeBy': 'none',
|
|
'showIconPreview': True,
|
|
'gridSpacing': 100.0,
|
|
'gridOffsetY': 0.0,
|
|
'showItemInfo': False,
|
|
'labelOnBottom': True,
|
|
'backgroundType': 2,
|
|
'backgroundColorRed': 1.0
|
|
}
|
|
alias = Alias().from_bytes(icvp['backgroundImageAlias'])
|
|
alias.volume.name = appname
|
|
alias.volume.posix_path = '/Volumes/' + appname
|
|
icvp['backgroundImageAlias'] = alias.to_bytes()
|
|
ds['.']['icvp'] = icvp
|
|
|
|
ds['.']['vSrn'] = ('long', 1)
|
|
|
|
ds['Applications']['Iloc'] = (370, 156)
|
|
ds['Bitcoin-Qt.app']['Iloc'] = (128, 156)
|
|
|
|
ds.flush()
|
|
ds.close()
|
|
|
|
# ------------------------------------------------
|
|
|
|
if platform.system() == "Darwin":
|
|
subprocess.check_call(f"codesign --deep --force --sign - {target}", shell=True)
|
|
|
|
print("+ Installing background.tiff +")
|
|
|
|
bg_path = os.path.join('dist', '.background', 'background.tiff')
|
|
os.mkdir(os.path.dirname(bg_path))
|
|
|
|
tiff_path = os.path.join('contrib', 'macdeploy', 'background.tiff')
|
|
shutil.copy2(tiff_path, bg_path)
|
|
|
|
# ------------------------------------------------
|
|
|
|
print("+ Generating symlink for /Applications +")
|
|
|
|
os.symlink("/Applications", os.path.join('dist', "Applications"))
|
|
|
|
# ------------------------------------------------
|
|
|
|
if config.dmg is not None:
|
|
|
|
print("+ Preparing .dmg disk image +")
|
|
|
|
if verbose:
|
|
print("Determining size of \"dist\"...")
|
|
size = 0
|
|
for path, dirs, files in os.walk("dist"):
|
|
for file in files:
|
|
size += os.path.getsize(os.path.join(path, file))
|
|
size += int(size * 0.15)
|
|
|
|
if verbose:
|
|
print("Creating temp image for modification...")
|
|
|
|
tempname: str = appname + ".temp.dmg"
|
|
|
|
run(["hdiutil", "create", tempname, "-srcfolder", "dist", "-format", "UDRW", "-size", str(size), "-volname", appname], check=True, universal_newlines=True)
|
|
|
|
if verbose:
|
|
print("Attaching temp image...")
|
|
output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, universal_newlines=True, stdout=PIPE).stdout
|
|
|
|
print("+ Finalizing .dmg disk image +")
|
|
|
|
run(["hdiutil", "detach", f"/Volumes/{appname}"], universal_newlines=True)
|
|
|
|
run(["hdiutil", "convert", tempname, "-format", "UDZO", "-o", appname, "-imagekey", "zlib-level=9"], check=True, universal_newlines=True)
|
|
|
|
os.unlink(tempname)
|
|
|
|
# ------------------------------------------------
|
|
|
|
print("+ Done +")
|
|
|
|
sys.exit(0)
|