Rewrite stub generator to also generate CMakeLists

This combines the C function stub generator and
the Objective-C class generator.

It also detects the dylib current and compat
versions.

It basically creates a stub folder that you can
copy directly into /src.
This commit is contained in:
Andrew Hyatt 2017-12-20 23:28:40 -08:00
parent 30e8242ada
commit fa856191b7

224
tools/darling-stub-gen Executable file
View File

@ -0,0 +1,224 @@
#!/usr/bin/env python3
import sys, os, subprocess, re
# Data
library = False
framework = False
private_framework = False
uses_objc = False
full_path = ""
output_dir = ""
target_name = ""
header_dir = ""
source_dir = ""
# Constants
library_prefix = "/usr/lib/"
framework_prefix = "/System/Library/Frameworks/"
private_framework_prefix = "/System/Library/PrivateFrameworks/"
class_dump = "~/bin/class-dump"
copyright ="""/*
This file is part of Darling.
Copyright (C) 2017 Lubos Dolezel
Darling is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Darling is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
"""
c_func_impl_stub = """
void* %s(void) {
if (verbose) puts("STUB: %s called");
return NULL;
}
"""
msg_handling = """- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes: \"v@:\"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@\"Stub called: %@ in %@\", NSStringFromSelector([anInvocation selector]), [self class]);
}
"""
# Utility functions
def usage():
print("Usage: " + sys.argv[0] + " <Mach-O> <output directory>")
exit(1)
def extract_library_name(name):
prefix_len = len(library_prefix) + len("lib")
ext_len = len(".dylib")
return name[prefix_len : len(name) - ext_len]
def extract_framework_name(name):
return name[name.rfind("/") + 1 :]
# Main program start
if len(sys.argv) != 3:
usage()
full_path = sys.argv[1]
output_dir = sys.argv[2]
try:
os.makedirs(output_dir)
except FileExistsError:
pass
if len(full_path) > len(library_prefix) and full_path[:len(library_prefix)] == library_prefix:
library = True
target_name = extract_library_name(full_path)
elif len(full_path) > len(framework_prefix) and full_path[:len(framework_prefix)] == framework_prefix:
framework = True
target_name = extract_framework_name(full_path)
elif len(full_path) > len(private_framework_prefix) and full_path[:len(private_framework_prefix)] == private_framework_prefix:
private_framework = True
target_name = extract_framework_name(full_path)
else:
print("Failed to recognize Mach-O location")
exit(1)
header_dir = output_dir + "/include/" + target_name + "/"
source_dir = output_dir + "/src/"
try:
os.makedirs(header_dir)
except FileExistsError:
pass
try:
os.makedirs(source_dir)
except FileExistsError:
pass
# Get C functions
c_func_out = subprocess.check_output(["nm", "-Ug", full_path])
c_func_out = c_func_out.decode('utf8').strip()
functions = []
for line in c_func_out.splitlines():
if line == "":
continue
address, id, name = line.split(" ")
# Remove the underscore
name = name[1 : ]
if id == "T":
functions.append(name)
c_header = open(header_dir + target_name + ".h", "w")
c_source = open(source_dir + target_name + ".c", "w")
c_header.write(copyright)
c_source.write(copyright)
c_source.write("""
#include <stdlib.h>
static int verbose = 0;
__attribute__((constructor))
static void initme(void) {
verbose = getenv("STUB_VERBOSE") != NULL;
}
""")
c_hdr_buffer = "\n#ifndef _%s_H_\n#define _%s_H_\n\n" % (target_name, target_name)
for funcname in functions:
#c_header.write("void* %s(void);\n" % funcname)
c_hdr_buffer += "void* %s(void);\n" % funcname
c_source.write(c_func_impl_stub % (funcname, funcname))
cmake = open(output_dir + "/CMakeLists.txt", "w")
cmake.write("project(%s)\n\n" % target_name)
# Get current and compat versions
otool_out = subprocess.check_output(["otool", "-L", full_path])
otool_out = otool_out.decode('utf8').strip()
version_line = otool_out.splitlines()[1]
get_versions = re.compile("\\(compatibility version (.*?), current version (.*?)\\)")
compat, current = get_versions.search(version_line).groups()
if library:
cmake.write("set(DYLIB_INSTALL_NAME \"%s\")\n" % full_path)
cmake.write("set(DYLIB_COMPAT_VERSION \"%s\")\n" % compat)
cmake.write("set(DYLIB_CURRENT_VERSION \"%s\")\n\n" % current)
class_dump_output = subprocess.check_output(["class-dump", full_path]).decode('utf8').strip()
uses_objc = "This file does not contain any Objective-C runtime information." not in class_dump_output
if uses_objc:
class_dump_all = subprocess.check_output(["class-dump", "-H", "-o", header_dir, full_path]).decode('utf8').strip()
get_class_names = re.compile("@interface (.+) :.+")
classes = get_class_names.findall(class_dump_output)
for classname in classes:
impl = open(source_dir + classname + ".m", "w")
impl.write(copyright)
impl.write("#import <%s/%s.h>\n\n" % (target_name, target_name))
impl.write("@implementation " + classname + "\n\n")
impl.write(msg_handling)
impl.write("@end\n")
c_header.write("#import <%s/%s.h>\n" % (target_name, classname))
if uses_objc:
cmake.write("file(GLOB %s_OBJC_SOURCES src/*.m)\n\n" % target_name)
if library:
source_files = "src/%s.c\n${%s_OBJC_SOURCES}" % (target_name, target_name)
cmake.write("add_darling_library(%s SHARED %s)\n" % (target_name, source_files))
cmake.write("make_fat(%s)\n" % target_name)
libraries = "system objc" if uses_objc else "system"
cmake.write("target_link_libraries(%s %s)\n" % (target_name, libraries))
cmake.write("install(TARGETS %s DESTINATION libexec/darling/usr/lib)\n" % target_name)
else:
cmake.write("add_framework(%s\n" %target_name)
cmake.write(" FAT\n CURRENT_VERSION\n")
if private_framework:
cmake.write(" PRIVATE\n")
cmake.write(" VERSION \"A\"\n\n")
cmake.write(" SOURCES\n")
cmake.write(" src/%s.c\n" % target_name)
if uses_objc:
cmake.write(" ${%s_OBJC_SOURCES}\n" % target_name)
cmake.write("\n")
cmake.write(" DEPENDENCIES\n")
cmake.write(" system\n")
if uses_objc:
cmake.write(" objc\n")
cmake.write(" Foundation\n")
cmake.write(")\n")
c_header.write(c_hdr_buffer)
c_header.write("\n#endif\n")