| | import distutils.ccompiler |
| | import distutils.log |
| | import distutils.sysconfig |
| | import os |
| | import platform |
| | import re |
| | import shutil |
| | import subprocess |
| | import tempfile |
| | import textwrap |
| |
|
| | import cffi |
| | import pkgconfig |
| |
|
| |
|
| | def local_build_flags(projdir, target): |
| | """Construct build flags for building against a checkout. |
| | |
| | :param projdir: The root directory of the deltachat-core-rust project. |
| | :param target: The rust build target, `debug` or `release`. |
| | """ |
| | flags = {} |
| | if platform.system() == "Darwin": |
| | flags["libraries"] = ["resolv", "dl"] |
| | flags["extra_link_args"] = [ |
| | "-framework", |
| | "CoreFoundation", |
| | "-framework", |
| | "CoreServices", |
| | "-framework", |
| | "Security", |
| | ] |
| | elif platform.system() == "Linux": |
| | flags["libraries"] = ["rt", "dl", "m"] |
| | flags["extra_link_args"] = [] |
| | else: |
| | raise NotImplementedError("Compilation not supported yet on Windows, can you help?") |
| | target_dir = os.environ.get("CARGO_TARGET_DIR") |
| | if target_dir is None: |
| | target_dir = os.path.join(projdir, "target") |
| | flags["extra_objects"] = [os.path.join(target_dir, target, "libdeltachat.a")] |
| | assert os.path.exists(flags["extra_objects"][0]), flags["extra_objects"] |
| | flags["include_dirs"] = [os.path.join(projdir, "deltachat-ffi")] |
| | return flags |
| |
|
| |
|
| | def system_build_flags(): |
| | """Construct build flags for building against an installed libdeltachat.""" |
| | return pkgconfig.parse("deltachat") |
| |
|
| |
|
| | def extract_functions(flags): |
| | """Extract the function definitions from deltachat.h. |
| | |
| | This creates a .h file with a single `#include <deltachat.h>` line |
| | in it. It then runs the C preprocessor to create an output file |
| | which contains all function definitions found in `deltachat.h`. |
| | """ |
| | distutils.log.set_verbosity(distutils.log.INFO) |
| | cc = distutils.ccompiler.new_compiler(force=True) |
| | distutils.sysconfig.customize_compiler(cc) |
| | tmpdir = tempfile.mkdtemp() |
| | try: |
| | src_name = os.path.join(tmpdir, "include.h") |
| | dst_name = os.path.join(tmpdir, "expanded.h") |
| | with open(src_name, "w") as src_fp: |
| | src_fp.write("#include <deltachat.h>") |
| | cc.preprocess( |
| | source=src_name, |
| | output_file=dst_name, |
| | include_dirs=flags["include_dirs"], |
| | macros=[("PY_CFFI", "1")], |
| | ) |
| | with open(dst_name, "r") as dst_fp: |
| | return dst_fp.read() |
| | finally: |
| | shutil.rmtree(tmpdir) |
| |
|
| |
|
| | def find_header(flags): |
| | """Use the compiler to find the deltachat.h header location. |
| | |
| | This uses a small utility in deltachat.h to find the location of |
| | the header file location. |
| | """ |
| | distutils.log.set_verbosity(distutils.log.INFO) |
| | cc = distutils.ccompiler.new_compiler(force=True) |
| | distutils.sysconfig.customize_compiler(cc) |
| | tmpdir = tempfile.mkdtemp() |
| | try: |
| | src_name = os.path.join(tmpdir, "where.c") |
| | obj_name = os.path.join(tmpdir, "where.o") |
| | dst_name = os.path.join(tmpdir, "where") |
| | with open(src_name, "w") as src_fp: |
| | src_fp.write( |
| | textwrap.dedent( |
| | """ |
| | #include <stdio.h> |
| | #include <deltachat.h> |
| | |
| | int main(void) { |
| | printf("%s", _dc_header_file_location()); |
| | return 0; |
| | } |
| | """, |
| | ), |
| | ) |
| | cwd = os.getcwd() |
| | try: |
| | os.chdir(tmpdir) |
| | cc.compile( |
| | sources=["where.c"], |
| | include_dirs=flags["include_dirs"], |
| | macros=[("PY_CFFI_INC", "1")], |
| | ) |
| | finally: |
| | os.chdir(cwd) |
| | cc.link_executable(objects=[obj_name], output_progname="where", output_dir=tmpdir) |
| | return subprocess.check_output(dst_name) |
| | finally: |
| | shutil.rmtree(tmpdir) |
| |
|
| |
|
| | def extract_defines(flags): |
| | """Extract the required #DEFINEs from deltachat.h. |
| | |
| | Since #DEFINEs are interpreted by the C preprocessor we can not |
| | use the compiler to extract these and need to parse the header |
| | file ourselves. |
| | |
| | The defines are returned in a string that can be passed to CFFIs |
| | cdef() method. |
| | """ |
| | header = find_header(flags) |
| | defines_re = re.compile( |
| | r""" |
| | \#define\s+ # The start of a define. |
| | ( # Begin capturing group which captures the define name. |
| | (?: # A nested group which is not captured, this allows us |
| | # to build the list of prefixes to extract without |
| | # creation another capture group. |
| | DC_EVENT |
| | | DC_QR |
| | | DC_MSG |
| | | DC_LP |
| | | DC_EMPTY |
| | | DC_CERTCK |
| | | DC_STATE |
| | | DC_STR |
| | | DC_CONTACT_ID |
| | | DC_GCL |
| | | DC_GCM |
| | | DC_SOCKET |
| | | DC_CHAT |
| | | DC_PROVIDER |
| | | DC_KEY_GEN |
| | | DC_IMEX |
| | | DC_CONNECTIVITY |
| | | DC_DOWNLOAD |
| | ) # End of prefix matching |
| | _[\w_]+ # Match the suffix, e.g. _RSA2048 in DC_KEY_GEN_RSA2048 |
| | ) # Close the capturing group, this contains |
| | # the entire name e.g. DC_MSG_TEXT. |
| | \s+\S+ # Ensure there is whitespace followed by a value. |
| | """, |
| | re.VERBOSE, |
| | ) |
| | defines = [] |
| | with open(header) as fp: |
| | for line in fp: |
| | match = defines_re.match(line) |
| | if match: |
| | defines.append(match.group(1)) |
| | return "\n".join(f"#define {d} ..." for d in defines) |
| |
|
| |
|
| | def ffibuilder(): |
| | projdir = os.environ.get("DCC_RS_DEV") |
| | if projdir: |
| | target = os.environ.get("DCC_RS_TARGET", "release") |
| | flags = local_build_flags(projdir, target) |
| | else: |
| | flags = system_build_flags() |
| | builder = cffi.FFI() |
| | builder.set_source( |
| | "deltachat.capi", |
| | """ |
| | #include <deltachat.h> |
| | int dc_event_has_string_data(int e) |
| | { |
| | return DC_EVENT_DATA2_IS_STRING(e); |
| | } |
| | """, |
| | **flags, |
| | ) |
| | builder.cdef( |
| | """ |
| | typedef int... time_t; |
| | void free(void *ptr); |
| | extern int dc_event_has_string_data(int); |
| | """, |
| | ) |
| | function_defs = extract_functions(flags) |
| | defines = extract_defines(flags) |
| | builder.cdef(function_defs) |
| | builder.cdef(defines) |
| | return builder |
| |
|
| |
|
| | if __name__ == "__main__": |
| | import os.path |
| |
|
| | pkgdir = os.path.join(os.path.dirname(__file__), "..") |
| | builder = ffibuilder() |
| | builder.compile(tmpdir=pkgdir, verbose=True) |
| |
|