diff --git a/python/src/libcpuid/_ffi_build.py b/python/src/libcpuid/_ffi_build.py index bdbbd1f3..6e24d124 100644 --- a/python/src/libcpuid/_ffi_build.py +++ b/python/src/libcpuid/_ffi_build.py @@ -2,71 +2,24 @@ Module for compiling the C FFI. """ -import subprocess import os +import sys from cffi import FFI +sys.path.append(os.path.dirname(os.path.abspath(__file__))) -class FFIBuildException(Exception): - """Generic exception for errors occuring during the CFFI build.""" - - -def find_header(): - """ - Obtains libcpuid header file location via pkg-config. - """ - try: - cflags = ( - subprocess.check_output(["pkg-config", "libcpuid", "--cflags-only-I"]) - .decode() - .strip() - .split() - ) - 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 - - # Find an existing libcpuid header file - header_path = None # pylint: disable=invalid-name - for cflag in cflags: - 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). - """ - 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 - - -header = find_header() +from _ffi_build_utils import ( # pylint: disable=import-error, wrong-import-position + get_include_flags, + find_header_file, + preprocess_header, + eval_sizeofs, +) +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(preprocess_header(header)) +ffibuilder.cdef(no_sizeof_header) ffibuilder.set_source_pkgconfig( "libcpuid._libcpuid_cffi", ["libcpuid"], "#include " ) diff --git a/python/src/libcpuid/_ffi_build_rtd.py b/python/src/libcpuid/_ffi_build_rtd.py index 10b88359..09d09ba7 100644 --- a/python/src/libcpuid/_ffi_build_rtd.py +++ b/python/src/libcpuid/_ffi_build_rtd.py @@ -2,25 +2,30 @@ Script for compiling the C FFI for the live documentation. """ -import subprocess 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 = sys.argv[1] + 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( - subprocess.check_output(["gcc", "-U __GNUC__", "-E", header]).decode() - ) + 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=[os.path.join(install_dir, "include", "libcpuid")], + 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 new file mode 100644 index 00000000..cfee3a9b --- /dev/null +++ b/python/src/libcpuid/_ffi_build_utils.py @@ -0,0 +1,107 @@ +""" +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("%d", {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