Last active
August 24, 2019 16:06
-
-
Save snipsnipsnip/1102438 to your computer and use it in GitHub Desktop.
bindist.rb: resolves dylib dependencies of a Mach-O executable to make a distributable package
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
=begin | |
== 概要 | |
Usage: ruby bindist.rb [-f|-q] 動かない実行ファイル 手直ししたバイナリを置くディレクトリ | |
野良ビルドしたライブラリを使ってビルドしたMach-O実行ファイルを配布できるように手直しするスクリプト。 | |
dylibの依存関係をotool -Lで調べて手元にコピーし、install_name_toolで再配線する。 | |
システム標準と思われるライブラリ(/System や /usr/lib 以下)は配布先にもあると思うのでコピーしない。 | |
== オプション | |
オプションを何もつけずに起動すると、依存関係を調べて、実行するであろうコマンドを出力する。 | |
-f オプションを挟むと実際にファイルのコピーと再配線を行う。 | |
-q オプションをつけると何も出力せずに実行する。 | |
== 動作例 | |
ruby bindist.rb -f a.out dist | |
とすると、dist/以下にa.outとその依存するライブラリが全てコピーされ、再配線される。 | |
つまり、distフォルダを他のMacにコピーして動かせる状態になる。 | |
distフォルダはなければ作成される。あれば内容が上書きされる。 | |
=end | |
require 'fileutils' | |
class Bindist | |
def self.main(argv=ARGV) | |
if argv.size == 3 && argv[0] =~ /\A-f\z|\A--force\z/i | |
new.run(argv[1], argv[2], FileUtils::Verbose, NameTool.new(false), DependencyWalker.new(true)) | |
elsif argv.size == 3 && argv[0] =~ /\A-q\z|\A--quiet\z/i | |
new.run(argv[1], argv[2], FileUtils, NameTool.new(nil), DependencyWalker.new(false)) | |
elsif argv.size == 2 | |
new.run(argv[0], argv[1], FileUtils::DryRun, NameTool.new(true), DependencyWalker.new(true)) | |
else | |
abort "usage: #{File.basename $0} [-f|-q] executable destdir" | |
end | |
end | |
def run(exe, destdir, utils, nametool, walker) | |
deps = walker.walk_dependencies(exe) | |
utils.mkpath destdir | |
deps.each do |lib, libdeps| | |
name = File.basename(lib) | |
copied = File.join(destdir, name) | |
utils.install(lib, destdir) | |
lib == exe or nametool.rewrite_id(copied, make_relative(name)) | |
libdeps.each do |dep| | |
nametool.rewrite_dependent_library(copied, dep, make_relative(dep)) | |
end | |
end | |
end | |
private | |
def make_relative(path) | |
File.join("@executable_path", File.basename(path)) | |
end | |
class DependencyWalker | |
def initialize(verbose) | |
@verbose = verbose | |
end | |
def verbose? | |
@verbose | |
end | |
def walk_dependencies(root) | |
build_graph(root) do |bin| | |
list_dependent_libraries(bin).reject {|lib| standard_library?(lib) } | |
end | |
end | |
private | |
def standard_library?(lib) | |
lib =~ %r{\A/usr/lib/|\A/System/} | |
end | |
def list_dependent_libraries(path) | |
warn "otool -L #{path}" if verbose? | |
list = `/usr/bin/otool -L '#{path}'` | |
$? == 0 or raise "otool failed: #$?" | |
list.scan(/^\s+(\/\S+)/).flatten | |
end | |
def build_graph(root) | |
graph = {} | |
stack = [root] | |
while node = stack.pop | |
next if graph.has_key?(node) | |
neighbors = yield(node) | |
graph[node] = neighbors | |
stack.concat neighbors | |
end | |
graph | |
end | |
end | |
class NameTool | |
def initialize(dry) | |
@dry = dry | |
end | |
def dry? | |
!!@dry | |
end | |
def verbose? | |
!@dry.nil? | |
end | |
def rewrite_id(lib, newid) | |
install_name_tool('-id', newid, lib) | |
end | |
def rewrite_dependent_library(lib, oldlib, newlib) | |
install_name_tool('-change', oldlib, newlib, lib) | |
end | |
private | |
def install_name_tool(*args) | |
warn "install_name_tool #{args.join(' ')}" if verbose? | |
return if dry? | |
system('/usr/bin/install_name_tool', *args) or raise "install_name_tool failed: #$?" | |
end | |
end | |
end | |
Bindist.main if $0 == __FILE__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment