diff --git a/macbuild/build4mac.py b/macbuild/build4mac.py index c4ca6933f..a627739b4 100755 --- a/macbuild/build4mac.py +++ b/macbuild/build4mac.py @@ -699,13 +699,11 @@ def DeployBinariesForBundle(): shutil.copy2( sourceDir0 + "/PkgInfo", targetDir0 ) # this file is not mandatory shutil.copy2( sourceDir1 + "/klayout", targetDirM ) shutil.copy2( sourceDir2 + "/klayout.icns", targetDirR ) - shutil.copy2( sourceDir2 + "/qt.conf", targetDirM ) os.chmod( targetDir0 + "/PkgInfo", 0o0644 ) os.chmod( targetDir0 + "/Info.plist", 0o0644 ) os.chmod( targetDirM + "/klayout", 0o0755 ) - os.chmod( targetDirM + "/qt.conf", 0o0644 ) os.chmod( targetDirR + "/klayout.icns", 0o0644 ) buddies = glob.glob( sourceDir3 + "/strm*" ) @@ -733,7 +731,7 @@ def DeployBinariesForBundle(): buddies = glob.glob( "klayout.app/Contents/Buddy/strm*" ) macdepQtOpt = "" for buddy in buddies: - macdepQtOpt += "-executable=%s " % buddy + macdepQtOpt += " -executable=%s" % buddy ret = SetChangeLibIdentificationName( buddy, "../Frameworks" ) if not ret == 0: os.chdir(ProjectDir) @@ -760,6 +758,10 @@ def DeployBinariesForBundle(): app_bundle = "klayout.app" options = macdepQtOpt + verbose + # Without the following, the plugin cocoa would not be found properly. + shutil.copy2( sourceDir2 + "/qt.conf", targetDirM ) + os.chmod( targetDirM + "/qt.conf", 0o0644 ) + os.chdir(ProjectDir) os.chdir(MacPkgDir) command = "%s %s %s" % ( deploytool, app_bundle, options ) diff --git a/macbuild/build4mac_env.py b/macbuild/build4mac_env.py index dfc2d9f68..d8c7a9df8 100755 --- a/macbuild/build4mac_env.py +++ b/macbuild/build4mac_env.py @@ -49,8 +49,8 @@ Qt5MacPorts = { 'qmake' : '/opt/local/libexec/qt5/bin/qmake', # Qt5 Custom (https://www1.qt.io/offline-installers/) # [Key Type Name] = 'Qt5Custom' -Qt5Custom = { 'qmake' : '~/Qt5.9.4/5.9.4/clang_64/bin/qmake', - 'deploy': '~/Qt5.9.4/5.9.4/clang_64/bin/macdeployqt' +Qt5Custom = { 'qmake' : '/usr/local/opt/qt/bin/qmake', + 'deploy': '/usr/local/opt/qt/bin/macdeployqt' } #----------------------------------------------------- @@ -192,9 +192,9 @@ Python36MacPorts= { 'exe': '/opt/local/Library/Frameworks/Python.framework/Versi # Python 3.6 from Brew *+*+*+ EXPERIMENTAL *+*+*+ # [Key Type Name] = 'pybrew' -Python36Brew= { 'exe': '/usr/local/opt/python3/Frameworks/Python.framework/Versions/Current/bin/python3.6m' , - 'inc': '/usr/local/opt/python3/Frameworks/Python.framework/Versions/Current/include/python3.6m', - 'lib': '/usr/local/opt/python3/Frameworks/Python.framework/Versions/Current/lib/libpython3.6m.dylib' +Python36Brew= { 'exe': '/usr/local/opt/python/libexec/bin/python' , + 'inc': '/usr/local/opt/python3/Frameworks/Python.framework/Versions/3.6/Headers', + 'lib': '/usr/local/opt/python3/Frameworks/Python.framework/Versions/3.6/Python' } # Consolidated dictionary kit for Python diff --git a/macbuild/build4mac_util.py b/macbuild/build4mac_util.py index e8357aee2..06dc780b3 100755 --- a/macbuild/build4mac_util.py +++ b/macbuild/build4mac_util.py @@ -16,6 +16,12 @@ import os import re import string import subprocess +import shutil +try: + from pathlib import Path + Path().expanduser() +except (ImportError,AttributeError): # python2 + from pathlib2 import Path #------------------------------------------------------------------------------- ## To import global dictionaries of different modules @@ -44,10 +50,10 @@ from build4mac_env import * def DecomposeLibraryDependency( depstr ): alllines = depstr.split('\n') numlines = len(alllines) - dependent = alllines[0].split(':')[0].strip(' ').strip('\t') + dependent = alllines[0].split(':')[0].strip() supporters = [] - for i in range(1, numlines): - supporter = alllines[i].split(' ')[0].strip(' ').strip('\t') + for line in alllines[1:]: + supporter = line.strip().split(' ')[0].strip() if not supporter == '': supporters.append(supporter) return { dependent: supporters } @@ -107,6 +113,8 @@ def SetChangeIdentificationNameOfDyLib( libdic ): # 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 @@ -170,6 +178,171 @@ def SetChangeLibIdentificationName( executable, relativedir ): # for-lib return 0 + +def WalkLibDependencyTree( dylibPath, depth=0, filter_regex=r'\t+/usr/local'): + NOTHINGTODO = [] # return empty list if nothing to do. + dylibPath = str(Path(dylibPath)) + 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(Path(lib)) + if lib != list(keys)[0]: + deplibs[idx] = WalkLibDependencyTree(lib, depth+1, filter_regex) + else: + return NOTHINGTODO + if depth == 0: + return deplibs + return exedepdic + else: + raise RuntimeError("Exceeded maximum recursion depth.") + +def WalkFrameworkPaths(*frameworkPaths, filter_regex=r'\.(so|dylib)$'): + dependency_dict = dict() + for frameworkPath in frameworkPaths: + frameworkPath = str(Path(frameworkPath)) + 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)} + 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(str(next(iter(deplib)))) + libNameChanges.extend(WalkDictTree(deplib, visited_files)) + else: + raise RuntimeError("Unexpected value: %s, %s" % (deplib, dependencies)) + 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): + path = Path(path) + root_path = Path(root_path) + relPath = path.relative_to(root_path) + return str(root_path / relPath.parts[0]) + +def ReplaceExecutablePath(path, executable_path): + executable_path = str(executable_path) + p = Path(str(path).replace("@executable_path", "/%s/" % executable_path)) + return p + +def FileExists(file_path, executable_path): + p = ReplaceExecutablePath(file_path, executable_path) + return p.exists() + +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", libdir=False): + 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 in replaceFromToPairs: + replaceFrom = str(Path(replaceFrom)) + replaceTo = str(Path(replaceTo)) + + if lib.find(replaceFrom) >= 0: + if libdir: + frameworkPath = FindFramework(lib, replaceFrom) + else: + frameworkPath = lib + destFrameworkPath = frameworkPath.replace(replaceFrom, replaceTo) + destFrameworkPath = ReplaceExecutablePath(destFrameworkPath, executable_path) + if not FileExists(lib.replace(replaceFrom, replaceTo), executable_path): + print (lib.replace(replaceFrom, replaceTo), "DOES NOT EXIST") + print ("COPY", frameworkPath, " -> ", destFrameworkPath) + shutil.copytree(frameworkPath, destFrameworkPath) + + fileName = ReplaceExecutablePath(lib.replace(replaceFrom, replaceTo), executable_path) + 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 + + fileName = ReplaceExecutablePath(lib.replace(replaceFrom, replaceTo), executable_path) + for dependency in dependencies: + if dependency.find(replaceFrom) >= 0: + print("In:", 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(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, 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 + + #------------------------------------------------------------------------------ ## To get KLayout's version from a file; most likely from 'version.sh' #