Skip to content

Commit a4c166c

Browse files
authored
Merge pull request #245 from bzz/initial-cffi-python-bindings
Initial cffi bindings for python
2 parents 31878fe + 6cf5bf2 commit a4c166c

File tree

6 files changed

+161
-0
lines changed

6 files changed

+161
-0
lines changed

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ LINUX_DIR=$(RESOURCES_DIR)/linux-x86-64
2929
LINUX_SHARED_LIB=$(LINUX_DIR)/libenry.so
3030
DARWIN_DIR=$(RESOURCES_DIR)/darwin
3131
DARWIN_SHARED_LIB=$(DARWIN_DIR)/libenry.dylib
32+
STATIC_LIB=$(RESOURCES_DIR)/libenry.a
3233
HEADER_FILE=libenry.h
3334
NATIVE_LIB=./shared/enry.go
3435

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

83+
84+
static: $(STATIC_LIB)
85+
86+
$(STATIC_LIB):
87+
CGO_ENABLED=1 go build -buildmode=c-archive -o $(STATIC_LIB) $(NATIVE_LIB)
88+
8289
.PHONY: benchmarks benchmarks-samples benchmarks-slow

java/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,6 @@ package:
5959
clean:
6060
rm -rf $(JAR)
6161
rm -rf $(RESOURCES_DIR)
62+
rm -rf $(JNAERATOR_JAR)
6263

64+
.PHONY: test package clean linux-shared darwin-shared os-shared-lib

python/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Python bindings for enry
2+
3+
Python bingings thoug cFFI (API, out-of-line) for calling enr Go functions though CGo wrapper.
4+
5+
## Build
6+
7+
```
8+
$ make static
9+
$ python enry_build.py
10+
```
11+
12+
Will build static library for Cgo wrapper `libenry`, then generate and build `enry.c`
13+
- a CPython extension that
14+
15+
## Run
16+
17+
Example for single exposed API function is provided.
18+
19+
```
20+
$ python enry.py
21+
```
22+
23+
## TODOs
24+
- [ ] try ABI mode, to aviod dependency on C compiler on install (+perf test?)
25+
- [ ] ready `libenry.h` and generate `ffibuilder.cdef` content
26+
- [ ] helpers for sending/recieving Go slices to C
27+
- [ ] cover the rest of enry API
28+
- [ ] add `setup.py`
29+
- [ ] build/release automation on CI (publish on pypi)

python/enry.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Python library calling enry Go implementation trough cFFI (API, out-of-line) and Cgo.
3+
"""
4+
5+
from _c_enry import ffi, lib
6+
7+
## Helpers
8+
9+
10+
def go_str_to_py(go_str):
11+
str_len = go_str.n
12+
if str_len > 0:
13+
return ffi.unpack(go_str.p, go_str.n).decode()
14+
return ""
15+
16+
17+
def py_str_to_go(py_str):
18+
str_bytes = py_str.encode()
19+
c_str = ffi.new("char[]", str_bytes)
20+
go_str = ffi.new("_GoString_ *", [c_str, len(str_bytes)])
21+
return go_str[0]
22+
23+
24+
def go_bool_to_py(go_bool):
25+
return go_bool == 1
26+
27+
28+
## API
29+
30+
31+
def language_by_extension(filename: str) -> str:
32+
fName = py_str_to_go(filename)
33+
guess = lib.GetLanguageByExtension(fName)
34+
lang = go_str_to_py(guess.r0)
35+
return lang
36+
37+
38+
def language_by_filename(filename: str) -> str:
39+
fName = py_str_to_go(filename)
40+
guess = lib.GetLanguageByFilename(fName)
41+
lang = go_str_to_py(guess.r0)
42+
return lang
43+
44+
45+
def is_vendor(filename: str) -> bool:
46+
fName = py_str_to_go(filename)
47+
guess = lib.IsVendor(fName)
48+
return go_bool_to_py(guess)
49+
50+
51+
## Tests
52+
53+
54+
def main():
55+
files = [
56+
"Parse.hs", "some.cpp", "and.go", "type.h", ".bashrc", ".gitignore"
57+
]
58+
59+
print("strategy: extension")
60+
for filename in files:
61+
lang = language_by_extension(filename)
62+
print("file: {:10s} language: '{}'".format(filename, lang))
63+
64+
print("\nstrategy: filename")
65+
for filename in files:
66+
lang = language_by_filename(filename)
67+
print("file: {:10s} language: '{}'".format(filename, lang))
68+
69+
print("\ncheck: is vendor?")
70+
for filename in files:
71+
vendor = is_vendor(filename)
72+
print("file: {:10s} vendor: '{}'".format(filename, vendor))
73+
74+
75+
if __name__ == "__main__":
76+
main()

python/enry_build.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from cffi import FFI
2+
ffibuilder = FFI()
3+
4+
# cdef() expects a single string declaring the C types, functions and
5+
# globals needed to use the shared object. It must be in valid C syntax.
6+
ffibuilder.cdef("""
7+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
8+
typedef _GoString_ GoString;
9+
typedef unsigned char GoUint8;
10+
11+
/* Return type for GetLanguageByExtension */
12+
struct GetLanguageByExtension_return {
13+
GoString r0; /* language */
14+
GoUint8 r1; /* safe */
15+
};
16+
17+
extern struct GetLanguageByExtension_return GetLanguageByExtension(GoString p0);
18+
19+
/* Return type for GetLanguageByFilename */
20+
struct GetLanguageByFilename_return {
21+
GoString r0; /* language */
22+
GoUint8 r1; /* safe */
23+
};
24+
25+
extern struct GetLanguageByFilename_return GetLanguageByFilename(GoString p0);
26+
27+
extern GoUint8 IsVendor(GoString p0);
28+
""")
29+
30+
# set_source() gives the name of the python extension module to
31+
# produce, and some C source code as a string. This C code needs
32+
# to make the declarated functions, types and globals available,
33+
# so it is often just the "#include".
34+
ffibuilder.set_source("_c_enry",
35+
"""
36+
#include "../.shared/libenry.h" // the C header of the library
37+
""",
38+
libraries=['enry'],
39+
library_dirs=['../.shared'
40+
]) # library name, for the linker
41+
42+
if __name__ == "__main__":
43+
ffibuilder.compile(verbose=True)

python/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
cffi==1.12.3
2+
Click==7.0
3+
pycparser==2.19
4+
yapf==0.27.0

0 commit comments

Comments
 (0)