1# -*- Mode: Python -*- 2# GObject-Introspection - a framework for introspecting GObject libraries 3# Copyright (C) 2008 Johan Dahlin 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2 of the License, or (at your option) any later version. 9# 10# This library 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 GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18# Boston, MA 02111-1307, USA. 19# 20 21import re 22import os 23import subprocess 24import platform 25import shutil 26import sys 27import time 28import giscanner.pkgconfig 29 30 31_debugflags = None 32 33 34def have_debug_flag(flag): 35 """Check for whether a specific debugging feature is enabled. 36Well-known flags: 37 * start: Drop into debugger just after processing arguments 38 * exception: Drop into debugger on fatalexception 39 * warning: Drop into debugger on warning 40 * posttrans: Drop into debugger just before introspectable pass 41""" 42 global _debugflags 43 if _debugflags is None: 44 _debugflags = os.environ.get('GI_SCANNER_DEBUG', '').split(',') 45 if '' in _debugflags: 46 _debugflags.remove('') 47 return flag in _debugflags 48 49 50def break_on_debug_flag(flag): 51 if have_debug_flag(flag): 52 import pdb 53 pdb.set_trace() 54 55 56# Copied from h2defs.py 57_upperstr_pat1 = re.compile(r'([^A-Z])([A-Z])') 58_upperstr_pat2 = re.compile(r'([A-Z][A-Z])([A-Z][0-9a-z])') 59_upperstr_pat3 = re.compile(r'^([A-Z])([A-Z])') 60 61 62def to_underscores(name): 63 """Converts a typename to the equivalent underscores name. 64 This is used to form the type conversion macros and enum/flag 65 name variables. 66 In particular, and differently from to_underscores_noprefix(), 67 this function treats the first character differently if it is 68 uppercase and followed by another uppercase letter.""" 69 name = _upperstr_pat1.sub(r'\1_\2', name) 70 name = _upperstr_pat2.sub(r'\1_\2', name) 71 name = _upperstr_pat3.sub(r'\1_\2', name, count=1) 72 return name 73 74 75def to_underscores_noprefix(name): 76 """Like to_underscores, but designed for "unprefixed" names. 77 to_underscores("DBusFoo") => dbus_foo, not d_bus_foo.""" 78 name = _upperstr_pat1.sub(r'\1_\2', name) 79 name = _upperstr_pat2.sub(r'\1_\2', name) 80 return name 81 82 83_libtool_pat = re.compile("dlname='([A-z0-9\\.\\-\\+]+)'\n") 84 85 86def _extract_dlname_field(la_file): 87 with open(la_file, encoding='utf-8') as f: 88 data = f.read() 89 m = _libtool_pat.search(data) 90 if m: 91 return m.groups()[0] 92 else: 93 return None 94 95 96_libtool_libdir_pat = re.compile("libdir='([^']+)'") 97 98 99def _extract_libdir_field(la_file): 100 with open(la_file, encoding='utf-8') as f: 101 data = f.read() 102 m = _libtool_libdir_pat.search(data) 103 if m: 104 return m.groups()[0] 105 else: 106 return None 107 108 109# Returns the name that we would pass to dlopen() the library 110# corresponding to this .la file 111def extract_libtool_shlib(la_file): 112 dlname = _extract_dlname_field(la_file) 113 if dlname is None: 114 return None 115 116 # Darwin uses absolute paths where possible; since the libtool files never 117 # contain absolute paths, use the libdir field 118 if platform.system() == 'Darwin': 119 dlbasename = os.path.basename(dlname) 120 libdir = _extract_libdir_field(la_file) 121 if libdir is None: 122 return dlbasename 123 return libdir + '/' + dlbasename 124 # Older libtools had a path rather than the raw dlname 125 return os.path.basename(dlname) 126 127 128# Returns arguments for invoking libtool, if applicable, otherwise None 129def get_libtool_command(options): 130 libtool_infection = not options.nolibtool 131 if not libtool_infection: 132 return None 133 134 libtool_path = options.libtool_path 135 if libtool_path: 136 # Automake by default sets: 137 # LIBTOOL = $(SHELL) $(top_builddir)/libtool 138 # To be strictly correct we would have to parse shell. For now 139 # we simply split(). 140 return libtool_path.split(' ') 141 142 libtool_cmd = 'libtool' 143 if platform.system() == 'Darwin': 144 # libtool on OS X is a completely different program written by Apple 145 libtool_cmd = 'glibtool' 146 try: 147 subprocess.check_call([libtool_cmd, '--version'], 148 stdout=open(os.devnull, 'w')) 149 except (subprocess.CalledProcessError, OSError): 150 # If libtool's not installed, assume we don't need it 151 return None 152 153 return [libtool_cmd] 154 155 156def files_are_identical(path1, path2): 157 with open(path1, 'rb') as f1, open(path2, 'rb') as f2: 158 buf1 = f1.read(8192) 159 buf2 = f2.read(8192) 160 while buf1 == buf2 and buf1 != b'': 161 buf1 = f1.read(8192) 162 buf2 = f2.read(8192) 163 return buf1 == buf2 164 165 166def cflag_real_include_path(cflag): 167 if not cflag.startswith("-I"): 168 return cflag 169 170 return "-I" + os.path.realpath(cflag[2:]) 171 172 173def host_os(): 174 return os.environ.get("GI_HOST_OS", os.name) 175 176 177def which(program): 178 def is_exe(fpath): 179 return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 180 181 def is_nt_exe(fpath): 182 return not fpath.lower().endswith('.exe') and \ 183 os.path.isfile(fpath + '.exe') and \ 184 os.access(fpath + '.exe', os.X_OK) 185 186 fpath, fname = os.path.split(program) 187 if fpath: 188 if is_exe(program): 189 return program 190 if os.name == 'nt' and is_nt_exe(program): 191 return program + '.exe' 192 else: 193 for path in os.environ["PATH"].split(os.pathsep): 194 path = path.strip('"') 195 exe_file = os.path.join(path, program) 196 if is_exe(exe_file): 197 return exe_file 198 if os.name == 'nt' and is_nt_exe(exe_file): 199 return exe_file + '.exe' 200 201 return None 202 203 204def get_user_cache_dir(dir=None): 205 ''' 206 This is a Python reimplemention of `g_get_user_cache_dir()` because we don't want to 207 rely on the python-xdg package and we can't depend on GLib via introspection. 208 If any changes are made to that function they'll need to be copied here. 209 ''' 210 211 xdg_cache_home = os.environ.get('XDG_CACHE_HOME') 212 if xdg_cache_home is not None: 213 if dir is not None: 214 xdg_cache_home = os.path.join(xdg_cache_home, dir) 215 try: 216 os.makedirs(xdg_cache_home, mode=0o755, exist_ok=True) 217 except EnvironmentError: 218 # Let's fall back to ~/.cache below 219 pass 220 else: 221 return xdg_cache_home 222 223 homedir = os.path.expanduser('~') 224 if homedir is not None: 225 cachedir = os.path.join(homedir, '.cache') 226 if dir is not None: 227 cachedir = os.path.join(cachedir, dir) 228 try: 229 os.makedirs(cachedir, mode=0o755, exist_ok=True) 230 except EnvironmentError: 231 return None 232 else: 233 return cachedir 234 235 return None 236 237 238def get_system_data_dirs(): 239 ''' 240 This is a Python reimplemention of `g_get_system_data_dirs()` because we don't want to 241 rely on the python-xdg package and we can't depend on GLib via introspection. 242 If any changes are made to that function they'll need to be copied here. 243 ''' 244 xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(os.pathsep)] 245 if not any(xdg_data_dirs) and os.name != 'nt': 246 xdg_data_dirs.append('/usr/local/share') 247 xdg_data_dirs.append('/usr/share') 248 249 return xdg_data_dirs 250 251 252def rmtree(*args, **kwargs): 253 ''' 254 A variant of shutil.rmtree() which waits and tries again in case one of 255 the files in the directory tree can't be deleted. 256 257 This can be the case if a file is still in use by some process, 258 for example a Virus scanner on Windows scanning .exe files when they are 259 created. 260 ''' 261 262 tries = 3 263 for i in range(1, tries + 1): 264 try: 265 shutil.rmtree(*args, **kwargs) 266 except OSError: 267 if i == tries: 268 raise 269 time.sleep(i) 270 continue 271 else: 272 return 273 274 275# Mainly used for builds against Python 3.8.x and later on Windows where we need to be 276# more explicit on where dependent DLLs are located, via the use of 277# os.add_dll_directory(). So, we make use of the envvar GI_EXTRA_BASE_DLL_DIRS and the 278# newly-added bindir() method of our pkgconfig module to acquire the paths where dependent 279# DLLs could be found. 280class dll_dirs(): 281 _cached_dll_dirs = None 282 _cached_added_dll_dirs = None 283 284 def __init__(self): 285 if os.name == 'nt' and hasattr(os, 'add_dll_directory'): 286 self._cached_dll_dirs = [] 287 self._cached_added_dll_dirs = [] 288 289 def add_dll_dirs(self, pkgs): 290 if os.name == 'nt' and hasattr(os, 'add_dll_directory'): 291 if 'GI_EXTRA_BASE_DLL_DIRS' in os.environ: 292 for path in os.environ.get('GI_EXTRA_BASE_DLL_DIRS').split(os.pathsep): 293 if path not in self._cached_dll_dirs: 294 self._cached_dll_dirs.append(path) 295 self._cached_added_dll_dirs.append(os.add_dll_directory(path)) 296 297 for path in giscanner.pkgconfig.bindir(pkgs): 298 if path not in self._cached_dll_dirs: 299 self._cached_dll_dirs.append(path) 300 self._cached_added_dll_dirs.append(os.add_dll_directory(path)) 301 302 def cleanup_dll_dirs(self): 303 if self._cached_added_dll_dirs is not None: 304 for added_dll_dir in self._cached_added_dll_dirs: 305 added_dll_dir.close() 306 if self._cached_dll_dirs is not None: 307 self._cached_dll_dirs.clear() 308 309 310# monkey patch distutils.cygwinccompiler 311# somehow distutils returns runtime library only up to 312# VS2010 / MSVC 10.0 (msc_ver 1600) 313def get_msvcr_overwrite(): 314 try: 315 return orig_get_msvcr() 316 except ValueError: 317 pass 318 319 msc_pos = sys.version.find('MSC v.') 320 if msc_pos != -1: 321 msc_ver = sys.version[msc_pos + 6:msc_pos + 10] 322 323 if msc_ver == '1700': 324 # VS2012 325 return ['msvcr110'] 326 elif msc_ver == '1800': 327 # VS2013 328 return ['msvcr120'] 329 elif msc_ver >= '1900': 330 # VS2015 331 return ['vcruntime140'] 332 333 334import distutils.cygwinccompiler 335orig_get_msvcr = distutils.cygwinccompiler.get_msvcr # type: ignore 336distutils.cygwinccompiler.get_msvcr = get_msvcr_overwrite # type: ignore 337