Jeremy W. Sherman

stay a while, and listen

Embedded Content Contains Swift

If you’re developing a QuickLook plugin using Swift, make sure you flip on the EMBEDDED_CONTENT_CONTAINS_SWIFT build setting for the target, otherwise bundle loading will fail in a spectacularly unhelpful way.

Creating a Mixed-Language QuickLook Plugin

Recently I decided to add a QuickLook plugin to my ImageSlicer utility app.

The default QuickLook plugin template stamps out an entirely C plugin. Changing the thumbnail/preview template files to have a .m suffix put us back in Obj-C land, but getting to Swift land takes a couple more steps.

Not to worry: Add a new Swift file to the target, and Xcode will offer to make bridging easy-peasy for you. Give it the go-ahead, and you should be good to go, right?

I add the main model and view classes from my app project to the QuickLook target, wire stuff up to load the document and render the view, and everything compiles and links all happy-like. Let’s test this thing!

Gatekeeper?

I fire up qlmanage, point it at my generator and a .slicedimage document, and I see That Error:

The bundle “QuickLookSlicedImage” couldn’t be loaded because it is damaged or missing necessary resources.

I’ve seen this error way too many times when I grab an older app bundle off the Internet. Every time before, “damaged or missing necessary resources” has been code for “no-one signed this app bundle”.

I’m asking the system to execute code, so, sure, that kind of makes sense?

I hare off looking at using spctl to whitelist my bundle, successfully whitelist it with spctl --add --label JWSDev path/to/QuickLookSlicedImage.qlgenerator, and spctl --assess is OK with it.

Let’s try again.

Not Gatekeeper

I see the same error. Hrm. What if it really is missing something? Now I want to see the smoking gun.

After sufficient rooting around, I eventually work through to where it loads the bundle, then the plugin, then finally to where the real business happens: dlopen.

After the call to dlopen, the CFBundle machinery checked for success with dlerror, and that gave me an actually informative error message (which I’ve abbreviated and hard-wrapped for readability):

1
2
3
4
5
(lldb) x/s $rax
0x100576819: "dlopen(LONG_PATH/QuickLookSlicedImage, 262):
Library not loaded: @rpath/libswiftAppKit.dylib\n
  Referenced from: LONG_PATH/QuickLookSlicedImage\n
  Reason: image not found"

Yup, missing Swift dylibs.

EMBEDDED_CONTENT_CONTAINS_SWIFT

The fix is to tell Xcode to copy all the Swift dylibs the built product needs into its bundle using the build setting EMBEDDED_CONTENT_CONTAINS_SWIFT=YES.

(The other fix is to ensure qlmanage is actually running the generator you’re building now, not the generator embedded in the copy of your app you built an hour or two ago that still has the missing-dylib issue. Oops.)

Take-Away

The take-away is this:

  • When Xcode offers to add a Swift–Obj-C bridging header for you,
  • Then that means the target was not previously configured for Swift,
  • And you should probably ensure that EMBEDDED_CONTENT_CONTAINS_SWIFT=YES gets set for the target.

The “probably” is there because, if you’re baking it into an app bundle that’s already embedding the Swift dylibs, you could probably mess with the rpath to get it to share those rather than having Yet Another Copy of the Swift support dylibs in your app bundle.

But that’ll be a pain, and disk space is cheap, so you’ll probably still want to just flip on EMBEDDED_CONTENT_CONTAINS_SWIFT=YES.