From d7d02e321626db4ae51ab8a66ccd12f1eb594957 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Mon, 22 Mar 2021 20:20:14 -0600 Subject: [PATCH 01/10] Embed Ruby + Python into test exe working --- .clang-format | 92 ++++++++++++++ CMakeLists.txt | 308 ++++++++++++++++++++++++---------------------- Measure.cpp | 10 ++ Measure.hpp | 37 ++++++ Measure.i | 25 ++++ Model.cpp | 13 ++ Model.hpp | 18 +++ Person.cpp | 32 ----- Person.hpp | 49 -------- Person.i | 46 ------- PythonEngine.cpp | 35 ++++++ PythonEngine.hpp | 31 +++++ RubyEngine.cpp | 37 ++++++ RubyEngine.hpp | 27 ++++ Runner.hpp | 26 ++++ ScriptEngine.hpp | 49 ++++++++ SpecialRunner.hpp | 26 ++++ main.cpp | 15 ++- 18 files changed, 597 insertions(+), 279 deletions(-) create mode 100644 .clang-format create mode 100644 Measure.cpp create mode 100644 Measure.hpp create mode 100644 Measure.i create mode 100644 Model.cpp create mode 100644 Model.hpp delete mode 100644 Person.cpp delete mode 100644 Person.hpp delete mode 100644 Person.i create mode 100644 PythonEngine.cpp create mode 100644 PythonEngine.hpp create mode 100644 RubyEngine.cpp create mode 100644 RubyEngine.hpp create mode 100644 Runner.hpp create mode 100644 ScriptEngine.hpp create mode 100644 SpecialRunner.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..2dd146e --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: true +ColumnLimit: 150 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentWidth: 2 +IndentPPDirectives: AfterHash +IndentWrappedFunctionNames: true +NamespaceIndentation: Inner +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false # No longer available in clang-format 6.0 +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +# SpaceBeforeCpp11BracedList: true # No longer available in clang-format 6.0 +# SpaceBeforeCtorInitializerColon: true # No longer available in clang-format 6.0 +# SpaceBeforeInheritanceColon: true # No longer available in clang-format 6.0 +SpaceBeforeParens: ControlStatements +# SpaceBeforeRangeBasedForLoopColon: true # No longer available in clang-format 6.0 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SortIncludes: false +SortUsingDeclarations: false +Standard: Cpp11 +TabWidth: 2 +UseTab: Never +... diff --git a/CMakeLists.txt b/CMakeLists.txt index a8d8a8f..c4dadc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,24 @@ -cmake_minimum_required(VERSION 3.18.0) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Do not enable compiler specific extensions, for eg on GCC use -std=c++1z (=c++17) and not -std=gnu++17 -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_VERBOSE_MAKEFILE TRUE) +cmake_minimum_required(VERSION 3.17.5) + +add_library(cpp_warning_options INTERFACE) +add_library(cpp_compile_options INTERFACE) + +target_compile_features(cpp_compile_options INTERFACE cxx_std_17) + +if(MSVC) + target_compile_options(cpp_warning_options INTERFACE /W4 /Werror) +else() + target_compile_options( + cpp_warning_options INTERFACE -Wall -Wextra -Wconversion -Wpedantic -Werror) + target_compile_options(cpp_compile_options + INTERFACE -fsanitize=undefined,address) + target_link_options(cpp_compile_options INTERFACE + -fsanitize=undefined,address) +endif() +# Do not enable compiler specific extensions, for eg on GCC use -std=c++1z +# (=c++17) and not -std=gnu++17 +set(CMAKE_CXX_EXTENSIONS OFF) # Use ccache is available, has to be before "project()" find_program(CCACHE_PROGRAM ccache) @@ -13,59 +27,24 @@ if(CCACHE_PROGRAM) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") endif() - project(test_swig VERSION 0.0.1) # Set a default build type if none was specified if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set(CMAKE_BUILD_TYPE + Release + CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif() - -############################################################################### -# N I N J A # -############################################################################### - -# Xcode/Ninja generators undefined MAKE -if(CMAKE_GENERATOR MATCHES "Make") - set(MAKE "$(MAKE)") -else() - set(MAKE make) + "MinSizeRel" "RelWithDebInfo") endif() -# Ninja support: has to be atop for it to take effect before anything else is done -# Add Color Output if Using Ninja -macro(AddCXXFlagIfSupported flag test) - CHECK_CXX_COMPILER_FLAG(${flag} ${test}) - if(${${test}}) - message("adding ${flag}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") - endif() -endmacro() - -if("Ninja" STREQUAL ${CMAKE_GENERATOR}) - # Clang - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - AddCXXFlagIfSupported(-fcolor-diagnostics COMPILER_SUPPORTS_fcolor-diagnostics) - endif() - - # g++ - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # For some reason it doesn't say its supported, but it works... - # AddCXXFlagIfSupported(-fdiagnostics-color COMPILER_SUPPORTS_fdiagnostics-color) - message(STATUS "Ninja: Forcing -fdiagnostics-color=always") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") - endif() -endif() - - -# Search first in the binary dir, where conan will install finders, then -# search for modules in the root dir to override cmake ones -# Start with ROOT, then PROJECT_BINARY_DIR -list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR} ${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/CMake") +# Search first in the binary dir, where conan will install finders, then search +# for modules in the root dir to override cmake ones Start with ROOT, then +# CMAKE_CURRENT_BINARY_DIR +list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/CMake") include_directories("${CMAKE_CURRENT_BINARY_DIR}") # Add to include path @@ -73,69 +52,56 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}") # Project source directory include_directories("${PROJECT_SOURCE_DIR}") -############################################################################### -# C M A K E C O N T R O L # -############################################################################### +# ############################################################################## +# C M A K E C O N T R O L # +# ############################################################################## -# High level project configuration -# Do we actually want everything to go to CMAKE_BINARY_DIR/Products, -# so that when you build OpenStudioApplication you get both OpenStudio (core) and Application in the same place? -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Products") -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Products") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Products") +# High level project configuration Do we actually want everything to go to +# CMAKE_BINARY_DIR/Products, so that when you build OpenStudioApplication you +# get both OpenStudio (core) and Application in the same place? +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Products") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Products") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Products") -set(LIBRARY_SEARCH_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Release" "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Debug") +set(LIBRARY_SEARCH_DIRECTORY + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Release" + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Debug") -# Search first in the binary dir, where conan will install finders, then -# search for modules in the root dir to override cmake ones -list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR} ${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/CMake") +# Search first in the binary dir, where conan will install finders, then search +# for modules in the root dir to override cmake ones +list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/CMake") -####################################################################### -# R E G U L A R O U P U T A N D L I B S # -####################################################################### +# ############################################################################## +# R E G U L A R O U P U T A N D L I B S # +# ############################################################################## set(target_name mylib) -set(${target_name}_src - Person.hpp - Person.cpp -) - -add_library(${target_name} ${${target_name}_src}) -target_link_libraries(${target_name} - ${${target_name}_depends} -) -# Executable -add_executable(Test - main.cpp -) - -target_link_libraries(Test - ${target_name} -) - +set(${target_name}_src Measure.hpp Measure.cpp Model.hpp Model.cpp Runner.hpp + SpecialRunner.hpp) -####################################################################### -# S W I G # -####################################################################### +add_library(${target_name} SHARED ${${target_name}_src}) +target_link_libraries(${target_name} PRIVATE cpp_warning_options + cpp_compile_options) +# ############################################################################## +# S W I G # +# ############################################################################## option(BUILD_RUBY_BINDINGS "Build Ruby bindings" ON) # Build CSharp bindings option(BUILD_CSHARP_BINDINGS "Build CSharp bindings" OFF) -# Build Python bindings -# Requires: SWIG Python +# Build Python bindings Requires: SWIG Python option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON) - # lib swig files -set(${target_name}_swig_src - Person.i -) +set(${target_name}_swig_src Measure.i) -foreach (SWIG_FILE ${${target_name}_swig_src}) +foreach(SWIG_FILE ${${target_name}_swig_src}) message("SWIG_FILE=${SWIG_FILE}") set_source_files_properties(${SWIG_FILE} PROPERTIES CPLUSPLUS ON) endforeach() @@ -147,101 +113,147 @@ include(${SWIG_USE_FILE}) enable_testing() include(CTest) - -if (BUILD_PYTHON_BINDINGS) +if(BUILD_PYTHON_BINDINGS) set(swig_target_name ${swig_target_name}_python) - find_package(Python COMPONENTS Interpreter Development REQUIRED) - - include_directories(SYSTEM ${Python_INCLUDE_DIRS}) + find_package( + Python + COMPONENTS Interpreter Development + REQUIRED) - swig_add_library(${swig_target_name} + swig_add_library( + ${swig_target_name} TYPE MODULE - LANGUAGE python - OUTPUT_DIR "${PROJECT_BINARY_DIR}/Products/python" - OUTFILE_DIR "${PROJECT_BINARY_DIR}/python_wrapper" - SOURCES ${${target_name}_swig_src} - ) - - set_target_properties(${swig_target_name} PROPERTIES OUTPUT_NAME ${target_name}) - - set_target_properties(${swig_target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/python/") - set_target_properties(${swig_target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/python/") - set_target_properties(${swig_target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/python/") - - - swig_link_libraries(${swig_target_name} ${target_name}) - - - if (MSVC) + LANGUAGE python OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/Products/python" + OUTFILE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper" + SOURCES ${${target_name}_swig_src}) + + set_target_properties(${swig_target_name} PROPERTIES OUTPUT_NAME + ${target_name}) + + set_target_properties( + ${swig_target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY + "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/python/") + set_target_properties( + ${swig_target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/python/") + set_target_properties( + ${swig_target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/python/") + +# target_include_directories(${swig_target_name} PRIVATE SYSTEM +# ${Python_INCLUDE_DIRS}) + + swig_link_libraries(${swig_target_name} PUBLIC ${target_name} PRIVATE + cpp_compile_options) + +# if(MSVC) swig_link_libraries(${swig_target_name} PRIVATE Python::Module) - endif() +# endif() add_test( - NAME test_ruby_only - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby - COMMAND ${Ruby_EXECUTABLE} test_ruby_only.rb - ) + NAME test_python_only + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/python + COMMAND ${Python_EXECUTABLE} test_python_only.rb) endif() if(BUILD_RUBY_BINDINGS) find_package(Ruby REQUIRED) - include_directories(SYSTEM ${Ruby_INCLUDE_DIRS}) - set(swig_target_name ${target_name}_ruby) - swig_add_library(${swig_target_name} + swig_add_library( + ${swig_target_name} TYPE MODULE - LANGUAGE ruby - OUTPUT_DIR "${PROJECT_BINARY_DIR}/Products/ruby" - OUTFILE_DIR "${PROJECT_BINARY_DIR}/ruby_wrapper" - SOURCES ${${target_name}_swig_src} - ) + LANGUAGE ruby OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/Products/ruby" + OUTFILE_DIR "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper" + SOURCES ${${target_name}_swig_src}) + + set_target_properties(${swig_target_name} PROPERTIES OUTPUT_NAME + ${target_name}) - set_target_properties(${swig_target_name} PROPERTIES OUTPUT_NAME ${target_name}) + set_target_properties( + ${swig_target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY + "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ruby/") + set_target_properties( + ${swig_target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ruby/") + set_target_properties( + ${swig_target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ruby/") - set_target_properties(${swig_target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ruby/") - set_target_properties(${swig_target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ruby/") - set_target_properties(${swig_target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ruby/") + target_include_directories( + ${swig_target_name} PRIVATE SYSTEM ${RUBY_INCLUDE_DIRS} ) - swig_link_libraries(${swig_target_name} ${target_name} ${Ruby_LIBRARIES}) + swig_link_libraries(${swig_target_name} PUBLIC ${target_name} PRIVATE + ${RUBY_LIBRARIES} cpp_compile_options) add_test( - NAME test_python_only - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/python - COMMAND ${Python_EXECUTABLE} test_python_only.py - ) + NAME test_ruby_only + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby + COMMAND ${RUBY_EXECUTABLE} test_ruby_only.py) endif() - if(BUILD_RUBY_BINDINGS AND BUILD_PYTHON_BINDINGS) add_test( NAME test_pycall WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby - COMMAND ${Ruby_EXECUTABLE} test_pycall.rb - ) + COMMAND ${RUBY_EXECUTABLE} test_pycall.rb) add_test( NAME test_pycall_workaround WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby - COMMAND ${Ruby_EXECUTABLE} test_pycall_workaround.rb - ) + COMMAND ${RUBY_EXECUTABLE} test_pycall_workaround.rb) add_test( NAME test_pycall_use_cpp_to_convert_pointers WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby - COMMAND ${Ruby_EXECUTABLE} test_pycall_use_cpp_to_convert_pointers.rb - ) + COMMAND ${RUBY_EXECUTABLE} test_pycall_use_cpp_to_convert_pointers.rb) add_test( NAME test_pycall_script WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby - COMMAND ${Ruby_EXECUTABLE} test_pycall_script.rb - ) + COMMAND ${RUBY_EXECUTABLE} test_pycall_script.rb) endif() + +# Must call CMake itself in order to set the SWIG_LIB env var for +# add_custom_command +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx" + COMMAND + ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" + "-python" -external-runtime + "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx") + +# Must call CMake itself in order to set the SWIG_LIB env var for +# add_custom_command +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" + COMMAND + ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" + "-ruby" -external-runtime + "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx") + +# Executable +add_executable( + Test + main.cpp + ScriptEngine.hpp + PythonEngine.hpp + PythonEngine.cpp + RubyEngine.hpp + RubyEngine.cpp + "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" + "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx") + +target_link_libraries(Test PRIVATE ${target_name} cpp_compile_options + cpp_warning_options ${RUBY_LIBRARY} Python::Python) + +target_include_directories(Test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper" "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper" + ${RUBY_INCLUDE_DIRS}) + diff --git a/Measure.cpp b/Measure.cpp new file mode 100644 index 0000000..b4dc5f1 --- /dev/null +++ b/Measure.cpp @@ -0,0 +1,10 @@ +#include "Measure.hpp" + +namespace Test { +std::vector Measure::arguments(const Model& model) const { + return arguments_impl(model); +} +bool Measure::run(Runner& runner, const std::vector& args) { + return run_impl(runner, args); +} +} // namespace Test \ No newline at end of file diff --git a/Measure.hpp b/Measure.hpp new file mode 100644 index 0000000..7be1be7 --- /dev/null +++ b/Measure.hpp @@ -0,0 +1,37 @@ +#ifndef MEASURE_HPP +#define MEASURE_HPP + +#include +#include +#include +#include + +namespace Test { +class Runner; + +class Model; + +class Measure +{ + public: + Measure() = default; + Measure(const Measure&) = delete; + Measure(Measure&&) = delete; + Measure& operator=(const Measure&) = delete; + Measure& operator=(Measure&&) = delete; + virtual ~Measure() = default; + + std::vector arguments(const Model&) const; + bool run(Runner&, const std::vector& ); + + protected: + // protected virtual here allows us to easily change the internal interface + // and behavior of measure + virtual std::vector arguments_impl(const Model&) const = 0; + virtual bool run_impl(Runner&, const std::vector& ) = 0; +}; + + +} // Namespace Test + +#endif // ifndef MEASURE_HPP diff --git a/Measure.i b/Measure.i new file mode 100644 index 0000000..597b464 --- /dev/null +++ b/Measure.i @@ -0,0 +1,25 @@ +#ifndef MEASURE_I +#define MEASURE_I + +%module mylib +%module(directors="1") mylib + +%feature("director") Measure; +%feature("director") Runner; + +%include +%include +%include + +%{ + #include + #include + #include +%} + +%include +%include +%include + +#endif //MEASURE_I + diff --git a/Model.cpp b/Model.cpp new file mode 100644 index 0000000..ec5beea --- /dev/null +++ b/Model.cpp @@ -0,0 +1,13 @@ +#include "Model.hpp" + +namespace Test +{ + Model::Model(std::string name) : name_(std::move(name)) + { + + } + + const std::string &Model::get_name() const { + return name_; + } +} \ No newline at end of file diff --git a/Model.hpp b/Model.hpp new file mode 100644 index 0000000..c7116a0 --- /dev/null +++ b/Model.hpp @@ -0,0 +1,18 @@ +#ifndef MODEL_HPP +#define MODEL_HPP + +#include + +namespace Test { +class Model +{ + public: + explicit Model(std::string name); + const std::string& get_name() const; + + private: + std::string name_; +}; +} // namespace Test + +#endif diff --git a/Person.cpp b/Person.cpp deleted file mode 100644 index 0a97770..0000000 --- a/Person.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "Person.hpp" - -#include - -namespace Test { - -Person::Person(const std::string& t_name) noexcept - : m_name(t_name) { } - -Person::Person(const Person& other) - : m_name(other.m_name) { } - - -std::string Person::getName() const { - return m_name; -} - -bool Person::setName(const std::string& t_newName) { - m_name = t_newName; - return true; -} - -std::ostream& operator<<(std::ostream& os, const Test::Person& p) { - os << "Person named '" << p.getName() << "'"; - return os; -} - -std::string personName(const Person& person) { - return person.getName(); -} - -} // namespace Test diff --git a/Person.hpp b/Person.hpp deleted file mode 100644 index f89f612..0000000 --- a/Person.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef EXAMPLE_HPP -#define EXAMPLE_HPP - -#include -#include -#include - -namespace Test { - -class Person { - public: - - Person(const std::string& name) noexcept; - - Person(const Person& other); - - std::string getName() const; - bool setName(const std::string& t_newName); - - private: - std::string m_name; -}; - -std::ostream& operator<<(std::ostream&, const Test::Person&); - -// A free-standing function taking an object as argument -std::string personName(const Person& person); -inline void setName(Person &p, const std::string &newname) { - p.setName(newname); -} - -// get an integral representation of the pointer that is this Person -inline long long toInt(Person &p) { - std::clog << "original pointer: " << &p << '\n'; - const auto result = reinterpret_cast(&p); - std::clog << "toInt from C++ " << result << '\n'; - return result; -} - -// take the integer from toInt and reinterpret_cast it back into a Person *, then return that as a reference -inline Person &fromInt(long long i) { - auto *ptr = reinterpret_cast(i); - std::clog << "Reclaimed pointer: " << ptr << '\n'; - return *ptr; -} - -} // Namespace Test - -#endif // ifndef PERSON_HPP diff --git a/Person.i b/Person.i deleted file mode 100644 index ff1ad08..0000000 --- a/Person.i +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef PERSON_I -#define PERSON_I - -%module mylib - -%include -%include - -%{ - #include - #include -%} - -%include - -%extend Test::Person { - // Use the overloaded operator<< for string representation - std::string __str__() { - std::ostringstream os; - os << *$self; - return os.str(); - } - #ifdef SWIGRUBY - // get an integral representation of the pointer that is this Person - inline long long __toInt() { - std::clog << "original pointer: " << $self << '\n'; - const auto result = reinterpret_cast($self); - std::clog << "toInt from C++ " << result << '\n'; - return result; - } - - - #endif - - #ifdef SWIGPYTHON - // take the integer from toInt and reinterpret_cast it back into a Person *, then return that as a reference - static inline Test::Person& _fromInt(long long i) { - auto *ptr = reinterpret_cast(i); - std::clog << "Reclaimed pointer: " << ptr << '\n'; - return *ptr; - } - #endif -}; - -#endif //PERSON_I - diff --git a/PythonEngine.cpp b/PythonEngine.cpp new file mode 100644 index 0000000..205f6d5 --- /dev/null +++ b/PythonEngine.cpp @@ -0,0 +1,35 @@ +#include "PythonEngine.hpp" +#include +#include + +namespace Test { + +PythonEngine::PythonEngine([[maybe_unused]] const int argc, const char* argv[]) : program(Py_DecodeLocale(argv[0], nullptr)) { + if (program == nullptr) { + fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); + exit(1); + } + + Py_SetProgramName(program); // optional but recommended + Py_Initialize(); +} + +PythonEngine::~PythonEngine() { + if (Py_FinalizeEx() < 0) { + exit(120); + } + PyMem_RawFree(program); +} + +ScriptObject PythonEngine::exec(std::string_view sv) { + std::string str{sv}; + PyRun_SimpleString(str.c_str()); + return ScriptObject{}; +} + +// convert the underlying object to the correct type, then return it as a void * +// so the above template function can provide it back to the caller. +void* PythonEngine::get_as(ScriptObject&, const std::type_info&) { + return nullptr; +} +} // namespace Test diff --git a/PythonEngine.hpp b/PythonEngine.hpp new file mode 100644 index 0000000..9efb690 --- /dev/null +++ b/PythonEngine.hpp @@ -0,0 +1,31 @@ +#ifndef PYTHONENGINE_included +#define PYTHONENGINE_included + +#include "ScriptEngine.hpp" + +namespace Test { +class PythonEngine final : public ScriptEngine +{ + public: + PythonEngine(const int argc, const char* argv[]); + ~PythonEngine() override; + + PythonEngine(const PythonEngine&) = delete; + PythonEngine(PythonEngine&&) = delete; + PythonEngine& operator=(const PythonEngine&) = delete; + PythonEngine& operator=(PythonEngine&&) = delete; + + ScriptObject exec(std::string_view sv) override; + + protected: + // convert the underlying object to the correct type, then return it as a void * + // so the above template function can provide it back to the caller. + void* get_as(ScriptObject& obj, const std::type_info&) override; + + private: + wchar_t *program; +}; +} + + +#endif diff --git a/RubyEngine.cpp b/RubyEngine.cpp new file mode 100644 index 0000000..a94d164 --- /dev/null +++ b/RubyEngine.cpp @@ -0,0 +1,37 @@ +#include "RubyEngine.hpp" +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wregister" +#endif + +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +namespace Test { + +RubyEngine::RubyEngine() { + ruby_setup(); +} + +RubyEngine::~RubyEngine() { + ruby_finalize(); +} + +ScriptObject RubyEngine::exec(std::string_view sv) { + std::string str{sv}; + rb_eval_string(str.c_str()); + return ScriptObject{}; +} + +// convert the underlying object to the correct type, then return it as a void * +// so the above template function can provide it back to the caller. +void* RubyEngine::get_as(ScriptObject&, const std::type_info&) { + return nullptr; +} + +} // namespace Test diff --git a/RubyEngine.hpp b/RubyEngine.hpp new file mode 100644 index 0000000..929ff56 --- /dev/null +++ b/RubyEngine.hpp @@ -0,0 +1,27 @@ +#ifndef RUBYENGINE_included +#define RUBYENGINE_included + +#include "ScriptEngine.hpp" + +namespace Test { +class RubyEngine final : public ScriptEngine +{ + public: + RubyEngine(); + ~RubyEngine() override; + + RubyEngine(const RubyEngine &) = delete; + RubyEngine(RubyEngine &&) = delete; + RubyEngine &operator=(const RubyEngine &) = delete; + RubyEngine &operator=(RubyEngine &&) = delete; + + ScriptObject exec(std::string_view sv) override; + + protected: + // convert the underlying object to the correct type, then return it as a void * + // so the above template function can provide it back to the caller. + void* get_as(ScriptObject& obj, const std::type_info&) override; +}; +} + +#endif diff --git a/Runner.hpp b/Runner.hpp new file mode 100644 index 0000000..824a67c --- /dev/null +++ b/Runner.hpp @@ -0,0 +1,26 @@ +#ifndef TEST_SWIG_RUNNER_HPP +#define TEST_SWIG_RUNNER_HPP + +namespace Test { +class Model; + +class Runner +{ + public: + virtual ~Runner() = default; + Runner &operator=(Runner &&) = delete; + Runner &operator=(const Runner &) = delete; + Runner(Runner &&) = delete; + Runner(const Runner &) = delete; + Runner() = default; + + Model &get_current_model() { return get_current_model_impl(); } + + protected: + + virtual Model& get_current_model_impl() = 0; + +}; +} // namespace Test + +#endif //TEST_SWIG_RUNNER_HPP diff --git a/ScriptEngine.hpp b/ScriptEngine.hpp new file mode 100644 index 0000000..454cbfd --- /dev/null +++ b/ScriptEngine.hpp @@ -0,0 +1,49 @@ +#ifndef SCRIPTENGINE_included +#define SCRIPTENGINE_included + +#include +#include +#include + +namespace Test +{ + class ScriptObject { + std::any object; + friend class ScriptEngine; + }; + + class ScriptEngine { + public: + ScriptEngine() = default; + virtual ~ScriptEngine() = default; + ScriptEngine(const ScriptEngine&) = delete; + ScriptEngine(ScriptEngine&&) = delete; + ScriptEngine& operator=(const ScriptEngine&) = delete; + ScriptEngine& operator=(ScriptEngine&&) = delete; + + virtual ScriptObject exec(std::string_view sv) = 0; + + template + T &get_as(ScriptObject &obj) { + void *result = get_as(obj, typeid(T)); + if (result) { + return static_cast(result); + } else { + throw std::bad_cast(); + } + } + + protected: + // convert the underlying object to the correct type, then return it as a void * + // so the above template function can provide it back to the caller. + virtual void *get_as(ScriptObject &obj, const std::type_info &) = 0; + + static std::any &getInternals(ScriptObject &obj) noexcept { return obj.object; } + }; + + +} + +#endif + + diff --git a/SpecialRunner.hpp b/SpecialRunner.hpp new file mode 100644 index 0000000..41ba71e --- /dev/null +++ b/SpecialRunner.hpp @@ -0,0 +1,26 @@ +#ifndef TEST_SWIG_SPECIALRUNNER_HPP +#define TEST_SWIG_SPECIALRUNNER_HPP + +#include "Runner.hpp" +#include "Model.hpp" + +namespace Test +{ +// a concrete implementation of the Runner class, for testing purposes +class SpecialRunner : public Runner{ + public: + SpecialRunner(Model model) : model_(std::move(model)) + { + + } + + protected: + Model &get_current_model_impl() override { + return model_; + } + private: + Model model_; +}; +} + +#endif //TEST_SWIG_SPECIALRUNNER_HPP diff --git a/main.cpp b/main.cpp index 8c17240..212801b 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,14 @@ -#include "Person.hpp" #include +#include "RubyEngine.hpp" +#include "PythonEngine.hpp" + + + +int main(const int argc, const char* argv[]) { + Test::RubyEngine ruby; + Test::PythonEngine python{argc, argv}; + + python.exec(R"(print("Hello From Python"))"); + ruby.exec(R"(puts("Hello from Ruby"))"); -int main() { - Test::Person p("John Doe"); - std::cout << p << '\n'; } From cfdb46314d1e25838294c4e0e6f72631c4f29c1a Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Wed, 24 Mar 2021 15:51:32 -0600 Subject: [PATCH 02/10] "Working" (except that it doesn't) --- CMakeLists.txt | 8 +-- Measure.cpp | 7 +- Measure.hpp | 7 +- Measure.i | 1 - Model.cpp | 22 +++--- Model.hpp | 8 ++- PythonEngine.cpp | 136 +++++++++++++++++++++++++++++++++++-- PythonEngine.hpp | 10 +-- RubyEngine.cpp | 58 ++++++++++++++-- RubyEngine.hpp | 15 ++-- ScriptEngine.hpp | 97 ++++++++++++++++---------- SpecialRunner.hpp | 18 +++-- main.cpp | 38 ++++++++++- python/measure.py | 9 --- python/test_measure.py | 22 ++++++ python/test_python_only.py | 29 -------- ruby/test_measure.rb | 14 ++++ 17 files changed, 365 insertions(+), 134 deletions(-) delete mode 100644 python/measure.py create mode 100644 python/test_measure.py delete mode 100644 python/test_python_only.py create mode 100644 ruby/test_measure.rb diff --git a/CMakeLists.txt b/CMakeLists.txt index c4dadc7..c81ab23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,10 @@ if(MSVC) else() target_compile_options( cpp_warning_options INTERFACE -Wall -Wextra -Wconversion -Wpedantic -Werror) - target_compile_options(cpp_compile_options - INTERFACE -fsanitize=undefined,address) - target_link_options(cpp_compile_options INTERFACE - -fsanitize=undefined,address) +# target_compile_options(cpp_compile_options +# INTERFACE -fsanitize=undefined,address) +# target_link_options(cpp_compile_options INTERFACE +# -fsanitize=undefined,address) endif() # Do not enable compiler specific extensions, for eg on GCC use -std=c++1z diff --git a/Measure.cpp b/Measure.cpp index b4dc5f1..ad91c92 100644 --- a/Measure.cpp +++ b/Measure.cpp @@ -1,10 +1,7 @@ #include "Measure.hpp" namespace Test { -std::vector Measure::arguments(const Model& model) const { - return arguments_impl(model); -} -bool Measure::run(Runner& runner, const std::vector& args) { - return run_impl(runner, args); +bool Measure::run(Runner& runner) { + return run_impl(runner); } } // namespace Test \ No newline at end of file diff --git a/Measure.hpp b/Measure.hpp index 7be1be7..30e6694 100644 --- a/Measure.hpp +++ b/Measure.hpp @@ -21,14 +21,13 @@ class Measure Measure& operator=(Measure&&) = delete; virtual ~Measure() = default; - std::vector arguments(const Model&) const; - bool run(Runner&, const std::vector& ); + virtual std::string name() = 0; + bool run(Runner&); protected: // protected virtual here allows us to easily change the internal interface // and behavior of measure - virtual std::vector arguments_impl(const Model&) const = 0; - virtual bool run_impl(Runner&, const std::vector& ) = 0; + virtual bool run_impl(Runner&) = 0; }; diff --git a/Measure.i b/Measure.i index 597b464..8fbc178 100644 --- a/Measure.i +++ b/Measure.i @@ -5,7 +5,6 @@ %module(directors="1") mylib %feature("director") Measure; -%feature("director") Runner; %include %include diff --git a/Model.cpp b/Model.cpp index ec5beea..b4f21e6 100644 --- a/Model.cpp +++ b/Model.cpp @@ -1,13 +1,17 @@ #include "Model.hpp" -namespace Test -{ - Model::Model(std::string name) : name_(std::move(name)) - { +namespace Test { +Model::Model(std::string name) : name_(std::move(name)) {} - } +const std::string& Model::getName() const { + return name_; +} - const std::string &Model::get_name() const { - return name_; - } -} \ No newline at end of file +void Model::pushOp(const std::string& op_name) { + opsPerformed_.push_back(op_name); +} + +const std::vector& Model::opsPerformed() const { + return opsPerformed_; +} +} // namespace Test \ No newline at end of file diff --git a/Model.hpp b/Model.hpp index c7116a0..c239b37 100644 --- a/Model.hpp +++ b/Model.hpp @@ -2,16 +2,22 @@ #define MODEL_HPP #include +#include namespace Test { class Model { public: explicit Model(std::string name); - const std::string& get_name() const; + const std::string& getName() const; + + void pushOp(const std::string& op_name); + + const std::vector& opsPerformed() const; private: std::string name_; + std::vector opsPerformed_; }; } // namespace Test diff --git a/PythonEngine.cpp b/PythonEngine.cpp index 205f6d5..2164ea5 100644 --- a/PythonEngine.cpp +++ b/PythonEngine.cpp @@ -1,7 +1,19 @@ #include "PythonEngine.hpp" -#include + #include +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#include +#include "SWIGPythonRuntime.hxx" + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + namespace Test { PythonEngine::PythonEngine([[maybe_unused]] const int argc, const char* argv[]) : program(Py_DecodeLocale(argv[0], nullptr)) { @@ -21,15 +33,125 @@ PythonEngine::~PythonEngine() { PyMem_RawFree(program); } -ScriptObject PythonEngine::exec(std::string_view sv) { - std::string str{sv}; - PyRun_SimpleString(str.c_str()); - return ScriptObject{}; +struct PythonObject { + PythonObject() = default; + + PythonObject(PyObject *obj) noexcept : obj_(obj) + { + if (obj_) { + Py_INCREF(obj_); + } + } + + PythonObject(const PythonObject &other) noexcept + : obj_(other.obj_) { + if (obj_) { + Py_INCREF(obj_); + } + } + + PythonObject(PythonObject &&other) noexcept + : obj_(other.obj_) { + // no reason to inc/dec, we just stole the ref counted object + // from other + other.obj_ = nullptr; + } + + PythonObject &operator=(const PythonObject &rhs) noexcept { + if (&rhs != this) { + obj_ = rhs.obj_; + + if (obj_) { + Py_INCREF(obj_); + } + } + + return *this; + } + + PythonObject &operator=(PythonObject &&rhs) noexcept { + if (&rhs != this) { + obj_ = rhs.obj_; + rhs.obj_ = nullptr; + } + + return *this; + } + + ~PythonObject() { + if (obj_) { + Py_DECREF(obj_); + } + } + + PyObject *obj_ = nullptr; +}; + +void PythonEngine::exec(std::string_view sv) { + std::string command{sv}; + + PyObject* m = PyImport_AddModule("__main__"); + if (m == nullptr) { + throw std::runtime_error("Unable to add module __main__ for python script execution"); + } + + PyObject* d = PyModule_GetDict(m); + PyObject* v = PyRun_String(command.c_str(), Py_file_input, d, d); + if (v == nullptr) { + PyErr_Print(); + throw std::runtime_error("Error executing Python code"); + } + + //decref count returned from Python + Py_DECREF(v); +} + +ScriptObject PythonEngine::eval(std::string_view sv) { + std::string command{sv}; + + PyObject* m = PyImport_AddModule("__main__"); + if (m == nullptr) { + throw std::runtime_error("Unable to add module __main__ for python script execution"); + } + + PyObject* d = PyModule_GetDict(m); + PyObject* v = PyRun_String(command.c_str(), Py_eval_input, d, d); + if (v == nullptr) { + PyErr_Print(); + throw std::runtime_error("Error executing Python code"); + } + + //share in ownership + PythonObject return_value(v); + + //decref count returned from Python + Py_DECREF(v); + + return ScriptObject{return_value}; } // convert the underlying object to the correct type, then return it as a void * // so the above template function can provide it back to the caller. -void* PythonEngine::get_as(ScriptObject&, const std::type_info&) { - return nullptr; +void* PythonEngine::getAs_impl(ScriptObject& obj, const std::type_info& ti) { + + auto val = std::any_cast(obj.object); + + const auto& type_name = getRegisteredTypeName(ti); + + void* return_value = nullptr; + + auto* type = SWIG_TypeQuery(type_name.c_str()); + + if (!type) { + throw std::runtime_error("Unable to find type in SWIG"); + } + + const auto result = SWIG_Python_ConvertPtr(val.obj_, &return_value, type, 0); + + if (!SWIG_IsOK(result)) { + throw std::runtime_error("Error getting object from SWIG/Python"); + } + + return return_value; } } // namespace Test diff --git a/PythonEngine.hpp b/PythonEngine.hpp index 9efb690..15f4dc5 100644 --- a/PythonEngine.hpp +++ b/PythonEngine.hpp @@ -15,17 +15,17 @@ class PythonEngine final : public ScriptEngine PythonEngine& operator=(const PythonEngine&) = delete; PythonEngine& operator=(PythonEngine&&) = delete; - ScriptObject exec(std::string_view sv) override; + ScriptObject eval(std::string_view sv) override; + void exec(std::string_view sv) override; protected: // convert the underlying object to the correct type, then return it as a void * // so the above template function can provide it back to the caller. - void* get_as(ScriptObject& obj, const std::type_info&) override; + void* getAs_impl(ScriptObject& obj, const std::type_info&) override; private: - wchar_t *program; + wchar_t* program; }; -} - +} // namespace Test #endif diff --git a/RubyEngine.cpp b/RubyEngine.cpp index a94d164..a4d17cd 100644 --- a/RubyEngine.cpp +++ b/RubyEngine.cpp @@ -1,12 +1,14 @@ #include "RubyEngine.hpp" + #include #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wregister" +# pragma GCC diagnostic ignored "-Wunused-parameter" #endif -#include +#include "SWIGRubyRuntime.hxx" #ifdef __GNUC__ # pragma GCC diagnostic pop @@ -22,16 +24,60 @@ RubyEngine::~RubyEngine() { ruby_finalize(); } -ScriptObject RubyEngine::exec(std::string_view sv) { +static VALUE evaluateSimpleImpl(VALUE arg) { + return rb_eval_string(StringValuePtr(arg)); +} + +VALUE evalString(const std::string& t_str) { + VALUE val = rb_str_new2(t_str.c_str()); + int error; + // save and restore the current working directory in case the call to ruby upsets it + VALUE result = rb_protect(evaluateSimpleImpl, val, &error); + if (error != 0) { + VALUE errval = rb_eval_string("$!.to_s"); + char* str = StringValuePtr(errval); + std::string err(str); + VALUE locval = rb_eval_string("$@.to_s"); + str = StringValuePtr(locval); + std::string loc(str); + throw std::runtime_error("Error '" + err + "' at " + loc); + } + + return result; +} + +ScriptObject RubyEngine::eval(std::string_view sv) { + std::string str{sv}; + return ScriptObject{evalString(str)}; +} + +void RubyEngine::exec(std::string_view sv) { std::string str{sv}; - rb_eval_string(str.c_str()); - return ScriptObject{}; + [[maybe_unused]] const auto result = evalString(str); } // convert the underlying object to the correct type, then return it as a void * // so the above template function can provide it back to the caller. -void* RubyEngine::get_as(ScriptObject&, const std::type_info&) { - return nullptr; +void* RubyEngine::getAs_impl(ScriptObject& obj, const std::type_info &ti) { + auto val = std::any_cast(obj.object); + + const auto &type_name = getRegisteredTypeName(ti); + + void *return_value = nullptr; + + auto *type = SWIG_TypeQuery(type_name.c_str()); + + if (!type) { + throw std::runtime_error("Unable to find type in SWIG"); + } + + const auto result = SWIG_ConvertPtr(val, &return_value, type, 0); + + if (!SWIG_IsOK(result)) { + throw std::runtime_error("Error getting object from SWIG/Ruby"); + } + + return return_value; } } // namespace Test diff --git a/RubyEngine.hpp b/RubyEngine.hpp index 929ff56..8eee703 100644 --- a/RubyEngine.hpp +++ b/RubyEngine.hpp @@ -10,18 +10,19 @@ class RubyEngine final : public ScriptEngine RubyEngine(); ~RubyEngine() override; - RubyEngine(const RubyEngine &) = delete; - RubyEngine(RubyEngine &&) = delete; - RubyEngine &operator=(const RubyEngine &) = delete; - RubyEngine &operator=(RubyEngine &&) = delete; + RubyEngine(const RubyEngine&) = delete; + RubyEngine(RubyEngine&&) = delete; + RubyEngine& operator=(const RubyEngine&) = delete; + RubyEngine& operator=(RubyEngine&&) = delete; - ScriptObject exec(std::string_view sv) override; + ScriptObject eval(std::string_view sv) override; + void exec(std::string_view sv) override; protected: // convert the underlying object to the correct type, then return it as a void * // so the above template function can provide it back to the caller. - void* get_as(ScriptObject& obj, const std::type_info&) override; + void* getAs_impl(ScriptObject& obj, const std::type_info&) override; }; -} +} // namespace Test #endif diff --git a/ScriptEngine.hpp b/ScriptEngine.hpp index 454cbfd..e5f5b45 100644 --- a/ScriptEngine.hpp +++ b/ScriptEngine.hpp @@ -1,49 +1,74 @@ #ifndef SCRIPTENGINE_included #define SCRIPTENGINE_included -#include #include +#include +#include #include +#include -namespace Test +namespace Test { +struct ScriptObject { - class ScriptObject { - std::any object; - friend class ScriptEngine; - }; + std::any object; +}; - class ScriptEngine { - public: - ScriptEngine() = default; - virtual ~ScriptEngine() = default; - ScriptEngine(const ScriptEngine&) = delete; - ScriptEngine(ScriptEngine&&) = delete; - ScriptEngine& operator=(const ScriptEngine&) = delete; - ScriptEngine& operator=(ScriptEngine&&) = delete; - - virtual ScriptObject exec(std::string_view sv) = 0; - - template - T &get_as(ScriptObject &obj) { - void *result = get_as(obj, typeid(T)); - if (result) { - return static_cast(result); - } else { - throw std::bad_cast(); - } - } - - protected: - // convert the underlying object to the correct type, then return it as a void * - // so the above template function can provide it back to the caller. - virtual void *get_as(ScriptObject &obj, const std::type_info &) = 0; - - static std::any &getInternals(ScriptObject &obj) noexcept { return obj.object; } - }; +class ScriptEngine +{ + public: + ScriptEngine() = default; + virtual ~ScriptEngine() = default; + ScriptEngine(const ScriptEngine&) = delete; + ScriptEngine(ScriptEngine&&) = delete; + ScriptEngine& operator=(const ScriptEngine&) = delete; + ScriptEngine& operator=(ScriptEngine&&) = delete; + virtual ScriptObject eval(std::string_view sv) = 0; -} + // execute string without expecting a return value + virtual void exec(std::string_view sv) = 0; -#endif + template + T getAs(ScriptObject& obj) { + void* result = getAs_impl(obj, typeid(T)); + if (result) { + return static_cast(result); + } else { + throw std::bad_cast(); + } + } + + template + void registerType(std::string name) { + types.emplace(std::cref(typeid(T)), std::move(name)); + } + protected: + // convert the underlying object to the correct type, then return it as a void * + // so the above template function can provide it back to the caller. + virtual void* getAs_impl(ScriptObject& obj, const std::type_info&) = 0; + const std::string& getRegisteredTypeName(const std::type_info& type) { + const auto& found_name = types.find(type); + + if (found_name != types.end()) { + return found_name->second; + } + + throw std::runtime_error("unknown type requested"); + } + + private: + struct Compare + { + bool operator()(const std::reference_wrapper &lhs, const std::reference_wrapper& rhs) { + return lhs.get().before(rhs.get()); + } + }; + + std::map, std::string, Compare> types; +}; + +} // namespace Test + +#endif diff --git a/SpecialRunner.hpp b/SpecialRunner.hpp index 41ba71e..b706fe8 100644 --- a/SpecialRunner.hpp +++ b/SpecialRunner.hpp @@ -4,23 +4,21 @@ #include "Runner.hpp" #include "Model.hpp" -namespace Test -{ +namespace Test { // a concrete implementation of the Runner class, for testing purposes -class SpecialRunner : public Runner{ +class SpecialRunner : public Runner +{ public: - SpecialRunner(Model model) : model_(std::move(model)) - { - - } + SpecialRunner(Model model) : model_(std::move(model)) {} protected: - Model &get_current_model_impl() override { + Model& get_current_model_impl() override { return model_; } - private: + + private: Model model_; }; -} +} // namespace Test #endif //TEST_SWIG_SPECIALRUNNER_HPP diff --git a/main.cpp b/main.cpp index 212801b..648dc37 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,11 @@ #include +#include #include "RubyEngine.hpp" #include "PythonEngine.hpp" +#include "Measure.hpp" +#include "SpecialRunner.hpp" - +#include "Model.hpp" int main(const int argc, const char* argv[]) { Test::RubyEngine ruby; @@ -11,4 +14,37 @@ int main(const int argc, const char* argv[]) { python.exec(R"(print("Hello From Python"))"); ruby.exec(R"(puts("Hello from Ruby"))"); + ruby.registerType("Test::Measure *"); + python.registerType("Test::Measure *"); + + ruby.exec("require '/home/jason/Cpp_Swig_Ruby_Python_MCVE/ruby/test_measure.rb'"); + auto ruby_measure = ruby.eval("RubyTestMeasure.new()"); + auto* ruby_measure_from_cpp = ruby.getAs(ruby_measure); + assert(ruby_measure_from_cpp); + std::cout << "Ruby measure name: " << ruby_measure_from_cpp->name() << '\n'; + + python.exec("import sys\nsys.path.append('/home/jason/Cpp_Swig_Ruby_Python_MCVE/python/')"); + python.exec("import test_measure"); + auto python_measure = python.eval("test_measure.PythonTestMeasure()"); + auto* python_measure_from_cpp = python.getAs(python_measure); + assert(python_measure_from_cpp); + + // this all works up until this moment right here. + // At this point it doesn't call the SWIG Director for Python + // It actually calls the SWIG Director for Ruby. + // Why? Because we've broken the One Definition Rule. + // SWIG defines functions with the same names for both the Python bindings and the Ruby bindings. + // whichever one gets loaded first wins. + // + // Can we work around this? Maybe. But having both Ruby and Python linked into the same application + // is asking for many problems. + std::cout << "Python measure name: " << python_measure_from_cpp->name() << '\n'; + + Test::SpecialRunner sr(Test::Model{"MyModel"}); + ruby_measure_from_cpp->run(sr); + // python_measure_from_cpp->run(sr); + + for (const auto& op : sr.get_current_model().opsPerformed()) { + std::cout << "Op 'run' from script: " << op << '\n'; + } } diff --git a/python/measure.py b/python/measure.py deleted file mode 100644 index a2ad292..0000000 --- a/python/measure.py +++ /dev/null @@ -1,9 +0,0 @@ -import mylib - -class PythonMeasureName: - def run(self, p: mylib.Person, name: str): - print(f"Python script.py: {p.getName()}") - print(f"Changing name to {name}") - p.setName(name) - print(f"Python script.py: {p.getName()}") - return True diff --git a/python/test_measure.py b/python/test_measure.py new file mode 100644 index 0000000..18d5c04 --- /dev/null +++ b/python/test_measure.py @@ -0,0 +1,22 @@ + +import sys + +sys.path.append("/home/jason/Cpp_Swig_Ruby_Python_MCVE/cmake-build-debug/Products/python/") + +import mylib + +class PythonTestMeasure(mylib.Measure): + def __init__(self): + mylib.Measure.__init__(self) + print("Created Object") + + def run(self, r: mylib.Runner): + r.get_current_model().pushOp("Op from Python") + return True + def name(self): + return "Python Test Measure" + + +def make_measure(): + return PythonTestMeasure() + diff --git a/python/test_python_only.py b/python/test_python_only.py deleted file mode 100644 index e58e2a0..0000000 --- a/python/test_python_only.py +++ /dev/null @@ -1,29 +0,0 @@ -import sys -from pathlib import Path -#import importlib.util - -python_lib_path = Path('../build/Products/python/mylib.py').resolve() - -if not python_lib_path.exists(): - print("Error, this assumes you already built the python bindings project in /build") - raise IOError(f"Could Not find {python_lib_path}") - -# spec = importlib.util.spec_from_file_location("person", - # str(python_lib_path)) -# person = importlib.util.module_from_spec(spec) -# spec.loader.exec_module(person) -sys.path.insert(0, str(python_lib_path.parent)) -import mylib - -def get_a_python_person(): - p = mylib.Person("John") - return p - -def print_a_person(p: mylib.Person): - return mylib.personName(p) - - -if __name__ == '__main__': - p = mylib.Person("John") - print(p) - print(f"{mylib.personName(p)=}") diff --git a/ruby/test_measure.rb b/ruby/test_measure.rb new file mode 100644 index 0000000..b696db9 --- /dev/null +++ b/ruby/test_measure.rb @@ -0,0 +1,14 @@ +require '/home/jason/Cpp_Swig_Ruby_Python_MCVE/cmake-build-debug/Products/ruby/mylib.so' + +class RubyTestMeasure < Mylib::Measure + def name + return "RubyTestMeasure" + end + + def run_impl(runner) + runner.get_current_model().pushOp("A Ruby Op") + return true; + end +end + + From 977e50b3cf34cda8b03a8c11ba24d23ef7fcb575 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Fri, 7 May 2021 11:11:29 +0200 Subject: [PATCH 03/10] Change path to my machine, add some imports (gcc-10 was complaining... evcentually I switched to gcc 7.5 since 10 wouldn't build) --- .gitignore | 2 +- Measure.i | 23 ++++++++++++++++++++++- PythonEngine.cpp | 1 + RubyEngine.cpp | 1 + ScriptEngine.hpp | 4 +++- main.cpp | 4 ++-- python/test_measure.py | 2 +- ruby/test_measure.rb | 2 +- 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3a6adca..dd458d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build/ +build*/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Measure.i b/Measure.i index 8fbc178..8e411a8 100644 --- a/Measure.i +++ b/Measure.i @@ -1,8 +1,29 @@ #ifndef MEASURE_I #define MEASURE_I + +%begin %{ + // ... code in begin section ... +%} + +%runtime %{ + // ... code in runtime section ... +%} + +%header %{ + // ... code in header section ... +%} + +%wrapper %{ + // ... code in wrapper section ... +%} + +%init %{ + // ... code in init section ... +%} + %module mylib -%module(directors="1") mylib +%module(directors="1") mylib %feature("director") Measure; diff --git a/PythonEngine.cpp b/PythonEngine.cpp index 2164ea5..51bdf1c 100644 --- a/PythonEngine.cpp +++ b/PythonEngine.cpp @@ -1,6 +1,7 @@ #include "PythonEngine.hpp" #include +#include #ifdef __GNUC__ # pragma GCC diagnostic push diff --git a/RubyEngine.cpp b/RubyEngine.cpp index a4d17cd..a1a4203 100644 --- a/RubyEngine.cpp +++ b/RubyEngine.cpp @@ -1,6 +1,7 @@ #include "RubyEngine.hpp" #include +#include #ifdef __GNUC__ # pragma GCC diagnostic push diff --git a/ScriptEngine.hpp b/ScriptEngine.hpp index e5f5b45..10a019d 100644 --- a/ScriptEngine.hpp +++ b/ScriptEngine.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace Test { struct ScriptObject @@ -65,7 +67,7 @@ class ScriptEngine return lhs.get().before(rhs.get()); } }; - + std::map, std::string, Compare> types; }; diff --git a/main.cpp b/main.cpp index 648dc37..721a4ee 100644 --- a/main.cpp +++ b/main.cpp @@ -17,13 +17,13 @@ int main(const int argc, const char* argv[]) { ruby.registerType("Test::Measure *"); python.registerType("Test::Measure *"); - ruby.exec("require '/home/jason/Cpp_Swig_Ruby_Python_MCVE/ruby/test_measure.rb'"); + ruby.exec("require '/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/ruby/test_measure.rb'"); auto ruby_measure = ruby.eval("RubyTestMeasure.new()"); auto* ruby_measure_from_cpp = ruby.getAs(ruby_measure); assert(ruby_measure_from_cpp); std::cout << "Ruby measure name: " << ruby_measure_from_cpp->name() << '\n'; - python.exec("import sys\nsys.path.append('/home/jason/Cpp_Swig_Ruby_Python_MCVE/python/')"); + python.exec("import sys\nsys.path.append('/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/python/')"); python.exec("import test_measure"); auto python_measure = python.eval("test_measure.PythonTestMeasure()"); auto* python_measure_from_cpp = python.getAs(python_measure); diff --git a/python/test_measure.py b/python/test_measure.py index 18d5c04..af6ea7e 100644 --- a/python/test_measure.py +++ b/python/test_measure.py @@ -1,7 +1,7 @@ import sys -sys.path.append("/home/jason/Cpp_Swig_Ruby_Python_MCVE/cmake-build-debug/Products/python/") +sys.path.append("/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/build-modif/Products/python/") import mylib diff --git a/ruby/test_measure.rb b/ruby/test_measure.rb index b696db9..33a768d 100644 --- a/ruby/test_measure.rb +++ b/ruby/test_measure.rb @@ -1,4 +1,4 @@ -require '/home/jason/Cpp_Swig_Ruby_Python_MCVE/cmake-build-debug/Products/ruby/mylib.so' +require '/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/build-modif/Products/ruby/mylib.so' class RubyTestMeasure < Mylib::Measure def name From e93286bd39d4c29001fed54fd2e62a14a7eac718 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Fri, 7 May 2021 11:11:58 +0200 Subject: [PATCH 04/10] When SWIG'ing to Python, rename "Measure" to "PythonMeasure" so that we don't have SwigDirector_Measure name clashing --- Measure.i | 8 +++++++- python/test_measure.py | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Measure.i b/Measure.i index 8e411a8..04fdef2 100644 --- a/Measure.i +++ b/Measure.i @@ -25,12 +25,18 @@ %module mylib %module(directors="1") mylib -%feature("director") Measure; + %include %include %include +%feature("director") Measure; + +#if defined(SWIGPYTHON) +%rename (PythonMeasure) Test::Measure; +#endif + %{ #include #include diff --git a/python/test_measure.py b/python/test_measure.py index af6ea7e..7c86c31 100644 --- a/python/test_measure.py +++ b/python/test_measure.py @@ -5,10 +5,10 @@ import mylib -class PythonTestMeasure(mylib.Measure): +class PythonTestMeasure(mylib.PythonMeasure): def __init__(self): - mylib.Measure.__init__(self) - print("Created Object") + mylib.PythonMeasure.__init__(self) + print("Created Object (Python __init__)") def run(self, r: mylib.Runner): r.get_current_model().pushOp("Op from Python") From 73f7ee1c07cd8802ff400ef8e85f2b2c2767b49c Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Fri, 7 May 2021 12:33:43 +0200 Subject: [PATCH 05/10] Uncomment python_measure.run in main: it crashes --- main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 721a4ee..bbdbf5f 100644 --- a/main.cpp +++ b/main.cpp @@ -42,7 +42,7 @@ int main(const int argc, const char* argv[]) { Test::SpecialRunner sr(Test::Model{"MyModel"}); ruby_measure_from_cpp->run(sr); - // python_measure_from_cpp->run(sr); + python_measure_from_cpp->run(sr); for (const auto& op : sr.get_current_model().opsPerformed()) { std::cout << "Op 'run' from script: " << op << '\n'; From 7354cfe449a05290a377a6803bbfbb4b85a29ee8 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Fri, 7 May 2021 12:33:59 +0200 Subject: [PATCH 06/10] The python measure.py should have run_impl inside, not run! it works --- python/test_measure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test_measure.py b/python/test_measure.py index 7c86c31..ea1ce1f 100644 --- a/python/test_measure.py +++ b/python/test_measure.py @@ -10,7 +10,7 @@ def __init__(self): mylib.PythonMeasure.__init__(self) print("Created Object (Python __init__)") - def run(self, r: mylib.Runner): + def run_impl(self, r: mylib.Runner): r.get_current_model().pushOp("Op from Python") return True def name(self): From 77d34ef85357a7a51f32efe44f51f9430061a836 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 10 May 2021 09:46:04 +0200 Subject: [PATCH 07/10] Comparator must be const: `static assertion failed: comparison object must be invocable as const` --- ScriptEngine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ScriptEngine.hpp b/ScriptEngine.hpp index 10a019d..85c9fb6 100644 --- a/ScriptEngine.hpp +++ b/ScriptEngine.hpp @@ -63,7 +63,7 @@ class ScriptEngine private: struct Compare { - bool operator()(const std::reference_wrapper &lhs, const std::reference_wrapper& rhs) { + bool operator()(const std::reference_wrapper &lhs, const std::reference_wrapper& rhs) const { return lhs.get().before(rhs.get()); } }; From 4774fa80a269f5a818fd9e32b23d717e0cb5bdc3 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 10 May 2021 17:09:44 +0200 Subject: [PATCH 08/10] Extend test to get and set Model name from C++, ruby, and python. Crashes --- Model.cpp | 8 +++++++- Model.hpp | 1 + main.cpp | 5 ++++- python/test_measure.py | 2 ++ ruby/test_measure.rb | 2 ++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Model.cpp b/Model.cpp index b4f21e6..d2aa454 100644 --- a/Model.cpp +++ b/Model.cpp @@ -7,6 +7,12 @@ const std::string& Model::getName() const { return name_; } +bool Model::setName(const std::string& name) { + name_ = name; + return true; +} + + void Model::pushOp(const std::string& op_name) { opsPerformed_.push_back(op_name); } @@ -14,4 +20,4 @@ void Model::pushOp(const std::string& op_name) { const std::vector& Model::opsPerformed() const { return opsPerformed_; } -} // namespace Test \ No newline at end of file +} // namespace Test diff --git a/Model.hpp b/Model.hpp index c239b37..6ac5b74 100644 --- a/Model.hpp +++ b/Model.hpp @@ -10,6 +10,7 @@ class Model public: explicit Model(std::string name); const std::string& getName() const; + bool setName(const std::string& name); void pushOp(const std::string& op_name); diff --git a/main.cpp b/main.cpp index bbdbf5f..00a20d1 100644 --- a/main.cpp +++ b/main.cpp @@ -40,10 +40,13 @@ int main(const int argc, const char* argv[]) { // is asking for many problems. std::cout << "Python measure name: " << python_measure_from_cpp->name() << '\n'; - Test::SpecialRunner sr(Test::Model{"MyModel"}); + Test::SpecialRunner sr(Test::Model{"C++ Model"}); + std::cout << "Starting out with a Model in C++ called: " << sr.get_current_model().getName() << '\n'; ruby_measure_from_cpp->run(sr); python_measure_from_cpp->run(sr); + std::cout << "After Running Ruby and Python: model is named " << sr.get_current_model().getName() << '\n'; + for (const auto& op : sr.get_current_model().opsPerformed()) { std::cout << "Op 'run' from script: " << op << '\n'; } diff --git a/python/test_measure.py b/python/test_measure.py index ea1ce1f..05d7d9f 100644 --- a/python/test_measure.py +++ b/python/test_measure.py @@ -11,7 +11,9 @@ def __init__(self): print("Created Object (Python __init__)") def run_impl(self, r: mylib.Runner): + print(f"Python Model named: {r.get_current_model().getName()}") r.get_current_model().pushOp("Op from Python") + r.get_current_model().setName("Python Model") return True def name(self): return "Python Test Measure" diff --git a/ruby/test_measure.rb b/ruby/test_measure.rb index 33a768d..efc4087 100644 --- a/ruby/test_measure.rb +++ b/ruby/test_measure.rb @@ -6,6 +6,8 @@ def name end def run_impl(runner) + puts "Ruby Model named: #{runner.get_current_model().getName()}" + runner.get_current_model().setName("Ruby Model") runner.get_current_model().pushOp("A Ruby Op") return true; end From 15178e7dcd65b30d2d92a5b6f1677b3da86aafb2 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 10 May 2021 17:10:07 +0200 Subject: [PATCH 09/10] Rremove pedantic, so ruby doesn't make it crash on some compilers... --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c81ab23..a0df375 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ if(MSVC) target_compile_options(cpp_warning_options INTERFACE /W4 /Werror) else() target_compile_options( - cpp_warning_options INTERFACE -Wall -Wextra -Wconversion -Wpedantic -Werror) + cpp_warning_options INTERFACE -Wall -Wextra -Wconversion -Werror) # target_compile_options(cpp_compile_options # INTERFACE -fsanitize=undefined,address) # target_link_options(cpp_compile_options INTERFACE From 94097a2a051c93be7b6dc789954260e51f265279 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Tue, 11 May 2021 09:49:09 +0200 Subject: [PATCH 10/10] Works, but dynamic: Extend tests to tweak model objects. Add ability to turn off python for testing --- CMakeLists.txt | 97 +++++++++++++++++++++++++++--------------- Measure.i | 4 ++ Model.cpp | 49 +++++++++++++++------ Model.hpp | 30 +++++++------ ModelObject.cpp | 19 +++++++++ ModelObject.hpp | 19 +++++++++ PythonEngine.cpp | 2 +- main.cpp | 58 ++++++++++++++++++++++--- python/test_measure.py | 15 +++++-- ruby/test_measure.rb | 16 +++++-- 10 files changed, 236 insertions(+), 73 deletions(-) create mode 100644 ModelObject.cpp create mode 100644 ModelObject.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a0df375..1d8b830 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}" set(target_name mylib) -set(${target_name}_src Measure.hpp Measure.cpp Model.hpp Model.cpp Runner.hpp +set(${target_name}_src Measure.hpp Measure.cpp Model.hpp Model.cpp ModelObject.hpp ModelObject.cpp Runner.hpp SpecialRunner.hpp) add_library(${target_name} SHARED ${${target_name}_src}) @@ -115,6 +115,8 @@ include(CTest) if(BUILD_PYTHON_BINDINGS) + add_compile_definitions(WITHPYTHON) + set(swig_target_name ${swig_target_name}_python) find_package( @@ -157,6 +159,15 @@ if(BUILD_PYTHON_BINDINGS) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/python COMMAND ${Python_EXECUTABLE} test_python_only.rb) + # Must call CMake itself in order to set the SWIG_LIB env var for + # add_custom_command + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx" + COMMAND + ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" + "-python" -external-runtime + "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx") + endif() if(BUILD_RUBY_BINDINGS) @@ -195,6 +206,16 @@ if(BUILD_RUBY_BINDINGS) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby COMMAND ${RUBY_EXECUTABLE} test_ruby_only.py) + + # Must call CMake itself in order to set the SWIG_LIB env var for + # add_custom_command + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" + COMMAND + ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" + "-ruby" -external-runtime + "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx") + endif() if(BUILD_RUBY_BINDINGS AND BUILD_PYTHON_BINDINGS) @@ -221,39 +242,47 @@ if(BUILD_RUBY_BINDINGS AND BUILD_PYTHON_BINDINGS) endif() -# Must call CMake itself in order to set the SWIG_LIB env var for -# add_custom_command -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx" - COMMAND - ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" - "-python" -external-runtime +if (BUILD_PYTHON_BINDINGS) + +endif() + + + +if (BUILD_PYTHON_BINDINGS) + + # Executable + add_executable( + Test + main.cpp + ScriptEngine.hpp + PythonEngine.hpp + PythonEngine.cpp + RubyEngine.hpp + RubyEngine.cpp + "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx") -# Must call CMake itself in order to set the SWIG_LIB env var for -# add_custom_command -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" - COMMAND - ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_DIR}" "${SWIG_EXECUTABLE}" "-v" - "-ruby" -external-runtime - "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx") - -# Executable -add_executable( - Test - main.cpp - ScriptEngine.hpp - PythonEngine.hpp - PythonEngine.cpp - RubyEngine.hpp - RubyEngine.cpp - "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" - "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper/SWIGPythonRuntime.hxx") - -target_link_libraries(Test PRIVATE ${target_name} cpp_compile_options - cpp_warning_options ${RUBY_LIBRARY} Python::Python) - -target_include_directories(Test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper" "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper" - ${RUBY_INCLUDE_DIRS}) + target_link_libraries(Test PRIVATE ${target_name} cpp_compile_options + cpp_warning_options ${RUBY_LIBRARY} Python::Python) + + target_include_directories(Test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper" "${CMAKE_CURRENT_BINARY_DIR}/python_wrapper" + ${RUBY_INCLUDE_DIRS}) +else() + + # Executable + add_executable( + Test + main.cpp + ScriptEngine.hpp + RubyEngine.hpp + RubyEngine.cpp + "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper/SWIGRubyRuntime.hxx" + ) + + target_link_libraries(Test PRIVATE ${target_name} cpp_compile_options + cpp_warning_options ${RUBY_LIBRARY}) + target_include_directories(Test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/ruby_wrapper" + ${RUBY_INCLUDE_DIRS}) + +endif() diff --git a/Measure.i b/Measure.i index 04fdef2..b21a16d 100644 --- a/Measure.i +++ b/Measure.i @@ -1,6 +1,7 @@ #ifndef MEASURE_I #define MEASURE_I +#include %begin %{ // ... code in begin section ... @@ -35,14 +36,17 @@ #if defined(SWIGPYTHON) %rename (PythonMeasure) Test::Measure; +//%rename ("Python%s") ""; #endif %{ + #include #include #include #include %} +%include %include %include %include diff --git a/Model.cpp b/Model.cpp index d2aa454..e65aec3 100644 --- a/Model.cpp +++ b/Model.cpp @@ -1,23 +1,44 @@ #include "Model.hpp" +#include "ModelObject.hpp" namespace Test { -Model::Model(std::string name) : name_(std::move(name)) {} + Model::Model(std::string name) : name_(std::move(name)) {} -const std::string& Model::getName() const { - return name_; -} + const std::string& Model::getName() const { + return name_; + } -bool Model::setName(const std::string& name) { - name_ = name; - return true; -} + bool Model::setName(const std::string& name) { + name_ = name; + return true; + } + void Model::pushOp(const std::string& op_name) { + opsPerformed_.push_back(op_name); + } -void Model::pushOp(const std::string& op_name) { - opsPerformed_.push_back(op_name); -} + const std::vector& Model::opsPerformed() const { + return opsPerformed_; + } + + int Model::numObjects() const { + return static_cast(m_objects.size()); + } + + std::vector Model::objectNames() const { + std::vector result; + for (auto objPtr : m_objects) { + result.push_back(objPtr.getName()); + } + return result; + } + + ModelObject& Model::getObject(size_t index) { + return m_objects[index]; + } + + void Model::pushObject(const std::string& objName) { + m_objects.emplace_back(objName); + } -const std::vector& Model::opsPerformed() const { - return opsPerformed_; -} } // namespace Test diff --git a/Model.hpp b/Model.hpp index 6ac5b74..df81973 100644 --- a/Model.hpp +++ b/Model.hpp @@ -3,23 +3,29 @@ #include #include +#include "ModelObject.hpp" namespace Test { -class Model -{ - public: - explicit Model(std::string name); - const std::string& getName() const; - bool setName(const std::string& name); + class Model + { + public: + explicit Model(std::string name); + const std::string& getName() const; + bool setName(const std::string& name); - void pushOp(const std::string& op_name); + void pushOp(const std::string& op_name); - const std::vector& opsPerformed() const; + const std::vector& opsPerformed() const; - private: - std::string name_; - std::vector opsPerformed_; -}; + int numObjects() const; + std::vector objectNames() const; + ModelObject& getObject(size_t index); + void pushObject(const std::string& objName); + private: + std::string name_; + std::vector opsPerformed_; + std::vector m_objects; + }; } // namespace Test #endif diff --git a/ModelObject.cpp b/ModelObject.cpp new file mode 100644 index 0000000..0c64261 --- /dev/null +++ b/ModelObject.cpp @@ -0,0 +1,19 @@ +#include "ModelObject.hpp" + +namespace Test { + + ModelObject::ModelObject(std::string name) + : name_(std::move(name)) + { + } + + const std::string& ModelObject::getName() const { + return name_; + } + + bool ModelObject::setName(const std::string& name) { + name_ = name; + return true; + } + +} // namespace Test diff --git a/ModelObject.hpp b/ModelObject.hpp new file mode 100644 index 0000000..b4db4b8 --- /dev/null +++ b/ModelObject.hpp @@ -0,0 +1,19 @@ +#ifndef MODELOBJECT_HPP +#define MODELOBJECT_HPP + +#include + +namespace Test { + + class ModelObject { + public: + explicit ModelObject(std::string name); + const std::string& getName() const; + bool setName(const std::string& name); + + private: + std::string name_; + }; +} // namespace Test + +#endif diff --git a/PythonEngine.cpp b/PythonEngine.cpp index 51bdf1c..c58a0a7 100644 --- a/PythonEngine.cpp +++ b/PythonEngine.cpp @@ -141,7 +141,7 @@ void* PythonEngine::getAs_impl(ScriptObject& obj, const std::type_info& ti) { void* return_value = nullptr; - auto* type = SWIG_TypeQuery(type_name.c_str()); + auto* type = SWIG_Python_TypeQuery(type_name.c_str()); if (!type) { throw std::runtime_error("Unable to find type in SWIG"); diff --git a/main.cpp b/main.cpp index 00a20d1..6aa550c 100644 --- a/main.cpp +++ b/main.cpp @@ -1,21 +1,35 @@ +// CMake defines WITHPYTHON (or not) + #include #include #include "RubyEngine.hpp" + +#ifdef WITHPYTHON #include "PythonEngine.hpp" +#endif + #include "Measure.hpp" #include "SpecialRunner.hpp" #include "Model.hpp" -int main(const int argc, const char* argv[]) { +int main([[maybe_unused]] const int argc, [[maybe_unused]] const char* argv[]) { Test::RubyEngine ruby; + #ifdef WITHPYTHON Test::PythonEngine python{argc, argv}; + #endif - python.exec(R"(print("Hello From Python"))"); ruby.exec(R"(puts("Hello from Ruby"))"); + #ifdef WITHPYTHON + python.exec(R"(print("Hello From Python"))"); + #endif + ruby.registerType("Test::Measure *"); + + #ifdef WITHPYTHON python.registerType("Test::Measure *"); + #endif ruby.exec("require '/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/ruby/test_measure.rb'"); auto ruby_measure = ruby.eval("RubyTestMeasure.new()"); @@ -23,11 +37,13 @@ int main(const int argc, const char* argv[]) { assert(ruby_measure_from_cpp); std::cout << "Ruby measure name: " << ruby_measure_from_cpp->name() << '\n'; + #ifdef WITHPYTHON python.exec("import sys\nsys.path.append('/home/julien/Software/Cpp_Swig_Ruby_Python_MCVE/python/')"); python.exec("import test_measure"); auto python_measure = python.eval("test_measure.PythonTestMeasure()"); auto* python_measure_from_cpp = python.getAs(python_measure); assert(python_measure_from_cpp); + #endif // this all works up until this moment right here. // At this point it doesn't call the SWIG Director for Python @@ -38,16 +54,46 @@ int main(const int argc, const char* argv[]) { // // Can we work around this? Maybe. But having both Ruby and Python linked into the same application // is asking for many problems. - std::cout << "Python measure name: " << python_measure_from_cpp->name() << '\n'; + #ifdef WITHPYTHON + std::cout << "Python measure name: " << python_measure_from_cpp->name() << '\n'; + #endif - Test::SpecialRunner sr(Test::Model{"C++ Model"}); + auto printObjectNames = [](Test::Model& m) { + for (int i = 0; i < m.numObjects(); ++i) { + std::cout << " * " << i <<" = " << m.getObject(i).getName() << '\n'; + } + }; + + Test::Model m{"C++ Model"}; + m.pushObject("C++ object"); + Test::SpecialRunner sr(m); std::cout << "Starting out with a Model in C++ called: " << sr.get_current_model().getName() << '\n'; + std::cout << "C++: starting with " << sr.get_current_model().numObjects() << " objects\n"; + printObjectNames(sr.get_current_model()); + + std::cout << "\n\n========== START RUBY ==========\n"; + ruby_measure_from_cpp->run(sr); - python_measure_from_cpp->run(sr); - std::cout << "After Running Ruby and Python: model is named " << sr.get_current_model().getName() << '\n'; + std::cout << "========== FINISHED RUBY ==========\n\n"; + std::cout << "C++: " << sr.get_current_model().numObjects() << " objects\n"; + printObjectNames(sr.get_current_model()); + + #ifdef WITHPYTHON + std::cout << "\n\n========== START PYTHON ==========\n"; + python_measure_from_cpp->run(sr); + std::cout << "========== FINISHED PYTHON ==========\n\n"; + std::cout << "After Running Ruby and Python: model is named " << sr.get_current_model().getName() << '\n'; + #else + std::cout << "\n\n########## PYTHON ISN'T ENABLED ##########\n\n"; + std::cout << "After Running Ruby only: model is named " << sr.get_current_model().getName() << '\n'; + #endif for (const auto& op : sr.get_current_model().opsPerformed()) { std::cout << "Op 'run' from script: " << op << '\n'; } + + std::cout << "C++: " << sr.get_current_model().numObjects() << " objects\n"; + printObjectNames(sr.get_current_model()); + } diff --git a/python/test_measure.py b/python/test_measure.py index 05d7d9f..fa49215 100644 --- a/python/test_measure.py +++ b/python/test_measure.py @@ -11,9 +11,18 @@ def __init__(self): print("Created Object (Python __init__)") def run_impl(self, r: mylib.Runner): - print(f"Python Model named: {r.get_current_model().getName()}") - r.get_current_model().pushOp("Op from Python") - r.get_current_model().setName("Python Model") + m = r.get_current_model() + print(f"Python Model named: {m.getName()}") + m.pushOp("Op from Python") + m.setName("Python Model") + print(f"Python: {m.numObjects()} objects") + m.pushObject("Python Space") + + for i in range(m.numObjects()): + print(f" * {i} = {m.getObject(i).getName()}") + + m.getObject(1).setName("MODIFIED FROM PYTHON") + return True def name(self): return "Python Test Measure" diff --git a/ruby/test_measure.rb b/ruby/test_measure.rb index efc4087..a5794f3 100644 --- a/ruby/test_measure.rb +++ b/ruby/test_measure.rb @@ -6,9 +6,19 @@ def name end def run_impl(runner) - puts "Ruby Model named: #{runner.get_current_model().getName()}" - runner.get_current_model().setName("Ruby Model") - runner.get_current_model().pushOp("A Ruby Op") + m = runner.get_current_model() + puts "Ruby Model named: #{m.getName()}" + m.setName("Ruby Model") + m.pushOp("A Ruby Op") + + puts "Ruby: Model has: #{m.numObjects()} spaces" + m.pushObject("Ruby Space") + m.numObjects().times do |i| + puts "* #{i} = #{m.getObject(i).getName()}" + end + + # This crashes + m.getObject(0).setName("MODIFIED FROM RUBY") return true; end end