#!/usr/bin/env python3 import os import sys import subprocess import datetime import argparse import re import json import sds_common from sds_common import fail import tempfile import shutil # We need to generate fake -Swift.h bridging headers that declare the Swift # types that our Objective-C files might use. This script does that. def ows_getoutput(cmd): proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, stderr = proc.communicate() return proc.returncode, stdout, stderr class Namespace: def __init__(self): self.swift_protocol_names = [] self.swift_class_names = [] def parse_swift_ast(file_path, namespace, ast): json_data = json.loads(ast) json_maps = json_data.get("key.substructure") if json_maps is None: return for json_map in json_maps: kind = json_map.get("key.kind") if kind is None: continue elif kind == "source.lang.swift.decl.protocol": # "key.kind" : "source.lang.swift.decl.protocol", # "key.length" : 1067, # "key.name" : "TypingIndicators", # "key.namelength" : 16, # "key.nameoffset" : 135, # "key.offset" : 126, # "key.runtime_name" : "OWSTypingIndicators", name = json_map.get("key.runtime_name") if name is None or len(name) < 1 or name.startswith("_"): name = json_map.get("key.name") if name is None or len(name) < 1: fail("protocol is missing name.") continue if name.startswith("_"): continue namespace.swift_protocol_names.append(name) elif kind == "source.lang.swift.decl.class": # "key.kind" : "source.lang.swift.decl.class", # "key.length" : 15057, # "key.name" : "TypingIndicatorsImpl", # "key.namelength" : 20, # "key.nameoffset" : 1251, # "key.offset" : 1245, # "key.runtime_name" : "OWSTypingIndicatorsImpl", name = json_map.get("key.runtime_name") if name is None or len(name) < 1 or name.startswith("_"): name = json_map.get("key.name") if name is None or len(name) < 1: fail("class is missing name.") continue if name.startswith("_"): continue namespace.swift_class_names.append(name) def process_file(file_path, namespace): filename = os.path.basename(file_path) if not filename.endswith(".swift"): return if filename == "EmojiWithSkinTones+String.swift": return command = ["sourcekitten", "structure", "--file", file_path] # for part in command: # print '\t', part # command = ' '.join(command).strip() # print 'command', command # output = commands.getoutput(command) # command = ' '.join(command).strip() # print 'command', command exit_code, output, error_output = ows_getoutput(command) if exit_code != 0: print("exit_code:", exit_code) fail("Are you missing sourcekitten? Install with homebrew?") if len(error_output.strip()) > 0: print("error_output:", error_output) # print 'output:', len(output) # exit(1) output = output.strip() # print 'output', output parse_swift_ast(file_path, namespace, output) def generate_swift_bridging_header(namespace, swift_bridging_path): output = [] for name in namespace.swift_protocol_names: output.append( """ @protocol %s @end """ % (name,) ) for name in namespace.swift_class_names: output.append( """ @interface %s : NSObject @end """ % (name,) ) output = "\n".join(output).strip() if len(output) < 1: return header = """// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // #import // NOTE: This file is generated by %s. // Do not manually edit it, instead run `sds_codegen.sh`. """ % ( sds_common.pretty_module_path(__file__), ) output = (header + output).strip() # print 'output:', output[:500] output = sds_common.clean_up_generated_swift(output) # print 'output:', output[:500] # print 'output', output parent_dir_path = os.path.dirname(swift_bridging_path) # print 'parent_dir_path', parent_dir_path if not os.path.exists(parent_dir_path): os.makedirs(parent_dir_path) print("Writing:", swift_bridging_path) with open(swift_bridging_path, "wt") as f: f.write(output) # --- def process_dir(src_dir_path, dir_name, dst_dir_path): namespace = Namespace() dir_path = os.path.abspath(os.path.join(src_dir_path, dir_name)) file_paths = [] for rootdir, dirnames, filenames in os.walk(dir_path): for filename in filenames: file_path = os.path.abspath(os.path.join(rootdir, filename)) file_paths.append(file_path) print(f"Found {len(file_paths)} files in {dir_path}") for idx, file_path in enumerate(file_paths): process_file(file_path, namespace) if idx % 100 == 99: print(f"... {idx+1} / {len(file_paths)}") bridging_header_path = os.path.abspath( os.path.join(dst_dir_path, dir_name, dir_name + "-Swift.h") ) generate_swift_bridging_header(namespace, bridging_header_path) # --- if __name__ == "__main__": parser = argparse.ArgumentParser(description="Parse Objective-C AST.") parser.add_argument( "--src-path", required=True, help="used to specify a path to process." ) parser.add_argument( "--swift-bridging-path", required=True, help="used to specify a path to process.", ) args = parser.parse_args() src_dir_path = os.path.abspath(args.src_path) swift_bridging_path = os.path.abspath(args.swift_bridging_path) if os.path.exists(swift_bridging_path): shutil.rmtree(swift_bridging_path) process_dir(src_dir_path, "SignalServiceKit", swift_bridging_path)