Skip to content

Commit 91839dc

Browse files
committed
perf(swift-interop): reuse the thunk object across app builds
The app build step keys the generated object on a hash of the extracted symbol graphs, the generator binary, the step itself, and the generator and compiler command lines. A relink with the same key reuses the previous object instead of regenerating, verifying and recompiling, so an unchanged module costs only its symbol graph extraction.
1 parent ccc57f4 commit 91839dc

2 files changed

Lines changed: 37 additions & 11 deletions

File tree

‎docs/swift-interop.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ Objective-C metadata step:
7070
with `-u` for every register symbol.
7171

7272
The step's logs and outputs are in `$(CONFIGURATION_BUILD_DIR)/NSSwiftInterop-<arch>`.
73-
Verification and compilation add build time (WeatherKit: about 19 s per
74-
architecture in a Release build).
73+
Generation and compilation take a while (WeatherKit: about 19 s per
74+
architecture in a Release build), so the object is reused while the
75+
extracted symbol graphs, the generator and the settings stay the same; an
76+
unchanged build pays only for the extraction.
7577

7678
## Generation units
7779

‎metadata-generator/build-step-swift-interop.py‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88
# SDK modules (WeatherKit), SwiftPM packages and the app's own module. They are
99
# generated as one unit. The script writes the object file and the extra
1010
# linker arguments, one per line, to
11-
# $CONFIGURATION_BUILD_DIR/NSSwiftInterop-<arch>/link-arguments.txt.
11+
# $CONFIGURATION_BUILD_DIR/NSSwiftInterop-<arch>/link-arguments.txt. When the
12+
# extracted symbol graphs, the generator and the settings are unchanged, the
13+
# previous object is reused.
1214
#
1315
# Optional settings:
1416
# NS_SWIFT_UNIT unit name (default: <product module>Swift)
1517
# NS_SWIFT_VERIFY NO skips compiler verification of the surface
1618
# NS_TYPESCRIPT_DECLARATIONS_PATH also writes swift!<Module>.d.ts there
1719

20+
import hashlib
21+
import json
1822
import os
1923
import shlex
2024
import shutil
@@ -42,8 +46,10 @@ def main(arch):
4246
modules = env("NS_SWIFT_MODULES").split()
4347
work = os.path.join(env("CONFIGURATION_BUILD_DIR"), "NSSwiftInterop-" + arch)
4448
arguments_file = os.path.join(work, "link-arguments.txt")
45-
shutil.rmtree(work, ignore_errors=True)
46-
os.makedirs(work)
49+
key_file = os.path.join(work, "cache-key.txt")
50+
for stale in ("graphs", "verify"):
51+
shutil.rmtree(os.path.join(work, stale), ignore_errors=True)
52+
os.makedirs(work, exist_ok=True)
4753
if not modules:
4854
open(arguments_file, "w").close()
4955
return
@@ -100,15 +106,33 @@ def resolve(paths):
100106
declarations = env("NS_TYPESCRIPT_DECLARATIONS_PATH")
101107
if declarations:
102108
generator += ["-output-typescript", os.path.join(declarations, "swift")]
103-
run(generator + ["Xclang"], os.path.join(work, "generator.log"))
104-
105109
thunks = os.path.join(work, unit + "+NativeScript.swift")
106110
obj = os.path.join(work, unit + "+NativeScript.o")
107111
optimization = "-Onone" if env("CONFIGURATION") == "Debug" else "-O"
108-
run(["xcrun", "swiftc", "-parse-as-library", "-c", thunks, "-o", obj,
109-
"-module-name", "NSSwiftInterop" + unit, "-target", target, "-sdk", sdk,
110-
"-swift-version", "5", optimization] + search_flags,
111-
os.path.join(work, "swiftc.log"))
112+
compile = ["xcrun", "swiftc", "-parse-as-library", "-c", thunks, "-o", obj,
113+
"-module-name", "NSSwiftInterop" + unit, "-target", target, "-sdk", sdk,
114+
"-swift-version", "5", optimization] + search_flags
115+
116+
# Everything the object depends on.
117+
digest = hashlib.sha256()
118+
for directory in graph_dirs:
119+
for name in sorted(os.listdir(directory)):
120+
with open(os.path.join(directory, name), "rb") as graph:
121+
digest.update(name.encode() + graph.read())
122+
tool = os.stat("./objc-metadata-generator")
123+
digest.update(json.dumps([generator, compile, tool.st_size, tool.st_mtime,
124+
os.stat(__file__).st_mtime]).encode())
125+
key = digest.hexdigest()
126+
previous = open(key_file).read() if os.path.exists(key_file) else ""
127+
if previous == key and os.path.exists(obj):
128+
print("Swift interop: {} is up to date".format(obj))
129+
else:
130+
if os.path.exists(key_file):
131+
os.remove(key_file)
132+
run(generator + ["Xclang"], os.path.join(work, "generator.log"))
133+
run(compile, os.path.join(work, "swiftc.log"))
134+
with open(key_file, "w") as output:
135+
output.write(key)
112136

113137
with open(arguments_file, "w") as output:
114138
output.write(obj + "\n")

0 commit comments

Comments
 (0)