Path: blob/main/Scripts/sds_codegen/sds_parse_objc.py
1 views
#!/usr/bin/env python312import os3import subprocess4import argparse5import re6import json7import sds_common8from sds_common import fail9import tempfile10import shutil1112git_repo_path = sds_common.git_repo_path131415def ows_getoutput(cmd: list[str]) -> tuple[int, str, str]:16proc = subprocess.Popen(17cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True18)19stdout, stderr = proc.communicate()2021return proc.returncode, stdout, stderr222324class LineProcessor:25def __init__(self, text):26self.lines = text.split("\n")2728def hasNext(self):29return len(self.lines) > 03031def next(self, should_pop=False):32if len(self.lines) == 0:33return None34line = self.lines[0]35if should_pop:36self.lines = self.lines[1:]37return line3839def popNext(self):40return self.next(should_pop=True)414243counter = 0444546def next_counter():47global counter48counter = counter + 149return counter505152class ParsedClass:53def __init__(self, name):54self.name = name55self.is_implemented = False56self.property_map = {}57self.super_class_name = None58self.counter = next_counter()59self.finalize_method_name = None60self.namespace = None61self.protocol_names = []6263def get_property(self, name):64result = self.property_map.get(name)65if result is None:66result = self.get_inherited_property(name)67return result6869def add_property(self, property):70self.property_map[property.name] = property7172def properties(self):73result = []74for name in sorted(self.property_map.keys()):75result.append(self.property_map[name])76return result7778def property_names(self):79return sorted(self.property_map.keys())8081def inherit_from_protocol(self, namespace, protocol_name):82self.namespace = namespace83self.protocol_names.append(protocol_name)8485def get_inherited_property(self, name):86for protocol in self.class_protocols():87result = protocol.get_property(name)88if result is not None:89return result90return None9192def all_properties(self):93result = self.properties()94# We need to include any properties synthesized by this class95# but declared in a protocol.96for protocol in self.class_protocols():97result.extend(protocol.all_properties())98return result99100def class_protocols(self):101result = []102for protocol_name in self.protocol_names:103if protocol_name == self.name:104# There are classes that implement a protocol of the same name, e.g. MTLModel105# Ignore them.106continue107108protocol = self.namespace.find_class(protocol_name)109if protocol is None:110if (111protocol_name.startswith("NS")112or protocol_name.startswith("AV")113or protocol_name.startswith("UI")114or protocol_name.startswith("MF")115or protocol_name.startswith("UN")116or protocol_name.startswith("CN")117):118# Ignore built in protocols.119continue120print("clazz:", self.name)121print("file_path:", file_path)122fail("Missing protocol:", protocol_name)123124result.append(protocol)125126return result127128129class ParsedProperty:130def __init__(self, name, objc_type, is_optional):131self.name = name132self.objc_type = objc_type133self.is_optional = is_optional134self.is_not_readonly = False135self.is_synthesized = False136137138class Namespace:139def __init__(self):140self.class_map = {}141142def upsert_class(self, class_name):143clazz = self.class_map.get(class_name)144if clazz is None:145clazz = ParsedClass(class_name)146self.class_map[class_name] = clazz147return clazz148149def find_class(self, class_name):150clazz = self.class_map.get(class_name)151return clazz152153def class_names(self):154return sorted(self.class_map.keys())155156157split_objc_ast_prefix_regex = re.compile(r"^([ |\-`]*)(.+)$")158159160# The AST emitted by clang uses punctuation to indicate the AST hierarchy.161# This function strips that out.162def split_objc_ast_prefix(line):163match = split_objc_ast_prefix_regex.search(line)164if match is None:165fail("Could not match line:", line)166prefix = match.group(1)167remainder = match.group(2)168return prefix, remainder169170171def process_objc_ast(namespace: Namespace, file_path: str, raw_ast: str) -> None:172m_filename = os.path.basename(file_path)173file_base, file_extension = os.path.splitext(m_filename)174if file_extension != ".m":175fail("Bad file extension:", file_extension)176h_filename = file_base + ".h"177178# TODO: Remove179lines = raw_ast.split("\n")180raw_ast = "\n".join(lines)181182lines = LineProcessor(raw_ast)183while lines.hasNext():184line = lines.popNext()185prefix, remainder = split_objc_ast_prefix(line)186187if remainder.startswith("ObjCInterfaceDecl "):188# |-ObjCInterfaceDecl 0x112510490 <SignalDataStoreCommon/ObjCMessage.h:14:1, line:25:2> line:14:12 ObjCMessage189process_objc_interface(namespace, file_path, lines, prefix, remainder)190elif remainder.startswith("ObjCCategoryDecl "):191# |-ObjCCategoryDecl 0x112510d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12192process_objc_category(namespace, file_path, lines, prefix, remainder)193elif remainder.startswith("ObjCImplementationDecl "):194# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage195process_objc_implementation(namespace, file_path, lines, prefix, remainder)196elif remainder.startswith("ObjCProtocolDecl "):197# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage198process_objc_protocol_decl(namespace, file_path, lines, prefix, remainder)199# TODO: Category impl.200elif remainder.startswith("TypedefDecl "):201# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage202process_objc_type_declaration(203namespace, file_path, lines, prefix, remainder204)205elif remainder.startswith("EnumDecl "):206# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage207process_objc_enum_declaration(208namespace, file_path, lines, prefix, remainder209)210211212# |-EnumDecl 0x7fd576047310 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:43, /Users/matthew/code/workspace/ows/Signal-iOS-2/SignalServiceKit/src/Messages/TSCall.h:12:29> col:29 RPRecentCallType 'NSUInteger':'unsigned long'213# | `-EnumExtensibilityAttr 0x7fd5760473f0 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:116:45, col:68> Open214# |-TypedefDecl 0x7fd576047488 </Users/matthew/code/workspace/ows/Signal-iOS-2/SignalServiceKit/src/Messages/TSCall.h:12:1, col:29> col:29 referenced RPRecentCallType 'enum RPRecentCallType':'enum RPRecentCallType'215# | `-ElaboratedType 0x7fd576047430 'enum RPRecentCallType' sugar216# | `-EnumType 0x7fd5760473d0 'enum RPRecentCallType'217# | `-Enum 0x7fd576047518 'RPRecentCallType'218# |-EnumDecl 0x7fd576047518 prev 0x7fd576047310 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:90,219process_objc_enum_declaration_regex = re.compile(r"^.+? ([^ ]+) '([^']+)':'([^']+)'$")220# process_objc_enum_declaration_regex = re.compile(r"^.+?'([^']+)'(:'([^']+)')?$")221222enum_type_map = {}223224225def process_objc_enum_declaration(namespace, file_path, lines, prefix, remainder):226match = process_objc_enum_declaration_regex.search(remainder)227if match is None:228print("file_path:", file_path)229print("Could not match line:", remainder)230return231type1 = get_match_group(match, 1)232type2 = get_match_group(match, 2)233type3 = get_match_group(match, 3)234235if type1.startswith("line:"):236return237if type1 in enum_type_map:238return239enum_type_map[type1] = type2240241242# |-TypedefDecl 0x7f8d8fb44748 <line:12:1, line:22:3> col:3 referenced RPRecentCallType 'enum RPRecentCallType':'RPRecentCallType'243process_objc_type_declaration_regex = re.compile(r"^.+?'([^']+)'(:'([^']+)')?$")244245246def process_objc_type_declaration(namespace, file_path, lines, prefix, remainder):247match = process_objc_type_declaration_regex.search(remainder)248if match is None:249print("file_path:", file_path)250fail("Could not match line:", remainder)251type1 = get_match_group(match, 1)252type2 = get_match_group(match, 2)253type3 = get_match_group(match, 3)254255if type1 is None or type3 is None:256return257is_enum = type1 == "enum " + type3258if not is_enum:259return260261if type3.startswith("line:"):262print("Ignoring invalid enum(2):", type1, type2, type3)263return264if type3 not in enum_type_map:265print("Enum has unknown type:", type3)266enum_type = "NSUInteger"267else:268enum_type = enum_type_map[type3]269enum_type_map[type3] = enum_type270271272# |-ObjCInterfaceDecl 0x10f5d2b60 <SignalDataStoreCommon/ObjCBaseModel.h:15:1, col:8> col:8 SDSDataStore273# |-ObjCInterfaceDecl 0x10f5d2c10 <line:17:1, line:29:2> line:17:12 ObjCBaseModel274# | |-ObjCPropertyDecl 0x10f5d2d40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic275# ...276# |-ObjCInterfaceDecl 0x10f5d3490 <SignalDataStoreCommon/ObjCMessage.h:14:1, line:25:2> line:14:12 ObjCMessage277# | |-super ObjCInterface 0x10f5d2c10 'ObjCBaseModel'278# | |-ObjCImplementation 0x10f5d3f20 'ObjCMessage'279# | |-ObjCPropertyDecl 0x10f5d35d0 <line:16:1, col:43> col:43 body 'NSString * _Nonnull':'NSString *' readonly nonatomic strong280def process_objc_interface(281namespace: Namespace, file_path: str, lines, decl_prefix, decl_remainder282):283# |-ObjCInterfaceDecl 0x10ab2fd58 </Users/matthew/code/workspace/ows/Signal-iOS-2/SignalDataStore/SignalDataStoreCommon/ObjCMessageWAuthor.h:13:1, line:26:2> line:13:12 ObjCMessageWAuthor284# | |-super ObjCInterface 0x10ab2f490 'ObjCMessage'285286super_class_name = None287if lines.hasNext():288line = lines.next()289prefix, remainder = split_objc_ast_prefix(line)290if len(prefix) > len(decl_prefix):291splits = remainder.split(" ")292if len(splits) >= 2 and splits[0] == "super":293super_class_name = splits[-1].strip()294if super_class_name.startswith("'") and super_class_name.endswith("'"):295super_class_name = super_class_name[1:-1]296297process_objc_class(298namespace,299file_path,300lines,301decl_prefix,302decl_remainder,303super_class_name=super_class_name,304)305306307# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12308# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'309# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong310# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'311def process_objc_category(namespace, file_path, lines, decl_prefix, decl_remainder):312# |-ObjCCategoryDecl 0x1092f8440 <line:76:1, line:81:2> line:76:12 SomeCategory313# | |-ObjCInterface 0x1092f5d58 'ObjCMessageWAuthor'314# | |-ObjCCategoryImpl 0x1092f8608 'SomeCategory'315# | |-ObjCPropertyDecl 0x1092f8508 <line:79:1, col:53> col:53 fakeProperty2 'NSString * _Nullable':'NSString *' readonly nonatomic316# | `-ObjCMethodDecl 0x1092f8580 <col:53> col:53 implicit - fakeProperty2 'NSString * _Nullable':'NSString *'317if not lines.hasNext():318fail("Category missing interface.")319line = lines.next()320prefix, remainder = split_objc_ast_prefix(line)321if len(prefix) <= len(decl_prefix):322fail("Category missing interface.")323class_name = remainder.split(" ")[-1]324if class_name.startswith("'") and class_name.endswith("'"):325class_name = class_name[1:-1]326327process_objc_class(328namespace,329file_path,330lines,331decl_prefix,332decl_remainder,333custom_class_name=class_name,334)335336337# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12338# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'339# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong340# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'341def process_objc_implementation(342namespace, file_path, lines, decl_prefix, decl_remainder343):344clazz = process_objc_class(namespace, file_path, lines, decl_prefix, decl_remainder)345if clazz is not None:346clazz.is_implemented = True347348349def process_objc_protocol_decl(350namespace, file_path, lines, decl_prefix, decl_remainder351):352clazz = process_objc_class(namespace, file_path, lines, decl_prefix, decl_remainder)353if clazz is not None:354clazz.is_implemented = True355356357# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12358# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'359# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong360# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'361def process_objc_class(362namespace,363file_path,364lines,365decl_prefix,366decl_remainder,367custom_class_name=None,368super_class_name=None,369):370if custom_class_name is not None:371class_name = custom_class_name372else:373class_name = decl_remainder.split(" ")[-1]374375clazz = namespace.upsert_class(class_name)376377if super_class_name is not None:378if clazz.super_class_name is None:379clazz.super_class_name = super_class_name380elif clazz.super_class_name != super_class_name:381fail(382"super_class_name does not match:",383clazz.super_class_name,384super_class_name,385)386387while lines.hasNext():388line = lines.next()389prefix, remainder = split_objc_ast_prefix(line)390if len(prefix) <= len(decl_prefix):391# Declaration is over.392return clazz393394line = lines.popNext()395396# | |-ObjCPropertyDecl 0x10f5d2d40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic397398"""399TODO: We face interesting choices about how to process:400401* properties402* private properties403* properties with renamed ivars404* ivars without properties405* properties not backed by ivars (e.g. actually accessors).406"""407if remainder.startswith("ObjCPropertyDecl "):408process_objc_property(clazz, prefix, file_path, line, remainder)409elif remainder.startswith("ObjCPropertyImplDecl "):410process_objc_property_impl(clazz, prefix, file_path, line, remainder)411elif remainder.startswith("ObjCMethodDecl "):412process_objc_method_decl(clazz, prefix, file_path, line, remainder)413elif remainder.startswith("ObjCProtocol "):414process_objc_protocol(namespace, clazz, prefix, file_path, line, remainder)415416return clazz417418419process_objc_method_decl_regex = re.compile(r" - (sdsFinalize[^ ]*?) 'void'$")420421422def process_objc_method_decl(clazz, prefix, file_path, line, remainder):423match = process_objc_method_decl_regex.search(remainder)424if match is None:425return426method_name = match.group(1).strip()427clazz.finalize_method_name = method_name428429430# | |-ObjCProtocol 0x7f879888b8a8 'AppContext'431process_objc_protocol_regex = re.compile(r" '([^']+)'$")432433434def process_objc_protocol(namespace, clazz, prefix, file_path, line, remainder):435match = process_objc_protocol_regex.search(remainder)436if match is None:437return438protocol_name = match.group(1).strip()439clazz.inherit_from_protocol(namespace, protocol_name)440441442# | |-ObjCPropertyImplDecl 0x1092f6d68 <col:1, col:13> col:13 someSynthesizedProperty synthesize443# | |-ObjCPropertyImplDecl 0x1092f6f18 <col:1, col:35> col:13 someRenamedProperty synthesize444# | |-ObjCPropertyImplDecl 0x1092f7698 <<invalid sloc>, col:53> <invalid sloc> author synthesize445# | `-ObjCPropertyImplDecl 0x1092f77f8 <<invalid sloc>, col:53> <invalid sloc> somePrivateOptionalString synthesize446#447# ObjCPropertyDecl 0x7fc37e08f800 <line:37:1, col:28> col:28 shouldThreadBeVisible 'int' assign readwrite nonatomic unsafe_unretained448process_objc_property_impl_regex = re.compile(r"^.+ ([^ ]+) synthesize$")449450451def process_objc_property_impl(clazz, prefix, file_path, line, remainder):452match = process_objc_property_impl_regex.search(remainder)453if match is None:454print("file_path:", file_path)455fail("Could not match line:", line)456property_name = match.group(1).strip()457property = clazz.get_property(property_name)458if property is None:459if clazz.name == "AppDelegate" and property_name == "window":460# We can't parse properties synthesized locally but461# declared in a protocol defined in the iOS frameworks.462# So, special case these propert(y/ies) - we don't need463# to handle them.464return465466print("file_path:", file_path)467print("line:", line)468print("\t", "clazz", clazz.name, clazz.counter)469print("\t", "property_name", property_name)470for name in clazz.property_names():471print("\t\t", name)472fail("Can't find property:", property_name)473else:474property.is_synthesized = True475476477# | |-ObjCPropertyDecl 0x11250fd40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic478# | |-ObjCPropertyDecl 0x116afde80 <line:15:1, col:38> col:38 isUnread 'BOOL':'signed char' readonly nonatomic479#480# | |-ObjCPropertyDecl 0x7f8157089a00 <line:37:1, col:28> col:28 shouldThreadBeVisible 'int' assign readwrite nonatomic unsafe_unretained481#482# | |-ObjCPropertyDecl 0x7faf139af8e0 <line:37:1, col:28> col:28 shouldThreadBeVisible 'BOOL':'bool' assign readwrite nonatomic unsafe_unretained483# | |-ObjCPropertyDecl 0x7f879889f460 <line:46:1, col:40> col:40 mainWindow 'UIWindow * _Nullable':'UIWindow *' readwrite atomic strong484process_objc_property_regex = re.compile(r"^.+<.+> col:\d+(.+?)'(.+?)'(:'(.+)')?(.+)$")485486487# This convenience function handles None results and strips.488def get_match_group(match, index):489group = match.group(index)490if group is None:491return ""492return group.strip()493494495def process_objc_property(clazz, prefix, file_path, line, remainder):496497match = process_objc_property_regex.search(remainder)498if match is None:499print("file_path:", file_path)500print("remainder:", remainder)501fail("Could not match line:", line)502property_name = match.group(1).strip()503property_type_1 = get_match_group(match, 2)504property_type_2 = get_match_group(match, 4)505property_keywords = match.group(5).strip().split(" ")506507is_optional = (property_type_2 + " _Nullable") == property_type_1508is_readonly = "readonly" in property_keywords509510property_type = property_type_2511if len(property_type_2) < 1:512property_type = property_type_1513514primitive_types = ("BOOL", "NSInteger", "NSUInteger", "uint64_t", "int64_t")515if property_type_1 in primitive_types:516property_type = property_type_1517518property = clazz.get_property(property_name)519if property is None:520521property = ParsedProperty(property_name, property_type, is_optional)522clazz.add_property(property)523else:524if property.name != property_name:525fail("Property names don't match", property.name, property_name)526if property.is_optional != is_optional:527if clazz.name.startswith("DD"):528# CocoaLumberjack has nullability consistency issues.529# Ignore them.530return531print("file_path:", file_path)532print("clazz:", clazz.name)533fail("Property is_optional don't match", property_name)534if property.objc_type != property_type:535# There's a common pattern of using a mutable private property536# and exposing a non-mutable public property to prevent537# external modification of the property.538if (539property_type.startswith("NSMutable")540and property.objc_type == "NS" + property_type[len("NSMutable") :]541):542property.objc_type = property_type543else:544print("file_path:", file_path)545print("remainder:", remainder)546print("property.objc_type:", property.objc_type)547print("property_type:", property_type)548print("property_name:", property_name)549fail("Property types don't match", property.objc_type, property_type)550551if not is_readonly:552property.is_not_readonly = True553554555def emit_output(file_path, namespace):556classes = []557for class_name in namespace.class_names():558clazz = namespace.upsert_class(class_name)559if not clazz.is_implemented:560if not class_name.startswith("NS"):561pass562continue563564properties = []565566for property in clazz.all_properties():567if not property.is_synthesized:568continue569570property_dict = {571"name": property.name,572"objc_type": property.objc_type,573"is_optional": property.is_optional,574"class_name": class_name,575# This might not be necessary, thanks to is_synthesized576# 'is_readonly': (not property.is_not_readonly),577}578579properties.append(property_dict)580581class_dict = {582"name": class_name,583"properties": properties,584"filepath": sds_common.sds_to_relative_path(file_path),585"finalize_method_name": clazz.finalize_method_name,586}587if clazz.super_class_name is not None:588class_dict["super_class_name"] = clazz.super_class_name589classes.append(class_dict)590591enums = enum_type_map592593root = {594"classes": classes,595"enums": enums,596}597598return json.dumps(root, sort_keys=True, indent=4)599600601# We need to include search paths for every602# non-framework header.603def find_header_include_paths(include_path):604result = []605606def add_dir_if_has_header(dir_path):607# Only include subdirectories with header files.608for filename in os.listdir(dir_path):609if filename.endswith(".h"):610result.append("-I" + dir_path)611break612613# Add root if necessary.614add_dir_if_has_header(include_path)615616for rootdir, dirnames, filenames in os.walk(include_path):617for dirname in dirnames:618dir_path = os.path.abspath(os.path.join(rootdir, dirname))619add_dir_if_has_header(dir_path)620621return result622623624# --- Modules625626# Framework compilation gathers all framework headers627# in an include directory with the framework name, so628# that headers can be included like so:629#630# #import <framework_name/header_name.h>631#632# For example:633#634# #import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>635#636# To simulate this, we walk the Pods directory and copy637# headers into per-framework directories.638639640def copy_module_headers(src_dir_path, module_name, module_header_dir_path):641dst_dir_path = os.path.join(module_header_dir_path, module_name)642os.mkdir(dst_dir_path)643644for rootdir, dirnames, filenames in os.walk(src_dir_path):645for filename in filenames:646if not filename.endswith(".h"):647continue648src_file_path = os.path.abspath(os.path.join(rootdir, filename))649dst_file_path = os.path.abspath(os.path.join(dst_dir_path, filename))650shutil.copyfile(src_file_path, dst_file_path)651652653def gather_pod_headers(pods_dir_path, module_header_dir_path):654655for dirname in os.listdir(pods_dir_path):656src_dir_path = os.path.join(pods_dir_path, dirname)657if not os.path.isdir(src_dir_path):658continue659660copy_module_headers(src_dir_path, dirname, module_header_dir_path)661662663def gather_module_headers(pods_dir_path):664# Make a temp directory to gather framework headers in.665module_header_dir_path = tempfile.mkdtemp()666667gather_pod_headers(pods_dir_path, module_header_dir_path)668669for project_name in (670"SignalServiceKit",671"Signal",672):673src_dir_path = os.path.join(git_repo_path, project_name)674copy_module_headers(src_dir_path, project_name, module_header_dir_path)675676return module_header_dir_path677678679# --- PCH680681682def get_pch_include(file_path):683ssk_path = os.path.join(git_repo_path, "SignalServiceKit") + os.sep684s_path = os.path.join(git_repo_path, "Signal") + os.sep685sae_path = os.path.join(git_repo_path, "SignalShareExtension") + os.sep686if file_path.startswith(ssk_path):687return os.path.join(688git_repo_path, "SignalServiceKit/SignalServiceKit-prefix.pch"689)690elif file_path.startswith(s_path):691return os.path.join(git_repo_path, "Signal/Signal-Prefix.pch")692elif file_path.startswith(sae_path):693return os.path.join(694git_repo_path, "SignalShareExtension/SignalShareExtension-Prefix.pch"695)696else:697fail("Couldn't determine .pch for file:", file_path)698699700# --- Processing701702703def process_objc(704file_path: str,705iphoneos_sdk_path: str,706swift_bridging_path: str,707module_header_dir_path: str,708header_include_paths: list[str],709) -> None:710pch_include = get_pch_include(file_path)711712# These clang args can be found by building our workspace and looking at how XCode invokes clang.713clang_args = "-arch arm64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu11 -fobjc-arc -fobjc-weak -fmodules -gmodules -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -fapplication-extension -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Wno-objc-interface-ivars -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations".split(714" "715)716717# TODO: We'll never repro the correct search paths, so clang will always emit errors.718# We'll want to ignore these errors without silently failing.719command = (720[721"clang",722"-x",723"objective-c",724"-Xclang",725"-ast-dump",726"-fobjc-arc",727]728+ clang_args729+ [730"-isysroot",731iphoneos_sdk_path,732]733+ header_include_paths734+ [735("-I" + module_header_dir_path),736("-I" + swift_bridging_path),737"-include",738pch_include,739file_path,740]741)742743exit_code, output, error_output = ows_getoutput(command)744745output = output.strip()746raw_ast = output747748namespace = Namespace()749750process_objc_ast(namespace, file_path, raw_ast)751752output = emit_output(file_path, namespace)753754parsed_file_path = file_path + sds_common.SDS_JSON_FILE_EXTENSION755with open(parsed_file_path, "wt") as f:756f.write(output)757758759def process_file(760file_path,761iphoneos_sdk_path,762swift_bridging_path,763module_header_dir_path,764header_include_paths,765):766filename = os.path.basename(file_path)767768# TODO: Fix this file769if filename == "OWSDisappearingMessageFinderTest.m":770return771772_, file_extension = os.path.splitext(filename)773if file_extension == ".m":774process_objc(775file_path,776iphoneos_sdk_path,777swift_bridging_path,778module_header_dir_path,779header_include_paths,780)781782783# ---784785if __name__ == "__main__":786parser = argparse.ArgumentParser(description="Parse Objective-C AST.")787parser.add_argument(788"--src-path", required=True, help="used to specify a path to process."789)790parser.add_argument(791"--swift-bridging-path",792required=True,793help="used to specify a path to process.",794)795args = parser.parse_args()796797src_path = os.path.abspath(args.src_path)798swift_bridging_path = os.path.abspath(args.swift_bridging_path)799module_header_dir_path = gather_module_headers("Pods")800801command = [802"xcrun",803"--show-sdk-path",804"--sdk",805"iphoneos",806]807exit_code, output, error_output = ows_getoutput(command)808if int(exit_code) != 0:809fail("Could not find iOS SDK.")810iphoneos_sdk_path = output.strip()811812header_include_paths = []813header_include_paths.extend(find_header_include_paths("SignalServiceKit"))814815# SDS code generation uses clang to parse the AST of Objective-C files.816# We're parsing these files outside the context of an XCode workspace,817# so many things won't work - unless do some legwork.818#819# * Compiling of dependencies.820# * Workspace include and framework search paths.821# * Auto-generated files, like -Swift.h bridging headers.822# * .pch files.823824print(f"Parsing Obj-C files in {src_path}...")825if os.path.isfile(src_path):826process_file(827src_path, iphoneos_sdk_path, swift_bridging_path, module_header_dir_path828)829else:830# First clear out existing .sdsjson files.831for rootdir, dirnames, filenames in os.walk(src_path):832for filename in filenames:833if filename.endswith(sds_common.SDS_JSON_FILE_EXTENSION):834file_path = os.path.abspath(os.path.join(rootdir, filename))835os.remove(file_path)836837for rootdir, dirnames, filenames in os.walk(src_path):838for filename in filenames:839file_path = os.path.abspath(os.path.join(rootdir, filename))840process_file(841file_path,842iphoneos_sdk_path,843swift_bridging_path,844module_header_dir_path,845header_include_paths,846)847848849# TODO: We can't access ivars from Swift without public property accessors.850# TODO: We can't access private properties from Swift without public property accessors.851# We could generate "SDS Private" headers that exposes these properties, but only if they're backed by an ivar.852# TODO: Preprocessor macros & directives won't work properly with this AST parser.853854855