diff --git a/ctypesgen/__main__.py b/ctypesgen/__main__.py index b576d8a9..f92f0f1f 100644 --- a/ctypesgen/__main__.py +++ b/ctypesgen/__main__.py @@ -45,20 +45,20 @@ def main(givenargs=None): nargs="+", help="Sequence of header files", ) - parser.add_argument( - "-o", - "--output", - metavar="FILE", - help="write wrapper to FILE [default stdout]", - ) parser.add_argument( "-l", - "--libraries", - nargs="+", + "--library", + required=True, default=[], metavar="LIBRARY", help="link to LIBRARY", ) + parser.add_argument( + "-o", + "--output", + metavar="FILE", + help="write wrapper to FILE [default stdout]", + ) parser.add_argument( "--other-headers", nargs="+", @@ -322,8 +322,7 @@ def main(givenargs=None): # Figure out what names will be defined by imported Python modules args.other_known_names = find_names_in_modules(args.modules) - if len(args.libraries) == 0: - msgs.warning_message("No libraries specified", cls="usage") + assert args.library # Fetch printer for the requested output language if args.output_language == "py": diff --git a/ctypesgen/libraryloader.py b/ctypesgen/libraryloader.py index 19073d26..c77e1a1b 100644 --- a/ctypesgen/libraryloader.py +++ b/ctypesgen/libraryloader.py @@ -1,410 +1,36 @@ -""" -Load libraries - appropriately for all our supported platforms -""" -# ---------------------------------------------------------------------------- -# Copyright (c) 2008 David James -# Copyright (c) 2006-2008 Alex Holkner -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# * Neither the name of pyglet nor the names of its -# contributors may be used to endorse or promote products -# derived from this software without specific prior written -# permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ---------------------------------------------------------------------------- - +import sys import ctypes import ctypes.util -import glob -import os.path -import platform -import re -import sys - - -def _environ_path(name): - """Split an environment variable into a path-like list elements""" - if name in os.environ: - return os.environ[name].split(":") - return [] - - -class LibraryLoader: - """ - A base class For loading of libraries ;-) - Subclasses load libraries for specific platforms. - """ - - # library names formatted specifically for platforms - name_formats = ["%s"] - - class Lookup: - """Looking up calling conventions for a platform""" - - mode = ctypes.DEFAULT_MODE - - def __init__(self, path): - super(LibraryLoader.Lookup, self).__init__() - self.access = dict(cdecl=ctypes.CDLL(path, self.mode)) - - def get(self, name, calling_convention="cdecl"): - """Return the given name according to the selected calling convention""" - if calling_convention not in self.access: - raise LookupError( - "Unknown calling convention '{}' for function '{}'".format( - calling_convention, name - ) - ) - return getattr(self.access[calling_convention], name) - - def has(self, name, calling_convention="cdecl"): - """Return True if this given calling convention finds the given 'name'""" - if calling_convention not in self.access: - return False - return hasattr(self.access[calling_convention], name) - - def __getattr__(self, name): - return getattr(self.access["cdecl"], name) - - def __init__(self): - self.other_dirs = [] - - def __call__(self, libname): - """Given the name of a library, load it.""" - paths = self.getpaths(libname) - - for path in paths: - # noinspection PyBroadException - try: - return self.Lookup(path) - except Exception: # pylint: disable=broad-except - pass - - raise ImportError("Could not load %s." % libname) - - def getpaths(self, libname): - """Return a list of paths where the library might be found.""" - if os.path.isabs(libname): - yield libname - else: - # search through a prioritized series of locations for the library - - # we first search any specific directories identified by user - for dir_i in self.other_dirs: - for fmt in self.name_formats: - # dir_i should be absolute already - yield os.path.join(dir_i, fmt % libname) - - # check if this code is even stored in a physical file - try: - this_file = __file__ - except NameError: - this_file = None - - # then we search the directory where the generated python interface is stored - if this_file is not None: - for fmt in self.name_formats: - yield os.path.abspath(os.path.join(os.path.dirname(__file__), fmt % libname)) - - # now, use the ctypes tools to try to find the library - for fmt in self.name_formats: - path = ctypes.util.find_library(fmt % libname) - if path: - yield path - - # then we search all paths identified as platform-specific lib paths - for path in self.getplatformpaths(libname): - yield path - - # Finally, we'll try the users current working directory - for fmt in self.name_formats: - yield os.path.abspath(os.path.join(os.path.curdir, fmt % libname)) - - def getplatformpaths(self, _libname): # pylint: disable=no-self-use - """Return all the library paths available in this platform""" - return [] - - -# Darwin (Mac OS X) - - -class DarwinLibraryLoader(LibraryLoader): - """Library loader for MacOS""" - - name_formats = [ - "lib%s.dylib", - "lib%s.so", - "lib%s.bundle", - "%s.dylib", - "%s.so", - "%s.bundle", - "%s", - ] - - class Lookup(LibraryLoader.Lookup): - """ - Looking up library files for this platform (Darwin aka MacOS) - """ - - # Darwin requires dlopen to be called with mode RTLD_GLOBAL instead - # of the default RTLD_LOCAL. Without this, you end up with - # libraries not being loadable, resulting in "Symbol not found" - # errors - mode = ctypes.RTLD_GLOBAL - - def getplatformpaths(self, libname): - if os.path.pathsep in libname: - names = [libname] - else: - names = [fmt % libname for fmt in self.name_formats] - - for directory in self.getdirs(libname): - for name in names: - yield os.path.join(directory, name) - - @staticmethod - def getdirs(libname): - """Implements the dylib search as specified in Apple documentation: - - http://developer.apple.com/documentation/DeveloperTools/Conceptual/ - DynamicLibraries/Articles/DynamicLibraryUsageGuidelines.html - - Before commencing the standard search, the method first checks - the bundle's ``Frameworks`` directory if the application is running - within a bundle (OS X .app). - """ - - dyld_fallback_library_path = _environ_path("DYLD_FALLBACK_LIBRARY_PATH") - if not dyld_fallback_library_path: - dyld_fallback_library_path = [ - os.path.expanduser("~/lib"), - "/usr/local/lib", - "/usr/lib", - ] - - dirs = [] - - if "/" in libname: - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - else: - dirs.extend(_environ_path("LD_LIBRARY_PATH")) - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - dirs.extend(_environ_path("LD_RUN_PATH")) - - if hasattr(sys, "frozen") and getattr(sys, "frozen") == "macosx_app": - dirs.append(os.path.join(os.environ["RESOURCEPATH"], "..", "Frameworks")) - - dirs.extend(dyld_fallback_library_path) - - return dirs - - -# Posix - - -class PosixLibraryLoader(LibraryLoader): - """Library loader for POSIX-like systems (including Linux)""" - - _ld_so_cache = None - - _include = re.compile(r"^\s*include\s+(?P.*)") - - name_formats = ["lib%s.so", "%s.so", "%s"] - - class _Directories(dict): - """Deal with directories""" - - def __init__(self): - dict.__init__(self) - self.order = 0 - - def add(self, directory): - """Add a directory to our current set of directories""" - if len(directory) > 1: - directory = directory.rstrip(os.path.sep) - # only adds and updates order if exists and not already in set - if not os.path.exists(directory): - return - order = self.setdefault(directory, self.order) - if order == self.order: - self.order += 1 - - def extend(self, directories): - """Add a list of directories to our set""" - for a_dir in directories: - self.add(a_dir) - - def ordered(self): - """Sort the list of directories""" - return (i[0] for i in sorted(self.items(), key=lambda d: d[1])) - - def _get_ld_so_conf_dirs(self, conf, dirs): - """ - Recursive function to help parse all ld.so.conf files, including proper - handling of the `include` directive. - """ - - try: - with open(conf) as fileobj: - for dirname in fileobj: - dirname = dirname.strip() - if not dirname: - continue - - match = self._include.match(dirname) - if not match: - dirs.add(dirname) - else: - for dir2 in glob.glob(match.group("pattern")): - self._get_ld_so_conf_dirs(dir2, dirs) - except IOError: - pass - - def _create_ld_so_cache(self): - # Recreate search path followed by ld.so. This is going to be - # slow to build, and incorrect (ld.so uses ld.so.cache, which may - # not be up-to-date). Used only as fallback for distros without - # /sbin/ldconfig. - # - # We assume the DT_RPATH and DT_RUNPATH binary sections are omitted. - - directories = self._Directories() - for name in ( - "LD_LIBRARY_PATH", - "SHLIB_PATH", # HP-UX - "LIBPATH", # OS/2, AIX - "LIBRARY_PATH", # BE/OS - ): - if name in os.environ: - directories.extend(os.environ[name].split(os.pathsep)) - - self._get_ld_so_conf_dirs("/etc/ld.so.conf", directories) - - bitage = platform.architecture()[0] - - unix_lib_dirs_list = [] - if bitage.startswith("64"): - # prefer 64 bit if that is our arch - unix_lib_dirs_list += ["/lib64", "/usr/lib64"] - - # must include standard libs, since those paths are also used by 64 bit - # installs - unix_lib_dirs_list += ["/lib", "/usr/lib"] - if sys.platform.startswith("linux"): - # Try and support multiarch work in Ubuntu - # https://wiki.ubuntu.com/MultiarchSpec - if bitage.startswith("32"): - # Assume Intel/AMD x86 compat - unix_lib_dirs_list += ["/lib/i386-linux-gnu", "/usr/lib/i386-linux-gnu"] - elif bitage.startswith("64"): - # Assume Intel/AMD x86 compatible - unix_lib_dirs_list += [ - "/lib/x86_64-linux-gnu", - "/usr/lib/x86_64-linux-gnu", - ] - else: - # guess... - unix_lib_dirs_list += glob.glob("/lib/*linux-gnu") - directories.extend(unix_lib_dirs_list) - - cache = {} - lib_re = re.compile(r"lib(.*)\.s[ol]") - # ext_re = re.compile(r"\.s[ol]$") - for our_dir in directories.ordered(): - try: - for path in glob.glob("%s/*.s[ol]*" % our_dir): - file = os.path.basename(path) - - # Index by filename - cache_i = cache.setdefault(file, set()) - cache_i.add(path) - - # Index by library name - match = lib_re.match(file) - if match: - library = match.group(1) - cache_i = cache.setdefault(library, set()) - cache_i.add(path) - except OSError: - pass - - self._ld_so_cache = cache - - def getplatformpaths(self, libname): - if self._ld_so_cache is None: - self._create_ld_so_cache() - - result = self._ld_so_cache.get(libname, set()) - for i in result: - # we iterate through all found paths for library, since we may have - # actually found multiple architectures or other library types that - # may not load - yield i - - -# Windows - - -class WindowsLibraryLoader(LibraryLoader): - """Library loader for Microsoft Windows""" - - name_formats = ["%s.dll", "lib%s.dll", "%slib.dll", "%s"] - - class Lookup(LibraryLoader.Lookup): - """Lookup class for Windows libraries...""" - - def __init__(self, path): - super(WindowsLibraryLoader.Lookup, self).__init__(path) - self.access["stdcall"] = ctypes.windll.LoadLibrary(path) - - -# Platform switching - -# If your value of sys.platform does not appear in this dict, please contact -# the Ctypesgen maintainers. - -loaderclass = { - "darwin": DarwinLibraryLoader, - "cygwin": WindowsLibraryLoader, - "win32": WindowsLibraryLoader, - "msys": WindowsLibraryLoader, -} - -load_library = loaderclass.get(sys.platform, PosixLibraryLoader)() - - -def add_library_search_dirs(other_dirs): - """ - Add libraries to search paths. - If library paths are relative, convert them to absolute with respect to this - file's directory - """ - for path in other_dirs: - if not os.path.isabs(path): - path = os.path.abspath(path) - load_library.other_dirs.append(path) - - -del loaderclass +from pathlib import Path + + +def _find_library(libname, libdirs): + + if not libdirs: + return ctypes.util.find_library(libname) + + if sys.platform in ("win32", "cygwin", "msys"): + patterns = ["{}.dll", "lib{}.dll", "{}"] + elif sys.platform == "darwin": + patterns = ["lib{}.dylib", "{}.dylib", "lib{}.so", "{}.so", "{}"] + else: # assume unix pattern or plain libname + patterns = ["lib{}.so", "{}.so", "{}"] + + RELDIR = Path(__file__).parent + + for dir in libdirs: + # joining an absolute path silently discardy the path before + dir = (RELDIR / dir).resolve(strict=False) + for pat in patterns: + test_path = dir / pat.format(libname) + if test_path.is_file(): + return test_path + + +def load_library(libname, libdirs): + + libpath = _find_library(libname, libdirs) + if not libpath: + raise RuntimeError(f"Library '{libname}' could not be found in {libdirs if libdirs else 'system'}.") + + return ctypes.CDLL(libpath) diff --git a/ctypesgen/printer_json/printer.py b/ctypesgen/printer_json/printer.py index 5f89fe2c..2949868c 100755 --- a/ctypesgen/printer_json/printer.py +++ b/ctypesgen/printer_json/printer.py @@ -44,7 +44,7 @@ def __init__(self, outpath, options, data): if self.options.strip_build_path and self.options.strip_build_path[-1] != os.path.sep: self.options.strip_build_path += os.path.sep - self.print_group(self.options.libraries, "libraries", self.print_library) + self.print_library(self.options.library) method_table = { "function": self.print_function, @@ -114,15 +114,12 @@ def print_function(self, function): "args": todict(function.argtypes), "return": todict(function.restype), "attrib": function.attrib, + "source": self.options.library, } - if function.source_library: - res["source"] = function.source_library return res def print_variable(self, variable): - res = {"type": "variable", "ctype": todict(variable.ctype), "name": variable.c_name()} - if variable.source_library: - res["source"] = variable.source_library + res = {"type": "variable", "ctype": todict(variable.ctype), "name": variable.c_name(), "source": self.options.library} return res def print_macro(self, macro): diff --git a/ctypesgen/printer_python/printer.py b/ctypesgen/printer_python/printer.py index 4c09964f..50f77ccd 100755 --- a/ctypesgen/printer_python/printer.py +++ b/ctypesgen/printer_python/printer.py @@ -23,49 +23,67 @@ class WrapperPrinter: def __init__(self, outpath, options, data): status_message("Writing to %s." % (outpath or "stdout")) - self.file = open(outpath, "w") if outpath else sys.stdout - self.options = options - - if self.options.strip_build_path and self.options.strip_build_path[-1] != os.path.sep: - self.options.strip_build_path += os.path.sep - - if not self.options.embed_preamble and outpath: - self._copy_preamble_loader_files(outpath) - - self.print_header() - self.file.write("\n") - - self.print_preamble() - self.file.write("\n") - - self.print_loader() - self.file.write("\n") - - self.print_group(self.options.libraries, "libraries", self.print_library) - self.print_group(self.options.modules, "modules", self.print_module) - - method_table = { - "function": self.print_function, - "macro": self.print_macro, - "struct": self.print_struct, - "typedef": self.print_typedef, - "variable": self.print_variable, - "enum": self.print_enum, - "constant": self.print_constant, - "undef": self.print_undef, - } - - for kind, desc in data.output_order: - if desc.included: - method_table[kind](desc) - self.file.write("\n") - - self.print_group(self.options.inserted_files, "inserted files", self.insert_file) - - def __del__(self): - self.file.close() + + try: + self.options = options + + # FIXME(geisserml) see below + if self.options.strip_build_path and self.options.strip_build_path[-1] != os.path.sep: + self.options.strip_build_path += os.path.sep + + if not self.options.embed_preamble and outpath: + self._copy_preamble_loader_files(outpath) + + self.print_header() + self.file.write("\n") + + self.print_preamble() + self.file.write("\n") + + self.print_loader() + self.file.write("\n") + + self.print_library(self.options.library) + self.print_group(self.options.modules, "modules", self.print_module) + + method_table = { + "function": self.print_function, + "macro": self.print_macro, + "struct": self.print_struct, + "typedef": self.print_typedef, + "variable": self.print_variable, + "enum": self.print_enum, + "constant": self.print_constant, + "undef": self.print_undef, + } + + for kind, desc in data.output_order: + if desc.included: + method_table[kind](desc) + self.file.write("\n") + + self.print_group(self.options.inserted_files, "inserted files", self.insert_file) + + finally: + self.file.close() + + + def print_loader(self): + self.file.write("# Begin loader\n\n") + if self.options.embed_preamble: + with open(LIBRARYLOADER_PATH, "r") as loader_file: + self.file.write(loader_file.read()) + else: + self.file.write("from .ctypes_loader import *\n") + self.file.write("\n# End loader\n\n") + def print_library(self, library): + self.file.write( + f'_libdirs = {self.options.runtime_libdirs}\n' + f'_lib = load_library("{library}", _libdirs)\n' + ) + def print_group(self, list, name, function): if list: self.file.write("# Begin %s\n" % name) @@ -193,25 +211,6 @@ def from_param(cls, x): shutil.copyfile(LIBRARYLOADER_PATH, join(dst, "ctypes_loader.py")) - def print_loader(self): - self.file.write("_libs = {}\n") - self.file.write("_libdirs = %s\n\n" % self.options.compile_libdirs) - self.file.write("# Begin loader\n\n") - if self.options.embed_preamble: - with open(LIBRARYLOADER_PATH, "r") as loader_file: - self.file.write(loader_file.read()) - else: - self.file.write("from .ctypes_loader import *\n") - self.file.write("\n# End loader\n\n") - self.file.write( - "add_library_search_dirs([%s])" - % ", ".join([repr(d) for d in self.options.runtime_libdirs]) - ) - self.file.write("\n") - - def print_library(self, library): - self.file.write('_libs["%s"] = load_library("%s")\n' % (library, library)) - def print_module(self, module): self.file.write("from %s import *\n" % module) @@ -311,26 +310,19 @@ def print_function(self, function): def print_fixed_function(self, function): self.srcinfo(function.src) - CC = "stdcall" if function.attrib.get("stdcall", False) else "cdecl" - use_sourcelib = function.source_library or len(self.options.libraries) == 1 - - if use_sourcelib: - lib = function.source_library if function.source_library else self.options.libraries[0] - self.file.write( - 'if _libs["{L}"].has("{CN}", "{CC}"):\n' - ' {PN} = _libs["{L}"].get("{CN}", "{CC}")\n'.format( - L=lib, CN=function.c_name(), PN=function.py_name(), CC=CC - ) - ) - else: - self.file.write( - "for _lib in _libs.values():\n" - ' if not _lib.has("{CN}", "{CC}"):\n' - " continue\n" - ' {PN} = _lib.get("{CN}", "{CC}")\n'.format( - CN=function.c_name(), PN=function.py_name(), CC=CC - ) + # NOTE pypdfium2-ctypesgen currently does not support the windows-only stdcall convention + # this could theoretically be done by adding a second library handle _lib_stdcall = ctypes.WinDLL(...) on windows and using that for stdcall functions + # since this would cause additional complexity and/or produce an unnecessary/invalid handle for a non-stdcall library, it's not implemented ATM + + assert not function.attrib.get("stdcall", False) + + # TODO add option to skip hasattr() guard + self.file.write( + 'if hasattr(_lib, "{CN}"):\n' + ' {PN} = _lib.{CN}\n'.format( + L=self.options.library, CN=function.c_name(), PN=function.py_name(), ) + ) # Argument types self.file.write( @@ -346,74 +338,42 @@ def print_fixed_function(self, function): self.file.write( "\n %s.errcheck = %s" % (function.py_name(), function.errcheck.py_string()) ) - - if not use_sourcelib: - self.file.write("\n break") - + def print_variadic_function(self, function): - CC = "stdcall" if function.attrib.get("stdcall", False) else "cdecl" + # TODO see if we can remove the _variadic_function wrapper and use just plain ctypes + + assert not function.attrib.get("stdcall", False) self.srcinfo(function.src) - if function.source_library: - self.file.write( - 'if _libs["{L}"].has("{CN}", "{CC}"):\n' - ' _func = _libs["{L}"].get("{CN}", "{CC}")\n' - " _restype = {RT}\n" - " _errcheck = {E}\n" - " _argtypes = [{t0}]\n" - " {PN} = _variadic_function(_func,_restype,_argtypes,_errcheck)\n".format( - L=function.source_library, - CN=function.c_name(), - RT=function.restype.py_string(), - E=function.errcheck.py_string(), - t0=", ".join([a.py_string() for a in function.argtypes]), - PN=function.py_name(), - CC=CC, - ) - ) - else: - self.file.write( - "for _lib in _libs.values():\n" - ' if _lib.has("{CN}", "{CC}"):\n' - ' _func = _lib.get("{CN}", "{CC}")\n' - " _restype = {RT}\n" - " _errcheck = {E}\n" - " _argtypes = [{t0}]\n" - " {PN} = _variadic_function(_func,_restype,_argtypes,_errcheck)\n".format( - CN=function.c_name(), - RT=function.restype.py_string(), - E=function.errcheck.py_string(), - t0=", ".join([a.py_string() for a in function.argtypes]), - PN=function.py_name(), - CC=CC, - ) + self.file.write( + 'if hasattr(_lib, {CN}):\n' + ' _func = _lib.{CN}\n' + " _restype = {RT}\n" + " _errcheck = {E}\n" + " _argtypes = [{t0}]\n" + " {PN} = _variadic_function(_func,_restype,_argtypes,_errcheck)\n".format( + L=self.options.library, + CN=function.c_name(), + RT=function.restype.py_string(), + E=function.errcheck.py_string(), + t0=", ".join([a.py_string() for a in function.argtypes]), + PN=function.py_name(), ) + ) def print_variable(self, variable): self.srcinfo(variable.src) - if variable.source_library: - self.file.write( - "try:\n" - ' {PN} = ({PS}).in_dll(_libs["{L}"], "{CN}")\n' - "except:\n" - " pass\n".format( - PN=variable.py_name(), - PS=variable.ctype.py_string(), - L=variable.source_library, - CN=variable.c_name(), - ) - ) - else: - self.file.write( - "for _lib in _libs.values():\n" - " try:\n" - ' {PN} = ({PS}).in_dll(_lib, "{CN}")\n' - " break\n" - " except:\n" - " pass\n".format( - PN=variable.py_name(), PS=variable.ctype.py_string(), CN=variable.c_name() - ) + self.file.write( + "try:\n" + ' {PN} = ({PS}).in_dll(_lib, "{CN}")\n' + "except:\n" + " pass\n".format( + PN=variable.py_name(), + PS=variable.ctype.py_string(), + L=self.options.library, + CN=variable.c_name(), ) + ) def print_macro(self, macro): if macro.params: diff --git a/ctypesgen/processor/operations.py b/ctypesgen/processor/operations.py index 156bec29..58f6c0c7 100644 --- a/ctypesgen/processor/operations.py +++ b/ctypesgen/processor/operations.py @@ -249,30 +249,29 @@ def fix_conflicting_names(data, opts): def find_source_libraries(data, opts): - """find_source_libraries() determines which library contains each function - and variable.""" - - all_symbols = data.functions + data.variables + + # NOTE is_available is not currently used throughout ctypesgen because it's not clear what we should do with the info - we already have hasattr() if-guards anyway. And what is more, in practice, binary and headers should always match, otherwise the caller has probably made a mistake. + + all_symbols = set(data.functions + data.variables) + # default assumption: all symbols available + for symbol in all_symbols: + symbol.is_available = True + + if opts.no_load_library: + status_message(f"Bypass load_library '{opts.library}'.") + return + + try: + library = libraryloader.load_library(opts.library, opts.compile_libdirs) + except RuntimeError: + warning_message(f"Could not load library '{opts.library}'. Okay, I'll try to load it at runtime instead.", cls="missing-library") + return + + missing_symbols = set() for symbol in all_symbols: - symbol.source_library = None # FIXME probably unnecessary? - - libraryloader.add_library_search_dirs(opts.compile_libdirs) - - for library_name in opts.libraries: - if opts.no_load_library: - status_message("Bypass load_library %s." % library_name) - continue - - try: - library = libraryloader.load_library(library_name) - except ImportError: - warning_message( - f"Could not load library '{library_name}'. Okay, I'll try to load it at runtime instead.", - cls="missing-library", - ) - continue - for symbol in all_symbols: - # TODO warn if the same symbol is provided by multiple different libs? - if symbol.source_library is None: - if hasattr(library, symbol.c_name()): - symbol.source_library = library_name + symbol.is_available = hasattr(library, symbol.c_name()) + if not symbol.is_available: + missing_symbols.add(symbol) + + if missing_symbols: + warning_message(f"Some symbols could not be found - binary/headers mismatch suspected. {missing_symbols}", cls="other") diff --git a/tests/ctypesgentest.py b/tests/ctypesgentest.py index a567e2b4..7a7e398c 100644 --- a/tests/ctypesgentest.py +++ b/tests/ctypesgentest.py @@ -224,7 +224,7 @@ def _generate_common(file_name, common_lib, embed_preamble=True): test_options = options.get_default_options() test_options.headers = [f"{COMMON_DIR}/{file_name}.h"] test_options.include_search_paths = [COMMON_DIR] - test_options.libraries = [common_lib] + test_options.library = common_lib test_options.compile_libdirs = [COMMON_DIR] test_options.embed_preamble = embed_preamble if embed_preamble: