#!/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
#---------------