Created
October 11, 2024 13:57
-
-
Save niw/e9cfac7d77e8aea21a7fee6ce212b6eb to your computer and use it in GitHub Desktop.
Make frameworks portable.
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/env ruby | |
require 'optparse' | |
require 'pathname' | |
def update_name(name, options) | |
pattern = /#{options[:rpath]}/ | |
if name =~ pattern | |
suffix = $' | |
if options[:path].join(suffix).exist? | |
"@rpath/#{suffix}" | |
end | |
end | |
end | |
def update_install_names(file, type, options) | |
puts "Process #{file} (#{type})" | |
otool_output = `otool -L #{file}` | |
install_names = nil | |
otool_output.each_line do |line| | |
if !install_names and line =~ /^(.+?)(?: \(.+?\))?:$/ | |
install_names = [] | |
elsif line =~ /^\t(.+?)(?: \(.+?\))?$/ | |
install_name = $1 | |
install_names << install_name | |
end | |
end | |
if type == :dylib | |
install_id = install_names.shift | |
if updated_name = update_name(install_id, options) | |
puts "Update intall ID from #{install_id} to #{updated_name}" | |
unless options[:dry_run] | |
`install_name_tool -id "#{updated_name}" "#{file}"` | |
end | |
end | |
end | |
install_names.each do |install_name| | |
if updated_name = update_name(install_name, options) | |
puts "Update name from #{install_name} to #{updated_name}" | |
unless options[:dry_run] | |
`install_name_tool -change "#{install_name}" "#{updated_name}" "#{file}"` | |
end | |
end | |
end | |
if type == :executable | |
relative_path = options[:path].relative_path_from(Pathname(File.dirname(file))) | |
rpath = File.join("@executable_path", relative_path.to_s) | |
puts "Add rpath #{rpath}" | |
unless options[:dry_run] | |
`install_name_tool -add_rpath "#{rpath}" "#{file}"` | |
end | |
end | |
end | |
def update_all_install_names(options) | |
options[:path].glob("**/*").each do |file| | |
file_output = `file -h "#{file}"`.lines.first | |
type = case file_output | |
when /Mach-O .+ executable/ | |
:executable | |
when /Mach-O .+ dynamically linked shared library/ | |
:dylib | |
when /Mach-O .+ bundle/ | |
:bundle | |
when /Mach-O/ | |
STDERR.puts "Unknown Mach-O type: #{file_output}" | |
next | |
else | |
next | |
end | |
update_install_names(file, type, options) | |
end | |
end | |
options = {} | |
OptionParser.new do |opts| | |
opts.on("--dry-run", "Print what would be done without doing it.") do | |
options[:dry_run] = true | |
end | |
opts.on("--path PATH", "Path where is searched Mach-O files and repalced with @rpath.") do |value| | |
options[:path] = Pathname(value) | |
end | |
opts.on("--rpath PATH", "Path in Mach-O binaries where is replaced with @rpath, such as '/Library/Frameworks/'") do |value| | |
unless value =~ /\/$/ | |
value += "/" | |
end | |
options[:rpath] = Pathname(value) | |
end | |
opts.parse!(ARGV) | |
end | |
update_all_install_names(options) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment