mirror of
https://github.com/anrieff/libcpuid
synced 2024-11-20 23:01:51 +00:00
Python: Refactor the build of the bindings
When building the binding, link the dynamic C library as built by `make` instead of linking the system-installed C library. This simplifies packaging in Linux distributions and also the build of the live docs.
This commit is contained in:
parent
a0f07b8b26
commit
20fbc35374
5 changed files with 105 additions and 156 deletions
|
@ -14,13 +14,10 @@ build:
|
||||||
pre_install:
|
pre_install:
|
||||||
- libtoolize
|
- libtoolize
|
||||||
- autoreconf --install
|
- autoreconf --install
|
||||||
- mkdir ./install
|
- ./configure
|
||||||
- ./configure --prefix=`pwd`/install
|
|
||||||
- make
|
- make
|
||||||
- make install
|
|
||||||
- pip install cffi
|
- pip install cffi
|
||||||
- python ./python/src/libcpuid/_ffi_build_rtd.py ./libcpuid/libcpuid.h ./install
|
- python python/src/libcpuid/_ffi_build.py --runtime-link
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: python/docs/conf.py
|
configuration: python/docs/conf.py
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "cffi"]
|
requires = ["setuptools", "cffi", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
|
|
@ -3,23 +3,111 @@ Module for compiling the C FFI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import re
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
from cffi import FFI
|
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
|
class FFIBuildException(Exception):
|
||||||
get_include_flags,
|
"""Generic exception for errors occuring during the CFFI build."""
|
||||||
find_header_file,
|
|
||||||
preprocess_header,
|
|
||||||
eval_sizeofs,
|
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 <libcpuid.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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"
|
||||||
|
PYTHON_SRC_DIR = str(Path(LIBCPUID_DIR, "python", "src"))
|
||||||
|
|
||||||
|
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}",
|
||||||
|
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 = FFI()
|
||||||
ffibuilder.cdef(no_sizeof_header)
|
ffibuilder.cdef(NO_SIZEOF_HEADER)
|
||||||
ffibuilder.set_source_pkgconfig(
|
|
||||||
"libcpuid._libcpuid_cffi", ["libcpuid"], "#include <libcpuid.h>"
|
set_source_kwargs = {
|
||||||
)
|
"module_name": "libcpuid._libcpuid_cffi",
|
||||||
|
"source": f"#include <{LIBCPUID_MAIN_HEADER_FILENAME}>",
|
||||||
|
"libraries": [LIBCPUID_LIBRARY_NAME],
|
||||||
|
"include_dirs": [LIBCPUID_INCLUDE_DIR],
|
||||||
|
"library_dirs": [LIBCPUID_LIBRARY_DIR],
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("-r", "--runtime-link", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.runtime_link:
|
||||||
|
set_source_kwargs["extra_link_args"] = [f"-Wl,-rpath={LIBCPUID_LIBRARY_DIR}"]
|
||||||
|
|
||||||
|
ffibuilder.set_source(**set_source_kwargs)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ffibuilder.compile(PYTHON_SRC_DIR)
|
||||||
|
|
|
@ -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 <libcpuid.h>",
|
|
||||||
libraries=["cpuid"],
|
|
||||||
library_dirs=[library_dir],
|
|
||||||
include_dirs=[include_dir],
|
|
||||||
extra_link_args=[f"-Wl,-rpath={library_dir}"],
|
|
||||||
)
|
|
||||||
ffibuilder.compile(verbose=True)
|
|
|
@ -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 <libcpuid.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
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
|
|
Loading…
Reference in a new issue