Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Scripts/sds_codegen/sds_parse_swift_bridging.py
1 views
1
#!/usr/bin/env python3
2
3
import os
4
import sys
5
import subprocess
6
import datetime
7
import argparse
8
import re
9
import json
10
import sds_common
11
from sds_common import fail
12
import tempfile
13
import shutil
14
15
16
# We need to generate fake -Swift.h bridging headers that declare the Swift
17
# types that our Objective-C files might use. This script does that.
18
19
20
def ows_getoutput(cmd):
21
proc = subprocess.Popen(
22
cmd,
23
stdout=subprocess.PIPE,
24
stderr=subprocess.PIPE,
25
)
26
stdout, stderr = proc.communicate()
27
28
return proc.returncode, stdout, stderr
29
30
31
class Namespace:
32
def __init__(self):
33
self.swift_protocol_names = []
34
self.swift_class_names = []
35
36
37
def parse_swift_ast(file_path, namespace, ast):
38
json_data = json.loads(ast)
39
40
json_maps = json_data.get("key.substructure")
41
if json_maps is None:
42
return
43
44
for json_map in json_maps:
45
kind = json_map.get("key.kind")
46
if kind is None:
47
continue
48
elif kind == "source.lang.swift.decl.protocol":
49
# "key.kind" : "source.lang.swift.decl.protocol",
50
# "key.length" : 1067,
51
# "key.name" : "TypingIndicators",
52
# "key.namelength" : 16,
53
# "key.nameoffset" : 135,
54
# "key.offset" : 126,
55
# "key.runtime_name" : "OWSTypingIndicators",
56
name = json_map.get("key.runtime_name")
57
if name is None or len(name) < 1 or name.startswith("_"):
58
name = json_map.get("key.name")
59
if name is None or len(name) < 1:
60
fail("protocol is missing name.")
61
continue
62
if name.startswith("_"):
63
continue
64
namespace.swift_protocol_names.append(name)
65
elif kind == "source.lang.swift.decl.class":
66
# "key.kind" : "source.lang.swift.decl.class",
67
# "key.length" : 15057,
68
# "key.name" : "TypingIndicatorsImpl",
69
# "key.namelength" : 20,
70
# "key.nameoffset" : 1251,
71
# "key.offset" : 1245,
72
# "key.runtime_name" : "OWSTypingIndicatorsImpl",
73
name = json_map.get("key.runtime_name")
74
if name is None or len(name) < 1 or name.startswith("_"):
75
name = json_map.get("key.name")
76
if name is None or len(name) < 1:
77
fail("class is missing name.")
78
continue
79
if name.startswith("_"):
80
continue
81
namespace.swift_class_names.append(name)
82
83
84
def process_file(file_path, namespace):
85
filename = os.path.basename(file_path)
86
if not filename.endswith(".swift"):
87
return
88
if filename == "EmojiWithSkinTones+String.swift":
89
return
90
91
command = ["sourcekitten", "structure", "--file", file_path]
92
# for part in command:
93
# print '\t', part
94
# command = ' '.join(command).strip()
95
# print 'command', command
96
# output = commands.getoutput(command)
97
98
# command = ' '.join(command).strip()
99
# print 'command', command
100
exit_code, output, error_output = ows_getoutput(command)
101
if exit_code != 0:
102
print("exit_code:", exit_code)
103
fail("Are you missing sourcekitten? Install with homebrew?")
104
if len(error_output.strip()) > 0:
105
print("error_output:", error_output)
106
# print 'output:', len(output)
107
108
# exit(1)
109
110
output = output.strip()
111
# print 'output', output
112
113
parse_swift_ast(file_path, namespace, output)
114
115
116
def generate_swift_bridging_header(namespace, swift_bridging_path):
117
118
output = []
119
120
for name in namespace.swift_protocol_names:
121
output.append(
122
"""
123
@protocol %s
124
@end
125
"""
126
% (name,)
127
)
128
129
for name in namespace.swift_class_names:
130
output.append(
131
"""
132
@interface %s : NSObject
133
@end
134
"""
135
% (name,)
136
)
137
138
output = "\n".join(output).strip()
139
if len(output) < 1:
140
return
141
142
header = """//
143
// Copyright 2022 Signal Messenger, LLC
144
// SPDX-License-Identifier: AGPL-3.0-only
145
//
146
147
#import <Foundation/Foundation.h>
148
149
// NOTE: This file is generated by %s.
150
// Do not manually edit it, instead run `sds_codegen.sh`.
151
152
""" % (
153
sds_common.pretty_module_path(__file__),
154
)
155
output = (header + output).strip()
156
157
# print 'output:', output[:500]
158
159
output = sds_common.clean_up_generated_swift(output)
160
161
# print 'output:', output[:500]
162
163
# print 'output', output
164
165
parent_dir_path = os.path.dirname(swift_bridging_path)
166
# print 'parent_dir_path', parent_dir_path
167
if not os.path.exists(parent_dir_path):
168
os.makedirs(parent_dir_path)
169
170
print("Writing:", swift_bridging_path)
171
with open(swift_bridging_path, "wt") as f:
172
f.write(output)
173
174
175
# ---
176
177
178
def process_dir(src_dir_path, dir_name, dst_dir_path):
179
namespace = Namespace()
180
181
dir_path = os.path.abspath(os.path.join(src_dir_path, dir_name))
182
183
file_paths = []
184
for rootdir, dirnames, filenames in os.walk(dir_path):
185
for filename in filenames:
186
file_path = os.path.abspath(os.path.join(rootdir, filename))
187
file_paths.append(file_path)
188
189
print(f"Found {len(file_paths)} files in {dir_path}")
190
for idx, file_path in enumerate(file_paths):
191
process_file(file_path, namespace)
192
if idx % 100 == 99:
193
print(f"... {idx+1} / {len(file_paths)}")
194
195
bridging_header_path = os.path.abspath(
196
os.path.join(dst_dir_path, dir_name, dir_name + "-Swift.h")
197
)
198
generate_swift_bridging_header(namespace, bridging_header_path)
199
200
201
# ---
202
203
if __name__ == "__main__":
204
205
parser = argparse.ArgumentParser(description="Parse Objective-C AST.")
206
parser.add_argument(
207
"--src-path", required=True, help="used to specify a path to process."
208
)
209
parser.add_argument(
210
"--swift-bridging-path",
211
required=True,
212
help="used to specify a path to process.",
213
)
214
args = parser.parse_args()
215
216
src_dir_path = os.path.abspath(args.src_path)
217
swift_bridging_path = os.path.abspath(args.swift_bridging_path)
218
219
if os.path.exists(swift_bridging_path):
220
shutil.rmtree(swift_bridging_path)
221
222
process_dir(src_dir_path, "SignalServiceKit", swift_bridging_path)
223
224