Changeset View
Changeset View
Standalone View
Standalone View
contrib/macdeploy/macdeployqtplus.py
Show First 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | """ | ||||
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( | reOLine = re.compile( | ||||
r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$') | 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 | # Don't deploy system libraries (exception for libQtuitools and | ||||
# libQtlucene). | # libQtlucene). | ||||
if line.startswith("/System/Library/") or line.startswith( | if ( | ||||
"@executable_path") or (line.startswith("/usr/lib/") and "libQt" not in line): | 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) | ||||
info = cls() | info = cls() | ||||
info.sourceFilePath = path | info.sourceFilePath = path | ||||
info.installName = path | info.installName = path | ||||
if path.endswith(".dylib"): | if path.endswith(".dylib"): | ||||
dirname, filename = os.path.split(path) | dirname, filename = os.path.split(path) | ||||
info.frameworkName = filename | info.frameworkName = filename | ||||
info.frameworkDirectory = dirname | info.frameworkDirectory = dirname | ||||
info.frameworkPath = path | info.frameworkPath = path | ||||
info.binaryDirectory = dirname | info.binaryDirectory = dirname | ||||
info.binaryName = filename | info.binaryName = filename | ||||
info.binaryPath = path | info.binaryPath = path | ||||
info.version = "-" | info.version = "-" | ||||
info.installName = path | info.installName = path | ||||
info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName | info.deployedInstallName = ( | ||||
"@executable_path/../Frameworks/" + info.binaryName | |||||
) | |||||
info.sourceFilePath = path | info.sourceFilePath = path | ||||
info.destinationDirectory = cls.bundleFrameworkDirectory | info.destinationDirectory = cls.bundleFrameworkDirectory | ||||
else: | else: | ||||
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( | raise RuntimeError( | ||||
"Could not find .framework or .dylib in otool line: " + line) | "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.frameworkPath = os.path.join( | ||||
info.frameworkDirectory, info.frameworkName) | 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.binaryPath = os.path.join(info.binaryDirectory, info.binaryName) | ||||
info.binaryDirectory, info.binaryName) | |||||
info.version = parts[i + 2] | info.version = parts[i + 2] | ||||
info.deployedInstallName = "@executable_path/../Frameworks/" + \ | info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join( | ||||
os.path.join(info.frameworkName, info.binaryPath) | info.frameworkName, info.binaryPath | ||||
) | |||||
info.destinationDirectory = os.path.join( | info.destinationDirectory = os.path.join( | ||||
cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory) | cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory | ||||
) | |||||
info.sourceResourcesDirectory = os.path.join( | info.sourceResourcesDirectory = os.path.join( | ||||
info.frameworkPath, "Resources") | info.frameworkPath, "Resources" | ||||
info.sourceContentsDirectory = os.path.join( | ) | ||||
info.frameworkPath, "Contents") | info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents") | ||||
info.sourceVersionContentsDirectory = os.path.join( | info.sourceVersionContentsDirectory = os.path.join( | ||||
info.frameworkPath, "Versions", info.version, "Contents") | info.frameworkPath, "Versions", info.version, "Contents" | ||||
) | |||||
info.destinationResourcesDirectory = os.path.join( | info.destinationResourcesDirectory = os.path.join( | ||||
cls.bundleFrameworkDirectory, info.frameworkName, "Resources") | cls.bundleFrameworkDirectory, info.frameworkName, "Resources" | ||||
) | |||||
info.destinationVersionContentsDirectory = os.path.join( | info.destinationVersionContentsDirectory = os.path.join( | ||||
cls.bundleFrameworkDirectory, | cls.bundleFrameworkDirectory, | ||||
info.frameworkName, | info.frameworkName, | ||||
"Versions", | "Versions", | ||||
info.version, | info.version, | ||||
"Contents") | "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" | ||||
Show All 35 Lines | def usesFramework(self, name: str) -> bool: | ||||
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, | otool = subprocess.Popen( | ||||
"-L", | [otoolbin, "-L", binaryPath], | ||||
binaryPath], | |||||
stdout=subprocess.PIPE, | stdout=subprocess.PIPE, | ||||
stderr=subprocess.PIPE, | stderr=subprocess.PIPE, | ||||
universal_newlines=True) | 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( | raise RuntimeError(f"otool failed with return code {otool.returncode}") | ||||
f"otool failed with return code {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"): | ||||
# Frameworks and dylibs list themselves as a dependency. | # Frameworks and dylibs list themselves as a dependency. | ||||
otoolLines.pop(0) | otoolLines.pop(0) | ||||
libraries = [] | libraries = [] | ||||
Show All 9 Lines | def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: | ||||
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, | def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int): | ||||
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_name: str, binaryPath: str, verbose: int): | def changeIdentification(id_name: 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_name) | print(" to", id_name) | ||||
runInstallNameTool("id", id_name, binaryPath) | runInstallNameTool("id", id_name, 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, | def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[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) | ||||
Show All 11 Lines | 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( | linkfrom = os.path.join( | ||||
path, | path, | ||||
"Contents", | "Contents", | ||||
"Frameworks", | "Frameworks", | ||||
framework.frameworkName, | framework.frameworkName, | ||||
"Versions", | "Versions", | ||||
"Current") | "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( | toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory) | ||||
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( | toContentsDir = os.path.join( | ||||
path, framework.destinationVersionContentsDirectory) | 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) | ||||
# Copy qt_menu.nib (applies to non-framework layout) | # Copy qt_menu.nib (applies to non-framework layout) | ||||
elif framework.frameworkName.startswith("libQtGui"): | elif framework.frameworkName.startswith("libQtGui"): | ||||
qtMenuNibSourcePath = os.path.join( | qtMenuNibSourcePath = os.path.join( | ||||
framework.frameworkDirectory, "Resources", "qt_menu.nib") | framework.frameworkDirectory, "Resources", "qt_menu.nib" | ||||
) | |||||
qtMenuNibDestinationPath = os.path.join( | qtMenuNibDestinationPath = os.path.join( | ||||
path, "Contents", "Resources", "qt_menu.nib") | path, "Contents", "Resources", "qt_menu.nib" | ||||
) | |||||
if os.path.exists(qtMenuNibSourcePath) and not os.path.exists( | if os.path.exists(qtMenuNibSourcePath) and not os.path.exists( | ||||
qtMenuNibDestinationPath): | qtMenuNibDestinationPath | ||||
): | |||||
shutil.copytree( | shutil.copytree( | ||||
qtMenuNibSourcePath, | qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True | ||||
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, | def deployFrameworks( | ||||
verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo: | 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( | if framework.installName.startswith( | ||||
"@executable_path") or framework.installName.startswith(bundlePath): | "@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( | changeInstallName( | ||||
framework.installName, | framework.installName, framework.deployedInstallName, binaryPath, verbose | ||||
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( | changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose) | ||||
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( | changeInstallName( | ||||
dependency.installName, | dependency.installName, | ||||
dependency.deployedInstallName, | dependency.deployedInstallName, | ||||
deployedBinaryPath, | deployedBinaryPath, | ||||
verbose) | 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( | def deployFrameworksForAppBundle( | ||||
applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo: | 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( | print( | ||||
"Warning: Could not find any external frameworks to deploy in {}.".format( | "Warning: Could not find any external frameworks to deploy in {}.".format( | ||||
applicationBundle.path)) | applicationBundle.path | ||||
) | |||||
) | |||||
return DeploymentInfo() | return DeploymentInfo() | ||||
else: | else: | ||||
return deployFrameworks( | return deployFrameworks( | ||||
frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose) | frameworks, | ||||
applicationBundle.path, | |||||
applicationBundle.binaryPath, | |||||
strip, | |||||
verbose, | |||||
) | |||||
def deployPlugins(appBundleInfo: ApplicationBundleInfo, | def deployPlugins( | ||||
deploymentInfo: DeploymentInfo, strip: bool, verbose: int): | 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 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | ||||
if not deploymentInfo.usesFramework("Qt3DCore"): | if not deploymentInfo.usesFramework("Qt3DCore"): | ||||
continue | continue | ||||
for pluginName in filenames: | for pluginName in filenames: | ||||
pluginPath = os.path.join(pluginDirectory, pluginName) | pluginPath = os.path.join(pluginDirectory, pluginName) | ||||
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 | # Deploy accessibility for Qt3Support only if the Qt3Support is | ||||
# in use | # in use | ||||
if not deploymentInfo.usesFramework("Qt3Support"): | if not deploymentInfo.usesFramework("Qt3Support"): | ||||
continue | continue | ||||
Show All 12 Lines | for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath): | ||||
# QtVirtualKeyboard is in use | # 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( | print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...") | ||||
"Processing plugin", | |||||
os.path.join( | |||||
pluginDirectory, | |||||
pluginName), | |||||
"...") | |||||
sourcePath = os.path.join( | sourcePath = os.path.join( | ||||
deploymentInfo.pluginPath, | deploymentInfo.pluginPath, pluginDirectory, pluginName | ||||
pluginDirectory, | ) | ||||
pluginName) | destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory) | ||||
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( | changeInstallName( | ||||
dependency.installName, | dependency.installName, | ||||
dependency.deployedInstallName, | dependency.deployedInstallName, | ||||
destinationPath, | destinationPath, | ||||
verbose) | verbose, | ||||
) | |||||
# Deploy framework if necessary. | # Deploy framework if necessary. | ||||
if dependency.frameworkName not in deploymentInfo.deployedFrameworks: | if dependency.frameworkName not in deploymentInfo.deployedFrameworks: | ||||
deployFrameworks( | deployFrameworks( | ||||
[dependency], | [dependency], | ||||
appBundleInfo.path, | appBundleInfo.path, | ||||
destinationPath, | destinationPath, | ||||
strip, | strip, | ||||
verbose, | verbose, | ||||
deploymentInfo) | 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", | ap.add_argument( | ||||
help="application bundle to be deployed") | "app_bundle", | ||||
nargs=1, | |||||
metavar="app-bundle", | |||||
help="application bundle to be deployed", | |||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-verbose", | "-verbose", | ||||
type=int, | type=int, | ||||
nargs=1, | nargs=1, | ||||
default=[1], | default=[1], | ||||
metavar="<0-3>", | metavar="<0-3>", | ||||
help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug") | help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug", | ||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-no-plugins", | "-no-plugins", | ||||
dest="plugins", | dest="plugins", | ||||
action="store_false", | action="store_false", | ||||
default=True, | default=True, | ||||
help="skip plugin deployment") | help="skip plugin deployment", | ||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-no-strip", | "-no-strip", | ||||
dest="strip", | dest="strip", | ||||
action="store_false", | action="store_false", | ||||
default=True, | default=True, | ||||
help="don't run 'strip' on the binaries") | help="don't run 'strip' on the binaries", | ||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-sign", | "-sign", | ||||
dest="sign", | dest="sign", | ||||
action="store_true", | action="store_true", | ||||
default=False, | default=False, | ||||
help="sign .app bundle with codesign tool") | help="sign .app bundle with codesign tool", | ||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-dmg", | "-dmg", | ||||
nargs="?", | nargs="?", | ||||
const="", | const="", | ||||
metavar="basename", | metavar="basename", | ||||
help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used") | help=( | ||||
"create a .dmg disk image; if basename is not specified, a camel-cased version" | |||||
" of the app name is used" | |||||
), | |||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-fancy", | "-fancy", | ||||
nargs=1, | nargs=1, | ||||
metavar="plist", | metavar="plist", | ||||
default=[], | default=[], | ||||
help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work") | help=( | ||||
"make a fancy looking disk image using the given plist file with instructions;" | |||||
" requires -dmg to work" | |||||
), | |||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-add-qt-tr", | "-add-qt-tr", | ||||
nargs=1, | nargs=1, | ||||
metavar="languages", | metavar="languages", | ||||
default=[], | default=[], | ||||
help="add Qt translation files to the bundle's resources; the language list must be separated with commas, not with whitespace") | help=( | ||||
"add Qt translation files to the bundle's resources; the language list must be" | |||||
" separated with commas, not with whitespace" | |||||
), | |||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-translations-dir", | "-translations-dir", | ||||
nargs=1, | nargs=1, | ||||
metavar="path", | metavar="path", | ||||
default=None, | default=None, | ||||
help="Path to Qt's translation files") | help="Path to Qt's translation files", | ||||
) | |||||
ap.add_argument( | ap.add_argument( | ||||
"-add-resources", | "-add-resources", | ||||
nargs="+", | nargs="+", | ||||
metavar="path", | metavar="path", | ||||
default=[], | default=[], | ||||
help="list of additional files or folders to be copied into the bundle's resources; must be the last argument") | 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", | "-volname", | ||||
nargs=1, | nargs=1, | ||||
metavar="volname", | metavar="volname", | ||||
default=[], | default=[], | ||||
help="custom volume name for dmg") | 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( | sys.stderr.write(f'Error: Could not find app bundle "{app_bundle}"\n') | ||||
f"Error: Could not find app bundle \"{app_bundle}\"\n") | |||||
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( | sys.stderr.write( | ||||
f"Error: Could not find translation dir \"{translations_dir}\"\n") | f'Error: Could not find translation dir "{translations_dir}"\n' | ||||
) | |||||
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(f"Checking for \"{p}\"...") | print(f'Checking for "{p}"...') | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write( | sys.stderr.write(f'Error: Could not find additional resource file "{p}"\n') | ||||
f"Error: Could not find additional resource file \"{p}\"\n") | |||||
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( | sys.stderr.write( | ||||
"Error: Could not import plistlib which is required for fancy disk images.\n") | "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(f"Fancy: Loading \"{p}\"...") | print(f'Fancy: Loading "{p}"...') | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write( | sys.stderr.write(f'Error: Could not find fancy disk image plist at "{p}"\n') | ||||
f"Error: Could not find fancy disk image plist at \"{p}\"\n") | |||||
sys.exit(1) | sys.exit(1) | ||||
try: | try: | ||||
fancy = plistlib.readPlist(p) | fancy = plistlib.readPlist(p) | ||||
except BaseException: | except BaseException: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write( | sys.stderr.write( | ||||
f"Error: Could not parse fancy disk image plist at \"{p}\"\n") | f'Error: Could not parse fancy disk image plist at "{p}"\n' | ||||
) | |||||
sys.exit(1) | sys.exit(1) | ||||
try: | try: | ||||
assert "window_bounds" not in fancy or ( | assert "window_bounds" not in fancy or ( | ||||
isinstance( | isinstance(fancy["window_bounds"], list) | ||||
fancy["window_bounds"], | and len(fancy["window_bounds"]) == 4 | ||||
list) and len( | ) | ||||
fancy["window_bounds"]) == 4) | |||||
assert "background_picture" not in fancy or isinstance( | assert "background_picture" not in fancy or isinstance( | ||||
fancy["background_picture"], str) | 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( | assert "applications_symlink" not in fancy or isinstance( | ||||
fancy["applications_symlink"], bool) | 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( | assert ( | ||||
value, | isinstance(value, list) | ||||
list) and len(value) == 2 and isinstance( | and len(value) == 2 | ||||
value[0], | and isinstance(value[0], int) | ||||
int) and isinstance( | and isinstance(value[1], int) | ||||
value[1], | ) | ||||
int) | |||||
except BaseException: | except BaseException: | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write( | sys.stderr.write(f'Error: Bad format of fancy disk image plist at "{p}"\n') | ||||
f"Error: Bad format of fancy disk image plist at \"{p}\"\n") | |||||
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(f"Fancy: Resolving background picture \"{bp}\"...") | print(f'Fancy: Resolving background picture "{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( | sys.stderr.write( | ||||
"Error: Could not find background picture at \"{}\" or \"{}\"\n".format( | 'Error: Could not find background picture at "{}" or "{}"\n' | ||||
fancy["background_picture"], bp)) | .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 26 Lines | |||||
# ------------------------------------------------ | # ------------------------------------------------ | ||||
if verbose >= 2: | if verbose >= 2: | ||||
print("+ Deploying frameworks +") | print("+ Deploying frameworks +") | ||||
try: | try: | ||||
deploymentInfo = deployFrameworksForAppBundle( | deploymentInfo = deployFrameworksForAppBundle( | ||||
applicationBundle, config.strip, verbose) | 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( | sys.stderr.write( | ||||
"Warning: Could not detect Qt's path, skipping plugin deployment!\n") | "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(f"Error: {str(e)}\n") | sys.stderr.write(f"Error: {str(e)}\n") | ||||
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 = [f"qt_{lng}.qm" | add_qt_tr = [f"qt_{lng}.qm" for lng in config.add_qt_tr[0].split(",")] | ||||
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(f"Checking for \"{p}\"...") | print(f'Checking for "{p}"...') | ||||
if not os.path.exists(p): | if not os.path.exists(p): | ||||
if verbose >= 1: | if verbose >= 1: | ||||
sys.stderr.write( | sys.stderr.write( | ||||
f"Error: Could not find Qt translation file \"{lng_file}\"\n") | f'Error: Could not find Qt translation file "{lng_file}"\n' | ||||
) | |||||
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( | print( | ||||
os.path.join( | os.path.join(qt_tr_dir, lng_file), | ||||
qt_tr_dir, | |||||
lng_file), | |||||
"->", | "->", | ||||
os.path.join( | os.path.join(applicationBundle.resourcesPath, lng_file), | ||||
applicationBundle.resourcesPath, | ) | ||||
lng_file)) | |||||
shutil.copy2( | shutil.copy2( | ||||
os.path.join( | os.path.join(qt_tr_dir, lng_file), | ||||
qt_tr_dir, lng_file), os.path.join( | os.path.join(applicationBundle.resourcesPath, lng_file), | ||||
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(f"Code-signing app bundle {target}") | print(f"Code-signing app bundle {target}") | ||||
subprocess.check_call( | subprocess.check_call( | ||||
f"codesign --force {os.environ['CODESIGNARGS']} {target}", shell=True) | f"codesign --force {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 28 Lines | if config.dmg is not None: | ||||
if fancy is None: | if fancy is None: | ||||
try: | try: | ||||
runHDIUtil( | runHDIUtil( | ||||
"create", | "create", | ||||
dmg_name, | dmg_name, | ||||
srcfolder="dist", | srcfolder="dist", | ||||
format="UDBZ", | format="UDBZ", | ||||
volname=volname, | volname=volname, | ||||
ov=True) | 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( | runHDIUtil( | ||||
"create", | "create", | ||||
dmg_name + ".temp", | dmg_name + ".temp", | ||||
srcfolder="dist", | srcfolder="dist", | ||||
format="UDRW", | format="UDRW", | ||||
size=size, | size=size, | ||||
volname=volname, | volname=volname, | ||||
ov=True) | 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( | output = runHDIUtil( | ||||
"attach", | "attach", | ||||
dmg_name + ".temp", | dmg_name + ".temp", | ||||
readwrite=True, | readwrite=True, | ||||
noverify=True, | noverify=True, | ||||
noautoopen=True, | noautoopen=True, | ||||
capture_stdout=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( | bg_path = os.path.join( | ||||
disk_root, ".background", os.path.basename( | disk_root, ".background", os.path.basename(fancy["background_picture"]) | ||||
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( | os.symlink("/Applications", os.path.join(disk_root, "Applications")) | ||||
"/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" | ||||
Show All 13 Lines | else: | ||||
delay 5 | delay 5 | ||||
eject | eject | ||||
end tell | end tell | ||||
end tell | end tell | ||||
end run | end run | ||||
""") | """) | ||||
itemscript = Template( | itemscript = Template( | ||||
'set position of item "${item}" of container window to {${position}}') | '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( | params = { | ||||
[str(p) for p in position])} | "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( | params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]]) | ||||
[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( | ||||
do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """) | """set background picture of theViewOptions to file ".background:$bgpic" | ||||
do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """ | |||||
) | |||||
params["background_commands"] = bgscript.substitute( | params["background_commands"] = bgscript.substitute( | ||||
{"bgpic": os.path.basename(bg_path), "disk": params["disk"]}) | {"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( | runHDIUtil( | ||||
"convert", | "convert", | ||||
dmg_name + ".temp", | dmg_name + ".temp", | ||||
format="UDBZ", | format="UDBZ", | ||||
o=dmg_name + ".dmg", | o=dmg_name + ".dmg", | ||||
ov=True) | 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) |