Commit f6e6f3d7 authored by Nikola Dinev's avatar Nikola Dinev Committed by Tobias Lasser

Feature/python bindings

parent 5d96e144
Pipeline #278200 passed with stages
in 52 minutes and 26 seconds
...@@ -10,3 +10,6 @@ ...@@ -10,3 +10,6 @@
[submodule "thirdparty/quickvec"] [submodule "thirdparty/quickvec"]
path = thirdparty/quickvec path = thirdparty/quickvec
url = https://gitlab.lrz.de/IP/quickvec.git url = https://gitlab.lrz.de/IP/quickvec.git
[submodule "thirdparty/pybind11"]
path = thirdparty/pybind11
url = https://github.com/pybind/pybind11.git
...@@ -26,6 +26,7 @@ option(ELSA_INSTALL "Enable generating the install targets for make install" ${E ...@@ -26,6 +26,7 @@ option(ELSA_INSTALL "Enable generating the install targets for make install" ${E
option(ELSA_BUILD_EXAMPLES "Enable building of examples" ${ELSA_MASTER_PROJECT}) option(ELSA_BUILD_EXAMPLES "Enable building of examples" ${ELSA_MASTER_PROJECT})
option(ELSA_BUILD_CUDA_PROJECTORS "Enable building (or attempting to) the CUDA projectors" ON) option(ELSA_BUILD_CUDA_PROJECTORS "Enable building (or attempting to) the CUDA projectors" ON)
option(ELSA_BUILD_PYTHON_BINDINGS "Attempt to build python bindings if enabled (required libclang-dev)" ON)
option(ELSA_BUILD_WITH_MORE_WARNINGS "Enable all and extra warnings when building (-Wall -Wextra)" ON) option(ELSA_BUILD_WITH_MORE_WARNINGS "Enable all and extra warnings when building (-Wall -Wextra)" ON)
option(ELSA_CUDA_VECTOR "Build elsa with GPU DataContainer support and default" OFF) option(ELSA_CUDA_VECTOR "Build elsa with GPU DataContainer support and default" OFF)
...@@ -87,6 +88,7 @@ set(INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/elsa) ...@@ -87,6 +88,7 @@ set(INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/elsa)
# include the InstallElsaModule function # include the InstallElsaModule function
include(InstallElsaModule) include(InstallElsaModule)
add_subdirectory(thirdparty/pybind11)
# ------------ setup VectorCUDA -------- # ------------ setup VectorCUDA --------
# ------------ # ------------
...@@ -171,6 +173,11 @@ endif(ELSA_TESTING OR ELSA_BENCHMARKS) ...@@ -171,6 +173,11 @@ endif(ELSA_TESTING OR ELSA_BENCHMARKS)
# ------------ add code/docs ----------- # ------------ add code/docs -----------
# ------------ # ------------
# the bindings generator should be added before elsa, as we check whether the target can be built
if (ELSA_BUILD_PYTHON_BINDINGS)
add_subdirectory(tools/bindings_generation EXCLUDE_FROM_ALL)
endif()
# the elsa library # the elsa library
add_subdirectory(elsa) add_subdirectory(elsa)
......
find_program(LLVM_CONFIG_FOUND "llvm-config")
if (LLVM_CONFIG_FOUND)
execute_process(
COMMAND llvm-config --cxxflags
OUTPUT_VARIABLE LibClang_Flags)
string(STRIP ${LibClang_Flags} LibClang_Flags)
separate_arguments(LibClang_Flags NATIVE_COMMAND ${LibClang_Flags})
execute_process(
COMMAND llvm-config --includedir
OUTPUT_VARIABLE LibClang_INCLUDE_DIR)
find_library(LibClangCpp_LIBRARY NAMES clang-cpp PATH_SUFFIXES llvm-9/lib llvm-10/lib)
find_library(LibLLVM_LIBRARY NAMES LLVM PATH_SUFFIXES llvm-9/lib llvm-10/lib)
set(LibClang_LIBRARIES ${LibClangCpp_LIBRARY} ${LibLLVM_LIBRARY})
set(LibClang_INCLUDE_DIRS ${LibClang_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibClang DEFAULT_MSG LibClang_LIBRARIES LibClang_INCLUDE_DIR)
else()
message(WARNING "llvm-config couln't be located. Python bindings will not be generated")
endif()
\ No newline at end of file
...@@ -30,6 +30,47 @@ macro(ELSA_TEST NAME) ...@@ -30,6 +30,47 @@ macro(ELSA_TEST NAME)
catch_discover_tests(test_${NAME} TEST_SPEC ${ELSA_JUNIT_ARGUMENTS}) catch_discover_tests(test_${NAME} TEST_SPEC ${ELSA_JUNIT_ARGUMENTS})
endmacro(ELSA_TEST) endmacro(ELSA_TEST)
if (ELSA_BUILD_PYTHON_BINDINGS)
find_package(PythonLibs)
if(PYTHONLIBS_FOUND)
add_custom_target(pyelsa)
# macro for generation of the corresponding python module for the elsa target TARGET_NAME
# as a pre-build step the code for the python bindings will be generated and stored in BINDINGS_CODE_PATH
# the sources containing the public interface of TARGET_NAME should be specified as additional arguments
# you can omit a source file from the list to prevent the generation of bindings for that file
macro(GENERATE_BINDINGS TARGET_NAME BINDINGS_CODE_PATH HINTS_PATH)
if (TARGET pybind11_generator)
get_filename_component(CMAKE_CURRENT_LIST_DIR_LAST_PART ${CMAKE_CURRENT_LIST_DIR} NAME)
file(APPEND ${PROJECT_BINARY_DIR}/elsa/__init__.py "from .${CMAKE_CURRENT_LIST_DIR_LAST_PART} import *\n")
add_custom_command(OUTPUT ${BINDINGS_CODE_PATH}
COMMAND ${PROJECT_BINARY_DIR}/tools/bindings_generation/pybind11_generator
${ARGN}
--extra-arg=-I${PYTHON_INCLUDE_DIRS}
--extra-arg=-I${PYBIND11_INCLUDE_DIR}
--hints=${HINTS_PATH}
-o=${BINDINGS_CODE_PATH}
--name=${TARGET_NAME}
DEPENDS ${TARGET_NAME} pybind11_generator ${HINTS_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMENT "Generating bindings code for ${TARGET_NAME}"
VERBATIM)
pybind11_add_module(py${TARGET_NAME} ${BINDINGS_CODE_PATH})
target_include_directories(py${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/elsa/bindings/hints)
target_link_libraries(py${TARGET_NAME} PUBLIC ${TARGET_NAME})
target_compile_features(py${TARGET_NAME} PUBLIC cxx_std_17)
add_dependencies(pyelsa py${TARGET_NAME})
endif()
endmacro()
else()
message(STATUS "Couldn't find Python.h. Python bindings will not be generated.")
endif()
endif()
# add the elsa modules # add the elsa modules
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(logging) add_subdirectory(logging)
......
...@@ -89,6 +89,28 @@ target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17) ...@@ -89,6 +89,28 @@ target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17)
# set -fPIC # set -fPIC
set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
if (ELSA_BUILD_PYTHON_BINDINGS)
set(BINDINGS_SOURCES ${MODULE_SOURCES}
Descriptors/DataDescriptor.h
Descriptors/DescriptorUtils.h
Descriptors/VolumeDescriptor.h
Descriptors/BlockDescriptor.h
Descriptors/IdenticalBlocksDescriptor.h
Descriptors/PartitionDescriptor.h
Descriptors/RandomBlocksDescriptor.h
Geometry.h)
list(FILTER BINDINGS_SOURCES EXCLUDE REGEX DataHandler.*.cpp)
list(FILTER BINDINGS_SOURCES EXCLUDE REGEX .*Descriptor.*.cpp)
list(FILTER BINDINGS_SOURCES EXCLUDE REGEX Geometry.cpp)
list(FILTER BINDINGS_SOURCES EXCLUDE REGEX StrongTypes.cpp)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
bindings_hints.hpp
${BINDINGS_SOURCES})
endif()
# build the tests (if enabled) # build the tests (if enabled)
if(ELSA_TESTING) if(ELSA_TESTING)
......
#include "DataContainer.h"
#include "Descriptors/VolumeDescriptor.h"
#include "LinearOperator.h"
#include "DescriptorUtils.h"
#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include <functional>
namespace elsa
{
template <typename Class>
struct ClassHints {
};
namespace py = pybind11;
/// wrapper for variadic functions
template <typename T, std::size_t N, typename = std::make_index_sequence<N>>
struct invokeBestCommonVariadic;
template <typename T, std::size_t N, std::size_t... S>
struct invokeBestCommonVariadic<T, N, std::index_sequence<S...>> {
static auto exec(const py::args& args)
{
if (args.size() == N)
return elsa::bestCommon(args[S].template cast<T>()...);
if constexpr (N > 1) {
return invokeBestCommonVariadic<T, N - 1>::exec(args);
} else {
throw(std::logic_error("Unsupported number of variadic arguments"));
}
};
};
template <typename data_t>
LinearOperator<data_t> adjointHelper(const LinearOperator<data_t>& op)
{
return adjoint(op);
}
template <typename data_t>
LinearOperator<data_t> leafHelper(const LinearOperator<data_t>& op)
{
return leaf(op);
}
// define global variables and functions in the module hints
struct ModuleHints {
static void addCustomFunctions(py::module& m)
{
m.def("adjoint", &adjointHelper<float>)
.def("adjoint", &adjointHelper<double>)
.def("adjoint", &adjointHelper<std::complex<float>>)
.def("adjoint", &adjointHelper<std::complex<double>>);
m.def("leaf", &leafHelper<float>)
.def("leaf", &leafHelper<double>)
.def("leaf", &leafHelper<std::complex<float>>)
.def("leaf", &leafHelper<std::complex<double>>);
m.def("bestCommon", (std::unique_ptr<DataDescriptor>(*)(
const std::vector<const DataDescriptor*>&))(&bestCommon))
.def("bestCommon", &invokeBestCommonVariadic<const DataDescriptor&, 10>::exec);
}
};
template <typename data_t, typename type_, typename... options>
void addOperatorsDc(py::class_<type_, options...>& c)
{
c.def(
"__add__",
[](const DataContainer<data_t>& self, const DataContainer<data_t>& other) {
return DataContainer<data_t>(self + other);
},
py::return_value_policy::move)
.def(
"__mul__",
[](const DataContainer<data_t>& self, const DataContainer<data_t>& other) {
return DataContainer<data_t>(self * other);
},
py::return_value_policy::move)
.def(
"__sub__",
[](const DataContainer<data_t>& self, const DataContainer<data_t>& other) {
return DataContainer<data_t>(self - other);
},
py::return_value_policy::move)
.def(
"__truediv__",
[](const DataContainer<data_t>& self, const DataContainer<data_t>& other) {
return DataContainer<data_t>(self / other);
},
py::return_value_policy::move)
// TODO: make the generator automatically generate this __setitem__ function
.def("__setitem__", [](elsa::DataContainer<data_t>& dc, elsa::index_t i, data_t value) {
dc[i] = value;
});
}
template <typename data_t>
struct DataContainerHints : public ClassHints<elsa::DataContainer<data_t>> {
constexpr static std::tuple ignoreMethods = {
"operator()", "begin", "cbegin", "end", "cend", "rbegin", "crbegin", "rend", "crend"};
template <typename type_, typename... options>
static void addCustomMethods(py::class_<type_, options...>& c)
{
addOperatorsDc<data_t>(c);
}
template <typename type_, typename... options>
static void exposeBufferInfo(py::class_<type_, options...>& c)
{
c.def(py::init([](py::buffer b) {
py::buffer_info info = b.request();
if (info.format != py::format_descriptor<data_t>::format())
throw std::invalid_argument("Incompatible scalar types");
elsa::IndexVector_t coeffsPerDim(info.ndim);
ssize_t minStride = info.strides[0];
for (std::size_t i = 0; i < static_cast<std::size_t>(info.ndim); i++) {
if (info.strides[i] < minStride)
minStride = info.strides[i];
coeffsPerDim[static_cast<elsa::index_t>(i)] =
static_cast<elsa::index_t>(info.shape[i]);
}
if (static_cast<std::size_t>(minStride) / sizeof(data_t) != 1)
throw std::invalid_argument("Cannot convert strided buffer to DataContainer");
auto map = Eigen::Map<Eigen::Matrix<data_t, Eigen::Dynamic, 1>>(
static_cast<data_t*>(info.ptr), coeffsPerDim.prod());
elsa::VolumeDescriptor dd{coeffsPerDim};
return std::make_unique<elsa::DataContainer<data_t>>(dd, map);
})).def_buffer([](elsa::DataContainer<data_t>& m) {
std::vector<ssize_t> dims, strides;
auto coeffsPerDim = m.getDataDescriptor().getNumberOfCoefficientsPerDimension();
ssize_t combined = 1;
for (int i = 0; i < coeffsPerDim.size(); i++) {
dims.push_back(coeffsPerDim[i]);
strides.push_back(combined * static_cast<ssize_t>(sizeof(data_t)));
combined *= coeffsPerDim[i];
}
return py::buffer_info(
&m[0], sizeof(data_t), py::format_descriptor<data_t>::format(),
m.getDataDescriptor().getNumberOfDimensions(), coeffsPerDim, strides);
});
}
};
template <typename data_t>
struct LinearOperatorHints : public ClassHints<elsa::LinearOperator<data_t>> {
template <typename type_, typename... options>
static void addCustomMethods(py::class_<type_, options...>& c)
{
c.def(py::self + py::self).def(py::self * py::self);
}
};
template <typename data_t>
struct DataContainerComplexHints : public ClassHints<elsa::DataContainer<data_t>> {
constexpr static std::tuple ignoreMethods = {
"operator()", "begin", "cbegin", "end", "cend", "rbegin", "crbegin", "rend", "crend"};
template <typename type_, typename... options>
static void addCustomMethods(py::class_<type_, options...>& c)
{
addOperatorsDc<data_t>(c);
}
// TODO: pybind11 does not provide a default format_descriptor for complex types -> make a
// custom one for this to work
// CustomMethod<true, py::buffer_info, elsa::DataContainer<data_t>&> buffer =
// [](elsa::DataContainer<data_t>& m) {
// std::vector<ssize_t> dims, strides;
// auto coeffsPerDim = m.getDataDescriptor().getNumberOfCoefficientsPerDimension();
// ssize_t combined = 1;
// for (int i = 0; i < coeffsPerDim.size(); i++) {
// dims.push_back(coeffsPerDim[i]);
// strides.push_back(combined * static_cast<ssize_t>(sizeof(data_t)));
// combined *= coeffsPerDim[i];
// }
// return py::buffer_info(
// &m[0], /* Pointer to buffer */
// sizeof(data_t), /* Size of one scalar */
// py::format_descriptor<data_t>::format(),
// /* Python struct-style format descriptor
// */
// m.getDataDescriptor().getNumberOfDimensions(), /* Number of dimensions */
// coeffsPerDim, /* Buffer dimensions */
// strides);
// };
};
template struct DataContainerHints<float>;
template struct DataContainerComplexHints<std::complex<float>>;
template struct DataContainerHints<double>;
template struct DataContainerComplexHints<std::complex<double>>;
template struct DataContainerHints<index_t>;
template struct LinearOperatorHints<float>;
template struct LinearOperatorHints<double>;
template struct LinearOperatorHints<std::complex<float>>;
template struct LinearOperatorHints<std::complex<double>>;
} // namespace elsa
\ No newline at end of file
...@@ -61,6 +61,12 @@ if(ELSA_TESTING) ...@@ -61,6 +61,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -43,6 +43,12 @@ if(ELSA_TESTING) ...@@ -43,6 +43,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES} CircleTrajectoryGenerator.h)
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -37,6 +37,17 @@ target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17) ...@@ -37,6 +37,17 @@ target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17)
# set -fPIC # set -fPIC
set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
if (ELSA_BUILD_PYTHON_BINDINGS)
set(BINDINGS_SOURCES ${MODULE_SOURCES})
list(FILTER BINDINGS_SOURCES EXCLUDE REGEX ioUtils.cpp)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
bindings_hints.hpp
${BINDINGS_SOURCES})
endif()
# build the tests (if enabled) # build the tests (if enabled)
if(ELSA_TESTING) if(ELSA_TESTING)
......
#include "EDFHandler.h"
#include "MHDHandler.h"
#include <pybind11/pybind11.h>
#include <functional>
namespace elsa
{
template <typename Class>
struct ClassHints {
};
template <class Return, class... Args>
using CustomMethod = std::pair<bool, std::function<Return(Args...)>>;
namespace py = pybind11;
struct EDFHints : public ClassHints<EDF> {
constexpr static std::tuple ignoreMethods = {"read"};
template <typename type_, typename... options>
static void addCustomMethods(py::class_<type_, options...>& c)
{
c.def_static(
"readf", [](std::string filename) { return EDF::read<float>(filename); },
py::return_value_policy::move)
.def_static(
"readd", [](std::string filename) { return EDF::read<double>(filename); },
py::return_value_policy::move)
.def_static(
"readl", [](std::string filename) { return EDF::read<index_t>(filename); },
py::return_value_policy::move);
}
};
struct MHDHints : public ClassHints<MHD> {
constexpr static std::tuple ignoreMethods = {"read"};
template <typename type_, typename... options>
static void addCustomMethods(py::class_<type_, options...>& c)
{
c.def_static(
"readf", [](std::string filename) { return MHD::read<float>(filename); },
py::return_value_policy::move)
.def_static(
"readd", [](std::string filename) { return MHD::read<double>(filename); },
py::return_value_policy::move)
.def_static(
"readl", [](std::string filename) { return MHD::read<index_t>(filename); },
py::return_value_policy::move);
}
};
} // namespace elsa
\ No newline at end of file
...@@ -75,6 +75,8 @@ namespace elsa ...@@ -75,6 +75,8 @@ namespace elsa
/// default destructor /// default destructor
~BlockLinearOperator() override = default; ~BlockLinearOperator() override = default;
BlockLinearOperator& operator=(BlockLinearOperator&) = delete;
/// return the operator corresponding to the i-th block of the matrix /// return the operator corresponding to the i-th block of the matrix
const LinearOperator<data_t>& getIthOperator(index_t i) const; const LinearOperator<data_t>& getIthOperator(index_t i) const;
......
...@@ -52,6 +52,12 @@ if(ELSA_TESTING) ...@@ -52,6 +52,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -47,6 +47,12 @@ if(ELSA_TESTING) ...@@ -47,6 +47,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -58,6 +58,12 @@ if(ELSA_TESTING) ...@@ -58,6 +58,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -55,7 +55,13 @@ if (CMAKE_CUDA_COMPILER) ...@@ -55,7 +55,13 @@ if (CMAKE_CUDA_COMPILER)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
...@@ -43,6 +43,12 @@ if(ELSA_TESTING) ...@@ -43,6 +43,12 @@ if(ELSA_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif(ELSA_TESTING) endif(ELSA_TESTING)
if (ELSA_BUILD_PYTHON_BINDINGS)
GENERATE_BINDINGS(${ELSA_MODULE_TARGET_NAME}
${CMAKE_CURRENT_BINARY_DIR}/bind_${ELSA_MODULE_NAME}.cpp
""
${MODULE_SOURCES})
endif()
# register the module # register the module
registerComponent(${ELSA_MODULE_NAME}) registerComponent(${ELSA_MODULE_NAME})
......
Subproject commit d96c34516d276f4c33d25df23d5934af1c5b2406
cmake_minimum_required(VERSION 3.10)
find_package(LibClang)
if (LIBCLANG_FOUND)
add_executable(pybind11_generator Parser.cpp)
target_compile_options(pybind11_generator PUBLIC ${LibClang_Flags})
target_compile_features(pybind11_generator PUBLIC cxx_std_17)
target_link_libraries(pybind11_generator LLVM clang-cpp stdc++fs)
file(WRITE ${PROJECT_BINARY_DIR}/elsa/__init__.py "")
else()
message(WARNING "libclang-dev not found. Python bindings will not be generated.")
endif()
#pragma once
#include "Module.h"
#include <iostream>
#include <fstream>
#include <map>
#include <algorithm>
#include <assert.h>
// Determine wether to use <filesystem> or <experimental/filesystem>. Adapted from
// https://stackoverflow.com/questions/53365538/how-to-determine-whether-to-use-filesystem-or-experimental-filesystem,
// simplified by removing MSVC specific code, as this is expected to be run with clang
// We haven't checked which filesystem to include yet
#ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL