make_cffi.py
238 lines
| 7.0 KiB
| text/x-python
|
PythonLexer
Gregory Szorc
|
r30435 | # Copyright (c) 2016-present, Gregory Szorc | ||
# All rights reserved. | ||||
# | ||||
# This software may be modified and distributed under the terms | ||||
# of the BSD license. See the LICENSE file for details. | ||||
import cffi | ||||
Gregory Szorc
|
r30822 | import distutils.ccompiler | ||
Gregory Szorc
|
r30435 | import os | ||
Gregory Szorc
|
r30895 | import re | ||
Gregory Szorc
|
r30822 | import subprocess | ||
import tempfile | ||||
Gregory Szorc
|
r30435 | |||
HERE = os.path.abspath(os.path.dirname(__file__)) | ||||
Gregory Szorc
|
r44446 | SOURCES = [ | ||
"zstd/%s" % p | ||||
for p in ( | ||||
"common/debug.c", | ||||
"common/entropy_common.c", | ||||
"common/error_private.c", | ||||
"common/fse_decompress.c", | ||||
"common/pool.c", | ||||
"common/threading.c", | ||||
"common/xxhash.c", | ||||
"common/zstd_common.c", | ||||
"compress/fse_compress.c", | ||||
"compress/hist.c", | ||||
"compress/huf_compress.c", | ||||
"compress/zstd_compress.c", | ||||
"compress/zstd_compress_literals.c", | ||||
"compress/zstd_compress_sequences.c", | ||||
"compress/zstd_double_fast.c", | ||||
"compress/zstd_fast.c", | ||||
"compress/zstd_lazy.c", | ||||
"compress/zstd_ldm.c", | ||||
"compress/zstd_opt.c", | ||||
"compress/zstdmt_compress.c", | ||||
"decompress/huf_decompress.c", | ||||
"decompress/zstd_ddict.c", | ||||
"decompress/zstd_decompress.c", | ||||
"decompress/zstd_decompress_block.c", | ||||
"dictBuilder/cover.c", | ||||
"dictBuilder/fastcover.c", | ||||
"dictBuilder/divsufsort.c", | ||||
"dictBuilder/zdict.c", | ||||
) | ||||
] | ||||
Gregory Szorc
|
r30435 | |||
Gregory Szorc
|
r31796 | # Headers whose preprocessed output will be fed into cdef(). | ||
Gregory Szorc
|
r44446 | HEADERS = [ | ||
Gregory Szorc
|
r44605 | os.path.join(HERE, "zstd", *p) | ||
Augie Fackler
|
r46554 | for p in ( | ||
("zstd.h",), | ||||
("dictBuilder", "zdict.h"), | ||||
) | ||||
Gregory Szorc
|
r44446 | ] | ||
Gregory Szorc
|
r30895 | |||
Gregory Szorc
|
r44446 | INCLUDE_DIRS = [ | ||
os.path.join(HERE, d) | ||||
for d in ( | ||||
"zstd", | ||||
"zstd/common", | ||||
"zstd/compress", | ||||
"zstd/decompress", | ||||
"zstd/dictBuilder", | ||||
) | ||||
] | ||||
Gregory Szorc
|
r30435 | |||
Gregory Szorc
|
r30822 | # cffi can't parse some of the primitives in zstd.h. So we invoke the | ||
# preprocessor and feed its output into cffi. | ||||
compiler = distutils.ccompiler.new_compiler() | ||||
# Needed for MSVC. | ||||
Gregory Szorc
|
r44446 | if hasattr(compiler, "initialize"): | ||
Gregory Szorc
|
r30822 | compiler.initialize() | ||
# Distutils doesn't set compiler.preprocessor, so invoke the preprocessor | ||||
# manually. | ||||
Gregory Szorc
|
r44446 | if compiler.compiler_type == "unix": | ||
args = list(compiler.executables["compiler"]) | ||||
args.extend( | ||||
Augie Fackler
|
r46554 | [ | ||
"-E", | ||||
"-DZSTD_STATIC_LINKING_ONLY", | ||||
"-DZDICT_STATIC_LINKING_ONLY", | ||||
] | ||||
Gregory Szorc
|
r44446 | ) | ||
elif compiler.compiler_type == "msvc": | ||||
Gregory Szorc
|
r30822 | args = [compiler.cc] | ||
Gregory Szorc
|
r44446 | args.extend( | ||
Augie Fackler
|
r46554 | [ | ||
"/EP", | ||||
"/DZSTD_STATIC_LINKING_ONLY", | ||||
"/DZDICT_STATIC_LINKING_ONLY", | ||||
] | ||||
Gregory Szorc
|
r44446 | ) | ||
Gregory Szorc
|
r30822 | else: | ||
Gregory Szorc
|
r44446 | raise Exception("unsupported compiler type: %s" % compiler.compiler_type) | ||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r30895 | def preprocess(path): | ||
Gregory Szorc
|
r44446 | with open(path, "rb") as fh: | ||
Gregory Szorc
|
r31796 | lines = [] | ||
Gregory Szorc
|
r37513 | it = iter(fh) | ||
for l in it: | ||||
Gregory Szorc
|
r31796 | # zstd.h includes <stddef.h>, which is also included by cffi's | ||
# boilerplate. This can lead to duplicate declarations. So we strip | ||||
# this include from the preprocessor invocation. | ||||
# | ||||
# The same things happens for including zstd.h, so give it the same | ||||
# treatment. | ||||
# | ||||
# We define ZSTD_STATIC_LINKING_ONLY, which is redundant with the inline | ||||
# #define in zstdmt_compress.h and results in a compiler warning. So drop | ||||
# the inline #define. | ||||
Gregory Szorc
|
r44446 | if l.startswith( | ||
( | ||||
b"#include <stddef.h>", | ||||
b'#include "zstd.h"', | ||||
b"#define ZSTD_STATIC_LINKING_ONLY", | ||||
) | ||||
): | ||||
Gregory Szorc
|
r31796 | continue | ||
Gregory Szorc
|
r44446 | # The preprocessor environment on Windows doesn't define include | ||
# paths, so the #include of limits.h fails. We work around this | ||||
# by removing that import and defining INT_MAX ourselves. This is | ||||
# a bit hacky. But it gets the job done. | ||||
# TODO make limits.h work on Windows so we ensure INT_MAX is | ||||
# correct. | ||||
if l.startswith(b"#include <limits.h>"): | ||||
l = b"#define INT_MAX 2147483647\n" | ||||
Gregory Szorc
|
r31796 | # ZSTDLIB_API may not be defined if we dropped zstd.h. It isn't | ||
# important so just filter it out. | ||||
Gregory Szorc
|
r44446 | if l.startswith(b"ZSTDLIB_API"): | ||
l = l[len(b"ZSTDLIB_API ") :] | ||||
Gregory Szorc
|
r31796 | |||
lines.append(l) | ||||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r44446 | fd, input_file = tempfile.mkstemp(suffix=".h") | ||
os.write(fd, b"".join(lines)) | ||||
Gregory Szorc
|
r30895 | os.close(fd) | ||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r30895 | try: | ||
Gregory Szorc
|
r43207 | env = dict(os.environ) | ||
Gregory Szorc
|
r44446 | if getattr(compiler, "_paths", None): | ||
env["PATH"] = compiler._paths | ||||
Gregory Szorc
|
r44605 | process = subprocess.Popen( | ||
args + [input_file], stdout=subprocess.PIPE, env=env | ||||
) | ||||
Gregory Szorc
|
r30895 | output = process.communicate()[0] | ||
ret = process.poll() | ||||
if ret: | ||||
Gregory Szorc
|
r44446 | raise Exception("preprocessor exited with error") | ||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r30895 | return output | ||
finally: | ||||
os.unlink(input_file) | ||||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r30895 | |||
def normalize_output(output): | ||||
Gregory Szorc
|
r30822 | lines = [] | ||
for line in output.splitlines(): | ||||
# CFFI's parser doesn't like __attribute__ on UNIX compilers. | ||||
if line.startswith(b'__attribute__ ((visibility ("default"))) '): | ||||
Gregory Szorc
|
r44446 | line = line[len(b'__attribute__ ((visibility ("default"))) ') :] | ||
Gregory Szorc
|
r30822 | |||
Gregory Szorc
|
r44446 | if line.startswith(b"__attribute__((deprecated("): | ||
Gregory Szorc
|
r30895 | continue | ||
Gregory Szorc
|
r44446 | elif b"__declspec(deprecated(" in line: | ||
Gregory Szorc
|
r30895 | continue | ||
Gregory Szorc
|
r30822 | lines.append(line) | ||
Gregory Szorc
|
r44446 | return b"\n".join(lines) | ||
Gregory Szorc
|
r30435 | |||
Gregory Szorc
|
r30895 | |||
Gregory Szorc
|
r30435 | ffi = cffi.FFI() | ||
Gregory Szorc
|
r37513 | # zstd.h uses a possible undefined MIN(). Define it until | ||
# https://github.com/facebook/zstd/issues/976 is fixed. | ||||
Gregory Szorc
|
r31796 | # *_DISABLE_DEPRECATE_WARNINGS prevents the compiler from emitting a warning | ||
# when cffi uses the function. Since we statically link against zstd, even | ||||
# if we use the deprecated functions it shouldn't be a huge problem. | ||||
Gregory Szorc
|
r44446 | ffi.set_source( | ||
"_zstd_cffi", | ||||
""" | ||||
Gregory Szorc
|
r37513 | #define MIN(a,b) ((a)<(b) ? (a) : (b)) | ||
Gregory Szorc
|
r30435 | #define ZSTD_STATIC_LINKING_ONLY | ||
Gregory Szorc
|
r37513 | #include <zstd.h> | ||
Gregory Szorc
|
r30895 | #define ZDICT_STATIC_LINKING_ONLY | ||
Gregory Szorc
|
r31796 | #define ZDICT_DISABLE_DEPRECATE_WARNINGS | ||
Gregory Szorc
|
r37513 | #include <zdict.h> | ||
Gregory Szorc
|
r44446 | """, | ||
sources=SOURCES, | ||||
include_dirs=INCLUDE_DIRS, | ||||
extra_compile_args=["-DZSTD_MULTITHREAD"], | ||||
) | ||||
Gregory Szorc
|
r30435 | |||
Gregory Szorc
|
r44446 | DEFINE = re.compile(b"^\\#define ([a-zA-Z0-9_]+) ") | ||
Gregory Szorc
|
r30895 | |||
sources = [] | ||||
Gregory Szorc
|
r31796 | # Feed normalized preprocessor output for headers into the cdef parser. | ||
Gregory Szorc
|
r30895 | for header in HEADERS: | ||
preprocessed = preprocess(header) | ||||
sources.append(normalize_output(preprocessed)) | ||||
Gregory Szorc
|
r31796 | # #define's are effectively erased as part of going through preprocessor. | ||
# So perform a manual pass to re-add those to the cdef source. | ||||
Gregory Szorc
|
r44446 | with open(header, "rb") as fh: | ||
Gregory Szorc
|
r30895 | for line in fh: | ||
line = line.strip() | ||||
m = DEFINE.match(line) | ||||
if not m: | ||||
continue | ||||
Gregory Szorc
|
r44446 | if m.group(1) == b"ZSTD_STATIC_LINKING_ONLY": | ||
Gregory Szorc
|
r31796 | continue | ||
Gregory Szorc
|
r30895 | # The parser doesn't like some constants with complex values. | ||
Gregory Szorc
|
r44446 | if m.group(1) in (b"ZSTD_LIB_VERSION", b"ZSTD_VERSION_STRING"): | ||
Gregory Szorc
|
r30895 | continue | ||
Gregory Szorc
|
r31796 | # The ... is magic syntax by the cdef parser to resolve the | ||
# value at compile time. | ||||
Gregory Szorc
|
r44446 | sources.append(m.group(0) + b" ...") | ||
Gregory Szorc
|
r30895 | |||
Gregory Szorc
|
r44446 | cdeflines = b"\n".join(sources).splitlines() | ||
Gregory Szorc
|
r31796 | cdeflines = [l for l in cdeflines if l.strip()] | ||
Gregory Szorc
|
r44446 | ffi.cdef(b"\n".join(cdeflines).decode("latin1")) | ||
Gregory Szorc
|
r30435 | |||
Gregory Szorc
|
r44446 | if __name__ == "__main__": | ||
Gregory Szorc
|
r30435 | ffi.compile() | ||