|
| 1 | +#!/usr/bin/ruby |
| 2 | + |
| 3 | +# This script looks up an executable's list of shared libraries, copies |
| 4 | +# non-standard ones (ie. anything not under /usr or /System/) into the target's |
| 5 | +# bundle and updates the executable install_name to point to the "packaged" |
| 6 | +# version. |
| 7 | + |
| 8 | +# Usage: |
| 9 | +# Add the script as a Run Script build phase in the target using Xcode. |
| 10 | + |
| 11 | +# FIXMEs: |
| 12 | +# - only handles dylibs |
| 13 | +# - only tested against a framework target |
| 14 | +# - doesn't care about codesigning |
| 15 | + |
| 16 | + |
| 17 | +require 'fileutils' |
| 18 | +require 'ostruct' |
| 19 | + |
| 20 | +def err(msg) |
| 21 | + puts "error: " + msg |
| 22 | + exit 1 |
| 23 | +end |
| 24 | + |
| 25 | +def warn(msg) |
| 26 | + puts "warning: " + msg |
| 27 | +end |
| 28 | + |
| 29 | +def note(msg) |
| 30 | + puts "note: " + msg |
| 31 | +end |
| 32 | + |
| 33 | +envvars = %w( |
| 34 | + TARGET_BUILD_DIR |
| 35 | + EXECUTABLE_PATH |
| 36 | + CURRENT_ARCH |
| 37 | + FRAMEWORKS_FOLDER_PATH |
| 38 | +) |
| 39 | + |
| 40 | +envvars.each do |var| |
| 41 | + raise "Must be run in an Xcode Run Phase" unless ENV[var] |
| 42 | + Kernel.const_set var, ENV[var] |
| 43 | +end |
| 44 | + |
| 45 | +TARGET_EXECUTABLE_PATH = File.join(TARGET_BUILD_DIR, EXECUTABLE_PATH) |
| 46 | +TARGET_FRAMEWORKS_PATH = File.join(TARGET_BUILD_DIR, FRAMEWORKS_FOLDER_PATH) |
| 47 | + |
| 48 | +def extract_link_dependencies |
| 49 | + deps = `otool -arch #{CURRENT_ARCH} -L #{TARGET_EXECUTABLE_PATH}` |
| 50 | + |
| 51 | + lines = deps.split("\n").map(&:strip) |
| 52 | + lines.shift |
| 53 | + lines.shift |
| 54 | + lines.map do |dep| |
| 55 | + path, compat, current = /^(.*) \(compatibility version (.*), current version (.*)\)$/.match(dep)[1..3] |
| 56 | + err "Failed to parse #{dep}" if path.nil? |
| 57 | + |
| 58 | + dep = OpenStruct.new |
| 59 | + dep.install_name = path |
| 60 | + dep.current_version = current |
| 61 | + dep.compat_version = compat |
| 62 | + dep.type = File.extname(path) |
| 63 | + dep.name = File.basename(path) |
| 64 | + dep.is_packaged = (dep.install_name =~ /^@rpath/) |
| 65 | + dep.path = if dep.install_name =~ /^@rpath/ |
| 66 | + File.join(TARGET_FRAMEWORKS_PATH, dep.name) |
| 67 | + else |
| 68 | + dep.install_name |
| 69 | + end |
| 70 | + |
| 71 | + dep |
| 72 | + end |
| 73 | +end |
| 74 | + |
| 75 | +def repackage_dependency(dep) |
| 76 | + return if dep.is_packaged or dep.path =~ /^(\/usr\/lib|\/System\/Library)/ |
| 77 | + |
| 78 | + note "Packaging #{dep.name}…" |
| 79 | + |
| 80 | + FileUtils.mkdir(TARGET_FRAMEWORKS_PATH) unless Dir.exist?(TARGET_FRAMEWORKS_PATH) |
| 81 | + |
| 82 | + case dep.type |
| 83 | + when ".dylib" |
| 84 | + if File.exist?(File.join(TARGET_FRAMEWORKS_PATH, dep.name)) |
| 85 | + warn "#{dep.path} already in Frameworks directory, removing" |
| 86 | + FileUtils.rm File.join(TARGET_FRAMEWORKS_PATH, dep.name) |
| 87 | + end |
| 88 | + |
| 89 | + note "Copying #{dep[:path]} to TARGET_FRAMEWORKS_PATH" |
| 90 | + FileUtils.cp dep[:path], TARGET_FRAMEWORKS_PATH |
| 91 | + |
| 92 | + out = `install_name_tool -change #{dep.path} "@rpath/#{dep.name}" #{TARGET_EXECUTABLE_PATH}` |
| 93 | + if $? != 0 |
| 94 | + err "install_name_tool failed with error #{$?}:\n#{out}" |
| 95 | + end |
| 96 | + |
| 97 | + dep.path = File.join(TARGET_FRAMEWORKS_PATH, dep.name) |
| 98 | + dep.install_name = "@rpath/#{dep.name}" |
| 99 | + dep.is_packaged = true |
| 100 | + |
| 101 | + else |
| 102 | + warn "Unhandled type #{dep.type} for #{dep.path}, ignoring" |
| 103 | + end |
| 104 | +end |
| 105 | + |
| 106 | +extract_link_dependencies.each do |dep| |
| 107 | + repackage_dependency dep |
| 108 | +end |
| 109 | + |
| 110 | +note "Packaging done" |
| 111 | +exit 0 |
0 commit comments