···11-#!/usr/bin/env nix-shell
22-#!nix-shell -i python -p python3 swiftPackages.swift-unwrapped
33-44-"""
55-Generate a frameworks.nix for a macOS SDK.
66-77-You may point this tool at an Xcode bundled SDK, but more ideal is using the
88-SDK from Nixpkgs. For example:
99-1010-SDK_PATH="$(nix-build --no-link -A darwin.apple_sdk_11_0.MacOSX-SDK)"
1111-./gen-frameworks.py "$SDK_PATH" > ./new-frameworks.nix
1212-"""
1313-1414-import json
1515-import os
1616-import subprocess
1717-import sys
1818-1919-ALLOWED_LIBS = ["simd"]
2020-2121-HEADER = """\
2222-# This file is generated by gen-frameworks.nix.
2323-# Do not edit, put overrides in apple_sdk.nix instead.
2424-{ libs, frameworks }: with libs; with frameworks;
2525-{
2626-"""
2727-2828-FOOTER = """\
2929-}
3030-"""
3131-3232-3333-def eprint(*args):
3434- print(*args, file=sys.stderr)
3535-3636-3737-def name_from_ident(ident):
3838- return ident.get("swift", ident.get("clang"))
3939-4040-4141-def scan_sdk(sdk):
4242- # Find frameworks by scanning the SDK frameworks directory.
4343- frameworks = [
4444- framework.removesuffix(".framework")
4545- for framework in os.listdir(f"{sdk}/System/Library/Frameworks")
4646- if not framework.startswith("_")
4747- ]
4848- frameworks.sort()
4949-5050- # Determine the longest name for padding output.
5151- width = len(max(frameworks, key=len))
5252-5353- output = HEADER
5454-5555- for framework in frameworks:
5656- deps = []
5757-5858- # Use Swift to scan dependencies, because a module may have both Clang
5959- # and Swift parts. Using Clang only imports the Clang module, whereas
6060- # using Swift will usually import both Clang + Swift overlay.
6161- #
6262- # TODO: The above is an assumption. Not sure if it's possible a Swift
6363- # module completely shadows a Clang module. (Seems unlikely)
6464- #
6565- # TODO: Handle "module 'Foobar' is incompatible with feature 'swift'"
6666- #
6767- # If there were a similar Clang invocation for scanning, we could fix
6868- # the above todos, but that doesn't appear to exist.
6969- eprint(f"# scanning {framework}")
7070- result = subprocess.run(
7171- [
7272- "swiftc",
7373- "-scan-dependencies",
7474- # We provide a source snippet via stdin.
7575- "-",
7676- # Use the provided SDK.
7777- "-sdk",
7878- sdk,
7979- # This search path is normally added automatically by the
8080- # compiler based on the SDK, but we have a patch in place that
8181- # removes that for SDKs in /nix/store, because our xcbuild stub
8282- # SDK doesn't have the directory.
8383- # (swift-prevent-sdk-dirs-warning.patch)
8484- "-I",
8585- f"{sdk}/usr/lib/swift",
8686- # For some reason, 'lib/swift/shims' from both the SDK and
8787- # Swift compiler are picked up, causing redefinition errors.
8888- # This eliminates the latter.
8989- "-resource-dir",
9090- f"{sdk}/usr/lib/swift",
9191- ],
9292- input=f"import {framework}".encode(),
9393- stdout=subprocess.PIPE,
9494- )
9595- if result.returncode != 0:
9696- eprint(f"# Scanning {framework} failed (exit code {result.returncode})")
9797- result.stdout = b""
9898-9999- # Parse JSON output.
100100- if len(result.stdout) != 0:
101101- data = json.loads(result.stdout)
102102-103103- # Entries in the modules list come in pairs. The first is an
104104- # identifier (`{ swift: "foobar" }` or `{ clang: "foobar" }`), and
105105- # the second metadata for that module. Here we look for the pair
106106- # that matches the framework we're scanning (and ignore the rest).
107107- modules = data["modules"]
108108- for i in range(0, len(modules), 2):
109109- ident, meta = modules[i : i + 2]
110110-111111- # NOTE: We may match twice, for a Swift module _and_ for a
112112- # Clang module. So matching here doesn't break from the loop,
113113- # and deps is appended to.
114114- if name_from_ident(ident) == framework:
115115- dep_idents = meta["directDependencies"]
116116- deps += [name_from_ident(ident) for ident in dep_idents]
117117- # List unfiltered deps in progress output.
118118- eprint(ident, "->", dep_idents)
119119-120120- # Filter out modules that are not separate derivations.
121121- # Also filter out duplicates (when a Swift overlay imports the Clang module)
122122- allowed = frameworks + ALLOWED_LIBS
123123- deps = set([dep for dep in deps if dep in allowed])
124124-125125- # Filter out self-references. (Swift overlay importing Clang module.)
126126- if framework in deps:
127127- deps.remove(framework)
128128-129129- # Generate a Nix attribute line.
130130- if len(deps) != 0:
131131- deps = list(deps)
132132- deps.sort()
133133- deps = " ".join(deps)
134134- output += f" {framework.ljust(width)} = {{ inherit {deps}; }};\n"
135135- else:
136136- output += f" {framework.ljust(width)} = {{}};\n"
137137-138138- output += FOOTER
139139- sys.stdout.write(output)
140140-141141-142142-if __name__ == "__main__":
143143- if len(sys.argv) != 2:
144144- eprint(f"Usage: {sys.argv[0]} <path to MacOSX.sdk>")
145145- sys.exit(64)
146146-147147- scan_sdk(sys.argv[1])