Skip to content

Instantly share code, notes, and snippets.

@NQNStudios
Last active September 16, 2024 02:41
Show Gist options
  • Save NQNStudios/7145bcf6621891f5176c8caa165d6b93 to your computer and use it in GitHub Desktop.
Save NQNStudios/7145bcf6621891f5176c8caa165d6b93 to your computer and use it in GitHub Desktop.
#! /usr/bin/env python3
import subprocess
import sys
from os.path import join, basename
import os
from pathlib import Path
app_bundle = sys.argv[1]
dry_run = '--dry' in sys.argv
# Accept the inner binary path as valid input:
if '/Contents/MacOS/' in app_bundle:
app_bundle = app_bundle[0:app_bundle.find('/Contents/MacOS/')]
# Allow a / after .app:
if app_bundle.endswith('/'):
app_bundle = app_bundle[0:-1]
app_binary = join(app_bundle, 'Contents/MacOS', basename(app_bundle).split(".")[0])
app_frameworks = join(app_bundle, 'Contents/Frameworks')
binaries_unfiltered = [app_binary] + [join(app_frameworks, library) for library in os.listdir(app_frameworks)]
binaries = []
# Extend .framework paths to the actual binary
for binary in binaries_unfiltered:
if binary.endswith('.framework'):
current_symlink = join(binary, 'Versions/Current/' + basename(binary).split(".")[0])
current_realpath = str(Path(current_symlink).resolve())
binaries.append(current_realpath)
else:
binaries.append(binary)
dependency_map = {binary:subprocess.check_output(["otool", "-L", binary], text=True).splitlines()[1:] for binary in binaries}
for binary, dependencies in dependency_map.items():
print(binary)
is_framework = '.framework' in binary
if binary == app_binary:
# check if a new rpath is needed
new_rpath = '@loader_path/../Frameworks'
has_rpath = False
lines = reversed(subprocess.check_output(["otool", "-l", binary], text=True).splitlines())
for line in lines:
if line.strip().startswith('path '):
existing_rpath = line.strip()[len('path '):line.strip().find('(') - 1]
if existing_rpath == new_rpath:
has_rpath = True
break
if not has_rpath:
print('\tAdding rpath: ' + new_rpath)
if not dry_run:
print('\t' + subprocess.check_output(['install_name_tool', '-add_rpath', new_rpath, binary], text=True))
for dependency in dependencies:
end_index = dependency.index('(')
dep = dependency[0:end_index].strip()
dep_short_name = dep[dep.rfind('/')+1:]
if '.framework' in dep:
index = dep.find('.framework')
dep_short_name = dep[dep.rfind('/', 0, index)+1:]
if dep == binary or basename(dep_short_name) == basename(binary):
continue
for key in dependency_map:
if key.endswith(dep_short_name):
if binary == app_binary:
new_path = f'@rpath/{dep_short_name}'
if new_path == dep:
continue
print(f'\t{dep} -> {new_path}')
if not dry_run:
print('\t' + subprocess.check_output(['install_name_tool', '-change', dep, new_path, binary], text=True))
else:
new_path = f'@loader_path/{dep_short_name}'
if is_framework:
new_path = f'@loader_path/../../../{dep_short_name}'
if new_path == dep:
continue
print(f'\t{dep} -> {new_path}')
if not dry_run:
print('\t' + subprocess.check_output(['install_name_tool', '-change', dep, new_path, binary], text=True))
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment