Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,20 @@ src/%.o: src/%.cc .make/all-flags
$(CXX) $(CXXFLAGS) $(INCLUDE) -MD -fPIC -c $< -o $@

.PHONY: test
test: $(PYTHON_TESTS) $(TEST_MODULE)
test: $(PYTHON_TESTS) $(TEST_MODULE) tests/_test_automodule.so
GTEST_OUTPUT=$(GTEST_OUTPUT) \
$(LD_PRELOAD_VAR)="$(TEST_LD_PRELOAD)" \
ASAN_OPTIONS=$(ASAN_OPTIONS) \
LSAN_OPTIONS=$(LSAN_OPTIONS) \
LSAN_OPTIONS=$(LSAN_OPTIONS) \
GTEST_ARGS=--gtest_filter=$(GTEST_FILTER) \
$(PYTEST) tests/ $(PYTEST_ARGS)

tests/_test_automodule.o: tests/_test_automodule.cc .make/all-flags
$(CXX) $(CXXFLAGS) $(INCLUDE) -MD -fPIC -c $< -o $@

tests/_test_automodule.so: tests/_test_automodule.o
$(CXX) -shared -o $@ $< -lpthread $(LDFLAGS)

.PHONY: gdbtest
gdbtest: $(PYTHON_TESTS)
@LD_LIBRARY_PATH=. GTEST_BREAK_ON_FAILURE=$(GTEST_BREAK) \
Expand All @@ -215,9 +220,9 @@ tests/%.o: tests/%.cc .make/all-flags
-isystem submodules/googletest/googletest/src \
-MD -fPIC -c $< -o $@

$(TEST_MODULE): gtest.a $(TEST_OBJECTS) $(SONAME)
$(TEST_MODULE): gtest.a $(TEST_OBJECTS) libpy/libpy.so
Comment thread
gerrymanoim marked this conversation as resolved.
$(CXX) -shared -o $@ $(TEST_OBJECTS) gtest.a $(TEST_INCLUDE) \
-Wl,-rpath,`pwd` -lpthread -L. $(SONAME) $(LDFLAGS)
-Wl,-rpath,`pwd` -lpthread -L. $(SONAME) $(LDFLAGS)

gtest.o: $(GTEST_SRCS) .make/all-flags
$(CXX) $(filter-out $(WARNINGS),$(CXXFLAGS)) -I $(GTEST_DIR) \
Expand Down
14 changes: 7 additions & 7 deletions include/libpy/autoclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,32 +150,32 @@ class autoclass_impl {

// dispatch for free function that accepts as a first argument `T`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(T, Args...), impl>
struct free_function_impl<R(*)(T, Args...), impl>
: public free_function_base<impl, R, Args...> {};

// dispatch for free function that accepts as a first argument `T&`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(T&, Args...), impl>
struct free_function_impl<R(*)(T&, Args...), impl>
: public free_function_base<impl, R, Args...> {};

// dispatch for free function that accepts as a first argument `const T&`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(const T&, Args...), impl>
struct free_function_impl<R(*)(const T&, Args...), impl>
: public free_function_base<impl, R, Args...> {};

// dispatch for a noexcept free function that accepts as a first argument `T`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(T, Args...) noexcept, impl>
struct free_function_impl<R(*)(T, Args...) noexcept, impl>
: public free_function_base<impl, R, Args...> {};

// dispatch for noexcept free function that accepts as a first argument `T&`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(T&, Args...) noexcept, impl>
struct free_function_impl<R(*)(T&, Args...) noexcept, impl>
: public free_function_base<impl, R, Args...> {};

// dispatch for a noexcept free function that accepts as a first argument `const T&`
template<typename R, typename... Args, auto impl>
struct free_function_impl<R(const T&, Args...) noexcept, impl>
struct free_function_impl<R(*)(const T&, Args...) noexcept, impl>
: public free_function_base<impl, R, Args...> {};

template<auto impl, typename R, typename... Args>
Expand Down Expand Up @@ -246,7 +246,7 @@ class autoclass_impl {
static py::owned_ref<PyTypeObject> lookup_type() {
auto type_search = detail::autoclass_type_cache.get().find(typeid(T));
if (type_search != detail::autoclass_type_cache.get().end()) {
PyTypeObject* type = type_search->second->type;
PyTypeObject* type = type_search->second->type.get();
Py_INCREF(type);
return py::owned_ref(type);
}
Expand Down
86 changes: 86 additions & 0 deletions include/libpy/automodule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#pragma once

#include <forward_list>
#include <unordered_map>
#include <vector>

#include "libpy/abi.h"
#include "libpy/borrowed_ref.h"
#include "libpy/detail/api.h"
#include "libpy/detail/numpy.h"
#include "libpy/detail/python.h"
#include "libpy/owned_ref.h"

#define _libpy_XSTR(s) #s
#define _libpy_STR(s) _libpy_XSTR(s)

#define _libpy_MODULE_PATH(parent, name) _libpy_STR(parent) "." _libpy_STR(name)

#define _libpy_XCAT(a, b) a##b
#define _libpy_CAT(a, b) _libpy_XCAT(a, b)

#define _libpy_MODINIT_NAME(name) _libpy_CAT(PyInit_, name)
#define _libpy_MODULE_CREATE(path) PyModule_Create(&_libpy_module)

/** Define a Python module.

@param parent A symbol indicating the parent module.
@param name The leaf name of the module.
@param methods ({...}) list of objects representing the functions to add to the
module. Note this list must be surrounded by parentheses.

## Examples

Create a module `my_package.submodule.my_module` with two functions `f` and
`g` and one type `T`.

\code
LIBPY_AUTOMODULE(my_package.submodule,
my_module,
({py::autofunction<f>("f"),
py::autofunction<g>("g")}))
(py::borrowed_ref<> m) {
py::borrowed_ref t = py::autoclass<T>("T").new_().type();
return PyObject_SetAttrString(m.get(), "T", static_cast<PyObject*>(t));
}
/endcode
*/
#define LIBPY_AUTOMODULE(parent, name, methods) \
bool _libpy_user_mod_init(py::borrowed_ref<>); \
PyMODINIT_FUNC _libpy_MODINIT_NAME(name)() LIBPY_EXPORT; \
PyMODINIT_FUNC _libpy_MODINIT_NAME(name)() { \
import_array(); \
if (py::abi::ensure_compatible_libpy_abi()) { \
return nullptr; \
} \
static std::vector<PyMethodDef> ms methods; \
ms.emplace_back(py::end_method_list); \
static PyModuleDef _libpy_module{ \
PyModuleDef_HEAD_INIT, \
_libpy_MODULE_PATH(parent, name), \
nullptr, \
-1, \
ms.data(), \
}; \
py::owned_ref m(_libpy_MODULE_CREATE(_libpy_MODULE_PATH(parent, name))); \
if (!m) { \
return nullptr; \
} \
try { \
if (_libpy_user_mod_init(m)) { \
return nullptr; \
} \
} \
catch (const std::exception& e) { \
py::raise_from_cxx_exception(e); \
return nullptr; \
} \
catch (...) { \
if (!PyErr_Occurred()) { \
py::raise(PyExc_RuntimeError) << "an unknown C++ exception was raised"; \
return nullptr; \
} \
} \
return std::move(m).escape(); \
} \
bool _libpy_user_mod_init
3 changes: 2 additions & 1 deletion include/libpy/detail/autoclass_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <unordered_map>
#include <vector>

#include "libpy/borrowed_ref.h"
#include "libpy/detail/api.h"
#include "libpy/detail/no_destruct_wrapper.h"
#include "libpy/detail/python.h"
Expand All @@ -21,7 +22,7 @@ struct autoclass_storage {
unbox_fn unbox;

// Borrowed reference to the type that this struct contains storage for.
PyTypeObject* type;
py::borrowed_ref<PyTypeObject> type;

// The method storage for `type`. We may use a vector because this is just a
// collection of pointers and ints. `PyMethodDef` objects may move around until
Expand Down
2 changes: 1 addition & 1 deletion include/libpy/from_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ struct from_object<T&> {
throw invalid_conversion::make<T&>(ob);
}
int res = PyObject_IsInstance(ob.get(),
reinterpret_cast<PyObject*>(
static_cast<PyObject*>(
search->second->type));
if (res < 0) {
throw py::exception{};
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import libpy # noqa
35 changes: 35 additions & 0 deletions tests/_test_automodule.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "libpy/autoclass.h"
#include "libpy/automethod.h"
#include "libpy/automodule.h"

bool is_42(int arg) {
return arg == 42;
}

bool is_true(bool arg) {
return arg;
}

using int_float_pair = std::pair<int, float>;

int first(const int_float_pair& ob) {
return ob.first;
}

float second(const int_float_pair& ob) {
return ob.second;
}

LIBPY_AUTOMODULE(tests,
_test_automodule,
({py::autofunction<is_42>("is_42"),
py::autofunction<is_true>("is_true")}))
(py::borrowed_ref<> m) {
py::owned_ref t = py::autoclass<int_float_pair>("_test_automodule.int_float_pair")
.new_<int, float>()
.comparisons<int_float_pair>()
.def<first>("first")
.def<second>("second")
.type();
return PyObject_SetAttrString(m.get(), "int_float_pair", static_cast<PyObject*>(t));
}
21 changes: 21 additions & 0 deletions tests/test_automodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from . import _test_automodule as mod


def test_modname():
assert mod.__name__ == 'tests._test_automodule'


def test_function():
assert mod.is_42(42)
assert not mod.is_42(~42)


def test_type():
assert isinstance(mod.int_float_pair, type)
a = mod.int_float_pair(1, 2.5)
assert a.first() == 1
assert a.second() == 2.5
b = mod.int_float_pair(1, 2.5)
assert a == b
c = mod.int_float_pair(1, 3.5)
assert a != c