1 #!/usr/bin/env nix-shell
2 #!nix-shell -i python -p python3 swiftPackages.swift-unwrapped
5 Generate a frameworks.nix for a macOS SDK.
7 You may point this tool at an Xcode bundled SDK, but more ideal is using the
8 SDK from Nixpkgs. For example:
10 SDK_PATH="$(nix-build --no-link -A darwin.apple_sdk_11_0.MacOSX-SDK)"
11 ./gen-frameworks.py "$SDK_PATH" > ./new-frameworks.nix
19 ALLOWED_LIBS
= ["simd"]
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;
34 print(*args
, file=sys
.stderr
)
37 def name_from_ident(ident
):
38 return ident
.get("swift", ident
.get("clang"))
42 # Find frameworks by scanning the SDK frameworks directory.
44 framework
.removesuffix(".framework")
45 for framework
in os
.listdir(f
"{sdk}/System/Library/Frameworks")
46 if not framework
.startswith("_")
50 # Determine the longest name for padding output.
51 width
= len(max(frameworks
, key
=len))
55 for framework
in frameworks
:
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.
62 # TODO: The above is an assumption. Not sure if it's possible a Swift
63 # module completely shadows a Clang module. (Seems unlikely)
65 # TODO: Handle "module 'Foobar' is incompatible with feature 'swift'"
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(
74 # We provide a source snippet via stdin.
76 # Use the provided 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)
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.
90 f
"{sdk}/usr/lib/swift",
92 input=f
"import {framework}".encode(),
93 stdout
=subprocess
.PIPE
,
95 if result
.returncode
!= 0:
96 eprint(f
"# Scanning {framework} failed (exit code {result.returncode})")
100 if len(result
.stdout
) != 0:
101 data
= json
.loads(result
.stdout
)
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]
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
)
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
])
125 # Filter out self-references. (Swift overlay importing Clang module.)
126 if framework
in deps
:
127 deps
.remove(framework
)
129 # Generate a Nix attribute line.
133 deps
= " ".join(deps
)
134 output
+= f
" {framework.ljust(width)} = {{ inherit {deps}; }};\n"
136 output
+= f
" {framework.ljust(width)} = {{}};\n"
139 sys
.stdout
.write(output
)
142 if __name__
== "__main__":
143 if len(sys
.argv
) != 2:
144 eprint(f
"Usage: {sys.argv[0]} <path to MacOSX.sdk>")
147 scan_sdk(sys
.argv
[1])