this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a script to generate minimal stubs for some frameworks needed by Xcode

This is mainly used for the modular build, so that we have
dependency-free stubs for certain GUI frameworks that Xcode loads but
doesn't actually use in the CLI.

authored by

Ariel Abreu and committed by
Thomas A
8974de27 98c8acf3

+702
+702
tools/generate-xcode-stubs.py
··· 1 + #!/usr/bin/env python3 2 + 3 + import os 4 + from typing import List, Dict 5 + import subprocess 6 + import re 7 + import pathlib 8 + from enum import Enum 9 + import shutil 10 + import datetime 11 + 12 + SCRIPT_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 + 26 + DYLDINFO = 'x86_64-apple-darwin11-dyldinfo' 27 + OTOOL = 'x86_64-apple-darwin11-otool' 28 + NM = 'x86_64-apple-darwin11-nm' 29 + 30 + XCODE_PATH = '/data/darling/Applications/Xcode.app' 31 + SYSTEM_ROOT = '/usr/local/libexec/darling' 32 + OUT_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 + 43 + MACH_BINARY_MIME_TYPE = 'application/x-mach-binary' 44 + XCODE_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. 47 + EXTRA_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. 63 + TRY_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. 78 + EXCLUDED_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 127 + IMPORTANT_BINARIES = [ 128 + 'AppKit', 129 + 'AudioToolbox', 130 + 'Cocoa', 131 + 'CoreData', 132 + 'CoreGraphics', 133 + 'CoreText', 134 + 'ImageIO', 135 + 'OpenGL', 136 + 'QuartzCore', 137 + ] 138 + 139 + COPYRIGHT_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 + 160 + STUB_HEADER = """ 161 + #include <stdlib.h> 162 + #include <stdio.h> 163 + 164 + static int verbose = 0; 165 + 166 + __attribute__((constructor)) 167 + static void initme(void) { 168 + verbose = getenv("STUB_VERBOSE") != NULL; 169 + } 170 + 171 + void __simple_kprintf(const char* format, ...) __attribute__((format(printf, 1, 2))); 172 + 173 + #define LOG_FUNC __simple_kprintf 174 + """ 175 + 176 + TEMPLATE_STUB = """ 177 + void* {0}(void) {{ 178 + if (verbose) LOG_FUNC("STUB: {0} called\\n"); 179 + return NULL; 180 + }}; 181 + """ 182 + 183 + TEMPLATE_INTERFACE = """ 184 + @interface {} : NSObject 185 + @end 186 + """ 187 + 188 + TEMPLATE_CATEGORY = """ 189 + @interface {} 190 + @end 191 + """ 192 + 193 + TEMPLATE_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 + 208 + TEMPLATE_METHOD = """ 209 + {0} (id){1} 210 + {{ 211 + NSLog(@"Stub called: %@ in %@", NSStringFromSelector(_cmd), [self class]); 212 + return nil; 213 + }} 214 + """ 215 + 216 + CMAKE_TEMPLATE = """ 217 + project({0}_stub) 218 + 219 + set(DYLIB_COMPAT_VERSION "1.0.0") 220 + set(DYLIB_CURRENT_VERSION "1.0.0") 221 + set(FRAMEWORK_VERSION "{1}") 222 + 223 + add_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. 244 + VAR_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`. 276 + FUNC_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. 284 + ENSURE_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. 293 + ENSURE_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. 311 + PROLOGUES = { 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 + 325 + struct CGPoint { 326 + CGFloat x; 327 + CGFloat y; 328 + }; 329 + typedef struct CGPoint CGPoint; 330 + 331 + struct CGSize { 332 + CGFloat width; 333 + CGFloat height; 334 + }; 335 + typedef struct CGSize CGSize; 336 + 337 + struct CGVector { 338 + CGFloat dx; 339 + CGFloat dy; 340 + }; 341 + typedef struct CGVector CGVector; 342 + 343 + struct CGRect { 344 + CGPoint origin; 345 + CGSize size; 346 + }; 347 + typedef struct CGRect CGRect; 348 + """, 349 + 350 + 'QuartzCore': """ 351 + #ifdef __LP64__ 352 + typedef double CGFloat; 353 + #else 354 + typedef float CGFloat; 355 + #endif 356 + 357 + typedef 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 + 366 + for 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 + 372 + RPATH_REGEX = r"\s*cmd LC_RPATH\n\s*cmdsize [0-9]+\n\s*path ([^(]+?)\s*\(offset" 373 + DEPS_REGEX = r"^\s*([^(\n]+?)\s*\(" 374 + VAR_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_$]+)" 375 + FUNC_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_$]+)" 376 + CLASS_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_$]+)" 377 + NM_REGEX = r"^[0-9A-Fa-f]+\s+T\s+([A-Za-z_0-9$]+)" 378 + LAZY_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 + 380 + RPATHS: Dict[str, List[str]] = {} 381 + DEPS: Dict[str, List[str]] = {} 382 + NM_OVERRIDES: Dict[str, List[str]] = {} 383 + 384 + class ResolutionError(Exception): 385 + def __init__(self, message: str) -> None: 386 + self.message = message 387 + super().__init__(message) 388 + 389 + class SymbolType(Enum): 390 + VARIABLE = 1 391 + FUNCTION = 2 392 + OBJC_CLASS = 3 393 + 394 + class 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 + 408 + def 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 + 416 + def 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 + 455 + def 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 + 472 + def 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 + 537 + def 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 + 569 + def 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 + 582 + def 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 + 701 + if __name__ == '__main__': 702 + main()