1# This file is part of the Frescobaldi project, http://www.frescobaldi.org/ 2# 3# Copyright (c) 2008 - 2014 by Wilbert Berendsen 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18# See http://www.gnu.org/licenses/ for more information. 19 20""" 21Functions that have to do with Mac OS X-specific behaviour. 22 23Importing this package does nothing; the macosx.setup module sets up 24the Mac OS X-specific behaviour, such as the global menu and the file open event 25handler, etc. 26 27""" 28 29 30import os 31import sys 32 33 34def inside_app_bundle(): 35 """Return True if we are inside a .app bundle.""" 36 # Testing for sys.frozen == 'macosx_app' (i.e. are we inside a .app bundle 37 # packaged with py2app?) should be sufficient, but let's also check whether 38 # we are inside a .app-bundle-like folder structure. 39 return (getattr(sys, 'frozen', '') == 'macosx_app' or 40 '.app/Contents/MacOS' in os.path.abspath(sys.argv[0])) 41 42def inside_lightweight_app_bundle(): 43 """Return True if we are inside a lightweight .app bundle.""" 44 # A lightweight .app bundle (created with macosx/mac-app.py without 45 # the standalone option) contains a symlink to the Python interpreter 46 # instead of a copy. 47 return (inside_app_bundle() 48 and os.path.islink(os.getcwd() + '/../MacOS/python')) 49 50def use_osx_menu_roles(): 51 """Return True if Mac OS X-specific menu roles are to be used.""" 52 global _use_roles 53 try: 54 return _use_roles 55 except NameError: 56 _use_roles = inside_app_bundle() 57 return _use_roles 58 59def midi_so_arch(lilypondinfo): 60 """Find the midi.so library of the selected LilyPond installation, 61 if it exists, and return its architecture; otherwise return None. 62 63 """ 64 import subprocess 65 for d in [lilypondinfo.versionString(), 'current']: 66 midi_so = os.path.abspath(lilypondinfo.bindir() + '/../lib/lilypond/' + d + '/python/midi.so') 67 if os.access(midi_so, os.R_OK): 68 s = subprocess.run(['/usr/bin/file', midi_so], capture_output = True) 69 if b'x86_64' in s.stdout: 70 return 'x86_64' 71 else: 72 return 'i386' 73 return None 74 75def system_python(major, arch): 76 """Return a list containing the command line to run the system Python. 77 78 (One of) the system-provided Python interpreter(s) is selected to run 79 the tools included in LilyPond, e.g. convert-ly. 80 81 Unless LilyPond is provided by MacPorts, the system-provided Python 82 interpreter must be explicitly called: 83 - the LilyPond-provided interpreter lacks many modules (and is 84 difficult to run properly from outside the application bundle); 85 - searching for the interpreter in the path is unreliable, since it 86 might lack some modules or it could be an incompatible version; 87 moreover if Frescobaldi is launched as an application bundle, 88 the PATH variable is not set; 89 - the interpreter included in Frescobaldi's application bundle, 90 when present, lacks some modules; moreover, Frescobaldi now uses 91 Python 3, while LilyPond's tools prior to version 2.21.0 are written 92 in Python 2. 93 94 If Python 2 is requested, the earliest Python 2 version >= 2.4 is 95 called, possibly avoiding the following: 96 - Python >= 2.5 gives a "C API version mismatch" RuntimeWarning 97 on `import midi`; 98 - Python >= 2.6 gives a DeprecationWarning on `import popen2`. 99 A Python 2 interpreter is always available (as of macOS 10.15 Catalina). 100 If Python 3 is requested, the earliest Python 3 version >= 3.5 is 101 called. 102 103 In LilyPond <= 2.19.54 midi2ly depends on the binary library midi.so 104 (replaced in 2.19.55 by a Python module), which is 32 bit in the app 105 bundle distributed by LilyPond and might be in other cases as well. 106 Thus, the selected Python interpreter is called with the corresponding 107 architecture. 108 If 32 bit architecture is required but the system Pythons do not 109 support it (as is the case on macOS >= 10.15 Catalina), return None. 110 111 """ 112 import platform 113 mac_ver = platform.mac_ver() 114 if (arch == 'i386') and (int(mac_ver[0].split('.')[1]) >= 15): 115 return None 116 if major == 2: 117 minors = list(range(4, 8)) 118 elif major == 3: 119 minors = list(range(5, 9)) 120 for minor in minors: 121 version = str(major) + '.' + str(minor) 122 python = '/System/Library/Frameworks/Python.framework/Versions/' + version 123 python += '/bin/python' + version 124 if os.path.exists(python): 125 return ['/usr/bin/arch', '-' + arch, python, '-E'] 126 127def best_python(lilypondinfo, tool): 128 """Return a list containing the Python command required to run 129 LilyPond's tools (the list may be empty), or None if no suitable 130 Python is found. 131 132 If the selected LilyPond installation is provided by MacPorts, 133 the #! line of LilyPond's tools is already set correctly, so 134 no command needs to be prepended. 135 The use of the #! line can be forced in the settings of the 136 individual LilyPond installations. 137 Otherwise a suitable system Python is searched. 138 139 """ 140 if lilypondinfo.frommacports() or lilypondinfo.useshebang: 141 return [] 142 if (tool == 'midi2ly') and (lilypondinfo.version() <= (2, 19, 54)) and midi_so_arch(lilypondinfo): 143 arch = midi_so_arch(lilypondinfo) 144 else: 145 arch = 'x86_64' 146 if lilypondinfo.version() >= (2, 21, 0): 147 major = 3 148 else: 149 major = 2 150 return system_python(major, arch) 151 152