From 304a9f46e8b0cfa0adbd5ae9b75c8f688fe3f489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Thu, 29 Aug 2024 11:43:53 +0200 Subject: [PATCH] Refactor binding build --- .readthedocs.yml | 9 +- python/src/libcpuid/_ffi_build.py | 114 +++++++++++++++++++++--- python/src/libcpuid/_ffi_build_rtd.py | 31 ------- python/src/libcpuid/_ffi_build_utils.py | 105 ---------------------- 4 files changed, 103 insertions(+), 156 deletions(-) delete mode 100644 python/src/libcpuid/_ffi_build_rtd.py delete mode 100644 python/src/libcpuid/_ffi_build_utils.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 08665843..8dbd8fda 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,13 +14,10 @@ build: pre_install: - libtoolize - autoreconf --install - - mkdir ./install - - ./configure --prefix=`pwd`/install + - ldconfig -p + - ./configure - make - - make install - - pip install cffi - - python ./python/src/libcpuid/_ffi_build_rtd.py ./libcpuid/libcpuid.h ./install - + - python python/src/libcpuid/ffi_build.py sphinx: configuration: python/docs/conf.py diff --git a/python/src/libcpuid/_ffi_build.py b/python/src/libcpuid/_ffi_build.py index 6e24d124..27b01f05 100644 --- a/python/src/libcpuid/_ffi_build.py +++ b/python/src/libcpuid/_ffi_build.py @@ -3,23 +3,109 @@ """ import os -import sys +import subprocess +import tempfile +import re +from pathlib import Path from cffi import FFI -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from _ffi_build_utils import ( # pylint: disable=import-error, wrong-import-position - get_include_flags, - find_header_file, - preprocess_header, - eval_sizeofs, +class FFIBuildException(Exception): + """Generic exception for errors occuring during the CFFI build.""" + + +def preprocess_header(header_path): + """ + Preprocesses the header file (python-cffi only accepts preprocessed C definitions) + at the given path and returns it as a string. + """ + try: + return subprocess.check_output( + ["gcc", "-U __GNUC__", "-E", header_path] + ).decode() + except subprocess.CalledProcessError as e: + if e.returncode == 127: + raise FFIBuildException( + "The gcc compiler is necessary to build python-libcpuid." + ) from e + raise FFIBuildException( + f"Error preprocessing the libcpuid header file: {e.stderr}" + ) from e + + +def _get_sizeof_eval_source(sizeof): + return f""" +#include +#include + +int main() {{ + printf("%ld", {sizeof}); + return 0; +}} +""" + + +def eval_sizeofs(header, cflags): + """ + Evaluates each sizeof found in the given C header and replaces all + occurences of the sizeof with its computed value. + """ + sizeofs = set(re.findall(r"sizeof\([^\)]*\)", header)) + tmp_dir = tempfile.mkdtemp() + c_program_path = Path(tmp_dir, "sizeof.c") + executable_path = Path(tmp_dir, "sizeof") + + for sizeof in sizeofs: + with open(c_program_path, "w", encoding="UTF-8") as c_program_file: + c_program_file.write(_get_sizeof_eval_source(sizeof)) + subprocess.check_call(["gcc", c_program_path, *cflags, "-o", executable_path]) + size = subprocess.check_output([executable_path]).decode() + header = header.replace(sizeof, size) + + os.remove(c_program_path) + os.remove(executable_path) + os.rmdir(tmp_dir) + return header + + +LIBCPUID_DIR = str(Path(*(Path(os.path.abspath(__file__)).parts[:-4]))) +LIBCPUID_INCLUDE_DIR = str(Path(LIBCPUID_DIR, "libcpuid")) +LIBCPUID_LIBRARY_DIR = str(Path(LIBCPUID_DIR, "libcpuid", ".libs")) +LIBCPUID_MAIN_HEADER_FILENAME = "libcpuid.h" +LIBCPUID_MAIN_HEADER_PATH = str( + Path(LIBCPUID_INCLUDE_DIR, LIBCPUID_MAIN_HEADER_FILENAME) ) +LIBCPUID_LIBRARY_NAME = "cpuid" + +PREPROCESSED_HEADER = preprocess_header(LIBCPUID_MAIN_HEADER_PATH) +eval_sizeof_cflags = [ + f"-I{LIBCPUID_INCLUDE_DIR}", + f"-L{LIBCPUID_LIBRARY_DIR}", + f"-l{LIBCPUID_LIBRARY_NAME}", +] +if __name__ == "__main__": + eval_sizeof_cflags.append(f"-Wl,-rpath={LIBCPUID_LIBRARY_DIR}") + +NO_SIZEOF_HEADER = eval_sizeofs(PREPROCESSED_HEADER, eval_sizeof_cflags) -include_flags = get_include_flags() -preprocessed_header = preprocess_header(find_header_file(include_flags)) -no_sizeof_header = eval_sizeofs(preprocessed_header, include_flags) ffibuilder = FFI() -ffibuilder.cdef(no_sizeof_header) -ffibuilder.set_source_pkgconfig( - "libcpuid._libcpuid_cffi", ["libcpuid"], "#include " -) +ffibuilder.cdef(NO_SIZEOF_HEADER) + + +if __name__ == "__main__": + ffibuilder.set_source( + "libcpuid._libcpuid_cffi", + f"#include <{LIBCPUID_MAIN_HEADER_FILENAME}>", + libraries=[LIBCPUID_LIBRARY_NAME], + include_dirs=[LIBCPUID_INCLUDE_DIR], + library_dirs=[LIBCPUID_LIBRARY_DIR], + extra_link_args=[f"-Wl,-rpath={LIBCPUID_LIBRARY_DIR}"], + ) +else: + ffibuilder.set_source( + "libcpuid._libcpuid_cffi", + f"#include <{LIBCPUID_MAIN_HEADER_FILENAME}>", + libraries=[LIBCPUID_LIBRARY_NAME], + include_dirs=[LIBCPUID_INCLUDE_DIR], + library_dirs=[LIBCPUID_LIBRARY_DIR], + ) diff --git a/python/src/libcpuid/_ffi_build_rtd.py b/python/src/libcpuid/_ffi_build_rtd.py deleted file mode 100644 index 09d09ba7..00000000 --- a/python/src/libcpuid/_ffi_build_rtd.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Script for compiling the C FFI for the live documentation. -""" - -import sys -import os -from cffi import FFI - -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from _ffi_build_utils import ( # pylint: disable=import-error, wrong-import-position - preprocess_header, - eval_sizeofs, -) - -if __name__ == "__main__": - header_path = sys.argv[1] - install_dir = sys.argv[2] - library_dir = os.path.join(os.getcwd(), install_dir, "lib") - include_dir = os.path.join(install_dir, "include", "libcpuid") - ffibuilder = FFI() - ffibuilder.cdef(eval_sizeofs(preprocess_header(header_path), [f"-I{include_dir}"])) - ffibuilder.set_source( - "python.src.libcpuid._libcpuid_cffi", - "#include ", - libraries=["cpuid"], - library_dirs=[library_dir], - include_dirs=[include_dir], - extra_link_args=[f"-Wl,-rpath={library_dir}"], - ) - ffibuilder.compile(verbose=True) diff --git a/python/src/libcpuid/_ffi_build_utils.py b/python/src/libcpuid/_ffi_build_utils.py deleted file mode 100644 index 410bd8ca..00000000 --- a/python/src/libcpuid/_ffi_build_utils.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -Utility functions for building the FFI. -""" - -import subprocess -import os -import re -import tempfile - - -class FFIBuildException(Exception): - """Generic exception for errors occuring during the CFFI build.""" - - -def get_include_flags(): - """ - Obtains libcpuid include flags via pkg-config. - """ - try: - cflags = ( - subprocess.check_output(["pkg-config", "libcpuid", "--cflags-only-I"]) - .decode() - .strip() - .split() - ) - return cflags - except subprocess.CalledProcessError as e: - if e.returncode == 127: - raise FFIBuildException( - "The pkg-config command is necessary to build python-libcpuid." - ) from e - if e.returncode == 1: - raise FFIBuildException( - "The libcpuid C library (devel) was not found." - ) from e - raise FFIBuildException("Error looking for the libcpuid library") from e - - -def find_header_file(include_flags): - """ - Obtains main libcpuid header file location from include flags. - """ - header_path = None # pylint: disable=invalid-name - for cflag in include_flags: - header_candidate = os.path.join(cflag[2:], "libcpuid.h") - if os.path.isfile(header_candidate): - header_path = header_candidate - break - if header_path is None: - raise FFIBuildException("Could not find header file of the libcpuid library.") - return header_path - - -def preprocess_header(header_path): - """ - Preprocesses the header file (python-cffi only accepts preprocessed C definitions) - at the given path and returns it as a string. - """ - try: - return subprocess.check_output( - ["gcc", "-U __GNUC__", "-E", header_path] - ).decode() - except subprocess.CalledProcessError as e: - if e.returncode == 127: - raise FFIBuildException( - "The gcc compiler is necessary to build python-libcpuid." - ) from e - raise FFIBuildException( - f"Error preprocessing the libcpuid header file: {e.stderr}" - ) from e - - -def _get_sizeof_eval_source(sizeof): - return f""" -#include -#include - -int main() {{ - printf("%ld", {sizeof}); - return 0; -}} -""" - - -def eval_sizeofs(header, cflags): - """ - Evaluates each sizeof found in the given C header and replaces all - occurences of the sizeof with its computed value. - """ - sizeofs = set(re.findall(r"sizeof\([^\)]*\)", header)) - tmp_dir = tempfile.mkdtemp() - c_program_path = os.path.join(tmp_dir, "sizeof.c") - executable_path = os.path.join(tmp_dir, "sizeof") - - for sizeof in sizeofs: - with open(c_program_path, "w", encoding="UTF-8") as c_program_file: - c_program_file.write(_get_sizeof_eval_source(sizeof)) - subprocess.check_call(["gcc", c_program_path, *cflags, "-o", executable_path]) - size = subprocess.check_output([executable_path]).decode() - header = header.replace(sizeof, size) - - os.remove(c_program_path) - os.remove(executable_path) - os.rmdir(tmp_dir) - return header