Skip to content

Commit

Permalink
Merge pull request #245 from bzz/initial-cffi-python-bindings
Browse files Browse the repository at this point in the history
Initial cffi bindings for python
  • Loading branch information
bzz authored Oct 28, 2019
2 parents 31878fe + 6cf5bf2 commit a4c166c
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ LINUX_DIR=$(RESOURCES_DIR)/linux-x86-64
LINUX_SHARED_LIB=$(LINUX_DIR)/libenry.so
DARWIN_DIR=$(RESOURCES_DIR)/darwin
DARWIN_SHARED_LIB=$(DARWIN_DIR)/libenry.dylib
STATIC_LIB=$(RESOURCES_DIR)/libenry.a
HEADER_FILE=libenry.h
NATIVE_LIB=./shared/enry.go

Expand Down Expand Up @@ -79,4 +80,10 @@ $(LINUX_SHARED_LIB):
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -o $(LINUX_SHARED_LIB) $(NATIVE_LIB) && \
mv $(LINUX_DIR)/$(HEADER_FILE) $(RESOURCES_DIR)/$(HEADER_FILE)


static: $(STATIC_LIB)

$(STATIC_LIB):
CGO_ENABLED=1 go build -buildmode=c-archive -o $(STATIC_LIB) $(NATIVE_LIB)

.PHONY: benchmarks benchmarks-samples benchmarks-slow
2 changes: 2 additions & 0 deletions java/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ package:
clean:
rm -rf $(JAR)
rm -rf $(RESOURCES_DIR)
rm -rf $(JNAERATOR_JAR)

.PHONY: test package clean linux-shared darwin-shared os-shared-lib
29 changes: 29 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Python bindings for enry

Python bingings thoug cFFI (API, out-of-line) for calling enr Go functions though CGo wrapper.

## Build

```
$ make static
$ python enry_build.py
```

Will build static library for Cgo wrapper `libenry`, then generate and build `enry.c`
- a CPython extension that

## Run

Example for single exposed API function is provided.

```
$ python enry.py
```

## TODOs
- [ ] try ABI mode, to aviod dependency on C compiler on install (+perf test?)
- [ ] ready `libenry.h` and generate `ffibuilder.cdef` content
- [ ] helpers for sending/recieving Go slices to C
- [ ] cover the rest of enry API
- [ ] add `setup.py`
- [ ] build/release automation on CI (publish on pypi)
76 changes: 76 additions & 0 deletions python/enry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Python library calling enry Go implementation trough cFFI (API, out-of-line) and Cgo.
"""

from _c_enry import ffi, lib

## Helpers


def go_str_to_py(go_str):
str_len = go_str.n
if str_len > 0:
return ffi.unpack(go_str.p, go_str.n).decode()
return ""


def py_str_to_go(py_str):
str_bytes = py_str.encode()
c_str = ffi.new("char[]", str_bytes)
go_str = ffi.new("_GoString_ *", [c_str, len(str_bytes)])
return go_str[0]


def go_bool_to_py(go_bool):
return go_bool == 1


## API


def language_by_extension(filename: str) -> str:
fName = py_str_to_go(filename)
guess = lib.GetLanguageByExtension(fName)
lang = go_str_to_py(guess.r0)
return lang


def language_by_filename(filename: str) -> str:
fName = py_str_to_go(filename)
guess = lib.GetLanguageByFilename(fName)
lang = go_str_to_py(guess.r0)
return lang


def is_vendor(filename: str) -> bool:
fName = py_str_to_go(filename)
guess = lib.IsVendor(fName)
return go_bool_to_py(guess)


## Tests


def main():
files = [
"Parse.hs", "some.cpp", "and.go", "type.h", ".bashrc", ".gitignore"
]

print("strategy: extension")
for filename in files:
lang = language_by_extension(filename)
print("file: {:10s} language: '{}'".format(filename, lang))

print("\nstrategy: filename")
for filename in files:
lang = language_by_filename(filename)
print("file: {:10s} language: '{}'".format(filename, lang))

print("\ncheck: is vendor?")
for filename in files:
vendor = is_vendor(filename)
print("file: {:10s} vendor: '{}'".format(filename, vendor))


if __name__ == "__main__":
main()
43 changes: 43 additions & 0 deletions python/enry_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from cffi import FFI
ffibuilder = FFI()

# cdef() expects a single string declaring the C types, functions and
# globals needed to use the shared object. It must be in valid C syntax.
ffibuilder.cdef("""
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
typedef _GoString_ GoString;
typedef unsigned char GoUint8;
/* Return type for GetLanguageByExtension */
struct GetLanguageByExtension_return {
GoString r0; /* language */
GoUint8 r1; /* safe */
};
extern struct GetLanguageByExtension_return GetLanguageByExtension(GoString p0);
/* Return type for GetLanguageByFilename */
struct GetLanguageByFilename_return {
GoString r0; /* language */
GoUint8 r1; /* safe */
};
extern struct GetLanguageByFilename_return GetLanguageByFilename(GoString p0);
extern GoUint8 IsVendor(GoString p0);
""")

# set_source() gives the name of the python extension module to
# produce, and some C source code as a string. This C code needs
# to make the declarated functions, types and globals available,
# so it is often just the "#include".
ffibuilder.set_source("_c_enry",
"""
#include "../.shared/libenry.h" // the C header of the library
""",
libraries=['enry'],
library_dirs=['../.shared'
]) # library name, for the linker

if __name__ == "__main__":
ffibuilder.compile(verbose=True)
4 changes: 4 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cffi==1.12.3
Click==7.0
pycparser==2.19
yapf==0.27.0

0 comments on commit a4c166c

Please sign in to comment.