diff --git a/Makefile b/Makefile index fb978eb8..34c47b9f 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/java/Makefile b/java/Makefile index 0fadca6d..08ed5eb1 100644 --- a/java/Makefile +++ b/java/Makefile @@ -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 diff --git a/python/README.md b/python/README.md new file mode 100644 index 00000000..fe4ec132 --- /dev/null +++ b/python/README.md @@ -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) \ No newline at end of file diff --git a/python/enry.py b/python/enry.py new file mode 100644 index 00000000..cd388ba2 --- /dev/null +++ b/python/enry.py @@ -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() diff --git a/python/enry_build.py b/python/enry_build.py new file mode 100644 index 00000000..96ca8f64 --- /dev/null +++ b/python/enry_build.py @@ -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) diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 00000000..28dbbb14 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,4 @@ +cffi==1.12.3 +Click==7.0 +pycparser==2.19 +yapf==0.27.0