2 from __future__
import division
, print_function
, unicode_literals
4 # Copyright (C) 2011 Patrick "p2k" Schneider <me@p2k-network.org>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import subprocess
, sys
, re
, os
, shutil
, stat
, os
.path
, time
21 from string
import Template
22 from argparse
import ArgumentParser
24 # This is ported from the original macdeployqt with modifications
26 class FrameworkInfo(object):
28 self
.frameworkDirectory
= ""
29 self
.frameworkName
= ""
30 self
.frameworkPath
= ""
31 self
.binaryDirectory
= ""
36 self
.deployedInstallName
= ""
37 self
.sourceFilePath
= ""
38 self
.destinationDirectory
= ""
39 self
.sourceResourcesDirectory
= ""
40 self
.sourceVersionContentsDirectory
= ""
41 self
.sourceContentsDirectory
= ""
42 self
.destinationResourcesDirectory
= ""
43 self
.destinationVersionContentsDirectory
= ""
45 def __eq__(self
, other
):
46 if self
.__class
__ == other
.__class
__:
47 return self
.__dict
__ == other
.__dict
__
52 return """ Framework name: %s
53 Framework directory: %s
60 Deployed install name: %s
62 Deployed Directory (relative to bundle): %s
63 """ % (self
.frameworkName
,
64 self
.frameworkDirectory
,
71 self
.deployedInstallName
,
73 self
.destinationDirectory
)
76 return self
.frameworkName
.endswith(".dylib")
78 def isQtFramework(self
):
80 return self
.frameworkName
.startswith("libQt")
82 return self
.frameworkName
.startswith("Qt")
84 reOLine
= re
.compile(r
'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
85 bundleFrameworkDirectory
= "Contents/Frameworks"
86 bundleBinaryDirectory
= "Contents/MacOS"
89 def fromOtoolLibraryLine(cls
, line
):
90 # Note: line must be trimmed
94 # Don't deploy system libraries (exception for libQtuitools and libQtlucene).
95 if line
.startswith("/System/Library/") or line
.startswith("@executable_path") or (line
.startswith("/usr/lib/") and "libQt" not in line
):
98 m
= cls
.reOLine
.match(line
)
100 raise RuntimeError("otool line could not be parsed: " + line
)
105 info
.sourceFilePath
= path
106 info
.installName
= path
108 if path
.endswith(".dylib"):
109 dirname
, filename
= os
.path
.split(path
)
110 info
.frameworkName
= filename
111 info
.frameworkDirectory
= dirname
112 info
.frameworkPath
= path
114 info
.binaryDirectory
= dirname
115 info
.binaryName
= filename
116 info
.binaryPath
= path
119 info
.installName
= path
120 info
.deployedInstallName
= "@executable_path/../Frameworks/" + info
.binaryName
121 info
.sourceFilePath
= path
122 info
.destinationDirectory
= cls
.bundleFrameworkDirectory
124 parts
= path
.split("/")
126 # Search for the .framework directory
128 if part
.endswith(".framework"):
132 raise RuntimeError("Could not find .framework or .dylib in otool line: " + line
)
134 info
.frameworkName
= parts
[i
]
135 info
.frameworkDirectory
= "/".join(parts
[:i
])
136 info
.frameworkPath
= os
.path
.join(info
.frameworkDirectory
, info
.frameworkName
)
138 info
.binaryName
= parts
[i
+3]
139 info
.binaryDirectory
= "/".join(parts
[i
+1:i
+3])
140 info
.binaryPath
= os
.path
.join(info
.binaryDirectory
, info
.binaryName
)
141 info
.version
= parts
[i
+2]
143 info
.deployedInstallName
= "@executable_path/../Frameworks/" + os
.path
.join(info
.frameworkName
, info
.binaryPath
)
144 info
.destinationDirectory
= os
.path
.join(cls
.bundleFrameworkDirectory
, info
.frameworkName
, info
.binaryDirectory
)
146 info
.sourceResourcesDirectory
= os
.path
.join(info
.frameworkPath
, "Resources")
147 info
.sourceContentsDirectory
= os
.path
.join(info
.frameworkPath
, "Contents")
148 info
.sourceVersionContentsDirectory
= os
.path
.join(info
.frameworkPath
, "Versions", info
.version
, "Contents")
149 info
.destinationResourcesDirectory
= os
.path
.join(cls
.bundleFrameworkDirectory
, info
.frameworkName
, "Resources")
150 info
.destinationContentsDirectory
= os
.path
.join(cls
.bundleFrameworkDirectory
, info
.frameworkName
, "Contents")
151 info
.destinationVersionContentsDirectory
= os
.path
.join(cls
.bundleFrameworkDirectory
, info
.frameworkName
, "Versions", info
.version
, "Contents")
155 class ApplicationBundleInfo(object):
156 def __init__(self
, path
):
158 appName
= "Bitcoin-Qt"
159 self
.binaryPath
= os
.path
.join(path
, "Contents", "MacOS", appName
)
160 if not os
.path
.exists(self
.binaryPath
):
161 raise RuntimeError("Could not find bundle binary for " + path
)
162 self
.resourcesPath
= os
.path
.join(path
, "Contents", "Resources")
163 self
.pluginPath
= os
.path
.join(path
, "Contents", "PlugIns")
165 class DeploymentInfo(object):
168 self
.pluginPath
= None
169 self
.deployedFrameworks
= []
171 def detectQtPath(self
, frameworkDirectory
):
172 parentDir
= os
.path
.dirname(frameworkDirectory
)
173 if os
.path
.exists(os
.path
.join(parentDir
, "translations")):
174 # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
175 self
.qtPath
= parentDir
176 elif os
.path
.exists(os
.path
.join(parentDir
, "share", "qt4", "translations")):
177 # MacPorts layout, e.g. "/opt/local/share/qt4"
178 self
.qtPath
= os
.path
.join(parentDir
, "share", "qt4")
179 elif os
.path
.exists(os
.path
.join(os
.path
.dirname(parentDir
), "share", "qt4", "translations")):
180 # Newer Macports layout
181 self
.qtPath
= os
.path
.join(os
.path
.dirname(parentDir
), "share", "qt4")
183 self
.qtPath
= os
.getenv("QTDIR", None)
185 if self
.qtPath
is not None:
186 pluginPath
= os
.path
.join(self
.qtPath
, "plugins")
187 if os
.path
.exists(pluginPath
):
188 self
.pluginPath
= pluginPath
190 def usesFramework(self
, name
):
191 nameDot
= "%s." % name
192 libNameDot
= "lib%s." % name
193 for framework
in self
.deployedFrameworks
:
194 if framework
.endswith(".framework"):
195 if framework
.startswith(nameDot
):
197 elif framework
.endswith(".dylib"):
198 if framework
.startswith(libNameDot
):
202 def getFrameworks(binaryPath
, verbose
):
204 print("Inspecting with otool: " + binaryPath
)
205 otoolbin
=os
.getenv("OTOOL", "otool")
206 otool
= subprocess
.Popen([otoolbin
, "-L", binaryPath
], stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
207 o_stdout
, o_stderr
= otool
.communicate()
208 if otool
.returncode
!= 0:
210 sys
.stderr
.write(o_stderr
)
212 raise RuntimeError("otool failed with return code %d" % otool
.returncode
)
214 otoolLines
= o_stdout
.decode().split("\n")
215 otoolLines
.pop(0) # First line is the inspected binary
216 if ".framework" in binaryPath
or binaryPath
.endswith(".dylib"):
217 otoolLines
.pop(0) # Frameworks and dylibs list themselves as a dependency.
220 for line
in otoolLines
:
221 line
= line
.replace("@loader_path", os
.path
.dirname(binaryPath
))
222 info
= FrameworkInfo
.fromOtoolLibraryLine(line
.strip())
225 print("Found framework:")
227 libraries
.append(info
)
231 def runInstallNameTool(action
, *args
):
232 installnametoolbin
=os
.getenv("INSTALLNAMETOOL", "install_name_tool")
233 subprocess
.check_call([installnametoolbin
, "-"+action
] + list(args
))
235 def changeInstallName(oldName
, newName
, binaryPath
, verbose
):
237 print("Using install_name_tool:")
238 print(" in", binaryPath
)
239 print(" change reference", oldName
)
240 print(" to", newName
)
241 runInstallNameTool("change", oldName
, newName
, binaryPath
)
243 def changeIdentification(id, binaryPath
, verbose
):
245 print("Using install_name_tool:")
246 print(" change identification in", binaryPath
)
248 runInstallNameTool("id", id, binaryPath
)
250 def runStrip(binaryPath
, verbose
):
251 stripbin
=os
.getenv("STRIP", "strip")
253 print("Using strip:")
254 print(" stripped", binaryPath
)
255 subprocess
.check_call([stripbin
, "-x", binaryPath
])
257 def copyFramework(framework
, path
, verbose
):
258 if framework
.sourceFilePath
.startswith("Qt"):
259 #standard place for Nokia Qt installer's frameworks
260 fromPath
= "/Library/Frameworks/" + framework
.sourceFilePath
262 fromPath
= framework
.sourceFilePath
263 toDir
= os
.path
.join(path
, framework
.destinationDirectory
)
264 toPath
= os
.path
.join(toDir
, framework
.binaryName
)
266 if not os
.path
.exists(fromPath
):
267 raise RuntimeError("No file at " + fromPath
)
269 if os
.path
.exists(toPath
):
270 return None # Already there
272 if not os
.path
.exists(toDir
):
275 shutil
.copy2(fromPath
, toPath
)
277 print("Copied:", fromPath
)
278 print(" to:", toPath
)
280 permissions
= os
.stat(toPath
)
281 if not permissions
.st_mode
& stat
.S_IWRITE
:
282 os
.chmod(toPath
, permissions
.st_mode | stat
.S_IWRITE
)
284 if not framework
.isDylib(): # Copy resources for real frameworks
286 linkfrom
= os
.path
.join(path
, "Contents","Frameworks", framework
.frameworkName
, "Versions", "Current")
287 linkto
= framework
.version
288 if not os
.path
.exists(linkfrom
):
289 os
.symlink(linkto
, linkfrom
)
291 print("Linked:", linkfrom
, "->", linkto
)
292 fromResourcesDir
= framework
.sourceResourcesDirectory
293 if os
.path
.exists(fromResourcesDir
):
294 toResourcesDir
= os
.path
.join(path
, framework
.destinationResourcesDirectory
)
295 shutil
.copytree(fromResourcesDir
, toResourcesDir
, symlinks
=True)
297 print("Copied resources:", fromResourcesDir
)
298 print(" to:", toResourcesDir
)
299 fromContentsDir
= framework
.sourceVersionContentsDirectory
300 if not os
.path
.exists(fromContentsDir
):
301 fromContentsDir
= framework
.sourceContentsDirectory
302 if os
.path
.exists(fromContentsDir
):
303 toContentsDir
= os
.path
.join(path
, framework
.destinationVersionContentsDirectory
)
304 shutil
.copytree(fromContentsDir
, toContentsDir
, symlinks
=True)
306 print("Copied Contents:", fromContentsDir
)
307 print(" to:", toContentsDir
)
308 elif framework
.frameworkName
.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
309 qtMenuNibSourcePath
= os
.path
.join(framework
.frameworkDirectory
, "Resources", "qt_menu.nib")
310 qtMenuNibDestinationPath
= os
.path
.join(path
, "Contents", "Resources", "qt_menu.nib")
311 if os
.path
.exists(qtMenuNibSourcePath
) and not os
.path
.exists(qtMenuNibDestinationPath
):
312 shutil
.copytree(qtMenuNibSourcePath
, qtMenuNibDestinationPath
, symlinks
=True)
314 print("Copied for libQtGui:", qtMenuNibSourcePath
)
315 print(" to:", qtMenuNibDestinationPath
)
319 def deployFrameworks(frameworks
, bundlePath
, binaryPath
, strip
, verbose
, deploymentInfo
=None):
320 if deploymentInfo
is None:
321 deploymentInfo
= DeploymentInfo()
323 while len(frameworks
) > 0:
324 framework
= frameworks
.pop(0)
325 deploymentInfo
.deployedFrameworks
.append(framework
.frameworkName
)
328 print("Processing", framework
.frameworkName
, "...")
330 # Get the Qt path from one of the Qt frameworks
331 if deploymentInfo
.qtPath
is None and framework
.isQtFramework():
332 deploymentInfo
.detectQtPath(framework
.frameworkDirectory
)
334 if framework
.installName
.startswith("@executable_path") or framework
.installName
.startswith(bundlePath
):
336 print(framework
.frameworkName
, "already deployed, skipping.")
339 # install_name_tool the new id into the binary
340 changeInstallName(framework
.installName
, framework
.deployedInstallName
, binaryPath
, verbose
)
342 # Copy framework to app bundle.
343 deployedBinaryPath
= copyFramework(framework
, bundlePath
, verbose
)
344 # Skip the rest if already was deployed.
345 if deployedBinaryPath
is None:
349 runStrip(deployedBinaryPath
, verbose
)
351 # install_name_tool it a new id.
352 changeIdentification(framework
.deployedInstallName
, deployedBinaryPath
, verbose
)
353 # Check for framework dependencies
354 dependencies
= getFrameworks(deployedBinaryPath
, verbose
)
356 for dependency
in dependencies
:
357 changeInstallName(dependency
.installName
, dependency
.deployedInstallName
, deployedBinaryPath
, verbose
)
359 # Deploy framework if necessary.
360 if dependency
.frameworkName
not in deploymentInfo
.deployedFrameworks
and dependency
not in frameworks
:
361 frameworks
.append(dependency
)
363 return deploymentInfo
365 def deployFrameworksForAppBundle(applicationBundle
, strip
, verbose
):
366 frameworks
= getFrameworks(applicationBundle
.binaryPath
, verbose
)
367 if len(frameworks
) == 0 and verbose
>= 1:
368 print("Warning: Could not find any external frameworks to deploy in %s." % (applicationBundle
.path
))
369 return DeploymentInfo()
371 return deployFrameworks(frameworks
, applicationBundle
.path
, applicationBundle
.binaryPath
, strip
, verbose
)
373 def deployPlugins(appBundleInfo
, deploymentInfo
, strip
, verbose
):
374 # Lookup available plugins, exclude unneeded
376 if deploymentInfo
.pluginPath
is None:
378 for dirpath
, dirnames
, filenames
in os
.walk(deploymentInfo
.pluginPath
):
379 pluginDirectory
= os
.path
.relpath(dirpath
, deploymentInfo
.pluginPath
)
380 if pluginDirectory
== "designer":
381 # Skip designer plugins
383 elif pluginDirectory
== "phonon" or pluginDirectory
== "phonon_backend":
384 # Deploy the phonon plugins only if phonon is in use
385 if not deploymentInfo
.usesFramework("phonon"):
387 elif pluginDirectory
== "sqldrivers":
388 # Deploy the sql plugins only if QtSql is in use
389 if not deploymentInfo
.usesFramework("QtSql"):
391 elif pluginDirectory
== "script":
392 # Deploy the script plugins only if QtScript is in use
393 if not deploymentInfo
.usesFramework("QtScript"):
395 elif pluginDirectory
== "qmltooling" or pluginDirectory
== "qml1tooling":
396 # Deploy the qml plugins only if QtDeclarative is in use
397 if not deploymentInfo
.usesFramework("QtDeclarative"):
399 elif pluginDirectory
== "bearer":
400 # Deploy the bearer plugins only if QtNetwork is in use
401 if not deploymentInfo
.usesFramework("QtNetwork"):
403 elif pluginDirectory
== "position":
404 # Deploy the position plugins only if QtPositioning is in use
405 if not deploymentInfo
.usesFramework("QtPositioning"):
407 elif pluginDirectory
== "sensors" or pluginDirectory
== "sensorgestures":
408 # Deploy the sensor plugins only if QtSensors is in use
409 if not deploymentInfo
.usesFramework("QtSensors"):
411 elif pluginDirectory
== "audio" or pluginDirectory
== "playlistformats":
412 # Deploy the audio plugins only if QtMultimedia is in use
413 if not deploymentInfo
.usesFramework("QtMultimedia"):
415 elif pluginDirectory
== "mediaservice":
416 # Deploy the mediaservice plugins only if QtMultimediaWidgets is in use
417 if not deploymentInfo
.usesFramework("QtMultimediaWidgets"):
420 for pluginName
in filenames
:
421 pluginPath
= os
.path
.join(pluginDirectory
, pluginName
)
422 if pluginName
.endswith("_debug.dylib"):
425 elif pluginPath
== "imageformats/libqsvg.dylib" or pluginPath
== "iconengines/libqsvgicon.dylib":
426 # Deploy the svg plugins only if QtSvg is in use
427 if not deploymentInfo
.usesFramework("QtSvg"):
429 elif pluginPath
== "accessible/libqtaccessiblecompatwidgets.dylib":
430 # Deploy accessibility for Qt3Support only if the Qt3Support is in use
431 if not deploymentInfo
.usesFramework("Qt3Support"):
433 elif pluginPath
== "graphicssystems/libqglgraphicssystem.dylib":
434 # Deploy the opengl graphicssystem plugin only if QtOpenGL is in use
435 if not deploymentInfo
.usesFramework("QtOpenGL"):
437 elif pluginPath
== "accessible/libqtaccessiblequick.dylib":
438 # Deploy the accessible qtquick plugin only if QtQuick is in use
439 if not deploymentInfo
.usesFramework("QtQuick"):
442 plugins
.append((pluginDirectory
, pluginName
))
444 for pluginDirectory
, pluginName
in plugins
:
446 print("Processing plugin", os
.path
.join(pluginDirectory
, pluginName
), "...")
448 sourcePath
= os
.path
.join(deploymentInfo
.pluginPath
, pluginDirectory
, pluginName
)
449 destinationDirectory
= os
.path
.join(appBundleInfo
.pluginPath
, pluginDirectory
)
450 if not os
.path
.exists(destinationDirectory
):
451 os
.makedirs(destinationDirectory
)
453 destinationPath
= os
.path
.join(destinationDirectory
, pluginName
)
454 shutil
.copy2(sourcePath
, destinationPath
)
456 print("Copied:", sourcePath
)
457 print(" to:", destinationPath
)
460 runStrip(destinationPath
, verbose
)
462 dependencies
= getFrameworks(destinationPath
, verbose
)
464 for dependency
in dependencies
:
465 changeInstallName(dependency
.installName
, dependency
.deployedInstallName
, destinationPath
, verbose
)
467 # Deploy framework if necessary.
468 if dependency
.frameworkName
not in deploymentInfo
.deployedFrameworks
:
469 deployFrameworks([dependency
], appBundleInfo
.path
, destinationPath
, strip
, verbose
, deploymentInfo
)
472 Translations=Resources
476 ap
= ArgumentParser(description
="""Improved version of macdeployqt.
478 Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
479 Note, that the "dist" folder will be deleted before deploying on each run.
481 Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.
483 Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments
484 to the codesign tool.
485 E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""")
487 ap
.add_argument("app_bundle", nargs
=1, metavar
="app-bundle", help="application bundle to be deployed")
488 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")
489 ap
.add_argument("-no-plugins", dest
="plugins", action
="store_false", default
=True, help="skip plugin deployment")
490 ap
.add_argument("-no-strip", dest
="strip", action
="store_false", default
=True, help="don't run 'strip' on the binaries")
491 ap
.add_argument("-sign", dest
="sign", action
="store_true", default
=False, help="sign .app bundle with codesign tool")
492 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")
493 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")
494 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")
495 ap
.add_argument("-translations-dir", nargs
=1, metavar
="path", default
=None, help="Path to Qt's translation files")
496 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")
497 ap
.add_argument("-volname", nargs
=1, metavar
="volname", default
=[], help="custom volume name for dmg")
499 config
= ap
.parse_args()
501 verbose
= config
.verbose
[0]
503 # ------------------------------------------------
505 app_bundle
= config
.app_bundle
[0]
507 if not os
.path
.exists(app_bundle
):
509 sys
.stderr
.write("Error: Could not find app bundle \"%s\"\n" % (app_bundle
))
512 app_bundle_name
= os
.path
.splitext(os
.path
.basename(app_bundle
))[0]
514 # ------------------------------------------------
515 translations_dir
= None
516 if config
.translations_dir
and config
.translations_dir
[0]:
517 if os
.path
.exists(config
.translations_dir
[0]):
518 translations_dir
= config
.translations_dir
[0]
521 sys
.stderr
.write("Error: Could not find translation dir \"%s\"\n" % (translations_dir
))
523 # ------------------------------------------------
525 for p
in config
.add_resources
:
527 print("Checking for \"%s\"..." % p
)
528 if not os
.path
.exists(p
):
530 sys
.stderr
.write("Error: Could not find additional resource file \"%s\"\n" % (p
))
533 # ------------------------------------------------
535 if len(config
.fancy
) == 1:
537 print("Fancy: Importing plistlib...")
542 sys
.stderr
.write("Error: Could not import plistlib which is required for fancy disk images.\n")
547 print("Fancy: Loading \"%s\"..." % p
)
548 if not os
.path
.exists(p
):
550 sys
.stderr
.write("Error: Could not find fancy disk image plist at \"%s\"\n" % (p
))
554 fancy
= plistlib
.readPlist(p
)
557 sys
.stderr
.write("Error: Could not parse fancy disk image plist at \"%s\"\n" % (p
))
561 assert "window_bounds" not in fancy
or (isinstance(fancy
["window_bounds"], list) and len(fancy
["window_bounds"]) == 4)
562 assert "background_picture" not in fancy
or isinstance(fancy
["background_picture"], str)
563 assert "icon_size" not in fancy
or isinstance(fancy
["icon_size"], int)
564 assert "applications_symlink" not in fancy
or isinstance(fancy
["applications_symlink"], bool)
565 if "items_position" in fancy
:
566 assert isinstance(fancy
["items_position"], dict)
567 for key
, value
in fancy
["items_position"].items():
568 assert isinstance(value
, list) and len(value
) == 2 and isinstance(value
[0], int) and isinstance(value
[1], int)
571 sys
.stderr
.write("Error: Bad format of fancy disk image plist at \"%s\"\n" % (p
))
574 if "background_picture" in fancy
:
575 bp
= fancy
["background_picture"]
577 print("Fancy: Resolving background picture \"%s\"..." % bp
)
578 if not os
.path
.exists(bp
):
579 bp
= os
.path
.join(os
.path
.dirname(p
), bp
)
580 if not os
.path
.exists(bp
):
582 sys
.stderr
.write("Error: Could not find background picture at \"%s\" or \"%s\"\n" % (fancy
["background_picture"], bp
))
585 fancy
["background_picture"] = bp
589 # ------------------------------------------------
591 if os
.path
.exists("dist"):
593 print("+ Removing old dist folder +")
595 shutil
.rmtree("dist")
597 # ------------------------------------------------
599 if len(config
.volname
) == 1:
600 volname
= config
.volname
[0]
602 volname
= app_bundle_name
604 # ------------------------------------------------
606 target
= os
.path
.join("dist", "Bitcoin-Qt.app")
609 print("+ Copying source bundle +")
611 print(app_bundle
, "->", target
)
614 shutil
.copytree(app_bundle
, target
, symlinks
=True)
616 applicationBundle
= ApplicationBundleInfo(target
)
618 # ------------------------------------------------
621 print("+ Deploying frameworks +")
624 deploymentInfo
= deployFrameworksForAppBundle(applicationBundle
, config
.strip
, verbose
)
625 if deploymentInfo
.qtPath
is None:
626 deploymentInfo
.qtPath
= os
.getenv("QTDIR", None)
627 if deploymentInfo
.qtPath
is None:
629 sys
.stderr
.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
630 config
.plugins
= False
631 except RuntimeError as e
:
633 sys
.stderr
.write("Error: %s\n" % str(e
))
636 # ------------------------------------------------
640 print("+ Deploying plugins +")
643 deployPlugins(applicationBundle
, deploymentInfo
, config
.strip
, verbose
)
644 except RuntimeError as e
:
646 sys
.stderr
.write("Error: %s\n" % str(e
))
649 # ------------------------------------------------
651 if len(config
.add_qt_tr
) == 0:
654 if translations_dir
is not None:
655 qt_tr_dir
= translations_dir
657 if deploymentInfo
.qtPath
is not None:
658 qt_tr_dir
= os
.path
.join(deploymentInfo
.qtPath
, "translations")
660 sys
.stderr
.write("Error: Could not find Qt translation path\n")
662 add_qt_tr
= ["qt_%s.qm" % lng
for lng
in config
.add_qt_tr
[0].split(",")]
663 for lng_file
in add_qt_tr
:
664 p
= os
.path
.join(qt_tr_dir
, lng_file
)
666 print("Checking for \"%s\"..." % p
)
667 if not os
.path
.exists(p
):
669 sys
.stderr
.write("Error: Could not find Qt translation file \"%s\"\n" % (lng_file
))
672 # ------------------------------------------------
675 print("+ Installing qt.conf +")
677 with
open(os
.path
.join(applicationBundle
.resourcesPath
, "qt.conf"), "wb") as f
:
678 f
.write(qt_conf
.encode())
680 # ------------------------------------------------
682 if len(add_qt_tr
) > 0 and verbose
>= 2:
683 print("+ Adding Qt translations +")
685 for lng_file
in add_qt_tr
:
687 print(os
.path
.join(qt_tr_dir
, lng_file
), "->", os
.path
.join(applicationBundle
.resourcesPath
, lng_file
))
688 shutil
.copy2(os
.path
.join(qt_tr_dir
, lng_file
), os
.path
.join(applicationBundle
.resourcesPath
, lng_file
))
690 # ------------------------------------------------
692 if len(config
.add_resources
) > 0 and verbose
>= 2:
693 print("+ Adding additional resources +")
695 for p
in config
.add_resources
:
696 t
= os
.path
.join(applicationBundle
.resourcesPath
, os
.path
.basename(p
))
700 shutil
.copytree(p
, t
, symlinks
=True)
704 # ------------------------------------------------
706 if config
.sign
and 'CODESIGNARGS' not in os
.environ
:
707 print("You must set the CODESIGNARGS environment variable. Skipping signing.")
710 print("Code-signing app bundle %s"%(target
,))
711 subprocess
.check_call("codesign --force %s %s"%(os
.environ
['CODESIGNARGS'], target
), shell
=True)
713 # ------------------------------------------------
715 if config
.dmg
is not None:
717 #Patch in check_output for Python 2.6
718 if "check_output" not in dir( subprocess
):
719 def f(*popenargs
, **kwargs
):
720 if 'stdout' in kwargs
:
721 raise ValueError('stdout argument not allowed, it will be overridden.')
722 process
= subprocess
.Popen(stdout
=subprocess
.PIPE
, *popenargs
, **kwargs
)
723 output
, unused_err
= process
.communicate()
724 retcode
= process
.poll()
726 cmd
= kwargs
.get("args")
729 raise CalledProcessError(retcode
, cmd
)
731 subprocess
.check_output
= f
733 def runHDIUtil(verb
, image_basename
, **kwargs
):
734 hdiutil_args
= ["hdiutil", verb
, image_basename
+ ".dmg"]
735 if "capture_stdout" in kwargs
:
736 del kwargs
["capture_stdout"]
737 run
= subprocess
.check_output
740 hdiutil_args
.append("-quiet")
742 hdiutil_args
.append("-verbose")
743 run
= subprocess
.check_call
745 for key
, value
in kwargs
.items():
746 hdiutil_args
.append("-" + key
)
747 if not value
is True:
748 hdiutil_args
.append(str(value
))
750 return run(hdiutil_args
)
754 print("+ Creating .dmg disk image +")
756 print("+ Preparing .dmg disk image +")
759 dmg_name
= config
.dmg
761 spl
= app_bundle_name
.split(" ")
762 dmg_name
= spl
[0] + "".join(p
.capitalize() for p
in spl
[1:])
766 runHDIUtil("create", dmg_name
, srcfolder
="dist", format
="UDBZ", volname
=volname
, ov
=True)
767 except subprocess
.CalledProcessError
as e
:
768 sys
.exit(e
.returncode
)
771 print("Determining size of \"dist\"...")
773 for path
, dirs
, files
in os
.walk("dist"):
775 size
+= os
.path
.getsize(os
.path
.join(path
, file))
776 size
+= int(size
* 0.15)
779 print("Creating temp image for modification...")
781 runHDIUtil("create", dmg_name
+ ".temp", srcfolder
="dist", format
="UDRW", size
=size
, volname
=volname
, ov
=True)
782 except subprocess
.CalledProcessError
as e
:
783 sys
.exit(e
.returncode
)
786 print("Attaching temp image...")
788 output
= runHDIUtil("attach", dmg_name
+ ".temp", readwrite
=True, noverify
=True, noautoopen
=True, capture_stdout
=True)
789 except subprocess
.CalledProcessError
as e
:
790 sys
.exit(e
.returncode
)
792 m
= re
.search("/Volumes/(.+$)", output
.decode())
793 disk_root
= m
.group(0)
794 disk_name
= m
.group(1)
797 print("+ Applying fancy settings +")
799 if "background_picture" in fancy
:
800 bg_path
= os
.path
.join(disk_root
, ".background", os
.path
.basename(fancy
["background_picture"]))
801 os
.mkdir(os
.path
.dirname(bg_path
))
803 print(fancy
["background_picture"], "->", bg_path
)
804 shutil
.copy2(fancy
["background_picture"], bg_path
)
808 if fancy
.get("applications_symlink", False):
809 os
.symlink("/Applications", os
.path
.join(disk_root
, "Applications"))
811 # The Python appscript package broke with OSX 10.8 and isn't being fixed.
812 # So we now build up an AppleScript string and use the osascript command
813 # to make the .dmg file pretty:
814 appscript
= Template( """
816 tell application "Finder"
819 set current view of container window to icon view
820 set toolbar visible of container window to false
821 set statusbar visible of container window to false
822 set the bounds of container window to {$window_bounds}
823 set theViewOptions to the icon view options of container window
824 set arrangement of theViewOptions to not arranged
825 set icon size of theViewOptions to $icon_size
828 close -- close/reopen works around a bug...
830 update without registering applications
838 itemscript
= Template('set position of item "${item}" of container window to {${position}}')
840 if "items_position" in fancy
:
841 for name
, position
in fancy
["items_position"].items():
842 params
= { "item" : name
, "position" : ",".join([str(p
) for p
in position
]) }
843 items_positions
.append(itemscript
.substitute(params
))
847 "window_bounds" : "300,300,800,620",
849 "background_commands" : "",
850 "items_positions" : "\n ".join(items_positions
)
852 if "window_bounds" in fancy
:
853 params
["window_bounds"] = ",".join([str(p
) for p
in fancy
["window_bounds"]])
854 if "icon_size" in fancy
:
855 params
["icon_size"] = str(fancy
["icon_size"])
856 if bg_path
is not None:
857 # Set background file, then call SetFile to make it invisible.
858 # (note: making it invisible first makes set background picture fail)
859 bgscript
= Template("""set background picture of theViewOptions to file ".background:$bgpic"
860 do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """)
861 params
["background_commands"] = bgscript
.substitute({"bgpic" : os
.path
.basename(bg_path
), "disk" : params
["disk"]})
863 s
= appscript
.substitute(params
)
865 print("Running AppleScript:")
868 p
= subprocess
.Popen(['osascript', '-'], stdin
=subprocess
.PIPE
)
869 p
.communicate(input=s
.encode('utf-8'))
871 print("Error running osascript.")
874 print("+ Finalizing .dmg disk image +")
878 runHDIUtil("convert", dmg_name
+ ".temp", format
="UDBZ", o
=dmg_name
+ ".dmg", ov
=True)
879 except subprocess
.CalledProcessError
as e
:
880 sys
.exit(e
.returncode
)
882 os
.unlink(dmg_name
+ ".temp.dmg")
884 # ------------------------------------------------