Commits

Anthony Tuininga  committed f3cacc2

Added preliminary support for building Mac application images and DMG image files. Thanks to Rob
Reilink for the work done on this.

  • Participants
  • Parent commits 37e3680

Comments (0)

Files changed (4)

File cx_Freeze/__init__.py

 from cx_Freeze.dist import *
 if sys.platform == "win32" and sys.version_info[:2] >= (2, 5):
     from cx_Freeze.windist import *
+elif sys.platform == "darwin":
+    from cx_Freeze.macdist import *
 from cx_Freeze.finder import *
 from cx_Freeze.freezer import *
 from cx_Freeze.main import *

File cx_Freeze/dist.py

     if sys.platform == "win32":
         if sys.version_info[:2] >= (2, 5):
             _AddCommandClass(commandClasses, "bdist_msi", cx_Freeze.bdist_msi)
+    elif sys.platform == "darwin":
+        _AddCommandClass(commandClasses, "bdist_dmg", cx_Freeze.bdist_dmg)
+        _AddCommandClass(commandClasses, "bdist_mac", cx_Freeze.bdist_mac)
     else:
         _AddCommandClass(commandClasses, "bdist_rpm", cx_Freeze.bdist_rpm)
     _AddCommandClass(commandClasses, "build", build)

File 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" ]
+
+PLIST_TEMPLATE = \
+"""<?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">
+<dict>
+	<key>CFBundleIconFile</key>
+	<string>%(bundle_iconfile)s</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>%(bundle_executable)s</string>
+</dict>
+</plist>
+"""
+
+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):
+        pass
+
+    def buildDMG(self):
+        # Remove DMG if it already exists
+        if os.path.exists(self.dmgName):
+            os.unlink(self.dmgName)
+
+        # Create the dmg
+        if os.spawnlp(os.P_WAIT,'hdiutil','hdiutil','create','-fs','HFSX',
+            '-format','UDZO',self.dmgName, '-imagekey', 'zlib-level=9',
+            '-srcfolder',self.bundleDir,'-volname',self.volume_label)!=0:
+            raise OSError('creation of the dmg failed')
+
+    def run(self):
+        # Create the application bundle
+        self.run_command('bdist_mac')
+
+        # 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')
+
+        self.execute(self.buildDMG,())
+
+
+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')
+    ]
+
+    def initialize_options(self):
+        self.bundle_iconfile = 'icon.icns'
+
+    def finalize_options(self):
+        pass
+
+    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__)
+        plist.close()
+
+    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
+            subprocess.call(('install_name_tool','-id','@executable_path/'+file,filepath))
+
+            #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
+                    continue
+
+                path, name = os.path.split(referencedFile)
+
+                # See if we provide the referenced file, if so, change the reference
+                if name in files:
+                    newReference='@executable_path/'+name
+                    subprocess.call(('install_name_tool', '-change', referencedFile, newReference,
+                            filepath))
+
+    def run(self):
+        self.run_command('build')
+        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.mkpath(self.resourcesDir)
+        self.mkpath(self.binDir)
+
+        self.copy_tree(build.build_exe, self.binDir)
+
+        # Create the Info.plist file
+        self.execute(self.create_plist,())
+
+        # Make all references to libraries relative
+        self.execute(self.setRelativeReferencePaths,())
+

File samples/PyQt4/setup.py

         name = "simple_PyQt4",
         version = "0.1",
         description = "Sample cx_Freeze PyQt4 script",
+        options = {"build_exe" : {"includes" : "atexit" }},
         executables = [Executable("PyQt4app.py", base = base)])