Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make python bindings article partially work on win #157

Merged
merged 16 commits into from
Nov 8, 2020
12 changes: 12 additions & 0 deletions python-bindings/overview_article/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Real Python - Python Bindings Sample Code Repo

This is the repo to accompany the [Python Bindings](https://realpython.com/python-bindings-overview/) article.

To be able to run the code, you must first install the requirements:

```console
$ python -m pip install -r requirements.txt
```
This should be done inside a virtual environment.

Once that is installed, you can use the invoke tool mentioned in the article to build and run the tests. See the tasks.py file or run invoke --list to get more details.
1 change: 1 addition & 0 deletions python-bindings/overview_article/cffi_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# IDE might complain with "no module found" here, even when it exists
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because it doesn't exist when you start out. :) CFFI creates the module when you run the build.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but my IDE, PyCharm, didn't want to acknowledge it, even after making it. I actually lost a lot of time figuring out what the problem is, since I didn't even bother to try and run it, lol

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's odd. I wonder what pycharm is doing, but not enough to actually go find out, you know. :)

import cffi_example

if __name__ == "__main__":
Expand Down
8 changes: 7 additions & 1 deletion python-bindings/overview_article/cmult.h
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
float cmult(int int_param, float float_param);
#ifdef _MSC_VER
#define EXPORT_SYMBOL __declspec(dllexport)
#else
#define EXPORT_SYMBOL
#endif

EXPORT_SYMBOL float cmult(int int_param, float float_param);
9 changes: 5 additions & 4 deletions python-bindings/overview_article/ctypes_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env python
""" Simple examples of calling C functions through ctypes module. """
import ctypes
import pathlib

import sys

if __name__ == "__main__":
# Load the shared library into c types.
libname = pathlib.Path().absolute() / "libcmult.so"
c_lib = ctypes.CDLL(libname)
if sys.platform.startswith("win"):
c_lib = ctypes.CDLL("cmult.dll")
else:
c_lib = ctypes.CDLL("libcmult.so")

# Sample data for our call:
x, y = 6, 2.3
Expand Down
75 changes: 60 additions & 15 deletions python-bindings/overview_article/tasks.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,93 @@
""" Task definitions for invoke command line utility for python bindings
overview article. """
overview article.
"""
import cffi
import invoke
import pathlib
import sys
import os
import shutil
import re
import glob

on_win = sys.platform.startswith("win")


@invoke.task
def clean(c):
""" Remove any built objects """
for pattern in ["*.o", "*.so", "cffi_example* cython_wrapper.cpp"]:
c.run("rm -rf {}".format(pattern))
for file_pattern in (
"*.o",
"*.so",
"*.obj",
"*.dll",
"*.exp",
"*.lib",
"*.pyd",
"cffi_example*", # Is this a dir?
"cython_wrapper.cpp",
):
for file in glob.glob(file_pattern):
os.remove(file)
for dir_pattern in "Release":
for dir in glob.glob(dir_pattern):
shutil.rmtree(dir)


def print_banner(msg):
print("==================================================")
print("= {} ".format(msg))


@invoke.task
def build_cmult(c):
@invoke.task()
def build_cmult(c, path=None):
""" Build the shared library for the sample C code """
print_banner("Building C Library")
invoke.run("gcc -c -Wall -Werror -fpic cmult.c -I /usr/include/python3.7")
invoke.run("gcc -shared -o libcmult.so cmult.o")
print("* Complete")
# Moving this type hint into signature causes an error (???)
c: invoke.Context
if on_win:
if not path:
print("Path is missing")
else:
# Using c.cd didn't work with paths that have spaces :/
path = f'"{path}vcvars32.bat" x86' # Enter the VS venv
path += f'&& cd "{os.getcwd()}"' # Change to current dir
path += "&& cl /LD cmult.c" # Compile
# Uncomment line below, to suppress stdout
# path = path.replace("&&", " >nul &&") + " >nul"
c.run(path)
else:
print_banner("Building C Library")
cmd = "gcc -c -Wall -Werror -fpic cmult.c -I /usr/include/python3.7"
invoke.run(cmd)
invoke.run("gcc -shared -o libcmult.so cmult.o")
print("* Complete")


@invoke.task(build_cmult)
@invoke.task()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the build on windows taking significant time? If not, I'd prefer to leave the dependency here. It makes the individual targets a bit more fool-proof.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't. On windows, the problem is that it'll only build if we supply the argument pointing to visual studio.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh. I get it. That's fine. I missed that point.

def test_ctypes(c):
""" Run the script to test ctypes """
print_banner("Testing ctypes Module")
invoke.run("python3 ctypes_test.py", pty=True)
# pty and python3 didn't work for me (win).
if on_win:
invoke.run("python ctypes_test.py")
else:
invoke.run("python3 ctypes_test.py", pty=True)


@invoke.task(build_cmult)
@invoke.task()
def build_cffi(c):
""" Build the CFFI Python bindings """
print_banner("Building CFFI Module")
ffi = cffi.FFI()

this_dir = pathlib.Path().absolute()
this_dir = pathlib.Path().resolve()
h_file_name = this_dir / "cmult.h"
with open(h_file_name) as h_file:
ffi.cdef(h_file.read())
# cffi does not like our preprocessor directives, so we remove them
lns = h_file.read().splitlines()
flt = filter(lambda ln: not re.match(r" *#", ln), lns)
flt = map(lambda ln: ln.replace("EXPORT_SYMBOL ", ""), flt)
ffi.cdef(str("\n").join(flt))

ffi.set_source(
"cffi_example",
Expand All @@ -66,7 +111,7 @@ def build_cffi(c):
def test_cffi(c):
""" Run the script to test CFFI """
print_banner("Testing CFFI Module")
invoke.run("python3 cffi_test.py", pty=True)
invoke.run("python cffi_test.py", pty=not on_win)


@invoke.task()
Expand Down