build, tools: Add initial macOS support to lib_bundle.py
It will be useful later in my WIP work to move macOS scripts to GIMP infra.
This commit is contained in:
parent
897ffd5702
commit
8c0f92b39a
3 changed files with 75 additions and 32 deletions
|
|
@ -204,7 +204,7 @@ for dir in ["bin", "lib"]:
|
|||
for ext in ("*.dll", "*.exe"):
|
||||
for dep in search_dir.rglob(ext):
|
||||
subprocess.run([
|
||||
sys.executable, f"{GIMP_SOURCE}/build/windows/2_bundle-gimp-uni_dep.py",
|
||||
sys.executable, f"{GIMP_SOURCE}/tools/lib_bundle.py",
|
||||
str(dep), f"{GIMP_PREFIX}/", f"{MSYSTEM_PREFIX}/",
|
||||
str(GIMP_DISTRIB), "--output-dll-list", done_dll.as_posix()
|
||||
], check=True)
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ if (Test-Path "$X86_BUNDLE")
|
|||
Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):installer_files[collapsed=true]$([char]13)$([char]27)[0KGenerating 32-bit TWAIN dependencies list"
|
||||
$twain_list_file = 'build\windows\installer\base_twain32on64.list'
|
||||
Copy-Item $twain_list_file "$twain_list_file.bak"
|
||||
$twain_list = (python build\windows\2_bundle-gimp-uni_dep.py --debug debug-only $(Resolve-Path $X86_BUNDLE/lib/gimp/*/plug-ins/twain/twain.exe) $env:MSYS_ROOT/mingw32/ $X86_BUNDLE/ 32 |
|
||||
$twain_list = (python tools\lib_bundle.py --debug debug-only $(Resolve-Path $X86_BUNDLE/lib/gimp/*/plug-ins/twain/twain.exe) $env:MSYS_ROOT/mingw32/ $X86_BUNDLE/ 32 |
|
||||
Select-String 'Installed' -CaseSensitive -Context 0,1000) -replace " `t- ",'bin\'
|
||||
(Get-Content $twain_list_file) | Foreach-Object {$_ -replace "@DEPS_GENLIST@","$twain_list"} | Set-Content $twain_list_file
|
||||
(Get-Content $twain_list_file) | Select-string 'Installed' -notmatch | Set-Content $twain_list_file
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
################################################################################
|
||||
# Small python script to retrieve DLL dependencies with objdump
|
||||
# Small python script to retrieve DLL or DYLIB dependencies with object dumper
|
||||
################################################################################
|
||||
|
||||
################################################################################
|
||||
# Usage example
|
||||
#
|
||||
# python3 2_bundle-gimp-uni_dep.py /path/to/run.exe /winenv/ /path/install
|
||||
# python3 lib_bundle.py /path/to/run.exe /winenv/ /path/install
|
||||
#
|
||||
# In this case, the DLL dependencies for executable run.exe will be extracted
|
||||
# and copied into /path/install/bin folder. To copy the DLL, the root path to
|
||||
# Windows environnment should be passed, here /winenv/.
|
||||
# Windows environnment should be passed, here /winenv/ (usually MSYSTEM_PREFIX).
|
||||
#
|
||||
# python3 lib_bundle.py /path/to/run /macOSenv/ /path/install
|
||||
#
|
||||
# In this case, the DYLIB dependencies for executable run will be extracted
|
||||
# and copied into /path/install/Contents/Resources/lib folder. To copy the DYLIB,
|
||||
# the root path to macOS environnment should be passed, here /macOSenv/ (usually /opt/local).
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
|
@ -24,22 +30,26 @@ import struct
|
|||
################################################################################
|
||||
# Global variables
|
||||
|
||||
# Sets for executable and system DLL
|
||||
# Sets for executable and system DLL/DYLIB
|
||||
dlls = set()
|
||||
sys_dlls = set()
|
||||
|
||||
# Previously done DLLs in previous runs
|
||||
# Previously done DLLs/DYLIBs in previous runs
|
||||
done_dlls = set()
|
||||
|
||||
# Common paths
|
||||
bindir = 'bin'
|
||||
|
||||
# Platform mode (detected at runtime)
|
||||
is_macos = False
|
||||
|
||||
################################################################################
|
||||
# Functions
|
||||
|
||||
# Main function
|
||||
def main(binary, srcdirs, destdir, debug, dll_file):
|
||||
global done_dlls
|
||||
global is_macos
|
||||
try:
|
||||
if dll_file is not None:
|
||||
with open(dll_file, 'r') as f:
|
||||
|
|
@ -52,26 +62,26 @@ def main(binary, srcdirs, destdir, debug, dll_file):
|
|||
find_dependencies(os.path.abspath(binary), srcdirs)
|
||||
if debug in ['debug-only', 'debug-run']:
|
||||
if debug == 'debug-only':
|
||||
print("Running in debug-only mode (no DLL moved) for '{}'".format(binary))
|
||||
print("Running in debug-only mode (no DLL/DYLIB moved) for '{}'".format(binary))
|
||||
else:
|
||||
print("Running with debug output for '{}'".format(binary))
|
||||
|
||||
if len(dlls) > 0:
|
||||
sys.stdout.write("Needed DLLs:\n\t- ")
|
||||
sys.stdout.write("Needed DLLs/DYLIBs:\n\t- ")
|
||||
sys.stdout.write("\n\t- ".join(list(dlls)))
|
||||
print()
|
||||
if len(sys_dlls) > 0:
|
||||
sys.stdout.write('System DLLs:\n\t- ')
|
||||
sys.stdout.write('System DLLs/DYLIBs:\n\t- ')
|
||||
sys.stdout.write("\n\t- ".join(sys_dlls))
|
||||
print()
|
||||
removed_dlls = sys_dlls & dlls
|
||||
if len(removed_dlls) > 0:
|
||||
sys.stdout.write('Non installed DLLs:\n\t- ')
|
||||
sys.stdout.write('Non installed DLLs/DYLIBs:\n\t- ')
|
||||
sys.stdout.write("\n\t- ".join(removed_dlls))
|
||||
print()
|
||||
installed_dlls = dlls - sys_dlls
|
||||
if len(installed_dlls) > 0:
|
||||
sys.stdout.write('Installed DLLs:\n\t- ')
|
||||
sys.stdout.write('Installed DLLs/DYLIBs:\n\t- ')
|
||||
sys.stdout.write("\n\t- ".join(installed_dlls))
|
||||
print()
|
||||
|
||||
|
|
@ -86,8 +96,9 @@ def main(binary, srcdirs, destdir, debug, dll_file):
|
|||
|
||||
def find_dependencies(obj, srcdirs):
|
||||
'''
|
||||
List DLLs of an object file in a recursive way.
|
||||
List DLLs/DYLIBs of an object file in a recursive way.
|
||||
'''
|
||||
global is_macos
|
||||
if obj in set.union(done_dlls, sys_dlls):
|
||||
# Already processed, either in a previous run of the script
|
||||
# (done_dlls) or in this one.
|
||||
|
|
@ -95,16 +106,19 @@ def find_dependencies(obj, srcdirs):
|
|||
|
||||
if not os.path.isabs(obj):
|
||||
for srcdir in srcdirs:
|
||||
bindir = 'bin'
|
||||
if is_macos:
|
||||
bindir = 'lib'
|
||||
abs_dll = os.path.join(srcdir, bindir, obj)
|
||||
abs_dll = os.path.abspath(abs_dll)
|
||||
if find_dependencies(abs_dll, srcdirs):
|
||||
return True
|
||||
else:
|
||||
# Found in none of the srcdirs: consider it a system DLL
|
||||
# Found in none of the srcdirs: consider it a system DLL/DYLIB
|
||||
sys_dlls.add(os.path.basename(obj))
|
||||
return False
|
||||
elif os.path.exists(obj):
|
||||
# If DLL exists, extract dependencies.
|
||||
# If DLL/DYLIB exists, extract dependencies.
|
||||
objdump = None
|
||||
|
||||
try:
|
||||
|
|
@ -113,18 +127,28 @@ def find_dependencies(obj, srcdirs):
|
|||
pe_offset = struct.unpack('<I', f.read(4))[0]
|
||||
f.seek(pe_offset)
|
||||
pe_sig = f.read(4)
|
||||
if pe_sig != b'PE\0\0':
|
||||
return None
|
||||
f.seek(pe_offset + 24)
|
||||
file_type = struct.unpack('<H', f.read(2))[0]
|
||||
if file_type == 0x20b:
|
||||
objdump = 'x86_64-w64-mingw32-objdump'
|
||||
elif file_type == 0x10b:
|
||||
objdump = 'i686-w64-mingw32-objdump'
|
||||
if pe_sig == b'PE\0\0':
|
||||
f.seek(pe_offset + 24)
|
||||
file_type = struct.unpack('<H', f.read(2))[0]
|
||||
if file_type == 0x20b:
|
||||
objdump = 'x86_64-w64-mingw32-objdump'
|
||||
elif file_type == 0x10b:
|
||||
objdump = 'i686-w64-mingw32-objdump'
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
# If not PE, then check for Mach-O magic (Feed Face/Face Feed variants)
|
||||
f.seek(0) #rewind to start
|
||||
magic = f.read(4)
|
||||
if magic in [b'\xfe\xed\xfa\xce', b'\xfe\xed\xfa\xcf',
|
||||
b'\xce\xfa\xed\xfe', b'\xcf\xfa\xed\xfe',
|
||||
b'\xca\xfe\xba\xbe', b'\xbe\xba\xfe\xca']:
|
||||
is_macos = True
|
||||
objdump = 'no_cross_objdump'
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
sys.stderr.write("Cannot read PE header for {}: {}\n".format(obj, e))
|
||||
sys.stderr.write("Cannot read PE/Mach-O header for {}: {}\n".format(obj, e))
|
||||
return None
|
||||
|
||||
if objdump is None:
|
||||
|
|
@ -133,15 +157,28 @@ def find_dependencies(obj, srcdirs):
|
|||
elif shutil.which(objdump) is None:
|
||||
# For native objdump case.
|
||||
objdump = 'objdump.exe'
|
||||
if is_macos and shutil.which('otool'):
|
||||
objdump = 'otool'
|
||||
else:
|
||||
objdump = 'objdump'
|
||||
|
||||
if shutil.which(objdump) is None:
|
||||
sys.stderr.write("Executable doesn't exist: {}\n".format(objdump))
|
||||
sys.exit(1)
|
||||
|
||||
result = subprocess.run([objdump, '-p', obj], stdout=subprocess.PIPE)
|
||||
out = result.stdout.decode('utf-8')
|
||||
# Parse lines with DLL Name instead of lib*.dll directly
|
||||
for match in re.finditer(r"DLL Name: *(\S+.dll)", out, re.MULTILINE):
|
||||
if not is_macos:
|
||||
result = subprocess.run([objdump, '-p', obj], stdout=subprocess.PIPE)
|
||||
out = result.stdout.decode('utf-8')
|
||||
regex = re.finditer(r"DLL Name: *(\S+.dll)", out, re.MULTILINE)
|
||||
else:
|
||||
if objdump == 'otool':
|
||||
result = subprocess.run(['otool', '-l', obj], stdout=subprocess.PIPE)
|
||||
else:
|
||||
result = subprocess.run(['objdump', '--macho', '--private-headers', obj], stdout=subprocess.PIPE)
|
||||
out = result.stdout.decode('utf-8', errors='replace')
|
||||
regex = re.finditer(r"name\s+(?:\d+\s+)?(?:.*/)?([^/\s]+\.dylib)", out, re.MULTILINE)
|
||||
# Parse lines with DLL/DYLIB Name instead of lib*.dll/lib*.dylib directly
|
||||
for match in regex:
|
||||
dll = match.group(1)
|
||||
if dll not in set.union(done_dlls, dlls, sys_dlls):
|
||||
dlls.add(dll)
|
||||
|
|
@ -151,14 +188,20 @@ def find_dependencies(obj, srcdirs):
|
|||
else:
|
||||
return False
|
||||
|
||||
# Copy a DLL set into the /destdir/bin directory
|
||||
# Copy a DLL/DYLIB set into the /destdir/bin or /destdir/Contents/Resource/lib directory
|
||||
def copy_dlls(dll_list, srcdirs, destdir):
|
||||
destbin = os.path.join(destdir, bindir)
|
||||
global bindir
|
||||
dest_bindir = bindir
|
||||
if is_macos:
|
||||
dest_bindir = 'Contents/Resources/lib'
|
||||
destbin = os.path.join(destdir, dest_bindir)
|
||||
os.makedirs(destbin, exist_ok=True)
|
||||
for dll in dll_list:
|
||||
if not os.path.exists(os.path.join(destbin, dll)):
|
||||
# Do not overwrite existing files.
|
||||
for srcdir in srcdirs:
|
||||
if is_macos:
|
||||
bindir = 'lib'
|
||||
full_file_name = os.path.join(srcdir, bindir, dll)
|
||||
if os.path.isfile(full_file_name):
|
||||
sys.stdout.write("Bundling {} to {}\n".format(full_file_name, destbin))
|
||||
|
|
@ -167,7 +210,7 @@ def copy_dlls(dll_list, srcdirs, destdir):
|
|||
else:
|
||||
# This should not happen. We determined that the dll is in one
|
||||
# of the srcdirs.
|
||||
sys.stderr.write("Missing DLL: {}\n".format(dll))
|
||||
sys.stderr.write("Missing DLL/DYLIB: {}\n".format(dll))
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
Loading…
Reference in a new issue