diff --git a/.gitignore b/.gitignore index fd2939f..41318a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ # Build directory build docs/_build +tests/bin # Python *.egg-info __pycache__ +.eggs # VS Code .vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 744efea..2e9477f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8.12) project(python_cpp_example) -SET(SOURCE_DIR "python_cpp_example") +SET(SOURCE_DIR "src/python_cpp_example") # Tell cmake that headers are in alse in source_dir include_directories(${SOURCE_DIR}) SET(SOURCES "${SOURCE_DIR}/math.cpp") diff --git a/README.md b/README.md index edc90a7..a218666 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # python_cpp_example + This repository contains an example Python module which wraps C++ code. The code presented here was designed to meet four requirements: 1. Python bindings for C++ code (using [`pybind11`](http://pybind11.readthedocs.io/en/stable/index.html) and built with [CMake](http://cmake.org)) @@ -6,7 +7,9 @@ This repository contains an example Python module which wraps C++ code. The code 3. Unit tests for Python code (using `unittest`) 4. A `setuptools` setup.py script for building, installation, and testing -Please see the [blog post that accompanies this repository](http://www.benjack.io/2017/06/12/python-cpp-tests.html) for more information. +Please see the [blog post that accompanies this repository](http://www.benjack.io/2018/02/02/python-cpp-revisited.html) for more information. + +**NOTE**: If you'd like to see the version of the repository that corresponds to my [original June 2017 blog post](http://www.benjack.io/2017/06/12/python-cpp-tests.html), go to [this release](https://github.com/benjaminjack/python_cpp_example/tree/v0.1). However, I no longer recommend using the repository structure from this old release. # Installation diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index b4ff8ab..54753b8 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +#! /usr/bin/env python3 + import os import re import sys @@ -6,9 +8,10 @@ import subprocess from distutils.version import LooseVersion -from setuptools import setup, Extension +from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext from setuptools.command.test import test as TestCommand +from shutil import copyfile, copymode class CMakeExtension(Extension): @@ -65,39 +68,41 @@ def build_extension(self, ext): cwd=self.build_temp, env=env) subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + # Copy *_test file to tests directory + test_bin = os.path.join(self.build_temp, 'python_cpp_example_test') + self.copy_test_file(test_bin) print() # Add an empty line for cleaner output -class CatchTestCommand(TestCommand): - """ - A custom test runner to execute both python unittest tests and C++ Catch- - lib tests. - """ - def distutils_dir_name(self, dname): - """Returns the name of a distutils build directory""" - dir_name = "{dirname}.{platform}-{version[0]}.{version[1]}" - return dir_name.format(dirname=dname, - platform=sysconfig.get_platform(), - version=sys.version_info) - - def run(self): - # Run python tests - super(CatchTestCommand, self).run() - print("\nPython tests complete, now running C++ tests...\n") - # Run catch tests - subprocess.call(['./*_test'], - cwd=os.path.join('build', - self.distutils_dir_name('temp')), - shell=True) + def copy_test_file(self, src_file): + ''' + Copy ``src_file`` to ``dest_file`` ensuring parent directory exists. + By default, message like `creating directory /path/to/package` and + `copying directory /src/path/to/package -> path/to/package` are displayed on standard output. Adapted from scikit-build. + ''' + # Create directory if needed + dest_dir = os.path.join(os.path.dirname( + os.path.abspath(__file__)), 'tests', 'bin') + if dest_dir != "" and not os.path.exists(dest_dir): + print("creating directory {}".format(dest_dir)) + os.makedirs(dest_dir) + # Copy file + dest_file = os.path.join(dest_dir, os.path.basename(src_file)) + print("copying {} -> {}".format(src_file, dest_file)) + copyfile(src_file, dest_file) + copymode(src_file, dest_file) setup( name='python_cpp_example', - version='0.1', + version='0.2', author='Benjamin Jack', author_email='benjamin.r.jack@gmail.com', description='A hybrid Python/C++ test project', long_description='', - ext_modules=[CMakeExtension('python_cpp_example')], - cmdclass=dict(build_ext=CMakeBuild, test=CatchTestCommand), + packages=find_packages('src'), + package_dir={'':'src'}, + ext_modules=[CMakeExtension('python_cpp_example/python_cpp_example')], + cmdclass=dict(build_ext=CMakeBuild), + test_suite='tests', zip_safe=False, ) diff --git a/src/python_cpp_example/__init__.py b/src/python_cpp_example/__init__.py new file mode 100644 index 0000000..c221367 --- /dev/null +++ b/src/python_cpp_example/__init__.py @@ -0,0 +1 @@ +from .python_cpp_example import * diff --git a/python_cpp_example/bindings.cpp b/src/python_cpp_example/bindings.cpp similarity index 74% rename from python_cpp_example/bindings.cpp rename to src/python_cpp_example/bindings.cpp index c08188f..d76eb89 100644 --- a/python_cpp_example/bindings.cpp +++ b/src/python_cpp_example/bindings.cpp @@ -3,9 +3,8 @@ namespace py = pybind11; -PYBIND11_PLUGIN(python_cpp_example) -{ - py::module m("python_cpp_example", R"doc( +PYBIND11_PLUGIN(python_cpp_example) { + py::module m("python_cpp_example", R"doc( Python module ----------------------- .. currentmodule:: python_cpp_example @@ -16,17 +15,17 @@ PYBIND11_PLUGIN(python_cpp_example) subtract )doc"); - m.def("add", &add, R"doc( + m.def("add", &add, R"doc( Add two numbers Some other information about the add function. )doc"); - m.def("subtract", &subtract, R"doc( + m.def("subtract", &subtract, R"doc( Subtract two numbers Some other information about the subtract function. )doc"); - return m.ptr(); + return m.ptr(); } \ No newline at end of file diff --git a/src/python_cpp_example/hello.py b/src/python_cpp_example/hello.py new file mode 100644 index 0000000..caf5928 --- /dev/null +++ b/src/python_cpp_example/hello.py @@ -0,0 +1,3 @@ + +def say_hello(): + print("Hello world!") \ No newline at end of file diff --git a/python_cpp_example/math.cpp b/src/python_cpp_example/math.cpp similarity index 100% rename from python_cpp_example/math.cpp rename to src/python_cpp_example/math.cpp diff --git a/python_cpp_example/math.hpp b/src/python_cpp_example/math.hpp similarity index 100% rename from python_cpp_example/math.hpp rename to src/python_cpp_example/math.hpp diff --git a/tests/cpp_test.py b/tests/cpp_test.py new file mode 100644 index 0000000..ff1eaeb --- /dev/null +++ b/tests/cpp_test.py @@ -0,0 +1,15 @@ +import unittest +import subprocess +import os + + +class MainTest(unittest.TestCase): + def test_cpp(self): + print("\n\nTesting C++ code...") + subprocess.check_call(os.path.join(os.path.dirname( + os.path.relpath(__file__)), 'bin', 'python_cpp_example_test')) + print("\nResuming Python tests...\n") + + +if __name__ == '__main__': + unittest.main()