mirror of
https://github.com/KLayout/klayout.git
synced 2026-08-28 17:06:31 +02:00
git merge failed, probably because there were two parallel attempts to merge master into pymod leading to opposite resolutions.
541 lines
20 KiB
Python
Executable File
541 lines
20 KiB
Python
Executable File
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#=============================================================================================
|
|
# File: "macbuild/makeDMG4mac.py"
|
|
#
|
|
# Python script for making a DMG file of KLayout (http://www.klayout.de/index.php) bundles.
|
|
#
|
|
# 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 __future__ import print_function # to use print() of Python 3 in Python >= 2.7
|
|
from time import sleep
|
|
import sys
|
|
import os
|
|
import shutil
|
|
import glob
|
|
import platform
|
|
import optparse
|
|
import subprocess
|
|
import hashlib
|
|
|
|
#-------------------------------------------------------------------------------
|
|
## 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 GenOSName # generic OS name
|
|
global Platform # platform
|
|
global PkgDir # the package directory where "klayout.app" and "klayout.scripts" exist
|
|
global OpClean # 'clean' operation
|
|
global OpMake # 'make' operation
|
|
global DMGSerialNum # the DMG serial number
|
|
global QtIdentification # Qt identification
|
|
global Version # 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
|
|
# 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.25 or later on different Apple Mac OSX platforms.\n"
|
|
Usage += "\n"
|
|
Usage += "$ [python] ./makeDMG4mac.py \n"
|
|
Usage += " option & argument : descriptions | default value\n"
|
|
Usage += " ----------------------------------------------------------------------------------+---------------\n"
|
|
Usage += " <-p|--pkg <dir>> : package directory created by `build4mac.py` with [-y|-Y] | `` \n"
|
|
Usage += " : like 'qt5.pkg.macos-HighSierra-release' | \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 += " [-q|--qt <ID>] : ID name of deployed Qt | Qt510Xmp \n"
|
|
Usage += " [-s|--serial <num>] : DMG serial number | 1 \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 ['14', '5', '0']
|
|
if release == 14:
|
|
GenOSName = "MacOSX"
|
|
Platform = "Yosemite"
|
|
elif release == 15:
|
|
GenOSName = "MacOSX"
|
|
Platform = "ElCapitan"
|
|
elif release == 16:
|
|
GenOSName = "macOS"
|
|
Platform = "Sierra"
|
|
elif release == 17:
|
|
GenOSName = "macOS"
|
|
Platform = "HighSierra"
|
|
else:
|
|
Platform = ""
|
|
print("")
|
|
print( "!!! Sorry. Unsupported major OS release <%d>" % release, file=sys.stderr )
|
|
print(Usage)
|
|
quit()
|
|
|
|
if not Machine == "x86_64":
|
|
print("")
|
|
print( "!!! Sorry. Only x86_64 architecture machine is supported but found <%s>" % Machine, file=sys.stderr )
|
|
print(Usage)
|
|
quit()
|
|
|
|
PkgDir = ""
|
|
OpClean = False
|
|
OpMake = False
|
|
DMGSerialNum = 1
|
|
QtIdentification = "Qt510Xmp"
|
|
Version = GetKLayoutVersionFrom( "./version.sh" )
|
|
OccupiedDS = -1
|
|
BackgroundPNG = "KLayoutDMG-Back.png"
|
|
VolumeIcons = "KLayoutHDD.icns"
|
|
AppleScriptDMG = "macbuild/Resources/KLayoutDMG.applescript"
|
|
WorkDMG = "work-KLayout.dmg"
|
|
VolumeDMG = "KLayout"
|
|
TargetDMG = ""
|
|
RootApplications = "/Applications"
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To check the contents of the package directory
|
|
#
|
|
# @return on success, positive integer in [MB] that tells approx. occupied disc space;
|
|
# on failure, -1
|
|
#------------------------------------------------------------------------------
|
|
def CheckPkgDirectory():
|
|
|
|
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
|
|
|
|
os.chdir(PkgDir)
|
|
items = glob.glob( "*" ) # must be ['klayout.app', 'klayout.scripts']
|
|
if not len(items) == 2:
|
|
print( "! The package directory <%s> must have just <2> directories ['klayout.app', 'klayout.scripts']" % PkgDir, file=sys.stderr )
|
|
print( "" )
|
|
os.chdir(ProjectDir)
|
|
return -1
|
|
|
|
if not os.path.isdir( "klayout.app" ):
|
|
print( "! The package directory <%s> does not hold <klayout.app> bundle" % PkgDir, file=sys.stderr )
|
|
print( "" )
|
|
os.chdir(ProjectDir)
|
|
return -1
|
|
|
|
if not os.path.isdir( "klayout.scripts" ):
|
|
print( "! The package directory <%s> does not hold <klayout.scripts> subdirectory" % PkgDir, file=sys.stderr )
|
|
print( "" )
|
|
os.chdir(ProjectDir)
|
|
return -1
|
|
|
|
os.chdir( "klayout.scripts" )
|
|
if not os.path.isdir( "KLayoutEditor.app" ):
|
|
print( "! The package directory <%s> does not hold <KLayoutEditor.app> bundle under 'klayout.scripts/'" % PkgDir, file=sys.stderr )
|
|
print( "" )
|
|
os.chdir(ProjectDir)
|
|
return -1
|
|
|
|
if not os.path.isdir( "KLayoutViewer.app" ):
|
|
print( "! The package directory <%s> does not hold <KLayoutViewer.app> bundle under 'klayout.scripts/'" % PkgDir, file=sys.stderr )
|
|
print( "" )
|
|
os.chdir(ProjectDir)
|
|
return -1
|
|
|
|
os.chdir(ProjectDir)
|
|
os.chdir(PkgDir)
|
|
size1 = int( os.popen( "\du -sm klayout.app" ) .read().strip("\n").split("\t")[0] )
|
|
size2 = int( os.popen( "\du -sm klayout.scripts" ).read().strip("\n").split("\t")[0] )
|
|
os.chdir(ProjectDir)
|
|
return size1+size2
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To get command line parameters
|
|
#------------------------------------------------------------------------------
|
|
def ParseCommandLineArguments():
|
|
global ProjectDir
|
|
global Usage
|
|
global GenOSName
|
|
global Platform
|
|
global PkgDir
|
|
global OpClean
|
|
global OpMake
|
|
global DMGSerialNum
|
|
global QtIdentification
|
|
global Version
|
|
global OccupiedDS
|
|
global TargetDMG
|
|
|
|
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( '-q', '--qt',
|
|
dest='qt_identification',
|
|
help="Qt's ID" )
|
|
|
|
p.add_option( '-s', '--serial',
|
|
dest='dmg_serial',
|
|
help="DMG serial number" )
|
|
|
|
p.add_option( '-?', '--??',
|
|
action='store_true',
|
|
dest='checkusage',
|
|
default=False,
|
|
help='check usage' )
|
|
|
|
p.set_defaults( pkg_dir = "",
|
|
operation_clean = False,
|
|
operation_make = False,
|
|
qt_identification = "Qt510Xmp",
|
|
dmg_serial = "1",
|
|
checkusage = False )
|
|
|
|
opt, args = p.parse_args()
|
|
if (opt.checkusage):
|
|
print(Usage)
|
|
quit()
|
|
|
|
PkgDir = opt.pkg_dir
|
|
OpClean = opt.operation_clean
|
|
OpMake = opt.operation_make
|
|
QtIdentification = opt.qt_identification
|
|
DMGSerialNum = int(opt.dmg_serial)
|
|
TargetDMG = "klayout-%s-%s-%s-%d-%s.dmg" % (Version, GenOSName, Platform, DMGSerialNum, QtIdentification)
|
|
|
|
OccupiedDS = CheckPkgDirectory()
|
|
if not OccupiedDS > 0:
|
|
quit()
|
|
|
|
if (OpClean and OpMake) or (not OpClean and not OpMake):
|
|
print( "! Specify <-c|--clean> OR <-m|--make>", file=sys.stderr )
|
|
print(Usage)
|
|
quit()
|
|
|
|
#------------------------------------------------------------------------------
|
|
## 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='700',
|
|
FULL_PATH_DS_STORE='/Volumes/%s/.DS_Store' % VolumeDMG,
|
|
BACKGROUND_PNG_FILE=BackgroundPNG,
|
|
ITEM_1='klayout.app', X1='960', Y1='140',
|
|
ITEM_2='klayout.scripts', X2='610', Y2='140',
|
|
ITEM_3='Applications', X3='790', Y3='140',
|
|
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 + 20 # approx. occupied size plus 20[MB]
|
|
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 = "\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 = "\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()
|
|
if not output == "":
|
|
print( " STDOUT: %s" % output )
|
|
if not error == "":
|
|
print( " STDERR: %s" % error )
|
|
|
|
#--------------------------------------------------------
|
|
# (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 = "\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 = "\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)
|
|
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( "" )
|
|
|
|
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:
|
|
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
|
|
#---------------
|