contrib/python-zstandard/make_cffi.py
changeset 30924 c32454d69b85
parent 30822 b54a2984cdd4
child 31799 e0dc40530c5a
equal deleted inserted replaced
30923:5b60464efbde 30924:c32454d69b85
     7 from __future__ import absolute_import
     7 from __future__ import absolute_import
     8 
     8 
     9 import cffi
     9 import cffi
    10 import distutils.ccompiler
    10 import distutils.ccompiler
    11 import os
    11 import os
       
    12 import re
    12 import subprocess
    13 import subprocess
    13 import tempfile
    14 import tempfile
    14 
    15 
    15 
    16 
    16 HERE = os.path.abspath(os.path.dirname(__file__))
    17 HERE = os.path.abspath(os.path.dirname(__file__))
    17 
    18 
    18 SOURCES = ['zstd/%s' % p for p in (
    19 SOURCES = ['zstd/%s' % p for p in (
    19     'common/entropy_common.c',
    20     'common/entropy_common.c',
    20     'common/error_private.c',
    21     'common/error_private.c',
    21     'common/fse_decompress.c',
    22     'common/fse_decompress.c',
       
    23     'common/pool.c',
       
    24     'common/threading.c',
    22     'common/xxhash.c',
    25     'common/xxhash.c',
    23     'common/zstd_common.c',
    26     'common/zstd_common.c',
    24     'compress/fse_compress.c',
    27     'compress/fse_compress.c',
    25     'compress/huf_compress.c',
    28     'compress/huf_compress.c',
    26     'compress/zstd_compress.c',
    29     'compress/zstd_compress.c',
    27     'decompress/huf_decompress.c',
    30     'decompress/huf_decompress.c',
    28     'decompress/zstd_decompress.c',
    31     'decompress/zstd_decompress.c',
       
    32     'dictBuilder/cover.c',
    29     'dictBuilder/divsufsort.c',
    33     'dictBuilder/divsufsort.c',
    30     'dictBuilder/zdict.c',
    34     'dictBuilder/zdict.c',
       
    35 )]
       
    36 
       
    37 HEADERS = [os.path.join(HERE, 'zstd', *p) for p in (
       
    38     ('zstd.h',),
       
    39     ('common', 'pool.h'),
       
    40     ('dictBuilder', 'zdict.h'),
    31 )]
    41 )]
    32 
    42 
    33 INCLUDE_DIRS = [os.path.join(HERE, d) for d in (
    43 INCLUDE_DIRS = [os.path.join(HERE, d) for d in (
    34     'zstd',
    44     'zstd',
    35     'zstd/common',
    45     'zstd/common',
    51 if compiler.compiler_type == 'unix':
    61 if compiler.compiler_type == 'unix':
    52     args = list(compiler.executables['compiler'])
    62     args = list(compiler.executables['compiler'])
    53     args.extend([
    63     args.extend([
    54         '-E',
    64         '-E',
    55         '-DZSTD_STATIC_LINKING_ONLY',
    65         '-DZSTD_STATIC_LINKING_ONLY',
       
    66         '-DZDICT_STATIC_LINKING_ONLY',
    56     ])
    67     ])
    57 elif compiler.compiler_type == 'msvc':
    68 elif compiler.compiler_type == 'msvc':
    58     args = [compiler.cc]
    69     args = [compiler.cc]
    59     args.extend([
    70     args.extend([
    60         '/EP',
    71         '/EP',
    61         '/DZSTD_STATIC_LINKING_ONLY',
    72         '/DZSTD_STATIC_LINKING_ONLY',
       
    73         '/DZDICT_STATIC_LINKING_ONLY',
    62     ])
    74     ])
    63 else:
    75 else:
    64     raise Exception('unsupported compiler type: %s' % compiler.compiler_type)
    76     raise Exception('unsupported compiler type: %s' % compiler.compiler_type)
    65 
    77 
    66 # zstd.h includes <stddef.h>, which is also included by cffi's boilerplate.
    78 def preprocess(path):
    67 # This can lead to duplicate declarations. So we strip this include from the
    79     # zstd.h includes <stddef.h>, which is also included by cffi's boilerplate.
    68 # preprocessor invocation.
    80     # This can lead to duplicate declarations. So we strip this include from the
       
    81     # preprocessor invocation.
       
    82     with open(path, 'rb') as fh:
       
    83         lines = [l for l in fh if not l.startswith(b'#include <stddef.h>')]
    69 
    84 
    70 with open(os.path.join(HERE, 'zstd', 'zstd.h'), 'rb') as fh:
    85     fd, input_file = tempfile.mkstemp(suffix='.h')
    71     lines = [l for l in fh if not l.startswith(b'#include <stddef.h>')]
    86     os.write(fd, b''.join(lines))
       
    87     os.close(fd)
    72 
    88 
    73 fd, input_file = tempfile.mkstemp(suffix='.h')
    89     try:
    74 os.write(fd, b''.join(lines))
    90         process = subprocess.Popen(args + [input_file], stdout=subprocess.PIPE)
    75 os.close(fd)
    91         output = process.communicate()[0]
       
    92         ret = process.poll()
       
    93         if ret:
       
    94             raise Exception('preprocessor exited with error')
    76 
    95 
    77 args.append(input_file)
    96         return output
       
    97     finally:
       
    98         os.unlink(input_file)
    78 
    99 
    79 try:
       
    80     process = subprocess.Popen(args, stdout=subprocess.PIPE)
       
    81     output = process.communicate()[0]
       
    82     ret = process.poll()
       
    83     if ret:
       
    84         raise Exception('preprocessor exited with error')
       
    85 finally:
       
    86     os.unlink(input_file)
       
    87 
   100 
    88 def normalize_output():
   101 def normalize_output(output):
    89     lines = []
   102     lines = []
    90     for line in output.splitlines():
   103     for line in output.splitlines():
    91         # CFFI's parser doesn't like __attribute__ on UNIX compilers.
   104         # CFFI's parser doesn't like __attribute__ on UNIX compilers.
    92         if line.startswith(b'__attribute__ ((visibility ("default"))) '):
   105         if line.startswith(b'__attribute__ ((visibility ("default"))) '):
    93             line = line[len(b'__attribute__ ((visibility ("default"))) '):]
   106             line = line[len(b'__attribute__ ((visibility ("default"))) '):]
    94 
   107 
       
   108         if line.startswith(b'__attribute__((deprecated('):
       
   109             continue
       
   110         elif b'__declspec(deprecated(' in line:
       
   111             continue
       
   112 
    95         lines.append(line)
   113         lines.append(line)
    96 
   114 
    97     return b'\n'.join(lines)
   115     return b'\n'.join(lines)
    98 
   116 
       
   117 
    99 ffi = cffi.FFI()
   118 ffi = cffi.FFI()
   100 ffi.set_source('_zstd_cffi', '''
   119 ffi.set_source('_zstd_cffi', '''
       
   120 #include "mem.h"
   101 #define ZSTD_STATIC_LINKING_ONLY
   121 #define ZSTD_STATIC_LINKING_ONLY
   102 #include "zstd.h"
   122 #include "zstd.h"
       
   123 #define ZDICT_STATIC_LINKING_ONLY
       
   124 #include "pool.h"
       
   125 #include "zdict.h"
   103 ''', sources=SOURCES, include_dirs=INCLUDE_DIRS)
   126 ''', sources=SOURCES, include_dirs=INCLUDE_DIRS)
   104 
   127 
   105 ffi.cdef(normalize_output().decode('latin1'))
   128 DEFINE = re.compile(b'^\\#define ([a-zA-Z0-9_]+) ')
       
   129 
       
   130 sources = []
       
   131 
       
   132 for header in HEADERS:
       
   133     preprocessed = preprocess(header)
       
   134     sources.append(normalize_output(preprocessed))
       
   135 
       
   136     # Do another pass over source and find constants that were preprocessed
       
   137     # away.
       
   138     with open(header, 'rb') as fh:
       
   139         for line in fh:
       
   140             line = line.strip()
       
   141             m = DEFINE.match(line)
       
   142             if not m:
       
   143                 continue
       
   144 
       
   145             # The parser doesn't like some constants with complex values.
       
   146             if m.group(1) in (b'ZSTD_LIB_VERSION', b'ZSTD_VERSION_STRING'):
       
   147                 continue
       
   148 
       
   149             sources.append(m.group(0) + b' ...')
       
   150 
       
   151 ffi.cdef(u'\n'.join(s.decode('latin1') for s in sources))
   106 
   152 
   107 if __name__ == '__main__':
   153 if __name__ == '__main__':
   108     ffi.compile()
   154     ffi.compile()