//
you're reading...

Programming

Code Signing Failure due to Symlink Folders

I’ve stumbled upon the following question in a forum:

My application uses multiple frameworks. Each framework in turn is dependent on one or more other frameworks. This is achieved using symbolic links. I used this structure to reduce the application’s size.

See MyApp.app bundle structure as below:

myApp.app / Contents /
    Frameworks/
        ABC.framework /
            ABC (symlink to current version)
            Resources (symlink to current version)
            Versions/
                A /
                    ABC
                    Resources/
                Current / (symlink to A)
                Frameworks / (symlink to  myApp.app 's Framework folder)
        XYZ.framework/
            ABC (symlink to current version)
            Resources (symlink to current version)
            Versions/
                A /
                    ABC
                    Resources/
            Current / (symlink to A)
            Frameworks / (symlink to  myApp.app 's Framework folder)
    Info.plist
    MacOS/
    pkgInfo
    Resoures/

When I code sign MyApp.app and validate with command:
"codesign -vv / Path_To_Application_Bundle"

It gives below error all the time:
“unsealed contents present in the root directory of an embedded framework”

The Problem

What’s wrong with this setup is an incorrectly-configured framework bundle, which leads to incorrectly-configured application bundle. As a file structure, a bundle must not link to outside files. In turn, code-signing can’t handle this non-standard structure.

From the setup shown above, it looks like ABC.framework is trying to use code from XYZ.framework. The original poster solved this by creating symbolic links (probably a practice carried-over from Linux). However this isn’t the right way to do it on macOS.

Similarly shared resources of a framework would need to be linked via code and served that way. Not by symbolic links since this would violate the bundle structure.

[decorative] Library bookshelf

Solution to the Linking Problem

When a framework needs to use code from other frameworks, then the standard method is to configure its runpath search path (that is LD_RUNPATH_SEARCH_PATHS). This is a list of paths that the dynamic linker (dyld) would look for frameworks and libraries.

If both frameworks belong to the same application, then setting up the runpath search path is pretty straightforward. Configure LD_RUNPATH_SEARCH_PATHS of the framework to have @executable_path/../Frameworks as one of the designated search paths. Afterwards package those frameworks in the main application bundle’s Contents/Frameworks directory. Remember that the main application’s executable is in the Contents/MacOS directory – therefore @executable_path/../Frameworks points to the app bundle’s standard directory that contains frameworks and plain dynamic libraries.

For more information on these run paths, type man dyld on the Terminal to show the dynamic linker’s manual page and read up the Dynamic Library Loading section.

Shared Resources in Frameworks

However if the framework need to use non-code resources from other frameworks, then you’ll need to link it using code. Wrap it using a function which returns the resource that the framework has. Then the consuming library or framework would just need to call this function to obtain the resource.

Let’s say there are two frameworks contained within an application bundle. Then one framework would provide images or other resources to other frameworks or even the main executable.

  • SharedAssets.framework – contains shared images and other assets.
  • Consumer.framework – uses resources from the above framework.

Then you create a function in SharedAssets.framework to wrap the resources that it has. For example, a function that exposes image resources in the framework could look like the following snippet:

// Marker class to get the current framework bundle
fileprivate class Dummy: NSObject {}

public func GetFooImage() -> NSImage  {
    let bundle = Bundle(for: Dummy.self)
    let imageURL = bundle.url(forResource: ... )
    let image = NSImage(byReferencingURL: imageURL)
    return image
}

Thus getting an the image from SharedAssets.framework into Consumer.framework would be as simple as importing the library and calling the function:

// in `Consumer.framework`
import SharedAssets

func ... {
    ...
    let fooImage = GetFooImage()
    ... // use the `fooImage` object 
}

Next Steps

Have a look at your project’s custom build scripts and make sure that all bundles are self-contained. Also have a double check on all of your frameworks’ run path settings and ensure that they make sense.



Do you enjoy this post? Enter your e-mail address below to receive articles like this one in your mailbox (plus some occasional tips and updates on my work).

* indicates required

Discussion

No comments yet.

Leave a Reply