this repo has no description
at fixPythonPipStalling 702 lines 25 kB view raw
1#!/usr/bin/env python3 2 3import os 4from typing import List, Dict 5import subprocess 6import re 7import pathlib 8from enum import Enum 9import shutil 10import datetime 11 12SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 13 14# 15# USING THIS SCRIPT: 16# * edit `DYLDINFO`, `OTOOL`, and `NM` to refer to the corresponding programs to analyze x86_64 Mach-Os. 17# * edit `XCODE_PATH` to point to the Xcode application. 18# * edit `SYSTEM_ROOT` to point to the root of a macOS or stock Darling installation. 19# * if desired, edit `OUT_DIR` to point to where the stub frameworks will be generated; the default is the best in most cases. 20# 21 22### 23# EDIT THESE TO USE SCRIPT 24### 25 26DYLDINFO = 'x86_64-apple-darwin11-dyldinfo' 27OTOOL = 'x86_64-apple-darwin11-otool' 28NM = 'x86_64-apple-darwin11-nm' 29 30XCODE_PATH = '/data/darling/Applications/Xcode.app' 31SYSTEM_ROOT = '/usr/local/libexec/darling' 32OUT_DIR = os.path.join(SCRIPT_DIR, '..', 'src', 'frameworks', 'dev-stubs') 33 34### 35# DON'T EDIT ANYTHING ELSE 36### 37 38# 39# most of this was just trial-and-errored until i got something that worked. 40# in particular, the RPATH computations are iffy, but they get the job done. 41# 42 43MACH_BINARY_MIME_TYPE = 'application/x-mach-binary' 44XCODE_MAIN_BINARY = os.path.join(XCODE_PATH, 'Contents', 'MacOS', 'Xcode') 45 46# extra RPATHs added to the RPATHs of plugins (not the main Xcode binary) and their dependencies. 47EXTRA_PLUGIN_RPATHS = [ 48 os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks'), 49 os.path.join(XCODE_PATH, 'Contents', 'OtherFrameworks'), 50 os.path.join(XCODE_PATH, 'Contents', 'Frameworks'), 51 os.path.join(XCODE_PATH, 'Contents', 'PlugIns'), 52 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'Frameworks'), 53 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'usr', 'lib'), 54 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Library', 'Frameworks'), 55 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'PrivateFrameworks'), 56 57 os.path.join(XCODE_PATH, 'Contents', 'PlugIns', 'Xcode3Core.ideplugin', 'Contents', 'Frameworks'), 58 os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks', 'XCBuild.framework', 'Versions', 'A', 'Frameworks'), 59] 60 61# RPATH templates to try to add for each binary, if they exist. 62# `{}` is substituted with the full path to the binary. 63TRY_PATH_TEMPLATES = [ 64 '{}/../Frameworks', 65 '{}/Frameworks', 66 '{}/../SharedFrameworks', 67 '{}/SharedFrameworks', 68 '{}/../OtherFrameworks', 69 '{}/OtherFrameworks', 70 '{}/../SystemFrameworks', 71 '{}/SystemFrameworks', 72 '{}/../PlugIns', 73 '{}/PlugIns', 74] 75 76# these are binaries that are repeated often throughout the source and we don't really care about them. 77# it's mainly just lots of copies of the Swift system libraries. 78EXCLUDED_BINARIES = [ 79 'libswiftAccelerate.dylib', 80 'libswiftAppKit.dylib', 81 'libswiftAVFoundation.dylib', 82 'libswiftCloudKit.dylib', 83 'libswiftCompression.dylib', 84 'libswiftContacts.dylib', 85 'libswiftCoreAudio.dylib', 86 'libswiftCoreData.dylib', 87 'libswiftCore.dylib', 88 'libswiftCoreFoundation.dylib', 89 'libswiftCoreGraphics.dylib', 90 'libswiftCoreImage.dylib', 91 'libswiftCoreLocation.dylib', 92 'libswiftCoreMedia.dylib', 93 'libswiftCoreMIDI.dylib', 94 'libswiftCryptoTokenKit.dylib', 95 'libswiftDarwin.dylib', 96 'libswift_Differentiation.dylib', 97 'libswiftDispatch.dylib', 98 'libswiftFoundation.dylib', 99 'libswiftGameplayKit.dylib', 100 'libswiftGLKit.dylib', 101 'libswiftIntents.dylib', 102 'libswiftIOKit.dylib', 103 'libswiftMapKit.dylib', 104 'libswiftMetal.dylib', 105 'libswiftMetalKit.dylib', 106 'libswiftModelIO.dylib', 107 'libswiftNaturalLanguage.dylib', 108 'libswiftNetwork.dylib', 109 'libswiftObjectiveC.dylib', 110 'libswiftOpenCL.dylib', 111 'libswiftos.dylib', 112 'libswiftPhotos.dylib', 113 'libswiftQuartzCore.dylib', 114 'libswiftRemoteMirror.dylib', 115 'libswiftSafariServices.dylib', 116 'libswiftSceneKit.dylib', 117 'libswiftsimd.dylib', 118 'libswiftSpriteKit.dylib', 119 'libswiftSwiftLang.dylib', 120 'libswiftSwiftOnoneSupport.dylib', 121 'libswiftVision.dylib', 122 'libswiftXCTest.dylib', 123 'libswiftXPC.dylib', 124] 125 126# these are the short names for the binaries we want to generate stubs for 127IMPORTANT_BINARIES = [ 128 'AppKit', 129 'AudioToolbox', 130 'Cocoa', 131 'CoreData', 132 'CoreGraphics', 133 'CoreText', 134 'ImageIO', 135 'OpenGL', 136 'QuartzCore', 137] 138 139COPYRIGHT_HEADER = """ 140/* 141 * This file is part of Darling. 142 * 143 * Copyright (C) {} Darling Developers 144 * 145 * Darling is free software: you can redistribute it and/or modify 146 * it under the terms of the GNU General Public License as published by 147 * the Free Software Foundation, either version 3 of the License, or 148 * (at your option) any later version. 149 * 150 * Darling is distributed in the hope that it will be useful, 151 * but WITHOUT ANY WARRANTY; without even the implied warranty of 152 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 153 * GNU General Public License for more details. 154 * 155 * You should have received a copy of the GNU General Public License 156 * along with Darling. If not, see <http://www.gnu.org/licenses/>. 157 */ 158""" 159 160STUB_HEADER = """ 161#include <stdlib.h> 162#include <stdio.h> 163 164static int verbose = 0; 165 166__attribute__((constructor)) 167static void initme(void) { 168 verbose = getenv("STUB_VERBOSE") != NULL; 169} 170 171void __simple_kprintf(const char* format, ...) __attribute__((format(printf, 1, 2))); 172 173#define LOG_FUNC __simple_kprintf 174""" 175 176TEMPLATE_STUB = """ 177void* {0}(void) {{ 178 if (verbose) LOG_FUNC("STUB: {0} called\\n"); 179 return NULL; 180}}; 181""" 182 183TEMPLATE_INTERFACE = """ 184@interface {} : NSObject 185@end 186""" 187 188TEMPLATE_CATEGORY = """ 189@interface {} 190@end 191""" 192 193TEMPLATE_IMPL = """ 194@implementation {} 195 196- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 197{{ 198 return [NSMethodSignature signatureWithObjCTypes: "v@:"]; 199}} 200 201- (void)forwardInvocation:(NSInvocation *)anInvocation 202{{ 203 NSLog(@"Stub called: %@ in %@", NSStringFromSelector([anInvocation selector]), [self class]); 204}} 205 206""" 207 208TEMPLATE_METHOD = """ 209{0} (id){1} 210{{ 211 NSLog(@"Stub called: %@ in %@", NSStringFromSelector(_cmd), [self class]); 212 return nil; 213}} 214""" 215 216CMAKE_TEMPLATE = """ 217project({0}_stub) 218 219set(DYLIB_COMPAT_VERSION "1.0.0") 220set(DYLIB_CURRENT_VERSION "1.0.0") 221set(FRAMEWORK_VERSION "{1}") 222 223add_framework({0} 224 FAT 225 CURRENT_VERSION 226 VERSION ${{FRAMEWORK_VERSION}} 227 TARGET_NAME {0}${{STUB_SUFFIX}} 228 ${{NO_INSTALL_ARG}} 229 230 SOURCES 231 src/classes.m 232 src/main.m 233 234 DEPENDENCIES 235 system 236 Foundation 237) 238 239#target_include_directories({0}${{STUB_SUFFIX}} BEFORE PRIVATE include) 240""" 241 242# most variables are just string keys, so that's the default. 243# these overrides for the definitions of certain variables prevent them from being defined as strings and instead define them with the appropriate values. 244VAR_OVERRIDES = { 245 'AppKit': { 246 'NSUnderlineByWordMask': 'NSUInteger NSUnderlineByWordMask = 0x8000;', 247 'NSApp': 'NSObject *NSApp = nil;', 248 'NSFontWeightBold': 'const CGFloat NSFontWeightBold = 0x3fd99999a0000000;', 249 'NSFontWeightMedium': 'const CGFloat NSFontWeightMedium = 0x3fcd70a3e0000000;', 250 'NSFontWeightRegular': 'const CGFloat NSFontWeightRegular = 0x0000000000000000;', 251 'NSFontWeightSemibold': 'const CGFloat NSFontWeightSemibold = 0x3fd3333340000000;', 252 'NSFontWeightThin': 'const CGFloat NSFontWeightThin = 0xbfe3333340000000;', 253 'NSFontWeightLight': 'const CGFloat NSFontWeightLight = 0xbfd99999a0000000;', 254 'NSFontWeightUltraLight': 'const CGFloat NSFontWeightUltraLight = 0xbfe99999a0000000;', 255 'NSFontWeightBlack': 'const CGFloat NSFontWeightBlack = 0x3fe3d70a40000000;', 256 'NSFontWeightHeavy': 'const CGFloat NSFontWeightHeavy = 0x3fe1eb8520000000;', 257 'NSViewNoIntrinsicMetric': 'const CGFloat NSViewNoIntrinsicMetric = 0xbff0000000000000;', 258 'NSAppKitVersionNumber': 'const double NSAppKitVersionNumber = 1504;', 259 }, 260 'CoreGraphics': { 261 'CGRectZero': 'const CGRect CGRectZero = {{0, 0}, {0, 0}};', 262 'CGRectNull': 'const CGRect CGRectNull = {{INFINITY, INFINITY}, {0, 0}};', 263 'CGPointZero': 'const CGPoint CGPointZero = {0, 0};', 264 'CGSizeZero': 'const CGSize CGSizeZero = {0, 0};', 265 'CGRectInfinite': 'const CGRect CGRectInfinite = {{0, 0}, {INFINITY, INFINITY}};', 266 'kCGDisplayPixelHeight': 'unsigned int kCGDisplayPixelHeight = 1;', 267 'kCGDisplayPixelWidth': 'unsigned int kCGDisplayPixelWidth = 1;', 268 }, 269 'QuartzCore': { 270 'CATransform3DIdentity': 'const CATransform3D CATransform3DIdentity = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};' 271 }, 272} 273 274# sometimes, functions are incorrectly detected as variables. this can be used to override certain symbols as functions in those cases. 275# note that this should rarely be necessary, since we double-check with `nm`. 276FUNC_OVERRIDES = { 277 #'CoreGraphics': [ 278 # 'CGRectGetMinX', 279 # 'CGRectGetWidth', 280 #], 281} 282 283# this is necessary to ensure certain functions that we need but Xcode doesn't need still get stubbed. 284ENSURE_FUNCS = { 285 'CoreGraphics': [ 286 'CGSSetDenyWindowServerConnections', 287 ], 288} 289 290# this is necessary to ensure certain methods get stubbed as individual methods. 291# for objc, we can stub using an invocation forwarding method. this is fine for the vast majority of cases. 292# however, Xcode swizzles some methods, so they have to exist as individual methods. 293ENSURE_METHODS = { 294 'AppKit': { 295 'NSColorWell': [ 296 '-activate:', 297 '-deactivate', 298 ], 299 'NSObject (BindingSupport)': [ 300 '-bind:toObject:withKeyPath:options:', 301 '-infoForBinding:', 302 '-unbind:', 303 ], 304 'NSObject (NSKeyValueBindingCreation)': [ 305 '-exposedBindings', 306 ], 307 }, 308} 309 310# this is added to the top of the `main.m` source file for the corresponding framework. 311PROLOGUES = { 312 'AppKit': """ 313#import <CoreGraphics/CGGeometry.h> 314""", 315 316 'CoreGraphics': """ 317#include <math.h> 318 319#ifdef __LP64__ 320 typedef double CGFloat; 321#else 322 typedef float CGFloat; 323#endif 324 325struct CGPoint { 326 CGFloat x; 327 CGFloat y; 328}; 329typedef struct CGPoint CGPoint; 330 331struct CGSize { 332 CGFloat width; 333 CGFloat height; 334}; 335typedef struct CGSize CGSize; 336 337struct CGVector { 338 CGFloat dx; 339 CGFloat dy; 340}; 341typedef struct CGVector CGVector; 342 343struct CGRect { 344 CGPoint origin; 345 CGSize size; 346}; 347typedef struct CGRect CGRect; 348""", 349 350 'QuartzCore': """ 351#ifdef __LP64__ 352 typedef double CGFloat; 353#else 354 typedef float CGFloat; 355#endif 356 357typedef struct { 358 CGFloat m11, m12, m13, m14; 359 CGFloat m21, m22, m23, m24; 360 CGFloat m31, m32, m33, m34; 361 CGFloat m41, m42, m43, m44; 362} CATransform3D; 363""", 364} 365 366for bin in IMPORTANT_BINARIES: 367 if not bin in VAR_OVERRIDES: 368 VAR_OVERRIDES[bin] = {} 369 if not bin in FUNC_OVERRIDES: 370 FUNC_OVERRIDES[bin] = [] 371 372RPATH_REGEX = r"\s*cmd LC_RPATH\n\s*cmdsize [0-9]+\n\s*path ([^(]+?)\s*\(offset" 373DEPS_REGEX = r"^\s*([^(\n]+?)\s*\(" 374VAR_REGEX = r"^\s*(?:__DATA|__DATA_CONST)\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)" 375FUNC_REGEX = r"^\s*(?!__DATA|__DATA_CONST)[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)" 376CLASS_REGEX = r"^\s*[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+(_OBJC_[A-Za-z0-9_$]+)" 377NM_REGEX = r"^[0-9A-Fa-f]+\s+T\s+([A-Za-z_0-9$]+)" 378LAZY_REGEX = r"^\s*[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+0x[0-9a-fA-F]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)" 379 380RPATHS: Dict[str, List[str]] = {} 381DEPS: Dict[str, List[str]] = {} 382NM_OVERRIDES: Dict[str, List[str]] = {} 383 384class ResolutionError(Exception): 385 def __init__(self, message: str) -> None: 386 self.message = message 387 super().__init__(message) 388 389class SymbolType(Enum): 390 VARIABLE = 1 391 FUNCTION = 2 392 OBJC_CLASS = 3 393 394class Symbol: 395 def __init__(self, binary: str, name: str, type: SymbolType) -> None: 396 self.binary = binary 397 self.name = name 398 self.type = type 399 def __eq__(self, other: object) -> bool: 400 return isinstance(other, Symbol) and other.name == self.name and other.binary == self.binary 401 def __hash__(self) -> int: 402 return hash((self.binary, self.name)) 403 def __str__(self) -> str: 404 return f'({self.binary}, {self.name}, {self.type})' 405 def __repr__(self) -> str: 406 return str(self) 407 408def basename_no_ext(string: str) -> str: 409 result = string 410 while True: 411 result, ext = os.path.splitext(result) 412 if len(ext) == 0: 413 break 414 return result 415 416def get_rpaths(binary: str, executable: str | None = None, extra_rpaths: List[str] | None = None, ignore_cache: bool = False) -> List[str]: 417 if (not ignore_cache) and (binary in RPATHS): 418 return RPATHS[binary] 419 420 output = subprocess.check_output([OTOOL, '-l', binary]).decode() 421 matches = re.finditer(RPATH_REGEX, output, re.MULTILINE) 422 rpaths = [match.group(1) for match in matches] 423 424 if extra_rpaths != None: 425 rpaths += extra_rpaths 426 427 final_rpaths: List[str] = [] 428 for rpath in rpaths: 429 parts = pathlib.PurePosixPath(rpath).parts 430 if parts[0] == '@executable_path' or parts[0] == '@executable': 431 if executable == None: 432 raise ResolutionError('Cannot use @executable_path when no executable is specified') 433 else: 434 final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:]))) 435 elif parts[0] == '@loader_path': 436 final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:]))) 437 elif parts[0].startswith('@'): 438 raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in RPATH: ' + rpath) 439 else: 440 if rpath.startswith(XCODE_PATH + '/') or rpath.startswith(SYSTEM_ROOT + '/'): 441 final_rpaths.append(rpath) 442 else: 443 final_rpaths.append(os.path.normpath(os.path.join(SYSTEM_ROOT, os.path.relpath(rpath, '/')))) 444 445 for template in TRY_PATH_TEMPLATES: 446 path = template.format(os.path.dirname(binary)) 447 if os.path.exists(path): 448 final_rpaths.append(os.path.normpath(path)) 449 450 final_rpaths = sorted(set(final_rpaths)) 451 452 RPATHS[binary] = final_rpaths 453 return final_rpaths 454 455def generate_binary_list(xcode_path: str): 456 # this is used to collect the root binaries for the search; 457 # the Xcode binary in `MacOS` covers most things, but plug-ins are dynamically loaded by 458 # Xcode at runtime, so we won't automatically pull them in with our search. instead, we 459 # scan them as roots as well. 460 bins: List[str] = [XCODE_MAIN_BINARY] 461 for plugin in os.listdir(os.path.join(xcode_path, 'Contents', 'PlugIns')): 462 full_path = os.path.join(xcode_path, 'Contents', 'PlugIns', plugin) 463 basename = os.path.splitext(plugin)[0] 464 fw_path = os.path.join(full_path, 'Versions', 'A', basename) 465 bundle_path = os.path.join(full_path, 'Contents', 'MacOS', basename) 466 if os.path.exists(fw_path): 467 bins.append(fw_path) 468 elif os.path.exists(bundle_path): 469 bins.append(bundle_path) 470 return bins 471 472def get_deps(binary: str, recursive: bool = False, executable: str | None = None, processed: List[str] = []) -> List[str]: 473 if binary in processed: 474 return [] 475 476 processed.append(binary) 477 478 output = subprocess.check_output([OTOOL, '-L', binary]).decode() 479 matches = re.finditer(DEPS_REGEX, output, re.MULTILINE) 480 deps = [match.group(1) for match in matches] 481 482 final_deps: List[str] = [] 483 rpaths = get_rpaths(binary) 484 485 for dep in deps: 486 parts = pathlib.PurePosixPath(dep).parts 487 if parts[0] == '@executable_path' or parts[0] == '@executable': 488 if executable == None: 489 raise ResolutionError('Cannot use @executable_path when no executable is specified') 490 else: 491 final_deps.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:]))) 492 elif parts[0] == '@loader_path': 493 final_deps.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:]))) 494 elif parts[0] == '@rpath': 495 found = False 496 for rpath in rpaths: 497 if os.path.exists(os.path.join(rpath, *parts[1:])): 498 final_deps.append(os.path.normpath(os.path.join(rpath, *parts[1:]))) 499 found = True 500 break 501 if not found: 502 raise ResolutionError('Could not find dependency in RPATH: ' + dep + '\nSearched: ' + str(rpaths)) 503 elif parts[0].startswith('@'): 504 raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in dependency: ' + dep) 505 else: 506 if dep.startswith('/Applications/Xcode.app/'): 507 final_deps.append(os.path.join(XCODE_PATH, *parts[3:])) 508 else: 509 final_deps.append(os.path.normpath(dep)) 510 511 final_deps = sorted(set(final_deps)) 512 513 # some binaries reference themselves as dependencies (for some reason). fix that. 514 if binary in final_deps: 515 final_deps.remove(binary) 516 517 # get rid of some repetitive duplicated dependencies 518 final_deps = [x for x in final_deps if not os.path.basename(x) in EXCLUDED_BINARIES or x.startswith(SYSTEM_ROOT) or x.startswith('/usr')] 519 520 DEPS[binary] = final_deps 521 522 if recursive: 523 recursive_deps = final_deps.copy() 524 for dep in final_deps: 525 if dep.startswith(XCODE_PATH + '/'): 526 # overwrite the RPATH cache for the dependency since we're loading it 527 get_rpaths(dep, executable, extra_rpaths = EXTRA_PLUGIN_RPATHS + rpaths, ignore_cache = True) 528 try: 529 recursive_deps += get_deps(dep, True, executable, processed) 530 except ResolutionError as e: 531 raise ResolutionError('Encountered error while processing dependency "' + dep + '" of "' + binary + '":\n' + e.message) 532 recursive_deps = sorted(set(recursive_deps)) 533 return recursive_deps 534 else: 535 return final_deps 536 537def get_bind_symbols(binary: str, binary_map: Dict[str, str]) -> List[Symbol]: 538 get_bin_name = lambda name: binary_map[name] if name in binary_map else f'@{name}@' 539 output = subprocess.check_output([DYLDINFO, '-bind', binary]).decode() 540 var_matches = re.finditer(VAR_REGEX, output, re.MULTILINE) 541 vars = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.VARIABLE) for match in var_matches)) 542 func_matches = re.finditer(FUNC_REGEX, output, re.MULTILINE) 543 funcs = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in func_matches)) 544 class_matches = re.finditer(CLASS_REGEX, output, re.MULTILINE) 545 classes = list(set(Symbol(get_bin_name(match.group(1)), match.group(2)[len('_OBJC_CLASS_$_'):], SymbolType.OBJC_CLASS) for match in class_matches if match.group(2).startswith('_OBJC_CLASS_$_'))) 546 547 # most lazy symbols are functions, so let's assume that and override manually as necessary 548 output = subprocess.check_output([DYLDINFO, '-lazy_bind', binary]).decode() 549 matches = re.finditer(LAZY_REGEX, output, re.MULTILINE) 550 lazy = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in matches)) 551 552 symbols = vars + funcs + classes + lazy 553 554 for symbol in symbols: 555 if symbol.binary.startswith('@') and symbol.binary.endswith('@'): 556 continue 557 if not symbol.binary in NM_OVERRIDES: 558 if os.path.exists(SYSTEM_ROOT + symbol.binary) or os.path.exists(symbol.binary): 559 output = subprocess.check_output([NM, '-Ug', '-arch', 'x86_64', (SYSTEM_ROOT + symbol.binary) if os.path.exists(SYSTEM_ROOT + symbol.binary) else symbol.binary]).decode() 560 matches = re.finditer(NM_REGEX, output, re.MULTILINE) 561 NM_OVERRIDES[symbol.binary] = [x.group(1) for x in matches] 562 else: 563 NM_OVERRIDES[symbol.binary] = [] 564 if symbol.name in NM_OVERRIDES[symbol.binary]: 565 symbol.type = SymbolType.FUNCTION 566 567 return symbols 568 569def generate_method(method: str) -> str: 570 type = method[0] 571 signature = '' 572 split = method[1:].split(':') 573 if len(split) == 1: 574 signature = split[0] 575 else: 576 for previous, current in zip(split, split[1:]): 577 if len(signature) > 0: 578 signature += ' ' 579 signature += f'{previous}: (id){previous}' 580 return '\n\n' + TEMPLATE_METHOD.format(type, signature).strip() 581 582def main(): 583 bins = generate_binary_list(XCODE_PATH) 584 585 try: 586 # cache the RPATHs for the root binaries 587 for bin in bins: 588 get_rpaths(bin, executable = XCODE_MAIN_BINARY, extra_rpaths = None if bin == XCODE_MAIN_BINARY else EXTRA_PLUGIN_RPATHS) 589 590 all_bins: List[str] = [] 591 system_bins: List[str] = [] 592 xc_bins: List[str] = [] 593 594 # collect all the binaries that are loaded 595 for bin in bins: 596 all_bins.append(bin) 597 all_bins += get_deps(bin, True, XCODE_MAIN_BINARY) 598 599 all_bins = sorted(set(all_bins)) 600 601 system_bins_tmp = [x for x in all_bins if not x.startswith(XCODE_PATH + '/')] 602 xc_bins = sorted(set(all_bins).difference(set(system_bins_tmp))) 603 system_bins = sorted(x[len(SYSTEM_ROOT):] if x.startswith(SYSTEM_ROOT + '/') else x for x in system_bins_tmp) 604 605 for bin in system_bins: 606 print(bin) 607 608 binary_map = {basename_no_ext(os.path.basename(x)): x for x in all_bins} 609 important_bins = [x for x in all_bins if basename_no_ext(os.path.basename(x)) in IMPORTANT_BINARIES] 610 611 # now let's get a list of all the symbols required at load time (and their associated binaries) 612 all_symbols: List[Symbol] = [] 613 for bin in xc_bins: 614 all_symbols += get_bind_symbols(bin, binary_map) 615 for bin in system_bins: 616 short_bin_name = basename_no_ext(os.path.basename(bin)) 617 if not short_bin_name in IMPORTANT_BINARIES: 618 if os.path.exists(SYSTEM_ROOT + bin): 619 all_symbols += get_bind_symbols(SYSTEM_ROOT + bin, binary_map) 620 621 all_symbols = list(set(all_symbols)) 622 system_symbols = [x for x in all_symbols if x.binary in system_bins] 623 important_symbols = [x for x in all_symbols if x.binary in important_bins] 624 625 for symbol in important_symbols: 626 bin_name = basename_no_ext(os.path.basename(symbol.binary)) 627 if symbol.name in FUNC_OVERRIDES[bin_name] or symbol.name[1:] in FUNC_OVERRIDES[bin_name]: 628 symbol.type = SymbolType.FUNCTION 629 630 important_symbols_separated: Dict[str, List[Symbol]] = {} 631 632 for important_bin in IMPORTANT_BINARIES: 633 important_symbols_separated[important_bin] = [x for x in important_symbols if basename_no_ext(os.path.basename(x.binary)) == important_bin] 634 635 for important_bin, symbols in important_symbols_separated.items(): 636 THIS_DIR = os.path.join(OUT_DIR, important_bin) 637 shutil.rmtree(THIS_DIR, ignore_errors=True) 638 os.makedirs(THIS_DIR, exist_ok=True) 639 classes = sorted(x.name for x in symbols if x.type == SymbolType.OBJC_CLASS) 640 vars = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.VARIABLE) 641 funcs = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.FUNCTION) 642 643 os.makedirs(os.path.join(THIS_DIR, 'src'), exist_ok=True) 644 #os.makedirs(os.path.join(THIS_DIR, 'include'), exist_ok=True) 645 #os.makedirs(os.path.join(THIS_DIR, 'include', important_bin), exist_ok=True) 646 647 with open(os.path.join(THIS_DIR, 'src', 'classes.m'), 'w') as classes_source: 648 classes_source.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n') 649 classes_source.write('#import <Foundation/Foundation.h>\n\n') 650 651 for klass in classes: 652 classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + '\n\n' + TEMPLATE_IMPL.format(klass).strip()) 653 if important_bin in ENSURE_METHODS and klass in ENSURE_METHODS[important_bin]: 654 for method in ENSURE_METHODS[important_bin][klass]: 655 classes_source.write(generate_method(method)) 656 classes_source.write('\n\n@end\n\n') 657 658 if important_bin in ENSURE_METHODS: 659 for klass in ENSURE_METHODS[important_bin]: 660 if not klass in classes: 661 classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + f'\n\n@implementation {klass}') 662 for method in ENSURE_METHODS[important_bin][klass]: 663 classes_source.write(generate_method(method)) 664 classes_source.write('\n\n@end\n\n') 665 666 with open(os.path.join(THIS_DIR, 'CMakeLists.txt'), 'w') as cmake_txt: 667 cmake_txt.write(CMAKE_TEMPLATE.format(important_bin, 'C' if important_bin == 'AppKit' else 'A').strip() + '\n') 668 669 with open(os.path.join(THIS_DIR, 'src', 'main.m'), 'w') as file: 670 file.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n') 671 file.write('#import <objc/NSObject.h>\n\n') 672 file.write('@interface NSString : NSObject\n@end\n\n') 673 if important_bin in PROLOGUES: 674 file.write(PROLOGUES[important_bin].strip() + '\n\n') 675 file.write(STUB_HEADER.strip() + '\n\n') 676 677 # most variables are just string keys, so default to that and then manually fix as necessary 678 # 679 # note that we don't really care what the value is in the string case (these are stubs, after all) 680 for var in vars: 681 if var in VAR_OVERRIDES[important_bin]: 682 file.write(VAR_OVERRIDES[important_bin][var] + '\n') 683 else: 684 file.write(f'NSString *const {var} = @"{var}";\n') 685 686 file.write('\n') 687 688 for func in funcs: 689 file.write(TEMPLATE_STUB.format(func).strip() + '\n\n') 690 691 file.write('\n') 692 693 if important_bin in ENSURE_FUNCS: 694 for func in ENSURE_FUNCS[important_bin]: 695 file.write(TEMPLATE_STUB.format(func).strip() + '\n\n') 696 697 except ResolutionError as e: 698 print(e.message) 699 exit(1) 700 701if __name__ == '__main__': 702 main()