#!/usr/bin/env python3 # -*- coding: utf-8 -*- #============================================================================================= # File: "macbuild/makeDMG4mac.py" # # Python script for making a DMG file of KLayout (http://www.klayout.de/index.php) bundle. # # This is a derivative work of Ref. 2) below. Refer to "macbuild/LICENSE" file. # Ref. # 1) https://el-tramo.be/guides/fancy-dmg/ # 2) https://github.com/andreyvit/create-dmg.git #============================================================================================= from time import sleep import sys import os import re import shutil import zipfile import glob import platform import optparse import subprocess import hashlib import string #------------------------------------------------------------------------------- ## To import global dictionaries of different modules and utility functions #------------------------------------------------------------------------------- mydir = os.path.dirname(os.path.abspath(__file__)) sys.path.append( mydir + "/macbuild" ) from build4mac_util import * #------------------------------------------------------------------------------- ## To set global variables including present directory and platform info. #------------------------------------------------------------------------------- def SetGlobals(): global ProjectDir # project directory where "build.sh" exists global Usage # string on usage global LatestOS # the latest generic OS name global GenOSName # generic OS name global Platform # platform global PkgDir # the package directory where "klayout.app" exists global UnsafePkg # flags whether to proceed to making "invalid" dmg global OpClean # 'clean' operation global OpMake # 'make' operation global DefaultBundleName # the default bundle name 'klayout.app' global BundleName # application bundle name in the DMG global DMGSerialNum # the DMG serial number global PackagePrefix # the package prefix: LW-', 'HW-', or 'EX-' global QtIdentification # Qt identification global BuildType # build type ['release', 'debug'] global RubyPythonID # Ruby- and Python-identification global KLVersion # KLayout's version global OccupiedDS # approx. occupied disc space global BackgroundPNG # the background PNG image file global VolumeIcons # the volume icon file global AppleScriptDMG # the AppleScript for KLayout DMG global WorkDMG # work DMG file created under ProjectDir/ global VolumeDMG # the volume name of DMG global TargetDMG # the name of target DMG file global RootApplications # reserved directory name for applications global LatestOSMacPorts # True if 'LatestOS with MacPorts' and targeting LW-* global LatestOSHomebrew # True if 'LatestOS with Homebrew' and targeting LW-* global LatestOSAnaconda3 # True if 'LatestOS with Anaconda3' and targeting LW-* global LatestOSHomebrewH # True if 'LatestOS with Homebrew' and targeting HW-* global DicStdLightHeavyW # dictionary for LW-* and HW-* packages global Item3AppleScript # ITEM_3 in the Apple script # auxiliary variables on platform global System # 6-tuple from platform.uname() global Node # - do - global Release # - do - global Version # - do - global Machine # - do - global Processor # - do - global Bit # machine bit-size Usage = "\n" Usage += "---------------------------------------------------------------------------------------------------------\n" Usage += "<< Usage of 'makeDMG4mac.py' >>\n" Usage += " for making a DMG file of KLayout 0.30.2 or later on different Apple macOS platforms.\n" Usage += "\n" Usage += "$ [python] ./makeDMG4mac.py\n" Usage += " option & argument : descriptions | default value\n" Usage += " ----------------------------------------------------------------------------------+-----------------\n" Usage += " <-p|--pkg > : package directory created by `build4mac.py` with [-y|-Y] | ``\n" Usage += " : like 'LW-qt5MP.pkg.macos-Sequoia-release-Rmp33Pmp312' | \n" Usage += " <-c|--clean> : clean the work directory | disabled\n" Usage += " <-m|--make> : make a compressed DMG file | disabled\n" Usage += " : <-c|--clean> and <-m|--make> are mutually exclusive | \n" Usage += " [-b|--bundle ] : forcibly use this bundle name in the DMG | ''\n" Usage += " [-s|--serial ] : DMG serial number | 1\n" Usage += " [-u|--unsafe] : Ignores a few checks (use with caution) | disabled\n" Usage += " [-t|--targetdmg] : Specify output .dmg filename | chosen by script\n" Usage += " [-?|--?] : print this usage and exit | disabled\n" Usage += "-------------------------------------------------------------------------------------+-------------------\n" ProjectDir = os.getcwd() (System, Node, Release, Version, Machine, Processor) = platform.uname() if not System == "Darwin": print("") print( "!!! Sorry. Your system <%s> looks like non-Mac" % System, file=sys.stderr ) print(Usage) quit() release = int( Release.split(".")[0] ) # take the first of ['21', '0', '0'] LatestOS = "" if release == 24: GenOSName = "macOS" Platform = "Sequoia" LatestOS = Platform elif release == 23: GenOSName = "macOS" Platform = "Sonoma" LatestOS = Platform elif release == 22: GenOSName = "macOS" Platform = "Ventura" LatestOS = Platform elif release == 21: GenOSName = "macOS" Platform = "Monterey" LatestOS = Platform else: Platform = "" print("") print( "!!! Sorry. Unsupported major OS release <%d>" % release, file=sys.stderr ) print(Usage) sys.exit(1) if not Machine == "x86_64": # with an Apple Silicon Chip? if Machine == "arm64" and Platform in ["Sequoia", "Sonoma", "Ventura", "Monterey"]: print("") print( "### Your Mac equips an Apple Silicon Chip ###" ) print("") else: print("") print( "!!! Sorry. Only x86_64/arm64 architecture machine is supported but found <%s>" % Machine, file=sys.stderr ) print(Usage) sys.exit(1) PkgDir = "" UnsafePkg = False OpClean = False OpMake = False DefaultBundleName = "klayout.app" BundleName = "" DMGSerialNum = 1 PackagePrefix = "" QtIdentification = "" BuildType = "" RubyPythonID = "" KLVersion = GetKLayoutVersionFrom( "./version.sh" ) OccupiedDS = -1 BackgroundPNG = None VolumeIcons = "KLayoutHDD.icns" AppleScriptDMG = "macbuild/Resources/KLayoutDMG.applescript" WorkDMG = "work-KLayout.dmg" VolumeDMG = "KLayout" TargetDMG = "" RootApplications = "/Applications" LatestOSMacPorts = False LatestOSHomebrew = False LatestOSAnaconda3 = False LatestOSHomebrewH = False DicStdLightHeavyW = dict() Item3AppleScript = "" # Populate DicStdLightHeavyW DicStdLightHeavyW[ "std" ] = dict() # ST-* DicStdLightHeavyW[ "ports" ] = dict() # LW-* DicStdLightHeavyW[ "brew" ] = dict() # LW-* DicStdLightHeavyW[ "ana3" ] = dict() # LW-* DicStdLightHeavyW[ "brewH" ] = dict() # HW-* # "pbrew" is the alternative of "brew" using MacPorts' Qt and Homebrew's (Ruby, Python) # "pbrewHW" is the alternative of "brewH" using MacPorts' Qt and Homebrew's Python DicStdLightHeavyW[ "pbrew" ] = dict() # LW-* DicStdLightHeavyW[ "pbrewHW" ] = dict() # HW-* DicStdLightHeavyW[ "std" ]["zip"] = "macbuild/Resources/script-bundle-S.zip" DicStdLightHeavyW[ "std" ]["src"] = "script-bundle-S" DicStdLightHeavyW[ "std" ]["des"] = "MacStdUser-ReadMeFirst" DicStdLightHeavyW[ "std" ]["item3"] = 'set position of item "MacStdUser-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "ports" ]["zip"] = "macbuild/Resources/script-bundle-P.zip" DicStdLightHeavyW[ "ports" ]["src"] = "script-bundle-P" DicStdLightHeavyW[ "ports" ]["des"] = "MacPortsUser-ReadMeFirst" DicStdLightHeavyW[ "ports" ]["item3"] = 'set position of item "MacPortsUser-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "brew" ]["zip"] = "macbuild/Resources/script-bundle-B.zip" DicStdLightHeavyW[ "brew" ]["src"] = "script-bundle-B" DicStdLightHeavyW[ "brew" ]["des"] = "HomebrewUser-ReadMeFirst" DicStdLightHeavyW[ "brew" ]["item3"] = 'set position of item "HomebrewUser-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "ana3" ]["zip"] = "macbuild/Resources/script-bundle-A.zip" DicStdLightHeavyW[ "ana3" ]["src"] = "script-bundle-A" DicStdLightHeavyW[ "ana3" ]["des"] = "Anaconda3User-ReadMeFirst" DicStdLightHeavyW[ "ana3" ]["item3"] = 'set position of item "Anaconda3User-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "brewH" ]["zip"] = "macbuild/Resources/script-bundle-H.zip" DicStdLightHeavyW[ "brewH" ]["src"] = "script-bundle-H" DicStdLightHeavyW[ "brewH" ]["des"] = "Homebrew-HUser-ReadMeFirst" DicStdLightHeavyW[ "brewH" ]["item3"] = 'set position of item "Homebrew-HUser-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "pbrew" ]["zip"] = "macbuild/Resources/script-bundle-B.zip" DicStdLightHeavyW[ "pbrew" ]["src"] = "script-bundle-B" DicStdLightHeavyW[ "pbrew" ]["des"] = "HomebrewUser-ReadMeFirst" DicStdLightHeavyW[ "pbrew" ]["item3"] = 'set position of item "HomebrewUser-ReadMeFirst" to {700, 400}' DicStdLightHeavyW[ "pbrewHW" ]["zip"] = "macbuild/Resources/script-bundle-H.zip" DicStdLightHeavyW[ "pbrewHW" ]["src"] = "script-bundle-H" DicStdLightHeavyW[ "pbrewHW" ]["des"] = "Homebrew-HUser-ReadMeFirst" DicStdLightHeavyW[ "pbrewHW" ]["item3"] = 'set position of item "Homebrew-HUser-ReadMeFirst" to {700, 400}' #------------------------------------------------------------------------------ ## To check the contents of the package directory # # The package directory name should look like: # * ST-qt5MP.pkg.macos-Sequoia-release-RsysPsys # * LW-qt5Ana3.pkg.macos-Sequoia-release-Rana3Pana3 # * LW-qt6Brew.pkg.macos-Sequoia-release-Rhb34Phb312 --- (1) # * LW-qt5MP.pkg.macos-Sequoia-release-Rmp33Pmp312 # * HW-qt6Brew.pkg.macos-Sequoia-release-RsysPhb311 # # * ST-qt6MP.pkg.macos-Sequoia-release-RsysPsys # * LW-qt6MP.pkg.macos-Sequoia-release-Rmp33Pmp312 # # Generated DMG will be, for example, # (1) ---> LW-klayout-0.30.2-macOS-Sequoia-1-qt6Brew-Rhb34Phb312.dmg # # @return on success, positive integer in [MB] that tells approx. occupied disc space; # on failure, -1 #------------------------------------------------------------------------------ def CheckPkgDirectory(): global PkgDir global UnsafePkg global Platform global OpClean global OpMake global DefaultBundleName global BundleName global PackagePrefix global QtIdentification global BuildType global RubyPythonID global BackgroundPNG global LatestOSMacPorts global LatestOSHomebrew global LatestOSAnaconda3 global LatestOSHomebrewH global DicStdLightHeavyW global Item3AppleScript #----------------------------------------------------------------------------- # [1] Check the contents of the package directory #----------------------------------------------------------------------------- if PkgDir == "": print( "! Package directory is not specified", file=sys.stderr ) print(Usage) return -1 if not os.path.isdir(PkgDir): print( "! Specified package directory <%s> does not exist" % PkgDir, file=sys.stderr ) print( "" ) return -1 #----------------------------------------------------------------------------------------------- # [2] Identify (Qt, Ruby, Python) from PkgDir # * ST-qt5MP.pkg.macos-Sequoia-release-RsysPsys # * LW-qt5Ana3.pkg.macos-Sequoia-release-Rana3Pana3 # * LW-qt6Brew.pkg.macos-Sequoia-release-Rhb34Phb312 # * LW-qt5MP.pkg.macos-Sequoia-release-Rmp33Pmp312 # * HW-qt6Brew.pkg.macos-Sequoia-release-RsysPhb311 # * EX-qt5MP.pkg.macos-Sequoia-release-Rhb34Pmp312 # # * ST-qt6MP.pkg.macos-Sequoia-release-RsysPsys # * LW-qt6MP.pkg.macos-Sequoia-release-Rmp33Pmp312 #----------------------------------------------------------------------------------------------- # 0 1 2 3 4 5 6 7 patQRP = r'(ST|LW|HW|EX)([-])([qt5|qt6][0-9A-Za-z]+)([.]pkg[.])([A-Za-z]+[-][A-Za-z]+[-])(release|debug)([-])([0-9A-Za-z]+)' regQRP = re.compile(patQRP) if not regQRP.match(PkgDir): print( "! Cannot identify (Qt, Ruby, Python) from the package directory name" ) if UnsafePkg: print( "! Ignoring..." ) else: print( "" ) return -1 else: pkgdirComponents = regQRP.match(PkgDir).groups() PackagePrefix = pkgdirComponents[0] QtIdentification = pkgdirComponents[2] if QtIdentification.find('qt5') == 0: BackgroundPNG = "KLayoutDMG-BackQt5.png" elif QtIdentification.find('qt6') == 0: BackgroundPNG = "KLayoutDMG-BackQt6.png" else: BackgroundPNG = None raise Exception( "! neither qt5 nor qt6" ) if pkgdirComponents[5] == 'release': BuildType = 'release' elif pkgdirComponents[5] == 'debug': BuildType = 'debug' else: BuildType = None raise Exception( "! neither release nor debug" ) RubyPythonID = pkgdirComponents[7] #----------------------------------------------------------------------------- # [3] Check if the "LatestOS" with MacPorts / Homebrew / Anaconda3 #----------------------------------------------------------------------------- LatestOSSys = Platform == LatestOS LatestOSSys &= PackagePrefix == "ST" LatestOSSys &= QtIdentification in [ "qt5MP", "qt6MP" ] LatestOSSys &= RubyPythonID in [ "RsysPsys" ] LatestOSMacPorts = Platform == LatestOS LatestOSMacPorts &= PackagePrefix == "LW" LatestOSMacPorts &= QtIdentification in [ "qt5MP", "qt6MP" ] LatestOSMacPorts &= RubyPythonID in [ "Rmp33Pmp312", "Rmp33Pmp311" ] LatestOSHomebrew = Platform == LatestOS LatestOSHomebrew &= PackagePrefix == "LW" LatestOSHomebrew &= QtIdentification in [ "qt5Brew", "qt6Brew", "qt5MP", "qt6MP" ] # "qt[5|6]MP" are the alternatives LatestOSHomebrew &= RubyPythonID in [ "Rhb34Phb312", "Rhb34Phb311", "Rhb34Phbauto" ] LatestOSAnaconda3 = Platform == LatestOS LatestOSAnaconda3 &= PackagePrefix == "LW" LatestOSAnaconda3 &= QtIdentification in [ "qt5Ana3" ] LatestOSAnaconda3 &= RubyPythonID in [ "Rana3Pana3" ] LatestOSHomebrewH = Platform == LatestOS LatestOSHomebrewH &= PackagePrefix == "HW" LatestOSHomebrewH &= QtIdentification in [ "qt5Brew", "qt6Brew", "qt5MP", "qt6MP" ] # "qt[5|6]MP" are the alternatives LatestOSHomebrewH &= RubyPythonID in [ "RsysPhb311", "RsysPhbauto" ] # Sys-Homebrew hybrid if LatestOSSys: mydic = DicStdLightHeavyW["std"] srcDir = PkgDir + "/" + mydic["src"] desDir = PkgDir + "/" + mydic["des"] if OpMake: with zipfile.ZipFile( mydic["zip"], 'r' ) as zip_ref: zip_ref.extractall(PkgDir) os.rename( srcDir, desDir ) if OpClean: if os.path.isdir(srcDir): shutil.rmtree(srcDir) if os.path.isdir(desDir): shutil.rmtree(desDir) Item3AppleScript = mydic["item3"] if LatestOSMacPorts: mydic = DicStdLightHeavyW["ports"] srcDir = PkgDir + "/" + mydic["src"] desDir = PkgDir + "/" + mydic["des"] if OpMake: with zipfile.ZipFile( mydic["zip"], 'r' ) as zip_ref: zip_ref.extractall(PkgDir) os.rename( srcDir, desDir ) if OpClean: if os.path.isdir(srcDir): shutil.rmtree(srcDir) if os.path.isdir(desDir): shutil.rmtree(desDir) Item3AppleScript = mydic["item3"] if LatestOSHomebrew: mydic = DicStdLightHeavyW["brew"] srcDir = PkgDir + "/" + mydic["src"] desDir = PkgDir + "/" + mydic["des"] if OpMake: with zipfile.ZipFile( mydic["zip"], 'r' ) as zip_ref: zip_ref.extractall(PkgDir) os.rename( srcDir, desDir ) if OpClean: if os.path.isdir(srcDir): shutil.rmtree(srcDir) if os.path.isdir(desDir): shutil.rmtree(desDir) Item3AppleScript = mydic["item3"] if LatestOSAnaconda3: mydic = DicStdLightHeavyW["ana3"] srcDir = PkgDir + "/" + mydic["src"] desDir = PkgDir + "/" + mydic["des"] if OpMake: with zipfile.ZipFile( mydic["zip"], 'r' ) as zip_ref: zip_ref.extractall(PkgDir) os.rename( srcDir, desDir ) if OpClean: if os.path.isdir(srcDir): shutil.rmtree(srcDir) if os.path.isdir(desDir): shutil.rmtree(desDir) Item3AppleScript = mydic["item3"] if LatestOSHomebrewH: mydic = DicStdLightHeavyW["brewH"] srcDir = PkgDir + "/" + mydic["src"] desDir = PkgDir + "/" + mydic["des"] if OpMake: with zipfile.ZipFile( mydic["zip"], 'r' ) as zip_ref: zip_ref.extractall(PkgDir) os.rename( srcDir, desDir ) if OpClean: if os.path.isdir(srcDir): shutil.rmtree(srcDir) if os.path.isdir(desDir): shutil.rmtree(desDir) Item3AppleScript = mydic["item3"] #------------------------------------------------------ # [4] Check the presence of the default bundle #------------------------------------------------------ os.chdir(PkgDir) if not os.path.isdir( DefaultBundleName ): print( "! The package directory <%s> does not hold <%s> bundle" % (PkgDir, DefaultBundleName), file=sys.stderr ) print( "" ) os.chdir(ProjectDir) return -1 #------------------------------------------------------ # [5] Check the occupied disk space #------------------------------------------------------ command = r"\du -sm %s" % DefaultBundleName sizeApp = int( os.popen(command).read().strip("\n").split("\t")[0] ) #------------------------------------------------------ # [6] Change the application bundle name if required #------------------------------------------------------ if OpMake and BundleName != "" and BundleName != DefaultBundleName: os.rename( DefaultBundleName, BundleName ) os.chdir(ProjectDir) return sizeApp #------------------------------------------------------------------------------ ## To get command line parameters #------------------------------------------------------------------------------ def ParseCommandLineArguments(): global ProjectDir global Usage global GenOSName global Platform global PkgDir global OpClean global OpMake global BundleName global DMGSerialNum global UnsafePkg global PackagePrefix global QtIdentification global BuildType global RubyPythonID global KLVersion global OccupiedDS global TargetDMG global Machine p = optparse.OptionParser( usage=Usage ) p.add_option( '-p', '--pkg', dest='pkg_dir', help="the pkg directory" ) p.add_option( '-c', '--clean', action='store_true', dest='operation_clean', default=False, help="clean operation" ) p.add_option( '-m', '--make', action='store_true', dest='operation_make', default=False, help="make operation" ) p.add_option( '-b', '--bundle', dest='bundle_name', help="forcibly use this bundle name in the DMG" ) p.add_option( '-s', '--serial', dest='dmg_serial', help="DMG serial number" ) p.add_option( '-t', '--targetdmg', dest='target_dmg', help="output DMG filename" ) p.add_option( '-z', '--unsafe', action='store_true', dest='unsafe', default=False, help="If set, do not check whether pkg folder is empty" ) p.add_option( '-?', '--??', action='store_true', dest='checkusage', default=False, help='check usage' ) p.set_defaults( pkg_dir = "", operation_clean = False, operation_make = False, bundle_name = "", target_dmg = "", dmg_serial = "1", unsafe = False, checkusage = False ) #----------------------------------------------------------- # [1] Parse the command line options #----------------------------------------------------------- opt, args = p.parse_args() if (opt.checkusage): print(Usage) quit() PkgDir = opt.pkg_dir OpClean = opt.operation_clean OpMake = opt.operation_make DMGSerialNum = int(opt.dmg_serial) UnsafePkg = opt.unsafe if not opt.bundle_name == "": base, ext = os.path.splitext( os.path.basename(opt.bundle_name) ) BundleName = base + ".app" else: BundleName = DefaultBundleName if (OpClean and OpMake) or (not OpClean and not OpMake): print( "! Specify <-c|--clean> OR <-m|--make>", file=sys.stderr ) print(Usage) quit() #------------------------------------------------------------------------------------ # [2] Check the PKG directory to set QtIdentification, RubyPythonID, and BundleName #------------------------------------------------------------------------------------ OccupiedDS = CheckPkgDirectory() if not 0 < OccupiedDS and not UnsafePkg: print( "! Failed to check the PKG directory" ) print( "" ) quit() if opt.target_dmg != "": TargetDMG = opt.target_dmg else: TargetDMG = "%s-klayout-%s-%s-%s-%d-%s-%s.dmg" \ % (PackagePrefix, KLVersion, GenOSName, Platform, DMGSerialNum, QtIdentification, RubyPythonID) if Machine == "arm64": # with an Apple Silicon Chip TargetDMG = Machine + TargetDMG if BuildType == "debug": # in the case of 'debug' build TargetDMG = "debug-" + TargetDMG return #------------------------------------------------------------------------------ ## Make the target DMG file # # @param[in] msg message to print # # @return True on success; False on failure #------------------------------------------------------------------------------ def MakeTargetDMGFile(msg=""): #----------------------------------------------------------------------- # The work DMG is mounted like: # /dev/disk6s1 248Mi 228Mi 20Mi 93% 58449 5027 92% /Volumes/KLayout #----------------------------------------------------------------------- global MountDir # the mount directory: eg. /Volumes/KLayout global FileSys # the file system : eg. /dev/disk6s1 #------------------------------------------------------------- # [1] Print message #------------------------------------------------------------- if not msg == "": print(msg) #------------------------------------------------------------- # [2] Do the following jobs (0) through (14) sequentially #------------------------------------------------------------- #-------------------------------------------------------- # (0) Cleanup ProjectDir/ #-------------------------------------------------------- CleanUp() #-------------------------------------------------------- # (1) Read the AppleScript template file and generate # the actual one to execute later #-------------------------------------------------------- os.chdir(ProjectDir) print( ">>> (1) Preparing AppleScript to execute later..." ) tempScr = "macbuild/Resources/template-KLayoutDMG.applescript" try: fd = open( tempScr, "r" ) tmpl = fd.read() fd.close() except Exception as e: print( " ! Failed to read <%s>" % tempScr, file=sys.stderr ) return False else: t = string.Template(tmpl) # Figures below were determined by experiments for best fit applescript = t.safe_substitute( ORGX='50', ORGY='100', WIN_WIDTH='1000', WIN_HEIGHT='540', FULL_PATH_DS_STORE='/Volumes/%s/.DS_Store' % VolumeDMG, BACKGROUND_PNG_FILE=BackgroundPNG, ITEM_1='%s' % BundleName, X1='900', Y1='165', ITEM_2='Applications', X2='900', Y2='345', ITEM_3=Item3AppleScript, CHECK_BASH='[ -f " & dotDSStore & " ]; echo $?' ) try: # print(applescript) fd = open( AppleScriptDMG, "w" ) fd.write(applescript) fd.close() except Exception as e: print( "! Failed to write <%s>" % AppleScriptDMG, file=sys.stderr ) return False else: print( " saved <%s>" % AppleScriptDMG ) #---------------------------------------------------- # (2) Create a work disk image under ProjectDir/ #---------------------------------------------------- if os.path.exists(WorkDMG): os.remove(WorkDMG) dmgsize = OccupiedDS + int(0.2*OccupiedDS) # approx. occupied size plus 20[%] cmdline = 'hdiutil create -srcfolder %s -volname %s -fs HFS+ -fsargs "-c c=64,a=16,e=16" ' cmdline += '-format UDRW -size %dm %s' command = cmdline % (PkgDir, VolumeDMG, dmgsize, WorkDMG) print( ">>> (2) Creating a work DMG file <%s> of <%d> [MB] with the volume name of <%s>..." % (WorkDMG, dmgsize, VolumeDMG) ) os.system(command) MountDir = "/Volumes/%s" % VolumeDMG #-------------------------------------------------------- # (3) Check if the mount point 'MountDir' already exists. # If so, unmount it first. #-------------------------------------------------------- command1 = "hdiutil info | grep %s | grep \"/dev/\" | awk '{print $1}'" % VolumeDMG print ( ">>> (3) Checking if the mount point <%s> already exists..." % MountDir) FileSys = os.popen(command1).read().strip('\n') if os.path.isdir(MountDir) and not FileSys == "": command2 = "hdiutil detach %s" % FileSys os.system(command2) print( " Mount directory <%s> was detached" % MountDir ) else: print( " Mount directory <%s> does not exist; nothing to do" % MountDir ) #-------------------------------------------------------- # (4) Mount the DMG #-------------------------------------------------------- print( ">>> (4) Mounting <%s> to <%s>" % (WorkDMG, MountDir ) ) command1 = "hdiutil attach %s -readwrite -noverify -quiet -noautoopen" % WorkDMG os.system(command1) command2 = "hdiutil info | grep %s | grep \"/dev/\" | awk '{print $1}'" % VolumeDMG FileSys = os.popen(command2).read().strip('\n') if FileSys == "": print( "! Failed to identify the file system on which <%s> is mounted" % VolumeDMG ) return False else: print( " File System = %s" % FileSys ) #-------------------------------------------------------- # (5) Copy the background image #-------------------------------------------------------- print( ">>> (5) Copying the background image..." ) imageSrc = "macbuild/Resources/%s" % BackgroundPNG imageDest = "%s/.background" % MountDir if not os.path.isdir(imageDest): os.mkdir(imageDest) command = r"\cp -p %s %s/%s" % (imageSrc, imageDest, BackgroundPNG) os.system(command) #-------------------------------------------------------- # (6) Create a symbolic link to /Applications #-------------------------------------------------------- print( ">>> (6) Creating a symbolic link to /Applications..." ) command = r"\ln -s %s %s/%s" % (RootApplications, MountDir, RootApplications) os.system(command) #-------------------------------------------------------- # (7) Run the AppleScript #-------------------------------------------------------- print( ">>> (7) Running the AppleScript..." ) command = "/usr/bin/osascript %s %s" % (AppleScriptDMG, VolumeDMG) process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) output, error = process.communicate() outputStr = output.decode("utf-8") errorStr = error.decode("utf-8") if not outputStr == "": print( " STDOUT: %s" % outputStr ) if not errorStr == "": print( " STDERR: %s" % errorStr ) #-------------------------------------------------------- # (8) Copy the custom volume icon #-------------------------------------------------------- sleep(4) print( ">>> (8) Copying the volume icon..." ) iconsSrc = "macbuild/Resources/%s" % VolumeIcons iconsDest = "%s/.VolumeIcon.icns" % MountDir command1 = r"\cp -p %s %s" % (iconsSrc, iconsDest) command2 = "SetFile -c icnC %s" % iconsDest os.system(command1) sleep(2) os.system(command2) sleep(2) #-------------------------------------------------------- # (9) Change the permission #-------------------------------------------------------- print( ">>> (9) Changing permission to 755..." ) command = r"\chmod -Rf 755 %s &> /dev/null" % MountDir os.system(command) #-------------------------------------------------------- # (10) Set volume bootability and startup disk options. # The folder will open on mount. #-------------------------------------------------------- print( ">>> (10) Setting volume bootability and startup disk options..." ) #command = "bless --folder %s --openfolder %s" % (MountDir, MountDir) #bless: The 'openfolder' option is deprecated command = "bless --folder %s" % MountDir os.system(command) sleep(2) #-------------------------------------------------------- # (11) Set attributes of files and directories #-------------------------------------------------------- print( ">>> (11) Setting attributes of files and directories..." ) command = "SetFile -a C %s" % MountDir # Custom icon (allowed on folders) os.system(command) sleep(2) #-------------------------------------------------------- # (12) Unmount the disk image #-------------------------------------------------------- print( ">>> (12) Unmounting the disk image..." ) command = "hdiutil detach %s" % FileSys os.system(command) #-------------------------------------------------------- # (13) Compress the disk image #-------------------------------------------------------- print( "" ) print( ">>> (13) Compressing the disk image..." ) command = "hdiutil convert %s -format UDZO -imagekey zlib-level=9 -o %s" % (WorkDMG, TargetDMG) os.system(command) os.remove(WorkDMG) print( "" ) print( " generated compressed target DMG <%s>" % TargetDMG ) #-------------------------------------------------------- # (14) Compute MD5 checksum #-------------------------------------------------------- print( "" ) print( ">>> (14) Computing MD5 checksum..." ) with open( TargetDMG, "rb" ) as f: data = f.read() md5 = hashlib.md5(data).hexdigest() md5 += " *%s\n" % TargetDMG f.close() path, ext = os.path.splitext( os.path.basename(TargetDMG) ) md5TargetDMG = path + ".dmg.md5" with open( md5TargetDMG, "w" ) as f: f.write(md5) f.close() print( " generated MD5 checksum file <%s>" % md5TargetDMG ) print( "" ) #------------------------------------------------------------------------- # [3] Rename back the application bundle to the default name if required #------------------------------------------------------------------------- if BundleName != "" and BundleName != DefaultBundleName: dirPresent = "%s/%s" % (PkgDir, BundleName) dirDefault = "%s/%s" % (PkgDir, DefaultBundleName) os.rename( dirPresent, dirDefault ) return True #------------------------------------------------------------------------------ ## Clean up # # @param[in] msg message to print # #------------------------------------------------------------------------------ def CleanUp(msg=""): #---------------------------------------------------- # [1] Print message #---------------------------------------------------- if not msg == "": print(msg) #---------------------------------------------------- # [2] Clean up *.dmg* #---------------------------------------------------- os.chdir(ProjectDir) dmgs = glob.glob( "*.dmg*" ) for item in dmgs: print("Removing %s" % item) os.system( "rm -Rf -- \"%s\"" % item ) #---------------------------------------------------- # [3] Clean up AppleScript if any #---------------------------------------------------- if os.path.exists(AppleScriptDMG) and os.path.isfile(AppleScriptDMG): os.remove(AppleScriptDMG) #------------------------------------------------------------------------------ ## The main function #------------------------------------------------------------------------------ def Main(): SetGlobals() ParseCommandLineArguments() if OpMake: print( "" ) print( " ### You are going to make <%s> from <%s>" % (TargetDMG, PkgDir) ) print( " KLayout bundles occupy about <%d> [MB] of disc space." % OccupiedDS ) print( "" ) ok = MakeTargetDMGFile() if not ok: print( " !!! Failed to make the target DMG <%s> ..." % TargetDMG, file=sys.stderr ) print( "", file=sys.stderr ) else: print( " ### Done making the target DMG" ) print( "" ) else: print( "" ) print( " ### You are going to clean up <%s> directory" % ProjectDir ) CleanUp() print( " ### Done cleaning up" ) print( "" ) #=================================================================================== if __name__ == "__main__": Main() #--------------- # End of file #---------------