2021-04-25 01:06:50 +00:00
|
|
|
|
#!/usr/bin/env ruby
|
|
|
|
|
|
|
|
|
|
require "open3"
|
|
|
|
|
require "fileutils"
|
|
|
|
|
|
|
|
|
|
$app_name = "melonDS"
|
|
|
|
|
$build_dmg = false
|
|
|
|
|
$build_dir = ""
|
|
|
|
|
$bundle = ""
|
2022-08-31 19:38:49 +00:00
|
|
|
|
$fallback_rpaths = []
|
2021-04-25 01:06:50 +00:00
|
|
|
|
|
|
|
|
|
def frameworks_dir
|
|
|
|
|
File.join($bundle, "Contents", "Frameworks")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def executable
|
|
|
|
|
File.join($bundle, "Contents", "MacOS", $app_name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def get_rpaths(lib)
|
2021-04-26 23:06:57 +00:00
|
|
|
|
out, _ = Open3.capture2("otool", "-l", lib)
|
|
|
|
|
out = out.split("\n")
|
2021-04-25 01:06:50 +00:00
|
|
|
|
rpaths = []
|
|
|
|
|
|
|
|
|
|
out.each_with_index do |line, i|
|
|
|
|
|
if line.match(/^ *cmd LC_RPATH$/)
|
|
|
|
|
rpaths << out[i + 2].strip.split(" ")[1]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return rpaths
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def get_load_libs(lib)
|
2021-04-26 23:06:57 +00:00
|
|
|
|
out, _ = Open3.capture2("otool", "-L", lib)
|
|
|
|
|
out.split("\n")
|
2021-04-25 01:06:50 +00:00
|
|
|
|
.drop(1)
|
|
|
|
|
.map { |it| it.strip.gsub(/ \(.*/, "") }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def expand_load_path(lib, path)
|
|
|
|
|
if path.match(/@(rpath|loader_path|executable_path)/)
|
|
|
|
|
path_type = $1
|
|
|
|
|
file_name = path.gsub(/^@#{path_type}\//, "")
|
|
|
|
|
|
|
|
|
|
case path_type
|
|
|
|
|
when "rpath"
|
|
|
|
|
get_rpaths(lib).each do |rpath|
|
|
|
|
|
file = File.join(rpath, file_name)
|
|
|
|
|
return file, :rpath if File.exist? file
|
2021-04-25 01:51:04 +00:00
|
|
|
|
if rpath.match(/^@executable_path(.*)/) != nil
|
2021-04-25 01:06:50 +00:00
|
|
|
|
relative = rpath.sub(/^@executable_path/, "")
|
2021-04-25 01:51:04 +00:00
|
|
|
|
return "#{$bundle}/Contents/MacOS#{relative}/#{file_name}", :executable_path
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
file = $fallback_rpaths
|
|
|
|
|
.map { |it| File.join(it, file_name) }
|
|
|
|
|
.find { |it| File.exist? it }
|
2022-08-31 19:38:49 +00:00
|
|
|
|
if file == nil
|
2021-07-23 04:31:20 +00:00
|
|
|
|
path = File.join(File.dirname(lib), file_name)
|
|
|
|
|
file = path if File.exist? path
|
|
|
|
|
end
|
2021-04-25 01:06:50 +00:00
|
|
|
|
return file, :rpath if file
|
|
|
|
|
when "executable_path"
|
|
|
|
|
file = File.join(File.dirname(executable), file_name)
|
|
|
|
|
return file, :executable_path if File.exist? file
|
|
|
|
|
when "loader_path"
|
|
|
|
|
file = File.join(File.dirname(lib), file_name)
|
|
|
|
|
return file, :loader_path if File.exist? file
|
|
|
|
|
else
|
|
|
|
|
throw "Unknown @path type"
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
return File.absolute_path(path), :absolute
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
def detect_framework(lib)
|
|
|
|
|
framework = lib.match(/(.*).framework/)
|
|
|
|
|
framework = framework.to_s if framework
|
|
|
|
|
|
|
|
|
|
if framework
|
|
|
|
|
fwname = File.basename(framework)
|
|
|
|
|
fwlib = lib.sub(framework + "/", "")
|
|
|
|
|
return true, framework, fwname, fwlib
|
|
|
|
|
else
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
def system_path?(path)
|
|
|
|
|
path.match(/^\/usr\/lib|^\/System/) != nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def system_lib?(lib)
|
|
|
|
|
system_path? File.dirname(lib)
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
def install_name_tool(exec, *options)
|
|
|
|
|
args = options.map do |it|
|
|
|
|
|
if it.is_a? Symbol then "-#{it.to_s}" else it end
|
|
|
|
|
end
|
2021-04-25 01:06:50 +00:00
|
|
|
|
|
2022-08-31 19:38:49 +00:00
|
|
|
|
Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread|
|
|
|
|
|
print stdout.read
|
|
|
|
|
err = stderr.read
|
|
|
|
|
unless err.match? "code signature"
|
|
|
|
|
print err
|
|
|
|
|
end
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-25 08:06:19 +00:00
|
|
|
|
def strip(lib)
|
2024-08-13 05:53:09 +00:00
|
|
|
|
out, _ = Open3.capture2("xcrun", "strip", "-no_code_signature_warning", "-Sx", lib)
|
2021-04-26 23:06:57 +00:00
|
|
|
|
print out
|
2021-04-25 08:06:19 +00:00
|
|
|
|
end
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
def fixup_libs(prog, orig_path)
|
|
|
|
|
throw "fixup_libs: #{prog} doesn't exist" unless File.exist? prog
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
libs = get_load_libs(prog)
|
|
|
|
|
.map { |it| expand_load_path(orig_path, it) }
|
|
|
|
|
.select { |it| not system_lib? it[0] }
|
2021-09-01 15:05:22 +00:00
|
|
|
|
|
|
|
|
|
FileUtils.chmod("u+w", prog)
|
2021-04-25 08:06:19 +00:00
|
|
|
|
strip prog
|
2021-04-25 01:06:50 +00:00
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
changes = []
|
|
|
|
|
|
|
|
|
|
isfw, _, fwname, fwlib = detect_framework(prog)
|
|
|
|
|
if isfw then
|
|
|
|
|
changes += [:id, File.join("@rpath", fwname, fwlib)]
|
|
|
|
|
else
|
|
|
|
|
changes += [:id, File.join("@rpath", File.basename(prog))]
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
libs.each do |lib|
|
|
|
|
|
libpath, libtype = lib
|
|
|
|
|
if File.basename(libpath) == File.basename(prog)
|
|
|
|
|
if libtype == :absolute
|
2024-08-13 05:53:09 +00:00
|
|
|
|
changes += [:change, libpath, File.join("@rpath", File.basename(libpath))]
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
next
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
is_framework, fwpath, fwname, fwlib = detect_framework(libpath)
|
2021-04-25 01:06:50 +00:00
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
if is_framework
|
2021-04-25 01:06:50 +00:00
|
|
|
|
unless libtype == :rpath
|
2024-08-13 05:53:09 +00:00
|
|
|
|
changes += [:change, libpath, File.join("@rpath", fwname, fwlib)]
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
next if File.exist? File.join(frameworks_dir, fwname)
|
2024-08-13 05:53:09 +00:00
|
|
|
|
expath, _ = expand_load_path(orig_path, fwpath)
|
2021-04-25 01:06:50 +00:00
|
|
|
|
FileUtils.cp_r(expath, frameworks_dir, preserve: true)
|
2021-07-23 04:31:20 +00:00
|
|
|
|
FileUtils.chmod_R("u+w", File.join(frameworks_dir, fwname))
|
2021-04-25 01:06:50 +00:00
|
|
|
|
fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath
|
|
|
|
|
else
|
2024-08-13 05:53:09 +00:00
|
|
|
|
reallibpath = File.realpath(libpath)
|
|
|
|
|
libname = File.basename(reallibpath)
|
2021-04-25 01:06:50 +00:00
|
|
|
|
dest = File.join(frameworks_dir, libname)
|
|
|
|
|
|
|
|
|
|
if libtype == :absolute
|
2024-08-13 05:53:09 +00:00
|
|
|
|
changes += [:change, libpath, File.join("@rpath", libname)]
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
next if File.exist? dest
|
2024-08-13 05:53:09 +00:00
|
|
|
|
expath, _ = expand_load_path(orig_path, reallibpath)
|
2021-04-25 01:06:50 +00:00
|
|
|
|
FileUtils.copy expath, frameworks_dir
|
2021-07-23 04:31:20 +00:00
|
|
|
|
FileUtils.chmod("u+w", dest)
|
2024-08-13 05:53:09 +00:00
|
|
|
|
fixup_libs dest, reallibpath
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
2024-08-13 05:53:09 +00:00
|
|
|
|
|
|
|
|
|
install_name_tool(prog, *changes)
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if ARGV[0] == "--dmg"
|
|
|
|
|
$build_dmg = true
|
|
|
|
|
ARGV.shift
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if ARGV.length != 1
|
|
|
|
|
puts "Usage: #{Process.argv0} [--dmg] <build-dir>"
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
$build_dir = ARGV[0]
|
|
|
|
|
unless File.exist? $build_dir
|
|
|
|
|
puts "#{$build_dir} doesn't exist"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$bundle = File.join($build_dir, "#{$app_name}.app")
|
|
|
|
|
|
|
|
|
|
unless File.exist? $bundle and File.exist? File.join($build_dir, "CMakeCache.txt")
|
|
|
|
|
puts "#{$build_dir} doesn't look like a valid build directory"
|
|
|
|
|
exit 1
|
|
|
|
|
end
|
|
|
|
|
|
2022-08-31 19:38:49 +00:00
|
|
|
|
for lib in get_load_libs(executable) do
|
|
|
|
|
next if system_lib? lib
|
|
|
|
|
|
|
|
|
|
path = File.dirname(lib)
|
|
|
|
|
|
|
|
|
|
if path.match? ".framework"
|
|
|
|
|
path = path.sub(/\/[^\/]+\.framework.*/, "")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
$fallback_rpaths << path unless $fallback_rpaths.include? path
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
$qt_major = nil
|
2021-04-25 01:06:50 +00:00
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
qt_dirs = File.read(File.join($build_dir, "CMakeCache.txt"))
|
|
|
|
|
.split("\n")
|
|
|
|
|
.select { |it| it.match /^Qt([\w]+)_DIR:PATH=.*/ }
|
|
|
|
|
.map { |dir|
|
|
|
|
|
dir.match /^Qt(5|6).*\=(.*)/
|
|
|
|
|
throw "Inconsistent Qt versions found." if $qt_major != nil && $qt_major != $1
|
|
|
|
|
$qt_major = $1
|
|
|
|
|
File.absolute_path("#{$2}/../../..")
|
|
|
|
|
}.uniq
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def locate_plugin(dirs, plugin)
|
|
|
|
|
plugin_paths = [
|
|
|
|
|
File.join("plugins", plugin),
|
|
|
|
|
File.join("lib", "qt-#{$qt_major}", "plugins", plugin),
|
|
|
|
|
File.join("libexec", "qt-#{$qt_major}", "plugins", plugin),
|
|
|
|
|
File.join("share", "qt", "plugins", plugin)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
dirs.each do |dir|
|
|
|
|
|
plugin_paths.each do |plug|
|
|
|
|
|
path = File.join(dir, plug)
|
|
|
|
|
return path if File.exists? path
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
puts "Couldn't find the required Qt plugin: #{plugin}"
|
|
|
|
|
puts "Tried the following prefixes: "
|
|
|
|
|
puts dirs.map { |dir| "- #{dir}"}.join("\n")
|
|
|
|
|
puts "With the following plugin paths:"
|
|
|
|
|
puts plugin_paths.map { |path| "- #{path}"}.join("\n")
|
2021-04-25 01:06:50 +00:00
|
|
|
|
exit 1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
FileUtils.mkdir_p(frameworks_dir)
|
|
|
|
|
fixup_libs(executable, executable)
|
|
|
|
|
|
|
|
|
|
bundle_plugins = File.join($bundle, "Contents", "PlugIns")
|
|
|
|
|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
want_plugins = [
|
|
|
|
|
"styles/libqmacstyle.dylib",
|
|
|
|
|
"platforms/libqcocoa.dylib",
|
|
|
|
|
"imageformats/libqsvg.dylib"
|
|
|
|
|
]
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
want_plugins.each do |plug|
|
2024-08-13 05:53:09 +00:00
|
|
|
|
pluginpath = locate_plugin(qt_dirs, plug)
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
destdir = File.join(bundle_plugins, File.dirname(plug))
|
|
|
|
|
FileUtils.mkdir_p(destdir)
|
2024-08-13 05:53:09 +00:00
|
|
|
|
FileUtils.copy(pluginpath, destdir)
|
|
|
|
|
fixup_libs File.join(bundle_plugins, plug), pluginpath
|
2021-04-25 01:06:50 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
want_rpath = "@executable_path/../Frameworks"
|
|
|
|
|
exec_rpaths = get_rpaths(executable)
|
|
|
|
|
exec_rpaths.select { |path| path != want_rpath }.each do |path|
|
|
|
|
|
install_name_tool executable, :delete_rpath, path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
unless exec_rpaths.include? want_rpath
|
|
|
|
|
install_name_tool executable, :add_rpath, want_rpath
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
exec_rpaths = get_rpaths(executable)
|
|
|
|
|
|
|
|
|
|
Dir.glob("#{frameworks_dir}/**/Headers").each do |dir|
|
|
|
|
|
FileUtils.rm_rf dir
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-26 23:06:57 +00:00
|
|
|
|
out, _ = Open3.capture2("codesign", "-s", "-", "-f", "--deep", $bundle)
|
|
|
|
|
print out
|
|
|
|
|
|
2021-04-25 01:06:50 +00:00
|
|
|
|
if $build_dmg
|
|
|
|
|
dmg_dir = File.join($build_dir, "dmg")
|
|
|
|
|
FileUtils.mkdir_p(dmg_dir)
|
|
|
|
|
FileUtils.cp_r($bundle, dmg_dir, preserve: true)
|
|
|
|
|
FileUtils.ln_s("/Applications", File.join(dmg_dir, "Applications"))
|
|
|
|
|
|
|
|
|
|
`hdiutil create -fs HFS+ -volname melonDS -srcfolder "#{dmg_dir}" -ov -format UDBZ "#{$build_dir}/melonDS.dmg"`
|
|
|
|
|
FileUtils.rm_rf(dmg_dir)
|
|
|
|
|
end
|