Changeset View
Changeset View
Standalone View
Standalone View
contrib/macdeploy/macdeployqtplus.py
- This file was moved from contrib/macdeploy/macdeployqtplus.
Show All 10 Lines | |||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | # GNU General Public License for more details. | ||||
# | # | ||||
# You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | # | ||||
import subprocess, sys, re, os, shutil, stat, os.path, time | import subprocess | ||||
import sys | |||||
import re | |||||
import os | |||||
import shutil | |||||
import stat | |||||
import os.path | |||||
import time | |||||
from string import Template | from string import Template | ||||
from argparse import ArgumentParser | from argparse import ArgumentParser | ||||
from typing import List, Optional | from typing import List, Optional | ||||
# This is ported from the original macdeployqt with modifications | # This is ported from the original macdeployqt with modifications | ||||
class FrameworkInfo(object): | class FrameworkInfo(object): | ||||
def __init__(self): | def __init__(self): | ||||
self.frameworkDirectory = "" | self.frameworkDirectory = "" | ||||
self.frameworkName = "" | self.frameworkName = "" | ||||
self.frameworkPath = "" | self.frameworkPath = "" | ||||
self.binaryDirectory = "" | self.binaryDirectory = "" | ||||
self.binaryName = "" | self.binaryName = "" | ||||
self.binaryPath = "" | self.binaryPath = "" | ||||
Show All 22 Lines | |||||
Binary directory: {} | Binary directory: {} | ||||
Binary path: {} | Binary path: {} | ||||
Version: {} | Version: {} | ||||
Install name: {} | Install name: {} | ||||
Deployed install name: {} | Deployed install name: {} | ||||
Source file Path: {} | Source file Path: {} | ||||
Deployed Directory (relative to bundle): {} | Deployed Directory (relative to bundle): {} | ||||
""".format(self.frameworkName, | """.format(self.frameworkName, | ||||
self.frameworkDirectory, | self.frameworkDirectory, | ||||
self.frameworkPath, | self.frameworkPath, | ||||
self.binaryName, | self.binaryName, | ||||
self.binaryDirectory, | self.binaryDirectory, | ||||
self.binaryPath, | self.binaryPath, | ||||
self.version, | self.version, | ||||
self.installName, | self.installName, | ||||
self.deployedInstallName, | self.deployedInstallName, | ||||
self.sourceFilePath, | self.sourceFilePath, | ||||
self.destinationDirectory) | self.destinationDirectory) | ||||
def isDylib(self): | def isDylib(self): | ||||
return self.frameworkName.endswith(".dylib") | return self.frameworkName.endswith(".dylib") | ||||
def isQtFramework(self): | def isQtFramework(self): | ||||
if self.isDylib(): | if self.isDylib(): | ||||
return self.frameworkName.startswith("libQt") | return self.frameworkName.startswith("libQt") | ||||
else: | else: | ||||
return self.frameworkName.startswith("Qt") | return self.frameworkName.startswith("Qt") | ||||
reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$') | reOLine = re.compile( | ||||
r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$') | |||||
bundleFrameworkDirectory = "Contents/Frameworks" | bundleFrameworkDirectory = "Contents/Frameworks" | ||||
bundleBinaryDirectory = "Contents/MacOS" | bundleBinaryDirectory = "Contents/MacOS" | ||||
@classmethod | @classmethod | ||||
def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: | def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: | ||||
# Note: line must be trimmed | # Note: line must be trimmed | ||||
if line == "": | if line == "": | ||||
return None | return None | ||||
# Don't deploy system libraries (exception for libQtuitools and libQtlucene). | # Don't deploy system libraries (exception for libQtuitools and | ||||
if line.startswith("/System/Library/") or line.startswith("@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line): | # libQtlucene). | ||||
if line.startswith("/System/Library/") or line.startswith( | |||||
"@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line): | |||||
return None | return None | ||||
m = cls.reOLine.match(line) | m = cls.reOLine.match(line) | ||||
if m is None: | if m is None: | ||||
raise RuntimeError("otool line could not be parsed: " + line) | raise RuntimeError("otool line could not be parsed: " + line) | ||||
path = m.group(1) | path = m.group(1) | ||||
Show All 20 Lines | def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: | ||||
parts = path.split("/") | parts = path.split("/") | ||||
i = 0 | i = 0 | ||||
# Search for the .framework directory | # Search for the .framework directory | ||||
for part in parts: | for part in parts: | ||||
if part.endswith(".framework"): | if part.endswith(".framework"): | ||||
break | break | ||||
i += 1 | i += 1 | ||||
if i == len(parts): | if i == len(parts): | ||||
raise RuntimeError("Could not find .framework or .dylib in otool line: " + line) | raise RuntimeError( | ||||
"Could not find .framework or .dylib in otool line: " + line) | |||||
info.frameworkName = parts[i] | info.frameworkName = parts[i] | ||||
info.frameworkDirectory = "/".join(parts[:i]) | info.frameworkDirectory = "/".join(parts[:i]) | ||||
info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName) | info.frameworkPath = os.path.join( | ||||
info.frameworkDirectory, info.frameworkName) | |||||
info.binaryName = parts[i+3] | info.binaryName = parts[i + 3] | ||||
info.binaryDirectory = "/".join(parts[i+1:i+3]) | info.binaryDirectory = "/".join(parts[i + 1:i + 3]) | ||||
info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName) | info.binaryPath = os.path.join( | ||||
info.binaryDirectory, info.binaryName) | |||||
info.version = parts[i+2] | info.version = parts[i + 2] | ||||
info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath) | info.deployedInstallName = "@executable_path/../Frameworks/" + \ | ||||
info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory) | os.path.join(info.frameworkName, info.binaryPath) | ||||
info.destinationDirectory = os.path.join( | |||||
info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources") | cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory) | ||||
info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents") | |||||
info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents") | info.sourceResourcesDirectory = os.path.join( | ||||
info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources") | info.frameworkPath, "Resources") | ||||
info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents") | 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 | return info | ||||
class ApplicationBundleInfo(object): | class ApplicationBundleInfo(object): | ||||
def __init__(self, path: str): | def __init__(self, path: str): | ||||
self.path = path | self.path = path | ||||
appName = "BitcoinABC-Qt" | appName = "BitcoinABC-Qt" | ||||
self.binaryPath = os.path.join(path, "Contents", "MacOS", appName) | self.binaryPath = os.path.join(path, "Contents", "MacOS", appName) | ||||
if not os.path.exists(self.binaryPath): | if not os.path.exists(self.binaryPath): | ||||
raise RuntimeError("Could not find bundle binary for " + path) | raise RuntimeError("Could not find bundle binary for " + path) | ||||
self.resourcesPath = os.path.join(path, "Contents", "Resources") | self.resourcesPath = os.path.join(path, "Contents", "Resources") | ||||
self.pluginPath = os.path.join(path, "Contents", "PlugIns") | self.pluginPath = os.path.join(path, "Contents", "PlugIns") | ||||
class DeploymentInfo(object): | class DeploymentInfo(object): | ||||
def __init__(self): | def __init__(self): | ||||
self.qtPath = None | self.qtPath = None | ||||
self.pluginPath = None | self.pluginPath = None | ||||
self.deployedFrameworks = [] | self.deployedFrameworks = [] | ||||
def detectQtPath(self, frameworkDirectory: str): | def detectQtPath(self, frameworkDirectory: str): | ||||
parentDir = os.path.dirname(frameworkDirectory) | parentDir = os.path.dirname(frameworkDirectory) | ||||
Show All 15 Lines | def usesFramework(self, name: str) -> bool: | ||||
if framework.endswith(".framework"): | if framework.endswith(".framework"): | ||||
if framework.startswith(nameDot): | if framework.startswith(nameDot): | ||||
return True | return True | ||||
elif framework.endswith(".dylib"): | elif framework.endswith(".dylib"): | ||||
if framework.startswith(libNameDot): | if framework.startswith(libNameDot): | ||||
return True | return True | ||||
return False | return False | ||||
def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: | def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Inspecting with otool: " + binaryPath) | print("Inspecting with otool: " + binaryPath) | ||||
otoolbin=os.getenv("OTOOL", "otool") | otoolbin = os.getenv("OTOOL", "otool") | ||||
otool = subprocess.Popen([otoolbin, "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) | otool = subprocess.Popen([otoolbin, | ||||
"-L", | |||||
binaryPath], | |||||
stdout=subprocess.PIPE, | |||||
stderr=subprocess.PIPE, | |||||
universal_newlines=True) | |||||
o_stdout, o_stderr = otool.communicate() | o_stdout, o_stderr = otool.communicate() | ||||
if otool.returncode != 0: | if otool.returncode != 0: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write(o_stderr) | sys.stderr.write(o_stderr) | ||||
sys.stderr.flush() | sys.stderr.flush() | ||||
raise RuntimeError("otool failed with return code {}".format(otool.returncode)) | raise RuntimeError( | ||||
"otool failed with return code {}".format( | |||||
otool.returncode)) | |||||
otoolLines = o_stdout.split("\n") | otoolLines = o_stdout.split("\n") | ||||
otoolLines.pop(0) # First line is the inspected binary | otoolLines.pop(0) # First line is the inspected binary | ||||
if ".framework" in binaryPath or binaryPath.endswith(".dylib"): | if ".framework" in binaryPath or binaryPath.endswith(".dylib"): | ||||
otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency. | # Frameworks and dylibs list themselves as a dependency. | ||||
otoolLines.pop(0) | |||||
libraries = [] | libraries = [] | ||||
for line in otoolLines: | for line in otoolLines: | ||||
line = line.replace("@loader_path", os.path.dirname(binaryPath)) | line = line.replace("@loader_path", os.path.dirname(binaryPath)) | ||||
info = FrameworkInfo.fromOtoolLibraryLine(line.strip()) | info = FrameworkInfo.fromOtoolLibraryLine(line.strip()) | ||||
if info is not None: | if info is not None: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Found framework:") | print("Found framework:") | ||||
print(info) | print(info) | ||||
libraries.append(info) | libraries.append(info) | ||||
return libraries | return libraries | ||||
def runInstallNameTool(action: str, *args): | def runInstallNameTool(action: str, *args): | ||||
installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool") | installnametoolbin = os.getenv("INSTALLNAMETOOL", "install_name_tool") | ||||
subprocess.check_call([installnametoolbin, "-"+action] + list(args)) | subprocess.check_call([installnametoolbin, "-" + action] + list(args)) | ||||
def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int): | |||||
def changeInstallName(oldName: str, newName: str, | |||||
binaryPath: str, verbose: int): | |||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Using install_name_tool:") | print("Using install_name_tool:") | ||||
print(" in", binaryPath) | print(" in", binaryPath) | ||||
print(" change reference", oldName) | print(" change reference", oldName) | ||||
print(" to", newName) | print(" to", newName) | ||||
runInstallNameTool("change", oldName, newName, binaryPath) | runInstallNameTool("change", oldName, newName, binaryPath) | ||||
def changeIdentification(id: str, binaryPath: str, verbose: int): | def changeIdentification(id: str, binaryPath: str, verbose: int): | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Using install_name_tool:") | print("Using install_name_tool:") | ||||
print(" change identification in", binaryPath) | print(" change identification in", binaryPath) | ||||
print(" to", id) | print(" to", id) | ||||
runInstallNameTool("id", id, binaryPath) | runInstallNameTool("id", id, binaryPath) | ||||
def runStrip(binaryPath: str, verbose: int): | def runStrip(binaryPath: str, verbose: int): | ||||
stripbin=os.getenv("STRIP", "strip") | stripbin = os.getenv("STRIP", "strip") | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Using strip:") | print("Using strip:") | ||||
print(" stripped", binaryPath) | print(" stripped", binaryPath) | ||||
subprocess.check_call([stripbin, "-x", binaryPath]) | subprocess.check_call([stripbin, "-x", binaryPath]) | ||||
def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[str]: | |||||
def copyFramework(framework: FrameworkInfo, path: str, | |||||
verbose: int) -> Optional[str]: | |||||
if framework.sourceFilePath.startswith("Qt"): | if framework.sourceFilePath.startswith("Qt"): | ||||
#standard place for Nokia Qt installer's frameworks | # standard place for Nokia Qt installer's frameworks | ||||
fromPath = "/Library/Frameworks/" + framework.sourceFilePath | fromPath = "/Library/Frameworks/" + framework.sourceFilePath | ||||
else: | else: | ||||
fromPath = framework.sourceFilePath | fromPath = framework.sourceFilePath | ||||
toDir = os.path.join(path, framework.destinationDirectory) | toDir = os.path.join(path, framework.destinationDirectory) | ||||
toPath = os.path.join(toDir, framework.binaryName) | toPath = os.path.join(toDir, framework.binaryName) | ||||
if not os.path.exists(fromPath): | if not os.path.exists(fromPath): | ||||
raise RuntimeError("No file at " + fromPath) | raise RuntimeError("No file at " + fromPath) | ||||
if os.path.exists(toPath): | if os.path.exists(toPath): | ||||
return None # Already there | return None # Already there | ||||
if not os.path.exists(toDir): | if not os.path.exists(toDir): | ||||
os.makedirs(toDir) | os.makedirs(toDir) | ||||
shutil.copy2(fromPath, toPath) | shutil.copy2(fromPath, toPath) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Copied:", fromPath) | print("Copied:", fromPath) | ||||
print(" to:", toPath) | print(" to:", toPath) | ||||
permissions = os.stat(toPath) | permissions = os.stat(toPath) | ||||
if not permissions.st_mode & stat.S_IWRITE: | if not permissions.st_mode & stat.S_IWRITE: | ||||
os.chmod(toPath, permissions.st_mode | stat.S_IWRITE) | os.chmod(toPath, permissions.st_mode | stat.S_IWRITE) | ||||
if not framework.isDylib(): # Copy resources for real frameworks | if not framework.isDylib(): # Copy resources for real frameworks | ||||
linkfrom = os.path.join(path, "Contents","Frameworks", framework.frameworkName, "Versions", "Current") | linkfrom = os.path.join( | ||||
path, | |||||
"Contents", | |||||
"Frameworks", | |||||
framework.frameworkName, | |||||
"Versions", | |||||
"Current") | |||||
linkto = framework.version | linkto = framework.version | ||||
if not os.path.exists(linkfrom): | if not os.path.exists(linkfrom): | ||||
os.symlink(linkto, linkfrom) | os.symlink(linkto, linkfrom) | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("Linked:", linkfrom, "->", linkto) | print("Linked:", linkfrom, "->", linkto) | ||||
fromResourcesDir = framework.sourceResourcesDirectory | fromResourcesDir = framework.sourceResourcesDirectory | ||||
if os.path.exists(fromResourcesDir): | if os.path.exists(fromResourcesDir): | ||||
toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory) | toResourcesDir = os.path.join( | ||||
path, framework.destinationResourcesDirectory) | |||||
shutil.copytree(fromResourcesDir, toResourcesDir, symlinks=True) | shutil.copytree(fromResourcesDir, toResourcesDir, symlinks=True) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Copied resources:", fromResourcesDir) | print("Copied resources:", fromResourcesDir) | ||||
print(" to:", toResourcesDir) | print(" to:", toResourcesDir) | ||||
fromContentsDir = framework.sourceVersionContentsDirectory | fromContentsDir = framework.sourceVersionContentsDirectory | ||||
if not os.path.exists(fromContentsDir): | if not os.path.exists(fromContentsDir): | ||||
fromContentsDir = framework.sourceContentsDirectory | fromContentsDir = framework.sourceContentsDirectory | ||||
if os.path.exists(fromContentsDir): | if os.path.exists(fromContentsDir): | ||||
toContentsDir = os.path.join(path, framework.destinationVersionContentsDirectory) | toContentsDir = os.path.join( | ||||
path, framework.destinationVersionContentsDirectory) | |||||
shutil.copytree(fromContentsDir, toContentsDir, symlinks=True) | shutil.copytree(fromContentsDir, toContentsDir, symlinks=True) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Copied Contents:", fromContentsDir) | print("Copied Contents:", fromContentsDir) | ||||
print(" to:", toContentsDir) | print(" to:", toContentsDir) | ||||
elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout) | # Copy qt_menu.nib (applies to non-framework layout) | ||||
qtMenuNibSourcePath = os.path.join(framework.frameworkDirectory, "Resources", "qt_menu.nib") | elif framework.frameworkName.startswith("libQtGui"): | ||||
qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib") | qtMenuNibSourcePath = os.path.join( | ||||
if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath): | framework.frameworkDirectory, "Resources", "qt_menu.nib") | ||||
shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True) | qtMenuNibDestinationPath = os.path.join( | ||||
path, "Contents", "Resources", "qt_menu.nib") | |||||
if os.path.exists(qtMenuNibSourcePath) and not os.path.exists( | |||||
qtMenuNibDestinationPath): | |||||
shutil.copytree( | |||||
qtMenuNibSourcePath, | |||||
qtMenuNibDestinationPath, | |||||
symlinks=True) | |||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Copied for libQtGui:", qtMenuNibSourcePath) | print("Copied for libQtGui:", qtMenuNibSourcePath) | ||||
print(" to:", qtMenuNibDestinationPath) | print(" to:", qtMenuNibDestinationPath) | ||||
return toPath | return toPath | ||||
def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo: | |||||
def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, | |||||
verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo: | |||||
if deploymentInfo is None: | if deploymentInfo is None: | ||||
deploymentInfo = DeploymentInfo() | deploymentInfo = DeploymentInfo() | ||||
while len(frameworks) > 0: | while len(frameworks) > 0: | ||||
framework = frameworks.pop(0) | framework = frameworks.pop(0) | ||||
deploymentInfo.deployedFrameworks.append(framework.frameworkName) | deploymentInfo.deployedFrameworks.append(framework.frameworkName) | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("Processing", framework.frameworkName, "...") | print("Processing", framework.frameworkName, "...") | ||||
# Get the Qt path from one of the Qt frameworks | # Get the Qt path from one of the Qt frameworks | ||||
if deploymentInfo.qtPath is None and framework.isQtFramework(): | if deploymentInfo.qtPath is None and framework.isQtFramework(): | ||||
deploymentInfo.detectQtPath(framework.frameworkDirectory) | deploymentInfo.detectQtPath(framework.frameworkDirectory) | ||||
if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath): | if framework.installName.startswith( | ||||
"@executable_path") or framework.installName.startswith(bundlePath): | |||||
if verbose >= 2: | if verbose >= 2: | ||||
print(framework.frameworkName, "already deployed, skipping.") | print(framework.frameworkName, "already deployed, skipping.") | ||||
continue | continue | ||||
# install_name_tool the new id into the binary | # install_name_tool the new id into the binary | ||||
changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose) | changeInstallName( | ||||
framework.installName, | |||||
framework.deployedInstallName, | |||||
binaryPath, | |||||
verbose) | |||||
# Copy framework to app bundle. | # Copy framework to app bundle. | ||||
deployedBinaryPath = copyFramework(framework, bundlePath, verbose) | deployedBinaryPath = copyFramework(framework, bundlePath, verbose) | ||||
# Skip the rest if already was deployed. | # Skip the rest if already was deployed. | ||||
if deployedBinaryPath is None: | if deployedBinaryPath is None: | ||||
continue | continue | ||||
if strip: | if strip: | ||||
runStrip(deployedBinaryPath, verbose) | runStrip(deployedBinaryPath, verbose) | ||||
# install_name_tool it a new id. | # install_name_tool it a new id. | ||||
changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose) | changeIdentification( | ||||
framework.deployedInstallName, | |||||
deployedBinaryPath, | |||||
verbose) | |||||
# Check for framework dependencies | # Check for framework dependencies | ||||
dependencies = getFrameworks(deployedBinaryPath, verbose) | dependencies = getFrameworks(deployedBinaryPath, verbose) | ||||
for dependency in dependencies: | for dependency in dependencies: | ||||
changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose) | changeInstallName( | ||||
dependency.installName, | |||||
dependency.deployedInstallName, | |||||
deployedBinaryPath, | |||||
verbose) | |||||
# Deploy framework if necessary. | # Deploy framework if necessary. | ||||
if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks: | if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks: | ||||
frameworks.append(dependency) | frameworks.append(dependency) | ||||
return deploymentInfo | return deploymentInfo | ||||
def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo: | |||||
def deployFrameworksForAppBundle( | |||||
applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo: | |||||
frameworks = getFrameworks(applicationBundle.binaryPath, verbose) | frameworks = getFrameworks(applicationBundle.binaryPath, verbose) | ||||
if len(frameworks) == 0 and verbose >= 1: | if len(frameworks) == 0 and verbose >= 1: | ||||
print("Warning: Could not find any external frameworks to deploy in {}.".format(applicationBundle.path)) | print( | ||||
"Warning: Could not find any external frameworks to deploy in {}.".format( | |||||
applicationBundle.path)) | |||||
return DeploymentInfo() | return DeploymentInfo() | ||||
else: | else: | ||||
return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose) | return deployFrameworks( | ||||
frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose) | |||||
def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: DeploymentInfo, strip: bool, verbose: int): | |||||
def deployPlugins(appBundleInfo: ApplicationBundleInfo, | |||||
deploymentInfo: DeploymentInfo, strip: bool, verbose: int): | |||||
# Lookup available plugins, exclude unneeded | # Lookup available plugins, exclude unneeded | ||||
plugins = [] | plugins = [] | ||||
if deploymentInfo.pluginPath is None: | if deploymentInfo.pluginPath is None: | ||||
return | return | ||||
for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | ||||
pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath) | pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath) | ||||
if pluginDirectory == "designer": | if pluginDirectory == "designer": | ||||
# Skip designer plugins | # Skip designer plugins | ||||
Show All 28 Lines | for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | ||||
# Deploy the sensor plugins only if QtSensors is in use | # Deploy the sensor plugins only if QtSensors is in use | ||||
if not deploymentInfo.usesFramework("QtSensors"): | if not deploymentInfo.usesFramework("QtSensors"): | ||||
continue | continue | ||||
elif pluginDirectory == "audio" or pluginDirectory == "playlistformats": | elif pluginDirectory == "audio" or pluginDirectory == "playlistformats": | ||||
# Deploy the audio plugins only if QtMultimedia is in use | # Deploy the audio plugins only if QtMultimedia is in use | ||||
if not deploymentInfo.usesFramework("QtMultimedia"): | if not deploymentInfo.usesFramework("QtMultimedia"): | ||||
continue | continue | ||||
elif pluginDirectory == "mediaservice": | elif pluginDirectory == "mediaservice": | ||||
# Deploy the mediaservice plugins only if QtMultimediaWidgets is in use | # Deploy the mediaservice plugins only if QtMultimediaWidgets is in | ||||
# use | |||||
if not deploymentInfo.usesFramework("QtMultimediaWidgets"): | if not deploymentInfo.usesFramework("QtMultimediaWidgets"): | ||||
continue | continue | ||||
elif pluginDirectory == "canbus": | elif pluginDirectory == "canbus": | ||||
# Deploy the canbus plugins only if QtSerialBus is in use | # Deploy the canbus plugins only if QtSerialBus is in use | ||||
if not deploymentInfo.usesFramework("QtSerialBus"): | if not deploymentInfo.usesFramework("QtSerialBus"): | ||||
continue | continue | ||||
elif pluginDirectory == "webview": | elif pluginDirectory == "webview": | ||||
# Deploy the webview plugins only if QtWebView is in use | # Deploy the webview plugins only if QtWebView is in use | ||||
if not deploymentInfo.usesFramework("QtWebView"): | if not deploymentInfo.usesFramework("QtWebView"): | ||||
continue | continue | ||||
elif pluginDirectory == "gamepads": | elif pluginDirectory == "gamepads": | ||||
# Deploy the webview plugins only if QtGamepad is in use | # Deploy the webview plugins only if QtGamepad is in use | ||||
if not deploymentInfo.usesFramework("QtGamepad"): | if not deploymentInfo.usesFramework("QtGamepad"): | ||||
continue | continue | ||||
elif pluginDirectory == "geoservices": | elif pluginDirectory == "geoservices": | ||||
# Deploy the webview plugins only if QtLocation is in use | # Deploy the webview plugins only if QtLocation is in use | ||||
if not deploymentInfo.usesFramework("QtLocation"): | if not deploymentInfo.usesFramework("QtLocation"): | ||||
continue | continue | ||||
elif pluginDirectory == "texttospeech": | elif pluginDirectory == "texttospeech": | ||||
# Deploy the texttospeech plugins only if QtTextToSpeech is in use | # Deploy the texttospeech plugins only if QtTextToSpeech is in use | ||||
if not deploymentInfo.usesFramework("QtTextToSpeech"): | if not deploymentInfo.usesFramework("QtTextToSpeech"): | ||||
continue | continue | ||||
elif pluginDirectory == "virtualkeyboard": | elif pluginDirectory == "virtualkeyboard": | ||||
# Deploy the virtualkeyboard plugins only if QtVirtualKeyboard is in use | # Deploy the virtualkeyboard plugins only if QtVirtualKeyboard is | ||||
# in use | |||||
if not deploymentInfo.usesFramework("QtVirtualKeyboard"): | if not deploymentInfo.usesFramework("QtVirtualKeyboard"): | ||||
continue | continue | ||||
elif pluginDirectory == "sceneparsers": | elif pluginDirectory == "sceneparsers": | ||||
# Deploy the virtualkeyboard plugins only if Qt3DCore is in use | # Deploy the virtualkeyboard plugins only if Qt3DCore is in use | ||||
if not deploymentInfo.usesFramework("Qt3DCore"): | if not deploymentInfo.usesFramework("Qt3DCore"): | ||||
continue | continue | ||||
elif pluginDirectory == "renderplugins": | elif pluginDirectory == "renderplugins": | ||||
# Deploy the renderplugins plugins only if Qt3DCore is in use | # Deploy the renderplugins plugins only if Qt3DCore is in use | ||||
Show All 9 Lines | for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | ||||
if pluginName.endswith("_debug.dylib"): | if pluginName.endswith("_debug.dylib"): | ||||
# Skip debug plugins | # Skip debug plugins | ||||
continue | continue | ||||
elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib": | elif pluginPath == "imageformats/libqsvg.dylib" or pluginPath == "iconengines/libqsvgicon.dylib": | ||||
# Deploy the svg plugins only if QtSvg is in use | # Deploy the svg plugins only if QtSvg is in use | ||||
if not deploymentInfo.usesFramework("QtSvg"): | if not deploymentInfo.usesFramework("QtSvg"): | ||||
continue | continue | ||||
elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib": | elif pluginPath == "accessible/libqtaccessiblecompatwidgets.dylib": | ||||
# Deploy accessibility for Qt3Support only if the Qt3Support is in use | # Deploy accessibility for Qt3Support only if the Qt3Support is | ||||
# in use | |||||
if not deploymentInfo.usesFramework("Qt3Support"): | if not deploymentInfo.usesFramework("Qt3Support"): | ||||
continue | continue | ||||
elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib": | elif pluginPath == "graphicssystems/libqglgraphicssystem.dylib": | ||||
# Deploy the opengl graphicssystem plugin only if QtOpenGL is in use | # Deploy the opengl graphicssystem plugin only if QtOpenGL is | ||||
# in use | |||||
if not deploymentInfo.usesFramework("QtOpenGL"): | if not deploymentInfo.usesFramework("QtOpenGL"): | ||||
continue | continue | ||||
elif pluginPath == "accessible/libqtaccessiblequick.dylib": | elif pluginPath == "accessible/libqtaccessiblequick.dylib": | ||||
# Deploy the accessible qtquick plugin only if QtQuick is in use | # Deploy the accessible qtquick plugin only if QtQuick is in | ||||
# use | |||||
if not deploymentInfo.usesFramework("QtQuick"): | if not deploymentInfo.usesFramework("QtQuick"): | ||||
continue | continue | ||||
elif pluginPath == "platforminputcontexts/libqtvirtualkeyboardplugin.dylib": | elif pluginPath == "platforminputcontexts/libqtvirtualkeyboardplugin.dylib": | ||||
# Deploy the virtualkeyboardplugin plugin only if QtVirtualKeyboard is in use | # Deploy the virtualkeyboardplugin plugin only if | ||||
# QtVirtualKeyboard is in use | |||||
if not deploymentInfo.usesFramework("QtVirtualKeyboard"): | if not deploymentInfo.usesFramework("QtVirtualKeyboard"): | ||||
continue | continue | ||||
plugins.append((pluginDirectory, pluginName)) | plugins.append((pluginDirectory, pluginName)) | ||||
for pluginDirectory, pluginName in plugins: | for pluginDirectory, pluginName in plugins: | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...") | print( | ||||
"Processing plugin", | |||||
sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName) | os.path.join( | ||||
destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory) | pluginDirectory, | ||||
pluginName), | |||||
"...") | |||||
sourcePath = os.path.join( | |||||
deploymentInfo.pluginPath, | |||||
pluginDirectory, | |||||
pluginName) | |||||
destinationDirectory = os.path.join( | |||||
appBundleInfo.pluginPath, pluginDirectory) | |||||
if not os.path.exists(destinationDirectory): | if not os.path.exists(destinationDirectory): | ||||
os.makedirs(destinationDirectory) | os.makedirs(destinationDirectory) | ||||
destinationPath = os.path.join(destinationDirectory, pluginName) | destinationPath = os.path.join(destinationDirectory, pluginName) | ||||
shutil.copy2(sourcePath, destinationPath) | shutil.copy2(sourcePath, destinationPath) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Copied:", sourcePath) | print("Copied:", sourcePath) | ||||
print(" to:", destinationPath) | print(" to:", destinationPath) | ||||
if strip: | if strip: | ||||
runStrip(destinationPath, verbose) | runStrip(destinationPath, verbose) | ||||
dependencies = getFrameworks(destinationPath, verbose) | dependencies = getFrameworks(destinationPath, verbose) | ||||
for dependency in dependencies: | for dependency in dependencies: | ||||
changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose) | changeInstallName( | ||||
dependency.installName, | |||||
dependency.deployedInstallName, | |||||
destinationPath, | |||||
verbose) | |||||
# Deploy framework if necessary. | # Deploy framework if necessary. | ||||
if dependency.frameworkName not in deploymentInfo.deployedFrameworks: | if dependency.frameworkName not in deploymentInfo.deployedFrameworks: | ||||
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo) | deployFrameworks( | ||||
[dependency], | |||||
appBundleInfo.path, | |||||
destinationPath, | |||||
strip, | |||||
verbose, | |||||
deploymentInfo) | |||||
qt_conf="""[Paths] | qt_conf = """[Paths] | ||||
Translations=Resources | Translations=Resources | ||||
Plugins=PlugIns | Plugins=PlugIns | ||||
""" | """ | ||||
ap = ArgumentParser(description="""Improved version of macdeployqt. | 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. | 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. | Note, that the "dist" folder will be deleted before deploying on each run. | ||||
Optionally, Qt translation files (.qm) and additional resources can be added to the bundle. | Optionally, Qt translation files (.qm) and additional resources can be added to the bundle. | ||||
Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments | Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments | ||||
to the codesign tool. | to the codesign tool. | ||||
E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""") | E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""") | ||||
ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed") | ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", | ||||
ap.add_argument("-verbose", type=int, nargs=1, default=[1], metavar="<0-3>", help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug") | help="application bundle to be deployed") | ||||
ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment") | ap.add_argument( | ||||
ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries") | "-verbose", | ||||
ap.add_argument("-sign", dest="sign", action="store_true", default=False, help="sign .app bundle with codesign tool") | type=int, | ||||
ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used") | nargs=1, | ||||
ap.add_argument("-fancy", nargs=1, metavar="plist", default=[], help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work") | default=[1], | ||||
ap.add_argument("-add-qt-tr", nargs=1, metavar="languages", default=[], help="add Qt translation files to the bundle's resources; the language list must be separated with commas, not with whitespace") | metavar="<0-3>", | ||||
ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translation files") | help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug") | ||||
ap.add_argument("-add-resources", nargs="+", metavar="path", default=[], help="list of additional files or folders to be copied into the bundle's resources; must be the last argument") | ap.add_argument( | ||||
ap.add_argument("-volname", nargs=1, metavar="volname", default=[], help="custom volume name for dmg") | "-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( | |||||
"-sign", | |||||
dest="sign", | |||||
action="store_true", | |||||
default=False, | |||||
help="sign .app bundle with codesign tool") | |||||
ap.add_argument( | |||||
"-dmg", | |||||
nargs="?", | |||||
const="", | |||||
metavar="basename", | |||||
help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used") | |||||
ap.add_argument( | |||||
"-fancy", | |||||
nargs=1, | |||||
metavar="plist", | |||||
default=[], | |||||
help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work") | |||||
ap.add_argument( | |||||
"-add-qt-tr", | |||||
nargs=1, | |||||
metavar="languages", | |||||
default=[], | |||||
help="add Qt translation files to the bundle's resources; the language list must be separated with commas, not with whitespace") | |||||
ap.add_argument( | |||||
"-translations-dir", | |||||
nargs=1, | |||||
metavar="path", | |||||
default=None, | |||||
help="Path to Qt's translation files") | |||||
ap.add_argument( | |||||
"-add-resources", | |||||
nargs="+", | |||||
metavar="path", | |||||
default=[], | |||||
help="list of additional files or folders to be copied into the bundle's resources; must be the last argument") | |||||
ap.add_argument( | |||||
"-volname", | |||||
nargs=1, | |||||
metavar="volname", | |||||
default=[], | |||||
help="custom volume name for dmg") | |||||
config = ap.parse_args() | config = ap.parse_args() | ||||
verbose = config.verbose[0] | verbose = config.verbose[0] | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
app_bundle = config.app_bundle[0] | app_bundle = config.app_bundle[0] | ||||
if not os.path.exists(app_bundle): | if not os.path.exists(app_bundle): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find app bundle \"{}\"\n".format(app_bundle)) | sys.stderr.write( | ||||
"Error: Could not find app bundle \"{}\"\n".format(app_bundle)) | |||||
sys.exit(1) | sys.exit(1) | ||||
app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0] | app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0] | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
translations_dir = None | translations_dir = None | ||||
if config.translations_dir and config.translations_dir[0]: | if config.translations_dir and config.translations_dir[0]: | ||||
if os.path.exists(config.translations_dir[0]): | if os.path.exists(config.translations_dir[0]): | ||||
translations_dir = config.translations_dir[0] | translations_dir = config.translations_dir[0] | ||||
else: | else: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find translation dir \"{}\"\n".format(translations_dir)) | sys.stderr.write( | ||||
"Error: Could not find translation dir \"{}\"\n".format(translations_dir)) | |||||
sys.exit(1) | sys.exit(1) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
for p in config.add_resources: | for p in config.add_resources: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Checking for \"%s\"..." % p) | print("Checking for \"{}\"...".format(p)) | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find additional resource file \"{}\"\n".format(p)) | sys.stderr.write( | ||||
"Error: Could not find additional resource file \"{}\"\n".format(p)) | |||||
sys.exit(1) | sys.exit(1) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if len(config.fancy) == 1: | if len(config.fancy) == 1: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Fancy: Importing plistlib...") | print("Fancy: Importing plistlib...") | ||||
try: | try: | ||||
import plistlib | import plistlib | ||||
except ImportError: | except ImportError: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n") | sys.stderr.write( | ||||
"Error: Could not import plistlib which is required for fancy disk images.\n") | |||||
sys.exit(1) | sys.exit(1) | ||||
p = config.fancy[0] | p = config.fancy[0] | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Fancy: Loading \"{}\"...".format(p)) | print("Fancy: Loading \"{}\"...".format(p)) | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find fancy disk image plist at \"{}\"\n".format(p)) | sys.stderr.write( | ||||
"Error: Could not find fancy disk image plist at \"{}\"\n".format(p)) | |||||
sys.exit(1) | sys.exit(1) | ||||
try: | try: | ||||
fancy = plistlib.readPlist(p) | fancy = plistlib.readPlist(p) | ||||
except: | except BaseException: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not parse fancy disk image plist at \"{}\"\n".format(p)) | sys.stderr.write( | ||||
"Error: Could not parse fancy disk image plist at \"{}\"\n".format(p)) | |||||
sys.exit(1) | sys.exit(1) | ||||
try: | try: | ||||
assert "window_bounds" not in fancy or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4) | assert "window_bounds" not in fancy or ( | ||||
assert "background_picture" not in fancy or isinstance(fancy["background_picture"], str) | isinstance( | ||||
fancy["window_bounds"], | |||||
list) and len( | |||||
fancy["window_bounds"]) == 4) | |||||
assert "background_picture" not in fancy or isinstance( | |||||
fancy["background_picture"], str) | |||||
assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int) | assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int) | ||||
assert "applications_symlink" not in fancy or isinstance(fancy["applications_symlink"], bool) | assert "applications_symlink" not in fancy or isinstance( | ||||
fancy["applications_symlink"], bool) | |||||
if "items_position" in fancy: | if "items_position" in fancy: | ||||
assert isinstance(fancy["items_position"], dict) | assert isinstance(fancy["items_position"], dict) | ||||
for key, value in fancy["items_position"].items(): | for key, value in fancy["items_position"].items(): | ||||
assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int) | assert isinstance( | ||||
except: | value, | ||||
list) and len(value) == 2 and isinstance( | |||||
value[0], | |||||
int) and isinstance( | |||||
value[1], | |||||
int) | |||||
except BaseException: | |||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Bad format of fancy disk image plist at \"{}\"\n".format(p)) | sys.stderr.write( | ||||
"Error: Bad format of fancy disk image plist at \"{}\"\n".format(p)) | |||||
sys.exit(1) | sys.exit(1) | ||||
if "background_picture" in fancy: | if "background_picture" in fancy: | ||||
bp = fancy["background_picture"] | bp = fancy["background_picture"] | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Fancy: Resolving background picture \"{}\"...".format(bp)) | print("Fancy: Resolving background picture \"{}\"...".format(bp)) | ||||
if not os.path.exists(bp): | if not os.path.exists(bp): | ||||
bp = os.path.join(os.path.dirname(p), bp) | bp = os.path.join(os.path.dirname(p), bp) | ||||
if not os.path.exists(bp): | if not os.path.exists(bp): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find background picture at \"{}\" or \"{}\"\n".format(fancy["background_picture"], bp)) | sys.stderr.write( | ||||
"Error: Could not find background picture at \"{}\" or \"{}\"\n".format( | |||||
fancy["background_picture"], bp)) | |||||
sys.exit(1) | sys.exit(1) | ||||
else: | else: | ||||
fancy["background_picture"] = bp | fancy["background_picture"] = bp | ||||
else: | else: | ||||
fancy = None | fancy = None | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
Show All 25 Lines | |||||
applicationBundle = ApplicationBundleInfo(target) | applicationBundle = ApplicationBundleInfo(target) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Deploying frameworks +") | print("+ Deploying frameworks +") | ||||
try: | try: | ||||
deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose) | deploymentInfo = deployFrameworksForAppBundle( | ||||
applicationBundle, config.strip, verbose) | |||||
if deploymentInfo.qtPath is None: | if deploymentInfo.qtPath is None: | ||||
deploymentInfo.qtPath = os.getenv("QTDIR", None) | deploymentInfo.qtPath = os.getenv("QTDIR", None) | ||||
if deploymentInfo.qtPath is None: | if deploymentInfo.qtPath is None: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n") | sys.stderr.write( | ||||
"Warning: Could not detect Qt's path, skipping plugin deployment!\n") | |||||
config.plugins = False | config.plugins = False | ||||
except RuntimeError as e: | except RuntimeError as e: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: {}\n".format(str(e))) | sys.stderr.write("Error: {}\n".format(str(e))) | ||||
sys.exit(1) | sys.exit(1) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
Show All 16 Lines | else: | ||||
if translations_dir is not None: | if translations_dir is not None: | ||||
qt_tr_dir = translations_dir | qt_tr_dir = translations_dir | ||||
else: | else: | ||||
if deploymentInfo.qtPath is not None: | if deploymentInfo.qtPath is not None: | ||||
qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations") | qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations") | ||||
else: | else: | ||||
sys.stderr.write("Error: Could not find Qt translation path\n") | sys.stderr.write("Error: Could not find Qt translation path\n") | ||||
sys.exit(1) | sys.exit(1) | ||||
add_qt_tr = ["qt_{}.qm".format(lng) for lng in config.add_qt_tr[0].split(",")] | add_qt_tr = ["qt_{}.qm".format(lng) | ||||
for lng in config.add_qt_tr[0].split(",")] | |||||
for lng_file in add_qt_tr: | for lng_file in add_qt_tr: | ||||
p = os.path.join(qt_tr_dir, lng_file) | p = os.path.join(qt_tr_dir, lng_file) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Checking for \"{}\"...".format(p)) | print("Checking for \"{}\"...".format(p)) | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write("Error: Could not find Qt translation file \"{}\"\n".format(lng_file)) | sys.stderr.write( | ||||
"Error: Could not find Qt translation file \"{}\"\n".format(lng_file)) | |||||
sys.exit(1) | sys.exit(1) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Installing qt.conf +") | print("+ Installing qt.conf +") | ||||
with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f: | with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f: | ||||
f.write(qt_conf.encode()) | f.write(qt_conf.encode()) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if len(add_qt_tr) > 0 and verbose >= 2: | if len(add_qt_tr) > 0 and verbose >= 2: | ||||
print("+ Adding Qt translations +") | print("+ Adding Qt translations +") | ||||
for lng_file in add_qt_tr: | for lng_file in add_qt_tr: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print(os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file)) | print( | ||||
shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file)) | os.path.join( | ||||
qt_tr_dir, | |||||
lng_file), | |||||
"->", | |||||
os.path.join( | |||||
applicationBundle.resourcesPath, | |||||
lng_file)) | |||||
shutil.copy2( | |||||
os.path.join( | |||||
qt_tr_dir, lng_file), os.path.join( | |||||
applicationBundle.resourcesPath, lng_file)) | |||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if len(config.add_resources) > 0 and verbose >= 2: | if len(config.add_resources) > 0 and verbose >= 2: | ||||
print("+ Adding additional resources +") | print("+ Adding additional resources +") | ||||
for p in config.add_resources: | for p in config.add_resources: | ||||
t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p)) | t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p)) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print(p, "->", t) | print(p, "->", t) | ||||
if os.path.isdir(p): | if os.path.isdir(p): | ||||
shutil.copytree(p, t, symlinks=True) | shutil.copytree(p, t, symlinks=True) | ||||
else: | else: | ||||
shutil.copy2(p, t) | shutil.copy2(p, t) | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if config.sign and 'CODESIGNARGS' not in os.environ: | if config.sign and 'CODESIGNARGS' not in os.environ: | ||||
print("You must set the CODESIGNARGS environment variable. Skipping signing.") | print("You must set the CODESIGNARGS environment variable. Skipping signing.") | ||||
elif config.sign: | elif config.sign: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
print("Code-signing app bundle {}".format(target)) | print("Code-signing app bundle {}".format(target)) | ||||
subprocess.check_call("codesign --force {} {}".format(os.environ['CODESIGNARGS'], target), shell=True) | subprocess.check_call( | ||||
"codesign --force {} {}".format(os.environ['CODESIGNARGS'], target), shell=True) | |||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if config.dmg is not None: | if config.dmg is not None: | ||||
def runHDIUtil(verb: str, image_basename: str, **kwargs) -> int: | def runHDIUtil(verb: str, image_basename: str, **kwargs) -> int: | ||||
hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"] | hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"] | ||||
if "capture_stdout" in kwargs: | if "capture_stdout" in kwargs: | ||||
Show All 22 Lines | if config.dmg is not None: | ||||
if config.dmg != "": | if config.dmg != "": | ||||
dmg_name = config.dmg | dmg_name = config.dmg | ||||
else: | else: | ||||
spl = app_bundle_name.split(" ") | spl = app_bundle_name.split(" ") | ||||
dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:]) | dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:]) | ||||
if fancy is None: | if fancy is None: | ||||
try: | try: | ||||
runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=volname, ov=True) | runHDIUtil( | ||||
"create", | |||||
dmg_name, | |||||
srcfolder="dist", | |||||
format="UDBZ", | |||||
volname=volname, | |||||
ov=True) | |||||
except subprocess.CalledProcessError as e: | except subprocess.CalledProcessError as e: | ||||
sys.exit(e.returncode) | sys.exit(e.returncode) | ||||
else: | else: | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Determining size of \"dist\"...") | print("Determining size of \"dist\"...") | ||||
size = 0 | size = 0 | ||||
for path, dirs, files in os.walk("dist"): | for path, dirs, files in os.walk("dist"): | ||||
for file in files: | for file in files: | ||||
size += os.path.getsize(os.path.join(path, file)) | size += os.path.getsize(os.path.join(path, file)) | ||||
size += int(size * 0.15) | size += int(size * 0.15) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Creating temp image for modification...") | print("Creating temp image for modification...") | ||||
try: | try: | ||||
runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=volname, ov=True) | runHDIUtil( | ||||
"create", | |||||
dmg_name + ".temp", | |||||
srcfolder="dist", | |||||
format="UDRW", | |||||
size=size, | |||||
volname=volname, | |||||
ov=True) | |||||
except subprocess.CalledProcessError as e: | except subprocess.CalledProcessError as e: | ||||
sys.exit(e.returncode) | sys.exit(e.returncode) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print("Attaching temp image...") | print("Attaching temp image...") | ||||
try: | try: | ||||
output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True) | output = runHDIUtil( | ||||
"attach", | |||||
dmg_name + ".temp", | |||||
readwrite=True, | |||||
noverify=True, | |||||
noautoopen=True, | |||||
capture_stdout=True) | |||||
except subprocess.CalledProcessError as e: | except subprocess.CalledProcessError as e: | ||||
sys.exit(e.returncode) | sys.exit(e.returncode) | ||||
m = re.search("/Volumes/(.+$)", output) | m = re.search("/Volumes/(.+$)", output) | ||||
disk_root = m.group(0) | disk_root = m.group(0) | ||||
disk_name = m.group(1) | disk_name = m.group(1) | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Applying fancy settings +") | print("+ Applying fancy settings +") | ||||
if "background_picture" in fancy: | if "background_picture" in fancy: | ||||
bg_path = os.path.join(disk_root, ".background", os.path.basename(fancy["background_picture"])) | bg_path = os.path.join( | ||||
disk_root, ".background", os.path.basename( | |||||
fancy["background_picture"])) | |||||
os.mkdir(os.path.dirname(bg_path)) | os.mkdir(os.path.dirname(bg_path)) | ||||
if verbose >= 3: | if verbose >= 3: | ||||
print(fancy["background_picture"], "->", bg_path) | print(fancy["background_picture"], "->", bg_path) | ||||
shutil.copy2(fancy["background_picture"], bg_path) | shutil.copy2(fancy["background_picture"], bg_path) | ||||
else: | else: | ||||
bg_path = None | bg_path = None | ||||
if fancy.get("applications_symlink", False): | if fancy.get("applications_symlink", False): | ||||
os.symlink("/Applications", os.path.join(disk_root, "Applications")) | os.symlink( | ||||
"/Applications", | |||||
os.path.join( | |||||
disk_root, | |||||
"Applications")) | |||||
# The Python appscript package broke with OSX 10.8 and isn't being fixed. | # The Python appscript package broke with OSX 10.8 and isn't being fixed. | ||||
# So we now build up an AppleScript string and use the osascript command | # So we now build up an AppleScript string and use the osascript command | ||||
# to make the .dmg file pretty: | # to make the .dmg file pretty: | ||||
appscript = Template( """ | appscript = Template(""" | ||||
on run argv | on run argv | ||||
tell application "Finder" | tell application "Finder" | ||||
tell disk "$disk" | tell disk "$disk" | ||||
open | open | ||||
set current view of container window to icon view | set current view of container window to icon view | ||||
set toolbar visible of container window to false | set toolbar visible of container window to false | ||||
set statusbar visible of container window to false | set statusbar visible of container window to false | ||||
set the bounds of container window to {$window_bounds} | set the bounds of container window to {$window_bounds} | ||||
set theViewOptions to the icon view options of container window | set theViewOptions to the icon view options of container window | ||||
set arrangement of theViewOptions to not arranged | set arrangement of theViewOptions to not arranged | ||||
set icon size of theViewOptions to $icon_size | set icon size of theViewOptions to $icon_size | ||||
$background_commands | $background_commands | ||||
$items_positions | $items_positions | ||||
close -- close/reopen works around a bug... | close -- close/reopen works around a bug... | ||||
open | open | ||||
update without registering applications | update without registering applications | ||||
delay 5 | delay 5 | ||||
eject | eject | ||||
end tell | end tell | ||||
end tell | end tell | ||||
end run | end run | ||||
""") | """) | ||||
itemscript = Template('set position of item "${item}" of container window to {${position}}') | itemscript = Template( | ||||
'set position of item "${item}" of container window to {${position}}') | |||||
items_positions = [] | items_positions = [] | ||||
if "items_position" in fancy: | if "items_position" in fancy: | ||||
for name, position in fancy["items_position"].items(): | for name, position in fancy["items_position"].items(): | ||||
params = { "item" : name, "position" : ",".join([str(p) for p in position]) } | params = {"item": name, "position": ",".join( | ||||
[str(p) for p in position])} | |||||
items_positions.append(itemscript.substitute(params)) | items_positions.append(itemscript.substitute(params)) | ||||
params = { | params = { | ||||
"disk" : volname, | "disk": volname, | ||||
"window_bounds" : "300,300,800,620", | "window_bounds": "300,300,800,620", | ||||
"icon_size" : "96", | "icon_size": "96", | ||||
"background_commands" : "", | "background_commands": "", | ||||
"items_positions" : "\n ".join(items_positions) | "items_positions": "\n ".join(items_positions) | ||||
} | } | ||||
if "window_bounds" in fancy: | if "window_bounds" in fancy: | ||||
params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]]) | params["window_bounds"] = ",".join( | ||||
[str(p) for p in fancy["window_bounds"]]) | |||||
if "icon_size" in fancy: | if "icon_size" in fancy: | ||||
params["icon_size"] = str(fancy["icon_size"]) | params["icon_size"] = str(fancy["icon_size"]) | ||||
if bg_path is not None: | if bg_path is not None: | ||||
# Set background file, then call SetFile to make it invisible. | # Set background file, then call SetFile to make it invisible. | ||||
# (note: making it invisible first makes set background picture fail) | # (note: making it invisible first makes set background picture fail) | ||||
bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic" | bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic" | ||||
do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """) | do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """) | ||||
params["background_commands"] = bgscript.substitute({"bgpic" : os.path.basename(bg_path), "disk" : params["disk"]}) | params["background_commands"] = bgscript.substitute( | ||||
{"bgpic": os.path.basename(bg_path), "disk": params["disk"]}) | |||||
s = appscript.substitute(params) | s = appscript.substitute(params) | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("Running AppleScript:") | print("Running AppleScript:") | ||||
print(s) | print(s) | ||||
p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE) | p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE) | ||||
p.communicate(input=s.encode('utf-8')) | p.communicate(input=s.encode('utf-8')) | ||||
if p.returncode: | if p.returncode: | ||||
print("Error running osascript.") | print("Error running osascript.") | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Finalizing .dmg disk image +") | print("+ Finalizing .dmg disk image +") | ||||
time.sleep(5) | time.sleep(5) | ||||
try: | try: | ||||
runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True) | runHDIUtil( | ||||
"convert", | |||||
dmg_name + ".temp", | |||||
format="UDBZ", | |||||
o=dmg_name + ".dmg", | |||||
ov=True) | |||||
except subprocess.CalledProcessError as e: | except subprocess.CalledProcessError as e: | ||||
sys.exit(e.returncode) | sys.exit(e.returncode) | ||||
os.unlink(dmg_name + ".temp.dmg") | os.unlink(dmg_name + ".temp.dmg") | ||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Done +") | print("+ Done +") | ||||
sys.exit(0) | sys.exit(0) |