1. Anthony Tuininga
  2. cx_Freeze


cx_Freeze / cx_Freeze / macdist.py

from distutils.core import Command
import distutils.errors
import distutils.util
import os
import stat
import subprocess

__all__ = [ "bdist_dmg", "bdist_mac" ]

"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">

class bdist_dmg(Command):
    description = "create a Mac DMG disk image containing the Mac application bundle"
    user_options = [
        ('volume-label=', None, 'Volume label of the DMG disk image'),

    def initialize_options(self):
        self.volume_label = self.distribution.get_fullname()

    def finalize_options(self):

    def buildDMG(self):
        # Remove DMG if it already exists
        if os.path.exists(self.dmgName):

        # Create the dmg
        if os.spawnlp(os.P_WAIT,'hdiutil','hdiutil','create','-fs','HFSX',
            '-format','UDZO',self.dmgName, '-imagekey', 'zlib-level=9',
            raise OSError('creation of the dmg failed')

    def run(self):
        # Create the application bundle

        # Find the location of the application bundle and the build dir
        self.bundleDir = self.get_finalized_command('bdist_mac').bundleDir
        self.buildDir = self.get_finalized_command('build').build_base

        # Set the file name of the DMG to be built
        self.dmgName = os.path.join(self.buildDir, self.distribution.get_fullname() + '.dmg')


class bdist_mac(Command):
    description = "create a Mac application bundle"

    user_options = [
        ('bundle-iconfile=', None, 'Name of the application bundle icon file as stored in the '
                'Info.plist file'),
        ('qt-menu-nib=', None, 'Location of qt_menu.nib folder for Qt applications. '
                'Will be auto-detected by default.'),

    def initialize_options(self):
        self.bundle_iconfile = 'icon.icns'
        self.qt_menu_nib = False

    def finalize_options(self):

    def create_plist(self):
        """Create the Contents/Info.plist file"""
        plist = open(os.path.join(self.contentsDir, 'Info.plist'),'w')
        plist.write(PLIST_TEMPLATE % self.__dict__)

    def setRelativeReferencePaths(self):
        """ For all files in Contents/MacOS, check if they are binaries
            with references to other files in that dir. If so, make those references
            relative. The appropriate commands are applied to all files; they will
            just fail for files on which they do not apply."""
        files = os.listdir(self.binDir)
        for file in files:
            filepath = os.path.join(self.binDir, file)

            #Ensure write permissions
            mode = os.stat(filepath).st_mode
            if not (mode & stat.S_IWUSR):
                os.chmod(filepath, mode | stat.S_IWUSR)

            #Let the file itself know its place

            #Find the references: call otool -L on the file and read lines [1:]
            otool = subprocess.Popen(('otool','-L', filepath),stdout=subprocess.PIPE)
            references = otool.stdout.readlines()[1:]

            for reference in references:
                # Find the actual referenced file name
                referencedFile = reference.decode().strip().split()[0]

                if referencedFile.startswith('@'):
                    #The referencedFile is already a relative path

                path, name = os.path.split(referencedFile)

                # See if we provide the referenced file, if so, change the reference
                if name in files:
                    subprocess.call(('install_name_tool', '-change', referencedFile, newReference,

    def find_qt_menu_nib(self):
        """Returns a list of locations to try for a qt_menu.nib folder, or None
        if this is not a Qt application.
        if self.qt_menu_nib:
            return [self.qt_menu_nib]
        elif any(n.startswith("PyQt4.QtCore" for n in os.listdir(self.binDir)):
            from PyQt4 import QtCore
        elif any(n.startswith("PySide.QtCore" for n in os.listdir(self.binDir)):
            from PyQt4 import QtCore
            return None
        libpath = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.LibrariesPath)
        return [os.path.join(libpath, 'QtGui.framework/Resources/qt_menu.nib'),
                os.path.join(libpath, 'Resources/qt_menu.nib'),
    def prepare_qt_app(self):
        """Add resource files for a Qt application. Should do nothing if the
        application does not use QtCore.
        nib_locn = self.find_qt_menu_nib()
        if nib_locn is None:
        # Copy qt_menu.nib
        self.copy_tree(nib_locn, os.path.join(self.resourcesDir, 'qt_menu.nib'))
        # qt.conf needs to exist, but needn't have any content
        f = open(os.path.join(self.resourcesDir, 'qt.conf'), "w")

    def run(self):
        build = self.get_finalized_command('build')

        # Define the paths within the application bundle
        self.bundleDir = os.path.join(build.build_base, self.distribution.get_fullname()+".app")
        self.contentsDir = os.path.join(self.bundleDir, 'Contents')
        self.resourcesDir = os.path.join(self.contentsDir, 'Resources')
        self.binDir = os.path.join(self.contentsDir, 'MacOS')

        #Find the executable name
        executable = self.distribution.executables[0].targetName
        _, self.bundle_executable=os.path.split(executable)

        # Build the app directory structure

        self.copy_tree(build.build_exe, self.binDir)

        # Create the Info.plist file

        # Make all references to libraries relative
        # For a Qt application, run some tweaks
        self.execute(self.prepare_qt_app, ())