Sparkle as a Subframework

The Sparkle framework is built with an INSTALL_PATH set to @loader_path/../Frameworks.  This works fine when Sparkle is embedded into an app or a plugin, but not if you're embedding Sparkle inside of another framework, as a "sub" framework.

Short Explanation

The short explanation is that @loader_path is referenced to the executable inside the package, so the path from it to its target depends on the directory structure of the hosting app, plugin or framework.  The directory structure of a plugin is the same as that of an app, so it "just works", as explained in the ld man page.  But a framework is different, so it needs a different path.

Longer Explanation

The INSTALL_PATH of  @loader_path/../Frameworks works fine when Sparkle is embedded directly into an application or plugin.  In either case, the package looks like this…

   Contents
      MacOS
         MyPlugin (Unix executable)
      Resources
      Frameworks
         Sparkle.framework

because the @loader path is MacOS, the .. is Contents, and Frameworks is, nicely, a subfolder of Contents.

However, embedding Sparkle into another framework is different because of the different directory structure.

   UpperFramework.framework
      Versions
         A
            Resources
            UpperFramework (Unix executable)

Note that, in all cases,  @loader_path is the parent of the Unix executable.

Solution 1: Goofy Trick

In this solution, we leave the INSTALL_PATH of Sparkle as is, and change stuff in our upper framework's build to compensate for the different path

The "loader" in this case is the executable file of the upper framework.  The @loader_path is therefore the A folder of the upper framework, the .. is its Versions folder, and therefore in order for the upper framework to find Sparkle, Sparkle.framework must be put into a Frameworks subfolder of Versions.  Omitting the symlinks and code signatures, the package looks like this…

   UpperFramework.framework
      Versions
         A
            Resources
            UpperFramework (Unix executable)
         Frameworks
            Sparkle.framework
               ...

That looks goofy, to put a subfolder of Frameworks inside Versions.  The settings that you need to put it there, in a Copy Files Build Phase of the upper framework target in Xcode 4 look even more goofy…

• Set the "Destination" popup to "Wrapper"
• Set the "Subpath" to "Versions/Frameworks"

Amazingly, an app built in this way actually works.  It also passes Gatekeeper when signed with Developer ID, even though I did not explicitly sign the Sparkle framework because my code-signing script does not look in goofy locations.  I'm not sure how that works.  But I don't like it.


Solution 2: Use install_name_tool

Mike Ash's Friday Q&A on INSTALL_PATH indicated that I could fix this by using Apple's install_name_tool.

It took me a while to understand its sketchy man page.  Helped along by JeremyP's answer, I finally got that the dependent library referred to in the man page is Sparkle in this case, and you run the tool on your framework (the "upper" framework). Add a Copy Files Build Phase to the end of those in your framework, for copying iSparkle.

  • Set the "Destination" popup to "Frameworks".
  • Leave the "Subpath" blank.
  • Add Sparkle.framework as the single item in this phase

Following that, add a Run Script Build Phase, with the following code, running in shell /bin/sh …

cd "$BUILT_PRODUCTS_DIR/$EXECUTABLE_FOLDER_PATH"
install_name_tool -change @loader_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle @loader_path/Frameworks/Sparkle.framework/Versions/A/Sparkle "$EXECUTABLE_NAME"

That's it.  The structure of your built framework will now be sensible…

   UpperFramework.framework
      Versions
         A
            Resources
            UpperFramework (Unix executable)
            Frameworks
               Sparkle.framework
                  ...

and it works.