Search code examples
xcodeotool

Remove a framework and the link to the framework after the app binary is created by Xcode


I would like to build 2 versions of the same app in Xcode, one with, and one without the Sparkle framework. I don't want to maintain a separate target. Instead, I would like to use 2 separate schemes that rely on 2 separate build configurations (well, 4 in total, as I'll use one for the debug build and one for the release build).

The problem is the 'Copy Bundle Resources' and the 'Link Binary' steps in the 'Build Phases' are the same for all the schemes on a given target, as far as I can tell.

So my only option as I see it is to run a script after the build (and before signing) that would:

  1. Remove the framework from the app bundle.
  2. Unlink the framework from the executable (as listed with ottol -L).

Any suggestions?


Solution

  • After quite a bit of work, I have come up with a solution.

    1. First, make sure the framework linking is "weak", which Xcode calls "Optional" in the 'Link Binary' phase. Also make sure that any use of Sparkle in your code handles a missing Sparkle framework.

    Optional linking of framework

    1. Create separate build configurations for Debug and Release, e.g. called 'Debug-no_sparkle' and 'Release-no_sparkle', by going to the Project settings, under 'Info' in the 'Configurations' tab. Also create a corresponding scheme by going to the 'Schemes' popup menu, duplicating your normal scheme, and then using the 'no_sparkle' configurations in the various actions for the scheme.

    2. Add a 'Run Script' step to the Build phases in the target settings.

    Run script phase to remove Sparkle framework

    Here is the Ruby script I use:

    config = ENV['CONFIGURATION']
    if (config =~ /no_sparkle/)
    
        $stderr.puts "Removing Sparkle Framework"
        sparkle_path = "#{ENV['BUILT_PRODUCTS_DIR']}/#{ENV['FRAMEWORKS_FOLDER_PATH']}/Sparkle.framework"
        `rm -Rf "#{sparkle_path}"`
    
        $stderr.puts "Removing Sparkle Framework linking"
        binary_path = "#{ENV['BUILT_PRODUCTS_DIR']}/#{ENV['EXECUTABLE_PATH']}"
        `install_name_tool -change @rpath/Sparkle.framework/Versions/A/Sparkle @rpath/ "#{binary_path}"`
    end
    

    One thing I could not do is remove completely the line corresponding to the framework link with install_name_tool. I could only change it to '/' to remove any trace of it, so it would not even accidentally load if the framework was somehow accessible. It would be nice to be able to fully remove it.