I am able to do it via XCode and leveraging a bridging header and .tdb files, however, I'd like to do the same via the more streamlined swift package init
project, but don't understand how to link in a bridging header and .tdb files?
I've also tried to create a swift package init --type library
and then add that local lib as a dependancy to my Swift cli (--type tool
) project, however, it complains about not knowing what the ObjC symbols are when I try and call them from Swift.
The name of the module that goes with the headers <objc/whatever.h>
is ObjectiveC
, you should be able to import it directly without using any custom headers or linker options.
I've been looking at a lot of blogs etc, but it seems that SPM has changed a lot so most articles are no longer valid.
It seems that the most modern way would be to create an xframework to contain an 'umbrella' header for some private framework with some headers from class-dump and then I can link those in as deps to my Package.swift and then call it from my Swift like how I can via an XCode swift project with tbd and a bridging header?
Does that go for private frameworks too?
Oh, no, not so much. I didnβt realize that was the question, sorry. What ObjC functions are in a private framework, though?
EDIT: I misunderstood and thought you meant βObjC runtime functionsβ, not βAPIs written in ObjCβ. I think if youβre going to the length of writing your own TBD file for a private framework, you can also have that TBD file include the βinstall nameβ where that frameworkβs binary is located? And then you have to pass the custom linker search path for that TBD file and make sure you are trying to link against that library. (I havenβt done this in a while.)
Apple, of course, does not want you to do this, so I wouldnβt expect the tools to make it straightforward. The more you can make your library look like a third-party system-wide library the better, I guess?
Ah I'm probably using incorrect jargon. I meant ObjC classes and methods in the frameworks in the dyld_shared_cache. Not actual objc runtime functions etc.
One ex-Apple bit of information Iβll give here is that either the dyld shared cache isnβt special, in that you link to libraries as if they were still present on disk, or it is, in that none of this will work. The fact that you got it to work in Xcode suggests that itβs the former, and itβs merely a matter of linking against a third-party library that happens to have a TBD instead of a dylib (like the SDKs) and then that TBD happens to have an install name that refers to a library/framework binary path in the shared cache.
That sounds right to me, so I guess my question boils down to, what is the Package.swift syntax (and Swift package folder structure) that I need to link against a .tbd file (with supporting bridge header)? I assume it's probably just setting some cSettings (compiler flags etc)?
Here's an example of how to do it for any others that are curious - GitHub - NSAntoine/PrivateKits: Swift wrappers of various Private System Frameworks for Darwin platforms
FYI you can do this without unsafeFlags
if you generate a framework-bundle-based xcframework with the tbd file instead of an actual binary, and declare it in your SwiftPM manifest with binaryTarget
.
Can you point me to example of how to generate an xcframework bundle?
Assuming you have a framework that looks something like
Foo.framework/
Foo.tbd
Headers/
Foo.h
...
Modules/
module.modulemap (optional)
Info.plist
you can turn it into an xcframework by following Apple's docs:
If you just have a tbd and some header files, you might be able to use the -library
flag instead of -framework
, but there's a chance it'll complain about the tbd not being a real MachO file. If that happens you can create the framework structure yourself; since the tbd specifies an install_name that already exists on the system you don't need to worry about embedding/codesigning/etc.
Trying this with the previously linked src
tree XCFrameworks/CoreUI.framework/
XCFrameworks/CoreUI.framework/
βββ CoreUI.tbd
βββ Headers
β βββ CSIBitmapWrapper.h
β βββ CSIGenerator.h
β βββ CUICatalog.h
β βββ CUICommonAssetStorage.h
β βββ CUINamedLookup.h
β βββ CUIRenditionKey.h
β βββ CUIStructuredThemeStore.h
β βββ CUIThemeRendition.h
β βββ CoreUI.h
β βββ structs.h
βββ Info.plist
βββ Modules
βββ module.modulemap
When I try this it complains
xcodebuild -create-xcframework -framework XCFrameworks/CoreUI.framework -output CoreUI.xcframework
error: unable to read the file at 'XCFrameworks/CoreUI.framework/CoreUI'
Also recent macOS removed the on-disk DYLIBS in the same way they do on iOS, so they only exist in the dyld_shared_cache
This is a limitation with xcodebuild -create-xframework
rather than the xcframework format itself. You can stick any binary into the framework, name it CoreUI
, run -create-xcframework, and replace it with your CoreUI.tbd
inside the created xcframework.
Related tidbit: if you ever need a stub xcframework without a tbd or a Mach-O (helpful for using .swiftmodule
s with SwiftPM), you can echo "!<arch>" > Foo.framework/Foo
as that's a (dubiously) valid static library!
I'm trying to build a project following the XCFramework + TBD suggestion, but the linker refuses to work.
% swift build
Building for debugging...
error: link command failed with exit code 1 (use -v to see invocation)
ld: unknown file type in '/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug/StoreFoundation.framework/StoreFoundation.tbd'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: fatalError
[6/12] Linking libMASDownloadMetadata.dylib
% "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" -demangle -lto_library /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libLTO.dylib -dynamic -dylib -arch x86_64 -platform_version macos 10.13.0 10.13.0 -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk -o /Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug/libMASDownloadMetadata.dylib -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/usr/lib/swift -L/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib /Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug/MASDownloadMetadataLib.build/MASDownloadMetadata.m.o -rpath /usr/lib/swift -framework StoreFoundation -install_name @rpath/libMASDownloadMetadata.dylib -rpath @loader_path -lSystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/15.0.0/lib/darwin/libclang_rt.osx.a -F/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug -F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks -v
@(#)PROGRAM:ld PROJECT:ld-1053.12
BUILD 15:44:24 Feb 3 2024
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em
will use ld-classic for: armv6 armv7 armv7s arm64_32 i386 armv6m armv7k armv7m armv7em
LTO support using: LLVM version 15.0.0 (static support for 29, runtime is 29)
TAPI support using: Apple TAPI version 15.0.0 (tapi-1500.3.2.2)
Library search paths:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/usr/lib/swift
/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/usr/lib
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/usr/lib/swift
Framework search paths:
/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/System/Library/Frameworks
ld: unknown file type in '/Users/user/Documents/MyTool/.build/x86_64-apple-macosx/debug/StoreFoundation.framework/StoreFoundation.tbd'
The structure of the XCFramework seems to be okay and so does the TBD file.
% tree StoreFoundation.xcframework
StoreFoundation.xcframework
βββ Info.plist
βββ macos-arm64_x86_64
βββ StoreFoundation.framework
βββ Headers
β βββ StoreFoundation
β βββ ASDNotificationCenterDialogObserver-Protocol.h
β βββ ...
β βββ StoreFoundation.h
βββ Info.plist
βββ Modules
β βββ module.modulemap
βββ StoreFoundation.tbd
6 directories, 99 files
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
install-name: '/System/Library/PrivateFrameworks/StoreFoundation.framework/Versions/A/StoreFoundation'
exports:
- targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
symbols: [ OBJC_IVAR_$_CKDockMessaging._cachedIconPaths,
...,
_markMainQueue ]
objc-classes: [ CKBook,
...,
SSRequest ]
objc-ivars: [ CKBook._author,
...,
SSRequest._state ]
This is the Package.swift
for the project:
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MASDownloadMetadata",
products: [
.executable(name: "masdownloadmetadata", targets: ["MASDownloadMetadataBin"]),
.library(name: "MASDownloadMetadata", type: .dynamic, targets: ["MASDownloadMetadataLib"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
],
targets: [
.executableTarget(
name: "MASDownloadMetadataBin",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.target(name: "MASDownloadMetadataLib"),
]
),
.target(
name: "MASDownloadMetadataLib",
dependencies: [
.target(name: "StoreFoundation"),
]
),
.binaryTarget(
name: "StoreFoundation",
path: "StoreFoundation.xcframework"
)
]
)
Do you have any idea of what could be wrong with this configuration?
It looks like my TBD file was missing ...
at the end of the file, causing ld
to fail with unknown file type.
Fails to link:
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
install-name: '/System/Library/PrivateFrameworks/StoreFoundation.framework/Versions/A/StoreFoundation'
exports:
- targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
symbols: [ OBJC_IVAR_$_CKDockMessaging._cachedIconPaths,
_markMainQueue ]
objc-classes: [ CKBook,
SSRequest ]
objc-ivars: [ CKBook._author,
SSRequest._state ]
Succeeds:
--- !tapi-tbd
tbd-version: 4
targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
install-name: '/System/Library/PrivateFrameworks/StoreFoundation.framework/Versions/A/StoreFoundation'
exports:
- targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, arm64e-macos, arm64e-maccatalyst, armv7-ios, armv7s-ios, arm64-ios, arm64e-ios ]
symbols: [ OBJC_IVAR_$_CKDockMessaging._cachedIconPaths,
_markMainQueue ]
objc-classes: [ CKBook,
SSRequest ]
objc-ivars: [ CKBook._author,
SSRequest._state ]
...
@kabiroberai I'd be curious what you think about this?
Where I'm trying to auto-gen XCFrameworks for ANY dylib in the DSC. Any feedback would be great! :)
It just clicked that you created the ipsw tool β I've found it immensely useful for RE so thank you! XCFramework generation seems like it could be quite handy, and a repo with private frameworks would be neat. Once Objective-C is out of the way I wonder if this could be extended to regenerating private swiftinterface
files by extracting metadata from the binary; might be worth exploring as a future direction.