Package system-library targets as header-only frameworks#279
Conversation
…ule maps The ^module pattern matched only at string start, so a custom module map opening with comment lines was shipped with its plain module declaration unconverted. Inside a framework bundle, a non-framework module resolves header paths relative to Modules/ instead of Headers/, so such modules were unresolvable for consumers. Anchoring per line converts declarations on any line start; nested submodules are indented and stay untouched. The conversion also moves from NSRegularExpression to a Swift Regex literal, resolving the TODO. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
System-library targets fell into the library branch of ModuleTypeResolver and came out as a clang module with a nonexistent include directory and no headers. Resolve them as .system instead: the module directory is the include root (SwiftPM requires module.modulemap at the directory root and resolves its header paths relative to it), with the headers enumerated beneath it. Module map existence is validated at packaging time, where a missing file can be reported with a proper error. isFrameworkProducible records which target kinds an XCFramework can be produced for; the producer side adopts it separately. Resolved-packages caches written before this modeling carry the wrong module type for system modules, and packaging from them would produce empty frameworks, so such caches are discarded and re-resolved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
System-library targets reach the producer through dependency edges, where they crashed on a fatalError, and skipping them leaves consumers with a missing required module for every import of such a target. They have nothing to compile: SwiftPM exposes them to importers through header search paths, which consumers of prebuilt XCFrameworks do not get. The build-product graph now keeps every producible kind and drops the rest gracefully. SystemLibraryPackager assembles a header-only framework per SDK (headers, the shipped module map converted to a framework module, a generated Info.plist, and a per-architecture stub binary XCFramework creation requires to identify the slice platform) and combines them with the existing createXCFramework path, so import resolves through the framework search consumers already have. Framework caching works unchanged since cache keys carry no source hashes. Includes are rewritten against the union of all importers' dependency closures: a system-library target declares no dependencies of its own, and its headers compile in each importer's context. A target declaring pkgConfig is packaged with a warning; its headers can still resolve against the SDK and link directives survive in the module map, while pkg-config flags cannot be carried into a prebuilt framework. Dynamic builds also stop injecting -framework link flags for system modules: they build no product, so the linker had nothing to find. An end-to-end test builds a Swift library importing a system-library module whose header includes another module's header, and compiles a consumer against the produced XCFrameworks using only -F. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a44bd90 to
581ee9a
Compare
| /// The dependency closure of every module that transitively depends on this one: exactly | ||
| /// the compile contexts SwiftPM would have compiled these headers in. |
There was a problem hiding this comment.
| /// The dependency closure of every module that transitively depends on this one: exactly | |
| /// the compile contexts SwiftPM would have compiled these headers in. | |
| // The dependency closure of every module that transitively depends on this one: exactly | |
| // the compile contexts SwiftPM would have compiled these headers in. |
This comment is not docc comment
| /// Caches written before system-library support carry a wrong module type for system | ||
| /// modules; packaging from them would produce empty frameworks. |
There was a problem hiding this comment.
ditto
| /// Caches written before system-library support carry a wrong module type for system | |
| /// modules; packaging from them would produce empty frameworks. | |
| // Caches written before system-library support carry a wrong module type for system | |
| // modules; packaging from them would produce empty frameworks. |
There was a problem hiding this comment.
Fixed in 960a9df, reworded to lead with what it detects.
| id: \.target.name, | ||
| childIDs: { $0.target.dependencies.flatMap(\.moduleNames) } | ||
| ) | ||
| return graph.filter { $0.target.underlying.type.isFrameworkProducible } |
There was a problem hiding this comment.
I guess this line is not covered by test
| // Sorted because the enumeration order is unspecified: keeps the resolution | ||
| // result, and thus the serialized snapshot, deterministic. |
There was a problem hiding this comment.
How about adding a test to check this case?
There was a problem hiding this comment.
Covered in c1d3358: a nested header asserts recursive discovery in path-sorted order.
| throw Error.unexpectedModuleType(targetName: target.name) | ||
| } | ||
| guard fileSystem.exists(moduleMapPath) else { | ||
| throw Error.moduleMapNotFound(targetName: target.name, expectedPath: moduleMapPath) |
There was a problem hiding this comment.
Maybe we don't have a test for this line
There was a problem hiding this comment.
Covered in 2c1530c, together with the unexpectedModuleType path.
| let objectPath = workingDirectory.appending(component: "stub_\(architecture).o") | ||
| try await executor.execute([ | ||
| "/usr/bin/xcrun", | ||
| "--sdk", sdk.settingValue, |
There was a problem hiding this comment.
I'm not familiar with mac catalyst, but I'm worried whether this command can handle mac catalyst or not
There was a problem hiding this comment.
Good catch: maccatalyst is not an SDK name, so this failed under Catalyst. Fixed in 2c1530c: the stub compiles against the macOS SDK with a macabi target triple, covered by packagesMacCatalystSlice.
| return replaced | ||
| // Line-anchored so declarations below leading comment lines convert too; nested | ||
| // submodules are indented and stay untouched. | ||
| let moduleDeclaration = /^module/.anchorsMatchLineEndings() |
There was a problem hiding this comment.
(nit) I prefer /^module\b/, \s or something
maccatalyst names an xcodebuild destination, not an SDK: xcrun cannot locate it. The macabi target triple marks the slice platform instead. Tests cover the produced Catalyst slice and the packager error paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An executable reachable through a target dependency edge is dropped, and its parent is reconnected to the surviving library dependency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stale-cache note now leads with what it detects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The filter used to reconnect parents to a dropped node's children, so dependencies serving only executables or macros were packaged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The linkable walk mirrors the build-product pruning; without it, dynamic linking injected -framework flags for modules pruned from the products. System-library modules are packaged but resolve through the SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same precedence as the compiled path: custom contents replace the shipped module map wholesale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
SwiftPM's
.systemLibrarytargets carry a module map and optional headers and have nothing to compile:SwiftPM exposes such modules to importers through injected search paths, which only resolve while the package sources are on disk. Prebuilt-framework consumers only get
-F, so today the producer dies withfatalError("Unexpected target type"), and even if it skipped the target,import SysShimin consumers would fail withmissing required module 'SysShim'.This PR packages system-library targets as header-only frameworks, so the module resolves through the framework search paths consumers already have:
Behavior
pkgConfigdeclarations log a warning instead of failing: shim-style modules still resolve through the SDK and autolinking.-frameworkfor dependencies that produce no linkable framework: system modules resolve through the SDK, and non-producible targets build no product. Generated module maps'link frameworksections follow the same rule.fatalError("Unexpected target type").Testing
-F: themissing required modulefailure this PR removes.