mirror of
https://github.com/KLayout/klayout.git
synced 2026-08-29 01:14:35 +02:00
This fix assigns proper library IDs and load paths for the dependent libraries even if they are distributed among several directories. This is in particular important for the stream and tool plugins which exist in a folder next to the application but have to refer to libraries from the Frameworks folder. The fix consists of extending the library analysis within build4mac.py and supplying an additional directory listing the target directories.
404 lines
16 KiB
Python
Executable File
404 lines
16 KiB
Python
Executable File
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#===============================================================================
|
|
# File: "macbuild/build4mac_util.py"
|
|
#
|
|
# Here are utility functions and classes ...
|
|
# for building KLayout (http://www.klayout.de/index.php)
|
|
# version 0.25 or later on different Apple Mac OSX platforms.
|
|
#
|
|
# This file is imported by 'build4mac.py' script.
|
|
#===============================================================================
|
|
from __future__ import print_function # to use print() of Python 3 in Python >= 2.7
|
|
import sys
|
|
import os
|
|
import re
|
|
import string
|
|
import subprocess
|
|
import shutil
|
|
|
|
#-------------------------------------------------------------------------------
|
|
## To import global dictionaries of different modules
|
|
#-------------------------------------------------------------------------------
|
|
mydir = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.append( mydir )
|
|
from build4mac_env import *
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To decompose strings obtained by 'otool -L <*.dylib>' command and to
|
|
# generate a dictionary of KLayout's inter-library dependency.
|
|
#
|
|
# @param[in] depstr strings that tell dependency such as:
|
|
#
|
|
# libklayout_edt.0.dylib:
|
|
# libklayout_edt.0.dylib (compatibility version 0.25.0, current version 0.25.0)
|
|
# libklayout_tl.0.dylib (compatibility version 0.25.0, current version 0.25.0)
|
|
# libklayout_gsi.0.dylib (compatibility version 0.25.0, current version 0.25.0)
|
|
# libklayout_laybasic.0.dylib (compatibility version 0.25.0, current version 0.25.0)
|
|
# libklayout_db.0.dylib (compatibility version 0.25.0, current version 0.25.0)
|
|
# :
|
|
# :
|
|
#
|
|
# @return a dictionary
|
|
#------------------------------------------------------------------------------
|
|
def DecomposeLibraryDependency( depstr ):
|
|
alllines = depstr.split('\n')
|
|
numlines = len(alllines)
|
|
dependent = alllines[0].split(':')[0].strip()
|
|
supporters = []
|
|
for line in alllines[1:]:
|
|
supporter = line.strip().split(' ')[0].strip()
|
|
if not supporter == '':
|
|
supporters.append(supporter)
|
|
return { dependent: supporters }
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To print the contents of a library dependency dictionary
|
|
#
|
|
# @param[in] depdic dictionary
|
|
# @param[in] namedic dictionary name
|
|
#------------------------------------------------------------------------------
|
|
def PrintLibraryDependencyDictionary( depdic, pathdic, namedic ):
|
|
keys = depdic.keys()
|
|
print("")
|
|
print("##### Contents of <%s> #####:" % namedic )
|
|
for key in keys:
|
|
supporters = depdic[key]
|
|
keyName = os.path.basename(key)
|
|
print( " %s: (%s)" % (key, pathdic[keyName]) )
|
|
for item in supporters:
|
|
itemName = os.path.basename(item)
|
|
if itemName != keyName and (itemName in pathdic):
|
|
print( " %s (%s)" % (item, pathdic[itemName]) )
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To set and change identification name of KLayout's dylib
|
|
#
|
|
# @param[in] libdic inter-library dependency dictionary
|
|
#
|
|
# @return 0 on success; non-zero on failure
|
|
#------------------------------------------------------------------------------
|
|
def SetChangeIdentificationNameOfDyLib( libdic, pathDic ):
|
|
cmdNameId = XcodeToolChain['nameID']
|
|
cmdNameChg = XcodeToolChain['nameCH']
|
|
dependentLibs = libdic.keys()
|
|
|
|
for lib in dependentLibs:
|
|
#-----------------------------------------------------------
|
|
# [1] Set the identification name of each dependent library
|
|
#-----------------------------------------------------------
|
|
nameOld = "%s" % lib
|
|
libName = os.path.basename(lib)
|
|
nameNew = pathDic[libName]
|
|
command = "%s %s %s" % ( cmdNameId, nameNew, nameOld )
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to set the new identification name to <%s> !!!"
|
|
print( msg % lib, file=sys.stderr )
|
|
return 1
|
|
|
|
#-------------------------------------------------------------------------
|
|
# [2] Make the library aware of the new identifications of all supporters
|
|
#-------------------------------------------------------------------------
|
|
supporters = libdic[lib]
|
|
for sup in supporters:
|
|
supName = os.path.basename(sup)
|
|
if libName != supName and (supName in pathDic):
|
|
nameOld = "%s" % sup
|
|
nameNew = pathDic[supName]
|
|
command = "%s %s %s %s" % ( cmdNameChg, nameOld, nameNew, lib )
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to make the library aware of the new identification name <%s> of supporter <%s> !!!"
|
|
print( msg % (nameNew, sup), file=sys.stderr )
|
|
return 1
|
|
# for-lib
|
|
return 0
|
|
|
|
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To set the identification names of KLayout's libraries to an executable
|
|
# and make the application aware of the library locations
|
|
#
|
|
# @param[in] executable path/to/executable -- (1)
|
|
# @param[in] relativedir directory of dylib relative to executable -- (2)
|
|
#
|
|
# Example: (1) "klayout.app/Contents/MacOS/klayout"
|
|
# (2) "../Frameworks"
|
|
#
|
|
# klayout.app/+
|
|
# +-- Contents/+
|
|
# +-- Info.plist
|
|
# +-- PkgInfo
|
|
# +-- Resources/+
|
|
# | +-- 'klayout.icns'
|
|
# +-- Frameworks/+
|
|
# | +-- '*.framework'
|
|
# | +-- '*.dylib'
|
|
# +-- MacOS/+
|
|
# | +-- 'klayout'
|
|
# +-- Buddy/+
|
|
# +-- 'strm2cif'
|
|
# +-- 'strm2dxf'
|
|
# :
|
|
# +-- 'strmxor'
|
|
#
|
|
# @return 0 on success; non-zero on failure
|
|
#------------------------------------------------------------------------------
|
|
def SetChangeLibIdentificationName( executable, relativedir ):
|
|
cmdNameId = XcodeToolChain['nameID']
|
|
cmdNameChg = XcodeToolChain['nameCH']
|
|
otoolCm = "otool -L %s | grep libklayout" % executable
|
|
otoolOut = os.popen( otoolCm ).read()
|
|
exedepdic = DecomposeLibraryDependency( executable + ":\n" + otoolOut )
|
|
keys = exedepdic.keys()
|
|
deplibs = exedepdic[ list(keys)[0] ]
|
|
|
|
for lib in deplibs:
|
|
#-----------------------------------------------------------
|
|
# [1] Set the identification names for the library
|
|
#-----------------------------------------------------------
|
|
nameOld = "klayout.app/Contents/Frameworks/%s" % lib
|
|
nameNew = "@executable_path/%s/%s" % ( relativedir, lib )
|
|
command = "%s %s %s" % ( cmdNameId, nameNew, nameOld )
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to set the new identification name to <%s> !!!"
|
|
print( msg % lib, file=sys.stderr )
|
|
return 1
|
|
|
|
#-----------------------------------------------------------
|
|
# [2] Make the application aware of the new identification
|
|
#-----------------------------------------------------------
|
|
nameOld = "%s" % lib
|
|
nameNew = "@executable_path/%s/%s" % ( relativedir, lib )
|
|
command = "%s %s %s %s" % ( cmdNameChg, nameOld, nameNew, executable )
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to make the application aware of the new identification name <%s> !!!"
|
|
print( msg % nameNew, file=sys.stderr )
|
|
return 1
|
|
# for-lib
|
|
return 0
|
|
|
|
def WalkLibDependencyTree( dylibPath, depth=0, filter_regex=r'\t+/usr/local/opt'):
|
|
NOTHINGTODO = [] # return empty list if nothing to do.
|
|
cmdNameId = XcodeToolChain['nameID']
|
|
cmdNameChg = XcodeToolChain['nameCH']
|
|
otoolCm = 'otool -L %s | grep -E "%s"' % (dylibPath, filter_regex)
|
|
otoolOut = os.popen( otoolCm ).read()
|
|
exedepdic = DecomposeLibraryDependency( dylibPath + ":\n" + otoolOut )
|
|
keys = exedepdic.keys()
|
|
deplibs = exedepdic[ list(keys)[0] ]
|
|
|
|
if depth < 5:
|
|
if len(deplibs) > 0:
|
|
for idx, lib in enumerate(deplibs):
|
|
lib = str(lib)
|
|
if lib != list(keys)[0]:
|
|
deplibs[idx] = WalkLibDependencyTree(lib, depth+1, filter_regex)
|
|
if depth == 0:
|
|
return deplibs
|
|
return exedepdic
|
|
else:
|
|
raise RuntimeError("Exceeded maximum recursion depth.")
|
|
|
|
def WalkFrameworkPaths(frameworkPaths, filter_regex=r'\.(so|dylib)$', search_path_filter=r'\t+/usr/local/opt'):
|
|
|
|
if isinstance(frameworkPaths, str):
|
|
frameworkPathsIter = [frameworkPaths]
|
|
else:
|
|
frameworkPathsIter = frameworkPaths
|
|
|
|
dependency_dict = dict()
|
|
|
|
for frameworkPath in frameworkPathsIter:
|
|
# print("Calling:", 'find %s -type f | grep -E "%s"' % (frameworkPath, filter_regex))
|
|
find_grep_results = os.popen('find %s -type f | grep -E "%s"' % (frameworkPath, filter_regex)).read().split('\n')
|
|
framework_files = filter(lambda x: x != '',
|
|
map(lambda x: x.strip(),
|
|
find_grep_results))
|
|
|
|
dependency_dict[frameworkPath] = list()
|
|
for idx, file in enumerate(framework_files):
|
|
dict_file = {file: WalkLibDependencyTree(file, filter_regex=search_path_filter)}
|
|
dependency_dict[frameworkPath].append(dict_file)
|
|
return dependency_dict
|
|
|
|
def WalkDictTree(dependencyDict, visited_files):
|
|
libNameChanges = list()
|
|
for lib, dependencies in dependencyDict.items():
|
|
if lib in visited_files:
|
|
continue
|
|
|
|
dependency_list = list()
|
|
if isinstance(dependencies, list):
|
|
for deplib in dependencies:
|
|
if isinstance(deplib, str):
|
|
dependency_list.append(deplib)
|
|
if deplib not in visited_files:
|
|
visited_files.append(deplib)
|
|
elif isinstance(deplib, dict):
|
|
dependency_list.append(next(iter(deplib)))
|
|
libNameChanges.extend(WalkDictTree(deplib, visited_files))
|
|
else:
|
|
#raise RuntimeError("Unexpected value: %s" % deplib)
|
|
pass
|
|
else:
|
|
raise RuntimeError("Unexpected value: %s" % dependencies)
|
|
if len(dependency_list) > 0:
|
|
libNameChanges.append((lib, dependency_list))
|
|
else:
|
|
libNameChanges.append((lib, ))
|
|
visited_files.append(lib)
|
|
return libNameChanges
|
|
|
|
|
|
def FindFramework(path, root_path):
|
|
relPath = os.path.relpath(path, root_path)
|
|
return os.path.join(root_path, relPath.split(os.sep)[0])
|
|
def ResolveExecutablePath(path, executable_path):
|
|
""" Transforms @executable_path into executable_path"""
|
|
p = path.replace("@executable_path", "/%s/" % executable_path)
|
|
return p
|
|
|
|
def DetectChanges(frameworkDependencyDict):
|
|
visited_files = list()
|
|
libNameChanges = list()
|
|
for framework, libraries in frameworkDependencyDict.items():
|
|
for libraryDict in libraries:
|
|
libNameChanges.extend(WalkDictTree(libraryDict, visited_files))
|
|
# Changes are stored in libNameChanges in the form of ('lib.dylib', ['dep1.dylib', ...])
|
|
|
|
return libNameChanges
|
|
|
|
def PerformChanges(frameworkDependencyDict, replaceFromToPairs=None, executable_path="/tmp/klayout"):
|
|
libNameChanges = DetectChanges(frameworkDependencyDict)
|
|
cmdNameId = XcodeToolChain['nameID']
|
|
cmdNameChg = XcodeToolChain['nameCH']
|
|
|
|
if replaceFromToPairs is not None:
|
|
for libNameChange in libNameChanges:
|
|
libNameChangeIterator = iter(libNameChange)
|
|
lib = next(libNameChangeIterator)
|
|
try:
|
|
dependencies = next(libNameChangeIterator)
|
|
except StopIteration:
|
|
dependencies = list()
|
|
for replaceFrom, replaceTo, libdir in replaceFromToPairs:
|
|
fileName = ResolveExecutablePath(lib.replace(replaceFrom, replaceTo), executable_path)
|
|
if fileName.startswith('/usr'):
|
|
# print(f'skipping fileName: {fileName}')
|
|
continue
|
|
|
|
if lib.find(replaceFrom) >= 0:
|
|
if libdir:
|
|
frameworkPath = FindFramework(lib, replaceFrom)
|
|
else:
|
|
frameworkPath = lib
|
|
destFrameworkPath = frameworkPath.replace(replaceFrom, replaceTo)
|
|
destFrameworkPath = ResolveExecutablePath(destFrameworkPath, executable_path)
|
|
|
|
if not os.path.exists(fileName):
|
|
print (lib.replace(replaceFrom, replaceTo), "DOES NOT EXIST")
|
|
print ("COPY", frameworkPath, " -> ", destFrameworkPath)
|
|
shutil.copytree(frameworkPath, destFrameworkPath)
|
|
|
|
nameId = lib.replace(replaceFrom, replaceTo)
|
|
command = "%s %s %s" % ( cmdNameId, nameId, fileName )
|
|
if not os.access(fileName, os.W_OK):
|
|
command = "chmod u+w %s; %s; chmod u-w %s" % (fileName, command, fileName)
|
|
# print("\t%s" % command)
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to set the new identification name to <%s> !!!"
|
|
print( msg % fileName, file=sys.stderr )
|
|
return 1
|
|
|
|
for dependency in dependencies:
|
|
if dependency.find(replaceFrom) >= 0:
|
|
print("\tIn:", fileName)
|
|
print("\tRENAME", dependency, " -> ", dependency.replace(replaceFrom, replaceTo))
|
|
|
|
# Try changing id first
|
|
nameId = dependency.replace(replaceFrom, replaceTo)
|
|
command = "%s %s %s" % ( cmdNameId, nameId, fileName)
|
|
if not os.access(str(fileName), os.W_OK):
|
|
command = "chmod u+w %s; %s; chmod u-w %s" % (fileName, command, fileName)
|
|
# print("\t%s" % command)
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to set the new identification name to <%s> !!!"
|
|
print( msg % fileName, file=sys.stderr )
|
|
return 1
|
|
|
|
# Rename dependencies
|
|
nameOld = dependency
|
|
nameNew = dependency.replace(replaceFrom, replaceTo)
|
|
command = "%s %s %s %s" % ( cmdNameChg, nameOld, nameNew, str(fileName) )
|
|
if not os.access(str(fileName), os.W_OK):
|
|
command = "chmod u+w %s; %s; chmod u-w %s" % (fileName, command, fileName)
|
|
|
|
# print("\t%s" % command)
|
|
if subprocess.call( command, shell=True ) != 0:
|
|
msg = "!!! Failed to set the new identification name to <%s> !!!"
|
|
print( msg % fileName, file=sys.stderr )
|
|
return 1
|
|
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To get KLayout's version from a file; most likely from 'version.sh'
|
|
#
|
|
# @param[in] verfile version file from which version is retrieved
|
|
#
|
|
# @return version string
|
|
#------------------------------------------------------------------------------
|
|
def GetKLayoutVersionFrom( verfile='version.h' ):
|
|
version = "?.?.?"
|
|
try:
|
|
fd = open( verfile, "r" )
|
|
contents = fd.readlines()
|
|
fd.close()
|
|
except Exception as e:
|
|
return version
|
|
|
|
verReg = re.compile( u'(KLAYOUT_VERSION=\")([0-9A-Z_a-z\.]+)(\")' )
|
|
for line in contents:
|
|
m = verReg.match(line)
|
|
if m:
|
|
# print(m.group(0)) # KLAYOUT_VERSION="0.25.1"
|
|
# print(m.group(1)) # KLAYOUT_VERSION="
|
|
# print(m.group(2)) # 0.25.1
|
|
# print(m.group(3)) # "
|
|
version = m.group(2)
|
|
return version
|
|
return version
|
|
|
|
#------------------------------------------------------------------------------
|
|
## To generate the contents of "Info.plist" file from a template
|
|
#
|
|
# @param[in] keydic dictionary of four key words ['exe', 'icon', 'bname', 'ver']
|
|
# @param[in] templfile template file ("macbuild/Resources/Info.plist.template")
|
|
#
|
|
# @return generated strings
|
|
#------------------------------------------------------------------------------
|
|
def GenerateInfoPlist( keydic, templfile ):
|
|
val_exe = keydic['exe']
|
|
val_icon = keydic['icon']
|
|
val_bname = keydic['bname']
|
|
val_ver = keydic['ver']
|
|
|
|
try:
|
|
fd = open( templfile, "r" )
|
|
template = fd.read()
|
|
fd.close()
|
|
except Exception as e:
|
|
return "???"
|
|
|
|
t = string.Template(template)
|
|
s = t.substitute( EXECUTABLE = val_exe,
|
|
ICONFILE = val_icon,
|
|
BUNDLENAME = val_bname,
|
|
VERSION = val_ver)
|
|
return s
|
|
|
|
#----------------
|
|
# End of File
|
|
#----------------
|