···1-#!/usr/bin/env nix-shell
2-#!nix-shell -i python -p python3 swiftPackages.swift-unwrapped
3-4-"""
5-Generate a frameworks.nix for a macOS SDK.
6-7-You may point this tool at an Xcode bundled SDK, but more ideal is using the
8-SDK from Nixpkgs. For example:
9-10-SDK_PATH="$(nix-build --no-link -A darwin.apple_sdk_11_0.MacOSX-SDK)"
11-./gen-frameworks.py "$SDK_PATH" > ./new-frameworks.nix
12-"""
13-14-import json
15-import os
16-import subprocess
17-import sys
18-19-ALLOWED_LIBS = ["simd"]
20-21-HEADER = """\
22-# This file is generated by gen-frameworks.nix.
23-# Do not edit, put overrides in apple_sdk.nix instead.
24-{ libs, frameworks }: with libs; with frameworks;
25-{
26-"""
27-28-FOOTER = """\
29-}
30-"""
31-32-33-def eprint(*args):
34- print(*args, file=sys.stderr)
35-36-37-def name_from_ident(ident):
38- return ident.get("swift", ident.get("clang"))
39-40-41-def scan_sdk(sdk):
42- # Find frameworks by scanning the SDK frameworks directory.
43- frameworks = [
44- framework.removesuffix(".framework")
45- for framework in os.listdir(f"{sdk}/System/Library/Frameworks")
46- if not framework.startswith("_")
47- ]
48- frameworks.sort()
49-50- # Determine the longest name for padding output.
51- width = len(max(frameworks, key=len))
52-53- output = HEADER
54-55- for framework in frameworks:
56- deps = []
57-58- # Use Swift to scan dependencies, because a module may have both Clang
59- # and Swift parts. Using Clang only imports the Clang module, whereas
60- # using Swift will usually import both Clang + Swift overlay.
61- #
62- # TODO: The above is an assumption. Not sure if it's possible a Swift
63- # module completely shadows a Clang module. (Seems unlikely)
64- #
65- # TODO: Handle "module 'Foobar' is incompatible with feature 'swift'"
66- #
67- # If there were a similar Clang invocation for scanning, we could fix
68- # the above todos, but that doesn't appear to exist.
69- eprint(f"# scanning {framework}")
70- result = subprocess.run(
71- [
72- "swiftc",
73- "-scan-dependencies",
74- # We provide a source snippet via stdin.
75- "-",
76- # Use the provided SDK.
77- "-sdk",
78- sdk,
79- # This search path is normally added automatically by the
80- # compiler based on the SDK, but we have a patch in place that
81- # removes that for SDKs in /nix/store, because our xcbuild stub
82- # SDK doesn't have the directory.
83- # (swift-prevent-sdk-dirs-warning.patch)
84- "-I",
85- f"{sdk}/usr/lib/swift",
86- # For some reason, 'lib/swift/shims' from both the SDK and
87- # Swift compiler are picked up, causing redefinition errors.
88- # This eliminates the latter.
89- "-resource-dir",
90- f"{sdk}/usr/lib/swift",
91- ],
92- input=f"import {framework}".encode(),
93- stdout=subprocess.PIPE,
94- )
95- if result.returncode != 0:
96- eprint(f"# Scanning {framework} failed (exit code {result.returncode})")
97- result.stdout = b""
98-99- # Parse JSON output.
100- if len(result.stdout) != 0:
101- data = json.loads(result.stdout)
102-103- # Entries in the modules list come in pairs. The first is an
104- # identifier (`{ swift: "foobar" }` or `{ clang: "foobar" }`), and
105- # the second metadata for that module. Here we look for the pair
106- # that matches the framework we're scanning (and ignore the rest).
107- modules = data["modules"]
108- for i in range(0, len(modules), 2):
109- ident, meta = modules[i : i + 2]
110-111- # NOTE: We may match twice, for a Swift module _and_ for a
112- # Clang module. So matching here doesn't break from the loop,
113- # and deps is appended to.
114- if name_from_ident(ident) == framework:
115- dep_idents = meta["directDependencies"]
116- deps += [name_from_ident(ident) for ident in dep_idents]
117- # List unfiltered deps in progress output.
118- eprint(ident, "->", dep_idents)
119-120- # Filter out modules that are not separate derivations.
121- # Also filter out duplicates (when a Swift overlay imports the Clang module)
122- allowed = frameworks + ALLOWED_LIBS
123- deps = set([dep for dep in deps if dep in allowed])
124-125- # Filter out self-references. (Swift overlay importing Clang module.)
126- if framework in deps:
127- deps.remove(framework)
128-129- # Generate a Nix attribute line.
130- if len(deps) != 0:
131- deps = list(deps)
132- deps.sort()
133- deps = " ".join(deps)
134- output += f" {framework.ljust(width)} = {{ inherit {deps}; }};\n"
135- else:
136- output += f" {framework.ljust(width)} = {{}};\n"
137-138- output += FOOTER
139- sys.stdout.write(output)
140-141-142-if __name__ == "__main__":
143- if len(sys.argv) != 2:
144- eprint(f"Usage: {sys.argv[0]} <path to MacOSX.sdk>")
145- sys.exit(64)
146-147- scan_sdk(sys.argv[1])