Repository: NREL/OpenStudio (language: python)

Issue:
Python Measures: Factor Ruby related source out of cli and into a shared library
This is probably the highest point of risk in the introduction of Python Measures. The idea is to put the ruby related embedded files, the ruby engine, and all "the trimmings" into a shared library, therefore making the cli no longer fully self contained. Currently this content is statically linked and embedded directly into the eopnstudio command line executable program. 
Python Measures: Factor Ruby related source out of cli and into a shared library
This is probably the highest point of risk in the introduction of Python Measures. The idea is to put the ruby related embedded files, the ruby engine, and all "the trimmings" into a shared library, therefore making the cli no longer fully self contained. Currently this content is statically linked and embedded directly into the eopnstudio command line executable program. 


Discussion / hints:

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 496470e1d39..c2b1ef780ea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,5 @@
-cmake_minimum_required(VERSION 3.14.0)
+message(STATUS "Using CMake ${CMAKE_VERSION}")
+cmake_minimum_required(VERSION 3.19.0)
 cmake_policy(SET CMP0048 NEW)
 
 set(CMAKE_CXX_STANDARD 20)
@@ -343,6 +344,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clan
 endif()
 
 if(APPLE)
+  # Without this, cmake will give "Module" libraries the ".so" extension on Apple
+  # This appears to be a legacy choice by the cmake developers, and seems out of place,
+  # so the OpenStudio project will use the library suffix ".dylib"
+  set(CMAKE_SHARED_MODULE_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX})
   find_library(COREFOUNDATION_LIBRARY CoreFoundation)
   add_definitions(-D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR)
   #add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED)
@@ -1005,6 +1010,7 @@ set(project_directories
   sdd
   lib
   install_utility
+  scriptengine
 )
 
 # resources must be added after find EnergyPlus
diff --git a/ConanInstall.cmake b/ConanInstall.cmake
index 10f56fd2983..e3057a485d2 100644
--- a/ConanInstall.cmake
+++ b/ConanInstall.cmake
@@ -177,6 +177,7 @@ if(NOT CONAN_OPENSTUDIO_ALREADY_RUN)
     "geographiclib/1.52#76536a9315a003ef3511919310b2fe37"
     "swig/4.0.2#9fcccb1e39eed9acd53a4363d8129be5"
     "tinygltf/2.5.0#c8b2aca9505e86312bb42aa0e1c639ec"
+    "cli11/2.2.0#33cd38722fa134b15ae308dfb4e6c942"
     ${CONAN_GTEST}
     ${CONAN_BENCHMARK}
     # Override to avoid dependency mismatches
diff --git a/ruby/CMakeLists.txt b/ruby/CMakeLists.txt
index 951372199d4..95658408d0a 100644
--- a/ruby/CMakeLists.txt
+++ b/ruby/CMakeLists.txt
@@ -1,191 +1,7 @@
-if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(Apple)?Clang$")
-  # using Clang
-  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -undefined dynamic_lookup")
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
-  # using GCC
-  #set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --unresolved-symbols=ignore-all")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --unresolved-symbols=ignore-all")
-endif()
+# OpenStudio Ruby components
 
-
-include_directories(${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} )
-
-# common library for openstudio.so and openstudio.exe
-
-add_library( init_openstudio OBJECT
-  init_openstudio.cpp
-  RubyAPI.hpp
-)
-target_include_directories(init_openstudio SYSTEM PRIVATE ${RUBY_INCLUDE_DIRS})
-
-if(MSVC)
-  # TODO: JM 2019-01-04: @macumber it seems like all three targets use the same COMPILE_FLAGS,
-  # so instead of defining that three times, perhaps modify CMAKE_CXX_FLAGS?
-  # wd4996=no deprecated warnings ; wd5033=register
-  set_target_properties(init_openstudio PROPERTIES COMPILE_FLAGS "/bigobj /wd4996 /wd5033")
-elseif (UNIX)
-  # Disable register warnings
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=register -Wno-register")
-endif()
-
-
-# openstudio ruby bindings to load into normal ruby
-
-add_library(
-  openstudio_rb
-  MODULE
-  RubyAPI.hpp
-  openstudio_rb.cpp
-)
-set_target_properties(openstudio_rb PROPERTIES PREFIX "")
-set_target_properties(openstudio_rb PROPERTIES OUTPUT_NAME openstudio)
-target_include_directories(openstudio_rb SYSTEM PRIVATE ${RUBY_INCLUDE_DIRS})
-
-# change output directory so the openstudio.pdb file does not conflict between this target and the CLI
-set_target_properties(openstudio_rb PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ruby/")
-set_target_properties(openstudio_rb PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ruby/")
-set_target_properties(openstudio_rb PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ruby/")
-
-if(APPLE)
-  set_target_properties(openstudio_rb PROPERTIES SUFFIX ".bundle" )
-else()
-  set_target_properties(openstudio_rb PROPERTIES SUFFIX ".so" )
-endif()
-
-if(MSVC)
-  set_target_properties(openstudio_rb PROPERTIES COMPILE_FLAGS "/bigobj /wd4996 /wd5033")
-endif()
-
-add_custom_command(TARGET openstudio_rb
-  POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/openstudio.rb $<TARGET_FILE_DIR:openstudio_rb>/openstudio.rb
-)
-
-file(GLOB_RECURSE OPENSTUDIO_FILES FOLLOW_SYMLINKS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/" "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/**/*.*")
-foreach(OPENSTUDIO_FILE ${OPENSTUDIO_FILES})
-  add_custom_command(TARGET openstudio_rb
-    POST_BUILD
-    COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/${OPENSTUDIO_FILE}" "$<TARGET_FILE_DIR:openstudio_rb>/openstudio/${OPENSTUDIO_FILE}"
-  )
-endforeach()
-
-
-target_link_libraries(
-  openstudio_rb
-  PRIVATE
-  init_openstudio
-  ${ALL_RUBY_BINDING_TARGETS}
-)
-
-if( WIN32 )
-  # Dynamically link to openstudiolib.dll on Windows only
-  target_link_libraries(
-    openstudio_rb
-    PUBLIC
-    openstudiolib
-  )
-  target_link_libraries(openstudio_rb PRIVATE ${RUBY_MINGW_STUB_LIB})
-else()
-  # statically link everywhere else
-  target_link_libraries(
-    openstudio_rb
-    PRIVATE
-    openstudio_utilities
-    openstudio_airflow
-    openstudio_model
-    openstudio_energyplus
-    openstudio_epjson
-    openstudio_measure
-    openstudio_osversion
-    openstudio_sdd
-    openstudio_isomodel
-    openstudio_gbxml
-    openstudio_gltf
-    openstudio_radiance
-  )
-endif()
-
-if (APPLE)
-  target_link_libraries(openstudio_rb
-    PRIVATE
-    ${OPENSSL_CRYPTO_LIBRARY}
-    ${OPENSSL_SSL_LIBRARY}
-  )
-endif()
-
-
-install(TARGETS openstudio_rb
-  EXPORT openstudio
-  DESTINATION Ruby
-  COMPONENT "RubyAPI"
-  INCLUDES DESTINATION include include/openstudio
-)
-install(FILES openstudio.rb DESTINATION Ruby COMPONENT "RubyAPI")
-install(DIRECTORY openstudio DESTINATION Ruby COMPONENT "RubyAPI")
-
-
-###############################################################################
-#                        T E S T I N G:   C T E S T S                         #
-###############################################################################
-
-# find all tests
-file(GLOB RUBY_TEST_SRC "test/*.rb")
-
-# TODO: It doesn't not work with this executable that's in build/Ruby-install/bin
-# message("RUBY_EXECUTABLE=${RUBY_EXECUTABLE}")
-
-set(_RUBY_POSSIBLE_EXECUTABLE_NAMES
-  ruby
-  ruby2.7
-  ruby2.7.2
-  ruby27
-  ruby272)
-
-# TODO: this isn't great but I haven't found a better way to locate the system ruby (and avoid the one in build/Ruby-install/)
-find_program(SYSTEM_RUBY_EXECUTABLE NAMES ${_RUBY_POSSIBLE_EXECUTABLE_NAMES}
-  HINTS
-    "/usr/local/rvm/rubies/ruby-2.7.2/bin/"
-    "/usr/local/rvm/rubies/ruby-2.7.2/bin/ruby"
-    "/usr/share/rvm/rubies/ruby-2.7.2/bin/"
-    "$ENV{HOME}/.rvm/rubies/ruby-2.7.2/bin/"
-
-    "C:/Ruby27-x64/bin/"
-
-     "/usr/local/ruby272/bin/"
-     "/usr/local/ruby27/bin/"
-     "/usr/bin/"
-     "/usr/local/bin/"
-
-
-  NO_DEFAULT_PATH)
-
-if(NOT SYSTEM_RUBY_EXECUTABLE)
-  message(WARNING "Your system ruby wasn't found, you won't be able to run the `ctest -R RubyTest` command and the tests won't be created at all.")
-else()
-  message(STATUS "Found SYSTEM_RUBY_EXECUTABLE=${SYSTEM_RUBY_EXECUTABLE}")
-
-  # add a test for each unit test
-  set(RUBY_TEST_REQUIRES "#include test files")
-  foreach(f ${RUBY_TEST_SRC})
-
-    file(READ "${f}" CONTENTS)
-    string(REGEX MATCHALL "def +test_([A-Za-z_0-9 ]+)" FOUND_TESTS ${CONTENTS})
-
-    foreach(HIT ${FOUND_TESTS})
-      string(REGEX REPLACE "def +test_([A-Za-z_0-9]+)" "\\1" TEST_NAME ${HIT})
-      string(REGEX MATCH "/?([A-Za-z_0-9 ]+)\\.rb" FILE_NAME ${f})
-      string(REGEX REPLACE "/?([A-Za-z_0-9 ]+)\\.rb" "\\1" FILE_NAME ${FILE_NAME})
-      if(BUILD_TESTING)
-
-        # Call with Ruby itself
-        add_test(NAME "RubyTest-${FILE_NAME}-${TEST_NAME}"
-          COMMAND "${CMAKE_COMMAND}" -E chdir "${CMAKE_CURRENT_BINARY_DIR}"
-          "${SYSTEM_RUBY_EXECUTABLE}" "-I" "$<TARGET_FILE_DIR:openstudio_rb>" "${f}" "--name=test_${TEST_NAME}"
-        )
-
-        set_tests_properties("RubyTest-${FILE_NAME}-${TEST_NAME}" PROPERTIES TIMEOUT 660 )
-      endif()
-    endforeach()
-  endforeach()
-
-endif()
+add_subdirectory(interpreter)
+add_subdirectory(bindings)
+add_subdirectory(module)
+add_subdirectory(engine)
+add_subdirectory(test)
diff --git a/ruby/README.md b/ruby/README.md
new file mode 100644
index 00000000000..26ecea30058
--- /dev/null
+++ b/ruby/README.md
@@ -0,0 +1,17 @@
+# OpenStudio Ruby
+
+OpenStudio's Ruby implementation is divided into several pieces
+
+* `interpreter`: This is the bare bones Ruby interpreter with only some very minimal OpenStudio
+initializations and abstractions. There are two separate cmake targets related to the interpreter.
+
+    * The target `rubyinterpreter-declarations` is an interface library that merely adds Ruby to the include path. This is used by the standalone ruby bindings.
+    * The target `rubyinterpreter` adds Ruby to the include paths, but also links to the Ruby library. This is used by the OpenStudio Ruby engine.
+
+* `bindings`: This contains an interface to initialize the OpenStudio API bindings for Ruby. (e.g. `init_openstudio_model`)
+The `module` and `engine` components consume this.
+
+* `module`: This is the conventional OpenStudio Ruby bindings, for loading into a standalone Ruby interpreter.
+
+* `engine`: This is a combination of the OpenStudio Ruby bindings, the Ruby interpreter, and embedded files
+to be used by the OpenStudio cli to run Ruby based workflows.
diff --git a/ruby/bindings/CMakeLists.txt b/ruby/bindings/CMakeLists.txt
new file mode 100644
index 00000000000..e7e4eb51bfe
--- /dev/null
+++ b/ruby/bindings/CMakeLists.txt
@@ -0,0 +1,17 @@
+add_library( rubybindings OBJECT
+  InitRubyBindings.hpp
+  InitRubyBindings.cpp
+)
+
+target_include_directories(rubybindings PUBLIC
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+)
+target_link_libraries(rubybindings PRIVATE ${ALL_RUBY_BINDING_TARGETS} rubyinterpreter-declarations)
+
+# wd4996=no deprecated warnings ; wd5033=register
+target_compile_options(rubybindings PRIVATE
+  $<$<CXX_COMPILER_ID:MSVC>:/bigobj /wd4996 /wd5033>
+  $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:-Wno-error=register -Wno-register>
+  $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:-Wno-deprecated-volatile>
+  $<$<CXX_COMPILER_ID:GNU>:-Wno-volatile>
+)
diff --git a/ruby/init_openstudio.cpp b/ruby/bindings/InitRubyBindings.cpp
similarity index 93%
rename from ruby/init_openstudio.cpp
rename to ruby/bindings/InitRubyBindings.cpp
index 20a94ad7a00..a282d892598 100644
--- a/ruby/init_openstudio.cpp
+++ b/ruby/bindings/InitRubyBindings.cpp
@@ -27,7 +27,8 @@
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************************************************/
 
-#include "init_openstudio.hpp"
+#include "InitRubyBindings.hpp"
+#include "RubyEval.hpp"
 #include <ruby.h>
 #include <stdexcept>
 #include <iostream>
@@ -74,7 +75,10 @@ extern "C"
   ////void Init_openstudiomodeleditor(void); # happens separately in openstudio.so only, for SketchUp plug-in
 }
 
-void init_openstudio_internal_basic() {
+namespace openstudio {
+namespace ruby {
+
+void initBasicRubyBindings() {
   rb_provide("openstudio");
   rb_provide("openstudio.so");
 
@@ -119,7 +123,7 @@ void init_openstudio_internal_basic() {
   rb_provide("openstudioutilities.so");
 }
 
-void init_openstudio_internal_extended() {
+void initExtendedRubyBindings() {
   Init_openstudiomodel();
   rb_provide("openstudiomodel");
   rb_provide("openstudiomodel.so");
@@ -366,49 +370,13 @@ end # module OpenStudio
 
 )END";
 
-  evalString(ruby_typedef_script);
-}
-
-void init_openstudio_internal() {
-  init_openstudio_internal_basic();
-  init_openstudio_internal_extended();
+  openstudio::evalString(ruby_typedef_script);
 }
 
-class RubyException : public std::runtime_error
-{
- public:
-  RubyException(const std::string& msg, const std::string& location) : std::runtime_error(msg), m_location(location) {}
-
-  virtual ~RubyException() throw() {}
-
-  std::string location() const {
-    return m_location;
-  }
-
- private:
-  std::string m_location;
-};
-
-static VALUE evaluateSimpleImpl(VALUE arg) {
-  return rb_eval_string(StringValuePtr(arg));
+void initRubyBindings() {
+  initBasicRubyBindings();
+  initExtendedRubyBindings();
 }
 
-void evalString(const std::string& t_str) {
-
-  VALUE val = rb_str_new2(t_str.c_str());
-  int error;
-
-  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 RubyException(err, loc);
-  }
-}
+} // namespace ruby
+} // namespace openstudio
diff --git a/ruby/bindings/InitRubyBindings.hpp b/ruby/bindings/InitRubyBindings.hpp
new file mode 100644
index 00000000000..14428e537b4
--- /dev/null
+++ b/ruby/bindings/InitRubyBindings.hpp
@@ -0,0 +1,14 @@
+#ifndef INITRUBYBINDINGS_HPP
+#define INITRUBYBINDINGS_HPP
+
+namespace openstudio {
+namespace ruby {
+
+void initRubyBindings();
+void initBasicRubyBindings();
+void initExtendedRubyBindings();
+
+} // namespace ruby
+} // namespace openstudio
+
+#endif // INITRUBYBINDINGS_HPP
diff --git a/ruby/engine/CMakeLists.txt b/ruby/engine/CMakeLists.txt
new file mode 100644
index 00000000000..ce5d414d885
--- /dev/null
+++ b/ruby/engine/CMakeLists.txt
@@ -0,0 +1,287 @@
+# This defines a list of import targets for the native extensions: ruby_extension_libs
+include("${OPENSTUDIO_GEMS_DIR}/export-extensions.cmake")
+
+# This had been broken for a long time (was using a wrong variable name for the list, so InitMacros.hxx is empty) and it still worked fine. TODO: remove?
+set(INIT_CALLS "")
+set(INIT_DECLARATIONS "")
+foreach(LIB ${ruby_extension_libs})
+  get_filename_component(LIB_NAME ${LIB} NAME_WE)
+  #list (FIND LIBS "${LIB_NAME}" _index)
+  #if( ${_index} GREATER -1 )
+  set(INIT_DECLARATIONS "${INIT_DECLARATIONS}  void Init_${LIB_NAME}();\\\n" )
+  set(INIT_CALLS "${INIT_CALLS}  Init_${LIB_NAME}();\\\n" )
+  set(INIT_CALLS "${INIT_CALLS}  rb_provide(\"${LIB_NAME}.so\");\\\n" )
+  #endif()
+endforeach()
+
+CONFIGURE_FILE_WITH_CHECKSUM("InitMacros.hxx.in" "${CMAKE_CURRENT_BINARY_DIR}/InitMacros.hxx")
+
+set(MODULE_ROOT "${CONAN_OPENSTUDIO_RUBY_ROOT}")
+if ("${MODULE_ROOT}" STREQUAL "")
+  set(MODULE_ROOT "${CONAN_OPENSTUDIO_RUBY_ROOT_DEBUG}")
+endif()
+
+message("Searching for ruby modules in '${MODULE_ROOT}/lib/**/*.rb'")
+file(GLOB_RECURSE  EXTENSION_RB  FOLLOW_SYMLINKS "${MODULE_ROOT}/lib/**/*.rb")
+
+foreach( _FILE ${EXTENSION_RB} )
+  file(RELATIVE_PATH LOCATION ${MODULE_ROOT}/lib ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+set(GEMFILE_FILES "${OPENSTUDIO_GEMS_DIR}/openstudio-gems.gemspec" "${PROJECT_BINARY_DIR}/openstudio-gems/Gemfile" "${PROJECT_BINARY_DIR}/openstudio-gems/Gemfile.lock")
+file(GLOB_RECURSE RB_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.rb")
+file(GLOB_RECURSE ERB_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.erb")
+file(GLOB_RECURSE JS_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.js")
+file(GLOB_RECURSE CSS_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.css")
+file(GLOB_RECURSE GIF_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gif")
+file(GLOB_RECURSE PNG_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.png")
+file(GLOB_RECURSE HTML_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.html")
+file(GLOB_RECURSE IDF_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.idf")
+file(GLOB_RECURSE OSM_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.osm")
+file(GLOB_RECURSE EPW_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.epw")
+file(GLOB_RECURSE DDY_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.ddy")
+file(GLOB_RECURSE STAT_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.stat")
+file(GLOB_RECURSE CSV_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.csv")
+file(GLOB_RECURSE JSON_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.json")
+file(GLOB_RECURSE SPEC_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gemspec")
+file(GLOB_RECURSE GZ_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gz")
+file(GLOB_RECURSE YML_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.yml")
+file(GLOB_RECURSE OS_FILES FOLLOW_SYMLINKS "${CMAKE_SOURCE_DIR}/ruby/module/openstudio/**/*.rb")
+
+foreach( _FILE ${GEMFILE_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${RB_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${ERB_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${JS_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${CSS_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${GIF_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${PNG_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${HTML_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${IDF_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${OSM_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${EPW_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${DDY_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${STAT_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${CSV_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${JSON_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${SPEC_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${GZ_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+foreach( _FILE ${YML_FILES} )
+  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+# DLM: this is temporary, these files should live somewhere else and be included in the shared Ruby lib
+foreach( _FILE ${OS_FILES} )
+  file(RELATIVE_PATH LOCATION "${CMAKE_SOURCE_DIR}/ruby/module" ${_FILE})
+  list(APPEND FILES ${_FILE})
+  list(APPEND EMBEDDED_PATHS ${LOCATION})
+endforeach()
+
+list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/embedded_help.rb")
+list(APPEND EMBEDDED_PATHS "embedded_help.rb")
+
+list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/openstudio_cli.rb")
+list(APPEND EMBEDDED_PATHS "openstudio_cli.rb")
+
+list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/openstudio_init_extended.rb")
+list(APPEND EMBEDDED_PATHS "openstudio_init_extended.rb")
+
+
+list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/measure_manager.rb")
+list(APPEND EMBEDDED_PATHS "measure_manager.rb")
+
+list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/measure_manager_server.rb")
+list(APPEND EMBEDDED_PATHS "measure_manager_server.rb")
+
+embed_files("${FILES}" "${EMBEDDED_PATHS}" OUTPUT)
+
+set_source_files_properties(EmbeddedScripting.i
+  PROPERTIES CPLUSPLUS true
+)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR}
+  ${PROJECT_BINARY_DIR}
+  ${PROJECT_SOURCE_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${OPENSTUDIO_GEMS_DIR}
+)
+
+add_custom_command(
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
+  COMMAND ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_LIB}"
+          "${SWIG_EXECUTABLE}"
+          "-ruby"
+          "-c++"
+          -o "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
+          "-fvirtual"
+          "-I${PROJECT_SOURCE_DIR}/src"
+          "-I${PROJECT_BINARY_DIR}/src"
+          "-I${OPENSTUDIO_GEMS_DIR}"
+          "-D_WINDOWS"
+          "-Fmicrosoft"
+          "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedScripting.i"
+  DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedScripting.i"
+          "${CMAKE_CURRENT_BINARY_DIR}/embedded_files.hxx"
+          "EmbeddedHelp.hpp"
+)
+
+set_source_files_properties(${EMBED_SOURCE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE)
+source_group(embedded_files FILES ${OUTPUT})
+
+add_library(rubyengine
+  MODULE
+  RubyEngine.hpp
+  RubyEngine.cpp
+  InitRubyEngine.cpp
+  GC_Value.hpp
+  ${OUTPUT}
+  "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
+  #"${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
+)
+
+include(GenerateExportHeader)
+generate_export_header(rubyengine)
+
+# -Wno-deprecated-declaration, /wd4996: suppresses deprecated warning
+# -Wno-register, /wd5033: ISO C++1z does not allow register storage class specifier
+if(MSVC)
+  set_target_properties(rubyengine PROPERTIES COMPILE_FLAGS "/bigobj /wd4996 /wd5033")
+  set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx" PROPERTIES COMPILE_FLAGS "-DRUBY_EMBEDDED" )
+else()
+  set_target_properties(rubyengine PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-register")
+  set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx" PROPERTIES COMPILE_FLAGS "-DRUBY_EMBEDDED -Wno-unused-variable" )
+endif()
+
+#add_definitions(-DRUBY_EXTCONF_H="osruby_config.h")
+
+get_target_property(SCRIPT_ENGINE_INCLUDE_DIRS scriptengine INTERFACE_INCLUDE_DIRECTORIES)
+
+#target_link_libraries(rubyengine PRIVATE osrubyinit CONAN_PKG::openstudio_ruby ${ALL_RUBY_BINDING_TARGETS})
+
+if( WIN32 )
+  target_link_libraries(rubyengine
+    PRIVATE
+    scriptengine
+    openstudiolib
+    rubybindings
+    rubyinterpreter
+    CONAN_PKG::fmt
+    ${ruby_extension_libs}
+    ${ALL_RUBY_BINDING_TARGETS}
+    wsock32 ws2_32 Dbghelp Shlwapi Iphlpapi
+  )
+
+  set_target_properties(rubyengine PROPERTIES LINK_FLAGS_DEBUG "/NODEFAULTLIB:MSVCRT")
+else()
+  target_link_libraries(rubyengine
+    PRIVATE
+    scriptengine
+    openstudio_utilities
+    openstudio_airflow
+    openstudio_model
+    openstudio_energyplus
+    openstudio_epjson
+    openstudio_measure
+    openstudio_osversion
+    openstudio_sdd
+    openstudio_isomodel
+    openstudio_gbxml
+    openstudio_gltf
+    openstudio_radiance
+    rubybindings
+    rubyinterpreter
+    CONAN_PKG::fmt
+    ${ruby_extension_libs}
+    ${ALL_RUBY_BINDING_TARGETS}
+  )
+endif()
+
+install(TARGETS rubyengine EXPORT openstudio DESTINATION bin COMPONENT "CLI")
diff --git a/src/cli/EmbeddedHelp.hpp b/ruby/engine/EmbeddedHelp.hpp
similarity index 100%
rename from src/cli/EmbeddedHelp.hpp
rename to ruby/engine/EmbeddedHelp.hpp
diff --git a/src/cli/EmbeddedScripting.i b/ruby/engine/EmbeddedScripting.i
similarity index 100%
rename from src/cli/EmbeddedScripting.i
rename to ruby/engine/EmbeddedScripting.i
diff --git a/src/cli/GC_Value.hpp b/ruby/engine/GC_Value.hpp
similarity index 100%
rename from src/cli/GC_Value.hpp
rename to ruby/engine/GC_Value.hpp
diff --git a/src/cli/InitMacros.hxx.in b/ruby/engine/InitMacros.hxx.in
similarity index 100%
rename from src/cli/InitMacros.hxx.in
rename to ruby/engine/InitMacros.hxx.in
diff --git a/ruby/engine/InitRuby.hpp b/ruby/engine/InitRuby.hpp
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/ruby/engine/InitRubyEngine.cpp b/ruby/engine/InitRubyEngine.cpp
new file mode 100644
index 00000000000..39d8afbcfd3
--- /dev/null
+++ b/ruby/engine/InitRubyEngine.cpp
@@ -0,0 +1,387 @@
+#include "./RubyEngine.hpp"
+#include "./InitRubyEngine.hpp"
+#include "./GC_Value.hpp"
+#include <RubyInterpreter.hpp>
+#include <iostream>
+#include <embedded_files.hxx>
+
+namespace openstudio {
+
+void RubyEngine::initRubyEngine() {
+    RUBY_INIT_STACK;
+    ruby_init();
+
+    swig::GC_VALUE::hash_id = rb_intern("hash");
+    swig::GC_VALUE::lt_id = rb_intern("<");
+    swig::GC_VALUE::gt_id = rb_intern(">");
+    swig::GC_VALUE::eq_id = rb_intern("==");
+    swig::GC_VALUE::le_id = rb_intern("<=");
+    swig::GC_VALUE::ge_id = rb_intern(">=");
+
+    swig::GC_VALUE::pos_id = rb_intern("+@");
+    swig::GC_VALUE::neg_id = rb_intern("-@");
+    swig::GC_VALUE::inv_id = rb_intern("~");
+
+    swig::GC_VALUE::add_id = rb_intern("+");
+    swig::GC_VALUE::sub_id = rb_intern("-");
+    swig::GC_VALUE::mul_id = rb_intern("*");
+    swig::GC_VALUE::div_id = rb_intern("/");
+    swig::GC_VALUE::mod_id = rb_intern("%");
+
+    swig::GC_VALUE::and_id = rb_intern("&");
+    swig::GC_VALUE::or_id = rb_intern("|");
+    swig::GC_VALUE::xor_id = rb_intern("^");
+
+    swig::GC_VALUE::lshift_id = rb_intern("<<");
+    swig::GC_VALUE::rshift_id = rb_intern(">>");
+
+    // Need embedded_help for requiring files out of the embedded system
+    auto embedded_extensions_string = embedded_files::getFileAsString(":/embedded_help.rb");
+
+    try {
+      rubyInterpreter.evalString(embedded_extensions_string);
+    } catch (const std::exception& e) {
+      rubyInterpreter.evalString(R"(STDOUT.flush)");
+      std::cout << "Exception in embedded_help: " << e.what() << std::endl;  // endl will flush
+      ruby_cleanup(1);
+      throw;
+    } catch (...) {
+      rubyInterpreter.evalString(R"(STDOUT.flush)");
+      std::cout << "Unknown Exception in embedded_help" << std::endl;  // endl will flush
+      ruby_cleanup(1);
+      throw;
+    }
+
+    // encodings
+    // Get the symbols from: `DUMPBIN /ARCHIVEMEMBERS  <path/to/ruby/>/lib/enc/libenc.lib`
+    Init_encdb();
+    rb_provide("enc/encdb.so");
+    Init_big5();
+    rb_provide("enc/big5.so");
+    Init_cesu_8();
+    rb_provide("enc/cesu_8.so");
+    Init_cp949();
+    rb_provide("enc/cp949.so");
+    Init_emacs_mule();
+    rb_provide("enc/emacs_mule.so");
+    Init_euc_jp();
+    rb_provide("enc/euc_jp.so");
+    Init_euc_kr();
+    rb_provide("enc/euc_kr.so");
+    Init_euc_tw();
+    rb_provide("enc/euc_tw.so");
+    Init_gb18030();
+    rb_provide("enc/gb18030.so");
+    Init_gb2312();
+    rb_provide("enc/gb2312.so");
+    Init_gbk();
+    rb_provide("enc/gbk.so");
+    Init_iso_8859_1();
+    rb_provide("enc/iso_8859_1.so");
+    Init_iso_8859_10();
+    rb_provide("enc/iso_8859_10.so");
+    Init_iso_8859_11();
+    rb_provide("enc/iso_8859_11.so");
+    Init_iso_8859_13();
+    rb_provide("enc/iso_8859_13.so");
+    Init_iso_8859_14();
+    rb_provide("enc/iso_8859_14.so");
+    Init_iso_8859_15();
+    rb_provide("enc/iso_8859_15.so");
+    Init_iso_8859_16();
+    rb_provide("enc/iso_8859_16.so");
+    Init_iso_8859_2();
+    rb_provide("enc/iso_8859_2.so");
+    Init_iso_8859_3();
+    rb_provide("enc/iso_8859_3.so");
+    Init_iso_8859_4();
+    rb_provide("enc/iso_8859_4.so");
+    Init_iso_8859_5();
+    rb_provide("enc/iso_8859_5.so");
+    Init_iso_8859_6();
+    rb_provide("enc/iso_8859_6.so");
+    Init_iso_8859_7();
+    rb_provide("enc/iso_8859_7.so");
+    Init_iso_8859_8();
+    rb_provide("enc/iso_8859_8.so");
+    Init_iso_8859_9();
+    rb_provide("enc/iso_8859_9.so");
+    Init_koi8_r();
+    rb_provide("enc/koi8_r.so");
+    Init_koi8_u();
+    rb_provide("enc/koi8_u.so");
+    Init_shift_jis();
+    rb_provide("enc/shift_jis.so");
+    Init_utf_16be();
+    rb_provide("enc/utf_16be.so");
+    Init_utf_16le();
+    rb_provide("enc/utf_16le.so");
+    Init_utf_32be();
+    rb_provide("enc/utf_32be.so");
+    Init_utf_32le();
+    rb_provide("enc/utf_32le.so");
+    Init_windows_1250();
+    rb_provide("enc/windows_1250.so");
+    Init_windows_1251();
+    rb_provide("enc/windows_1251.so");
+    Init_windows_1252();
+    rb_provide("enc/windows_1252.so");
+    Init_windows_1253();
+    rb_provide("enc/windows_1253.so");
+    Init_windows_1254();
+    rb_provide("enc/windows_1254.so");
+    Init_windows_1257();
+    rb_provide("enc/windows_1257.so");
+    Init_windows_31j();
+    rb_provide("enc/windows_31j.so");
+
+    // Get the symbols from: `DUMPBIN /ARCHIVEMEMBERS  <path/to/ruby>/lib/enc/libtrans.lib`
+    Init_transdb();
+    rb_provide("enc/trans/transdb.so");
+
+    Init_trans_big5();
+    rb_provide("enc/trans/big5.so");
+
+    Init_trans_cesu_8();
+    rb_provide("enc/trans/cesu_8.so");
+
+    Init_trans_chinese();
+    rb_provide("enc/trans/chinese.so");
+
+    Init_trans_ebcdic();
+    rb_provide("enc/trans/ebcdic.so");
+
+    Init_trans_emoji();
+    rb_provide("enc/trans/emoji.so");
+
+    Init_trans_emoji_iso2022_kddi();
+    rb_provide("enc/trans/emoji_iso2022_kddi.so");
+
+    Init_trans_emoji_sjis_docomo();
+    rb_provide("enc/trans/emoji_sjis_docomo.so");
+
+    Init_trans_emoji_sjis_kddi();
+    rb_provide("enc/trans/emoji_sjis_kddi.so");
+
+    Init_trans_emoji_sjis_softbank();
+    rb_provide("enc/trans/emoji_sjis_softbank.so");
+
+    Init_trans_escape();
+    rb_provide("enc/trans/escape.so");
+
+    Init_trans_gb18030();
+    rb_provide("enc/trans/gb18030.so");
+
+    Init_trans_gbk();
+    rb_provide("enc/trans/gbk.so");
+
+    Init_trans_iso2022();
+    rb_provide("enc/trans/iso2022.so");
+
+    Init_trans_japanese();
+    rb_provide("enc/trans/japanese.so");
+
+    Init_trans_japanese_euc();
+    rb_provide("enc/trans/japanese_euc.so");
+
+    Init_trans_japanese_sjis();
+    rb_provide("enc/trans/japanese_sjis.so");
+
+    Init_trans_korean();
+    rb_provide("enc/trans/korean.so");
+
+    Init_trans_single_byte();
+    rb_provide("enc/trans/single_byte.so");
+
+    Init_trans_utf8_mac();
+    rb_provide("enc/trans/utf8_mac.so");
+
+    Init_trans_utf_16_32();
+    rb_provide("enc/trans/utf_16_32.so");
+
+    Init_bigdecimal();
+    rb_provide("bigdecimal");
+    rb_provide("bigdecimal.so");
+
+    Init_continuation();
+    rb_provide("continuation");
+    rb_provide("continuation.so");
+
+    Init_coverage();
+    rb_provide("coverage");
+    rb_provide("coverage.so");
+
+    Init_cparse();
+    rb_provide("cparse");
+    rb_provide("racc/cparse");
+    rb_provide("cparse.so");
+    rb_provide("racc/cparse.so");
+
+    Init_date_core();
+    rb_provide("date_core");
+    rb_provide("date_core.so");
+
+    Init_digest();
+    rb_provide("digest");
+    rb_provide("digest.so");
+
+    Init_escape();
+    rb_provide("escape");
+    rb_provide("escape.so");
+
+    Init_etc();
+    rb_provide("etc");
+    rb_provide("etc.so");
+
+    Init_fcntl();
+    rb_provide("fcntl");
+    rb_provide("fcntl.so");
+
+    Init_fiber();
+    rb_provide("fiber");
+    rb_provide("fiber.so");
+
+    Init_fiddle();
+    rb_provide("fiddle");
+    rb_provide("fiddle.so");
+
+// TODO: Fix this
+// #ifndef _WIN32
+    Init_generator();
+    rb_provide("json/ext/generator");
+    rb_provide("json/ext/generator.so");
+// #endif
+
+    Init_md5();
+    rb_provide("md5");
+    rb_provide("digest/md5");
+    rb_provide("md5.so");
+    rb_provide("digest/md5.so");
+
+    Init_monitor();
+    rb_provide("monitor");
+    rb_provide("monitor.so");
+
+    Init_nkf();
+    rb_provide("nkf");
+    rb_provide("nkf.so");
+
+    Init_nonblock();
+    rb_provide("nonblock");
+    rb_provide("nonblock.so");
+    rb_provide("io/nonblock");
+    rb_provide("io/nonblock.so");
+
+    Init_ruby_description();
+
+    Init_objspace();
+    rb_provide("objspace");
+    rb_provide("objspace.so");
+
+// TODO: Fix this
+// #ifndef _WIN32
+    Init_parser();
+    rb_provide("json/ext/parser");
+    rb_provide("json/ext/parser.so");
+// #endif
+
+    Init_pathname();
+    rb_provide("pathname");
+    rb_provide("pathname.so");
+
+// TODO: Fix this
+// #ifndef _WIN32
+    Init_psych();
+    rb_provide("psych");
+    rb_provide("psych.so");
+// #endif
+
+    Init_ripper();
+    rb_provide("ripper");
+    rb_provide("ripper.so");
+
+    Init_rmd160();
+    rb_provide("rmd160");
+    rb_provide("digest/rmd160");
+    rb_provide("rmd160.so");
+    rb_provide("digest/rmd160.so");
+
+    Init_sdbm();
+    rb_provide("sdbm");
+    rb_provide("sdbm.so");
+
+    Init_sha1();
+    rb_provide("sha1");
+    rb_provide("digest/sha1");
+    rb_provide("sha1.so");
+    rb_provide("digest/sha1.so");
+
+    Init_sha2();
+    rb_provide("sha2");
+    rb_provide("digest/sha2");
+    rb_provide("sha2.so");
+    rb_provide("digest/sha2.so");
+
+    Init_sizeof();
+    rb_provide("sizeof");
+    rb_provide("sizeof.so");
+
+    Init_socket();
+    rb_provide("socket");
+    rb_provide("socket.so");
+
+    Init_stringio();
+    rb_provide("stringio");
+    rb_provide("stringio.so");
+
+    Init_strscan();
+    rb_provide("strscan");
+    rb_provide("strscan.so");
+
+    Init_wait();
+    rb_provide("wait");
+    rb_provide("io/wait");
+    rb_provide("wait.so");
+    rb_provide("io/wait.so");
+
+    Init_zlib();
+    rb_provide("zlib");
+    rb_provide("zlib.so");
+
+    Init_openssl();
+    rb_provide("openssl");
+    rb_provide("openssl.so");
+
+    Init_nonblock();
+    rb_provide("io/nonblock");
+    rb_provide("io/nonblock.so");
+
+#ifndef _WIN32
+    // Init_console on Windows fails to load openssl
+    Init_console();
+    rb_provide("console");
+    rb_provide("console.so");
+
+    Init_dbm();
+    rb_provide("dbm");
+    rb_provide("dbm.so");
+
+    Init_gdbm();
+    rb_provide("gdbm");
+    rb_provide("gdbm.so");
+
+    Init_pty();
+    rb_provide("pty");
+    rb_provide("pty.so");
+
+    Init_readline();
+    rb_provide("readline");
+    rb_provide("readline.so");
+
+    Init_syslog();
+    rb_provide("syslog");
+    rb_provide("syslog.so");
+#endif
+}
+
+} // namespace openstudio
diff --git a/ruby/engine/InitRubyEngine.hpp b/ruby/engine/InitRubyEngine.hpp
new file mode 100644
index 00000000000..e7f6ad39fec
--- /dev/null
+++ b/ruby/engine/InitRubyEngine.hpp
@@ -0,0 +1,116 @@
+#ifndef INITRUBYENGINE_included
+#define INITRUBYENGINE_included
+
+extern "C"
+{
+  void Init_encdb();
+  void Init_big5();
+  void Init_cesu_8();
+  void Init_cp949();
+  void Init_emacs_mule();
+  void Init_euc_jp();
+  void Init_euc_kr();
+  void Init_euc_tw();
+  void Init_gb18030();
+  void Init_gb2312();
+  void Init_gbk();
+  void Init_iso_8859_1();
+  void Init_iso_8859_10();
+  void Init_iso_8859_11();
+  void Init_iso_8859_13();
+  void Init_iso_8859_14();
+  void Init_iso_8859_15();
+  void Init_iso_8859_16();
+  void Init_iso_8859_2();
+  void Init_iso_8859_3();
+  void Init_iso_8859_4();
+  void Init_iso_8859_5();
+  void Init_iso_8859_6();
+  void Init_iso_8859_7();
+  void Init_iso_8859_8();
+  void Init_iso_8859_9();
+  void Init_koi8_r();
+  void Init_koi8_u();
+  void Init_shift_jis();
+  void Init_utf_16be();
+  void Init_utf_16le();
+  void Init_utf_32be();
+  void Init_utf_32le();
+  void Init_windows_1250();
+  void Init_windows_1251();
+  void Init_windows_1252();
+  void Init_windows_1253();
+  void Init_windows_1254();
+  void Init_windows_1257();
+  void Init_windows_31j();
+
+  void Init_transdb();
+  void Init_trans_big5();
+  void Init_trans_cesu_8();
+  void Init_trans_chinese();
+  void Init_trans_ebcdic();
+  void Init_trans_emoji();
+  void Init_trans_emoji_iso2022_kddi();
+  void Init_trans_emoji_sjis_docomo();
+  void Init_trans_emoji_sjis_kddi();
+  void Init_trans_emoji_sjis_softbank();
+  void Init_trans_escape();
+  void Init_trans_gb18030();
+  void Init_trans_gbk();
+  void Init_trans_iso2022();
+  void Init_trans_japanese();
+  void Init_trans_japanese_euc();
+  void Init_trans_japanese_sjis();
+  void Init_trans_korean();
+  void Init_trans_single_byte();
+  void Init_trans_utf8_mac();
+  void Init_trans_utf_16_32();
+
+  void Init_bigdecimal();
+  void Init_bigdecimal(void);
+  void Init_continuation(void);
+  void Init_coverage(void);
+  void Init_cparse(void);
+  void Init_date_core(void);
+  void Init_digest(void);
+  void Init_escape(void);
+  void Init_etc(void);
+  void Init_fcntl(void);
+  void Init_fiber(void);
+  void Init_fiddle(void);
+  void Init_generator(void);
+  void Init_md5(void);
+  void Init_monitor(void);
+  void Init_nkf(void);
+  void Init_nonblock(void);
+  void Init_objspace(void);
+  void Init_parser(void);
+  void Init_pathname(void);
+  void Init_psych(void);
+  void Init_ripper(void);
+  void Init_rmd160(void);
+  void Init_sdbm(void);
+  void Init_sha1(void);
+  void Init_sha2(void);
+  void Init_sizeof(void);
+  void Init_socket(void);
+  void Init_stringio(void);
+  void Init_strscan(void);
+  void Init_wait(void);
+  void Init_zlib(void);
+
+  void Init_openssl(void);
+  void Init_ruby_description(void);
+
+#ifndef _WIN32
+  void Init_console(void);
+  void Init_dbm(void);
+  void Init_gdbm(void);
+  void Init_pty(void);
+  void Init_readline(void);
+  void Init_syslog(void);
+#endif
+
+} // extern C
+
+#endif // INITRUBYENGINE_included
diff --git a/ruby/engine/RubyEngine.cpp b/ruby/engine/RubyEngine.cpp
new file mode 100644
index 00000000000..8ae44b3a34d
--- /dev/null
+++ b/ruby/engine/RubyEngine.cpp
@@ -0,0 +1,111 @@
+#include "RubyEngine.hpp"
+#include "InitRubyBindings.hpp"
+#include <embedded_files.hxx>
+#include <rubyengine_export.h>
+#include <signal.h>
+#include <stdexcept>
+#include <string>
+#include <time.h>
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wregister"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static int argc = 0;
+static char **argv = nullptr;
+
+unsigned init() {
+  ruby_sysinit(&argc, &argv);
+  ruby_setup();
+  return 0u;
+}
+
+static unsigned i = init();
+
+extern "C" {
+void Init_EmbeddedScripting(void);
+}
+
+namespace openstudio {
+
+VALUE initRestOfOpenStudio(...) {
+  openstudio::ruby::initExtendedRubyBindings();
+  return Qtrue;
+};
+
+RubyEngine::RubyEngine(int argc, char *argv[]) : ScriptEngine(argc, argv) {
+  Init_EmbeddedScripting();
+  initRubyEngine();
+
+  openstudio::ruby::initBasicRubyBindings();
+
+  auto rubymodule = rb_define_module("OpenStudio");
+  rb_define_module_function(rubymodule, "init_rest_of_openstudio", initRestOfOpenStudio, 0);
+
+  ruby_set_argv(argc, argv);
+
+  // DLM: this will interpret any strings passed on the command line as UTF-8
+  // can we be smarter and detect the correct encoding? use wmain on windows to get utf-16?
+  // or we might want to follow ruby and allow '--external-encoding=UTF-8' as an input argument?
+  rb_enc_set_default_external(rb_enc_from_encoding(rb_utf8_encoding()));
+}
+
+RubyEngine::~RubyEngine() { ruby_finalize(); }
+
+ScriptObject RubyEngine::eval(std::string_view sv) {
+  std::string str{sv};
+  return ScriptObject{openstudio::evalString(str)};
+}
+
+void RubyEngine::exec(std::string_view sv) {
+  std::string str{sv};
+  [[maybe_unused]] const auto result = openstudio::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::getAs_impl(ScriptObject &obj, const std::type_info &ti) {
+  auto val = std::any_cast<VALUE>(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 openstudio
+
+extern "C" {
+RUBYENGINE_EXPORT openstudio::ScriptEngine *makeScriptEngine(int argc, char *argv[]) { return new openstudio::RubyEngine(argc, argv); }
+
+int rb_hasFile(const char *t_filename) {
+  // TODO Consider expanding this to use the path which we have artificially defined in embedded_help.rb
+  std::string expandedName = std::string(":/ruby/2.7.0/") + std::string(t_filename) + ".rb";
+  return embedded_files::hasFile(expandedName);
+}
+
+int rb_require_embedded(const char *t_filename) {
+  std::string require_script = R"(require ')" + std::string(t_filename) + R"(')";
+  openstudio::evalString(require_script);
+  return 0;
+}
+}
diff --git a/ruby/engine/RubyEngine.hpp b/ruby/engine/RubyEngine.hpp
new file mode 100644
index 00000000000..ec0ea131089
--- /dev/null
+++ b/ruby/engine/RubyEngine.hpp
@@ -0,0 +1,35 @@
+#ifndef RUBYENGINE_included
+#define RUBYENGINE_included
+
+#include <ScriptEngine.hpp>
+#include <RubyInterpreter.hpp>
+
+namespace openstudio {
+
+class RubyEngine final : public ScriptEngine
+{
+ public:
+  RubyEngine(int argc = 0, char* argv[] = nullptr);
+  ~RubyEngine() override;
+
+  RubyEngine(const RubyEngine&) = delete;
+  RubyEngine(RubyEngine&&) = delete;
+  RubyEngine& operator=(const RubyEngine&) = delete;
+  RubyEngine& operator=(RubyEngine&&) = delete;
+
+  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* getAs_impl(ScriptObject& obj, const std::type_info&) override;
+
+  void initRubyEngine();
+  std::vector<std::string> includePaths;
+  RubyInterpreter rubyInterpreter{includePaths};
+};
+
+} // namespace openstudio
+
+#endif
diff --git a/src/cli/embedded_help.rb b/ruby/engine/embedded_help.rb
similarity index 100%
rename from src/cli/embedded_help.rb
rename to ruby/engine/embedded_help.rb
diff --git a/src/cli/measure_manager.rb b/ruby/engine/measure_manager.rb
similarity index 100%
rename from src/cli/measure_manager.rb
rename to ruby/engine/measure_manager.rb
diff --git a/src/cli/measure_manager_driver.rb b/ruby/engine/measure_manager_driver.rb
similarity index 100%
rename from src/cli/measure_manager_driver.rb
rename to ruby/engine/measure_manager_driver.rb
diff --git a/src/cli/measure_manager_server.rb b/ruby/engine/measure_manager_server.rb
similarity index 100%
rename from src/cli/measure_manager_server.rb
rename to ruby/engine/measure_manager_server.rb
diff --git a/src/cli/openstudio_cli.rb b/ruby/engine/openstudio_cli.rb
similarity index 100%
rename from src/cli/openstudio_cli.rb
rename to ruby/engine/openstudio_cli.rb
diff --git a/src/cli/openstudio_init_extended.rb b/ruby/engine/openstudio_init_extended.rb
similarity index 100%
rename from src/cli/openstudio_init_extended.rb
rename to ruby/engine/openstudio_init_extended.rb
diff --git a/ruby/init_openstudio.hpp b/ruby/init_openstudio.hpp
deleted file mode 100644
index ba49458a84c..00000000000
--- a/ruby/init_openstudio.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/***********************************************************************************************************************
-*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
-*
-*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
-*  following conditions are met:
-*
-*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
-*  disclaimer.
-*
-*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
-*  disclaimer in the documentation and/or other materials provided with the distribution.
-*
-*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
-*  derived from this software without specific prior written permission from the respective party.
-*
-*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
-*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
-*  written permission from Alliance for Sustainable Energy, LLC.
-*
-*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
-*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
-*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-***********************************************************************************************************************/
-
-#include <string>
-
-
-void init_openstudio_internal();
-
-void init_openstudio_internal_basic();
-void init_openstudio_internal_extended();
-
-void evalString(const std::string &t_str);
-
diff --git a/ruby/interpreter/CMakeLists.txt b/ruby/interpreter/CMakeLists.txt
new file mode 100644
index 00000000000..0216a323a41
--- /dev/null
+++ b/ruby/interpreter/CMakeLists.txt
@@ -0,0 +1,38 @@
+# "Link" to this when you need to include ruby.h,
+# but don't want to link to libruby.
+add_library(rubyinterpreter-declarations INTERFACE)
+get_target_property(RUBY_INCLUDE_DIRS CONAN_PKG::openstudio_ruby INTERFACE_INCLUDE_DIRECTORIES)
+target_include_directories(rubyinterpreter-declarations INTERFACE
+  ${RUBY_INCLUDE_DIRS}
+  ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+# 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}/SWIGRubyRuntime.hxx"
+  COMMAND ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_LIB}"
+          "${SWIG_EXECUTABLE}"
+          "-ruby"
+          -external-runtime "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
+)
+
+# Link to rubyinterpreter when you need to link to OpenStudio's flavor of the Ruby interpreter
+if (CMAKE_VERSION VERSION_LESS 3.19.0)
+  add_library(rubyinterpreter INTERFACE)
+  target_sources(rubyinterpreter PUBLIC INTERFACE
+    RubyInterpreter.hpp
+    RubyEval.hpp
+    RubyException.hpp
+    "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
+  )
+else()
+  add_library(rubyinterpreter INTERFACE
+    RubyInterpreter.hpp
+    RubyEval.hpp
+    RubyException.hpp
+    "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
+  )
+endif()
+
+target_include_directories(rubyinterpreter INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries(rubyinterpreter INTERFACE CONAN_PKG::openstudio_ruby)
diff --git a/ruby/interpreter/InitDeclarations.hpp b/ruby/interpreter/InitDeclarations.hpp
new file mode 100644
index 00000000000..e22419eb06a
--- /dev/null
+++ b/ruby/interpreter/InitDeclarations.hpp
@@ -0,0 +1,118 @@
+  //void Init_EmbeddedScripting(void);
+  //INIT_DECLARATIONS;
+
+  //VALUE init_rest_of_openstudio(...) {
+  //  init_openstudio_internal_extended();
+  //  return Qtrue;
+  //}
+
+extern "C"
+{
+  void Init_encdb();
+  void Init_big5();
+  void Init_cesu_8();
+  void Init_cp949();
+  void Init_emacs_mule();
+  void Init_euc_jp();
+  void Init_euc_kr();
+  void Init_euc_tw();
+  void Init_gb18030();
+  void Init_gb2312();
+  void Init_gbk();
+  void Init_iso_8859_1();
+  void Init_iso_8859_10();
+  void Init_iso_8859_11();
+  void Init_iso_8859_13();
+  void Init_iso_8859_14();
+  void Init_iso_8859_15();
+  void Init_iso_8859_16();
+  void Init_iso_8859_2();
+  void Init_iso_8859_3();
+  void Init_iso_8859_4();
+  void Init_iso_8859_5();
+  void Init_iso_8859_6();
+  void Init_iso_8859_7();
+  void Init_iso_8859_8();
+  void Init_iso_8859_9();
+  void Init_koi8_r();
+  void Init_koi8_u();
+  void Init_shift_jis();
+  void Init_utf_16be();
+  void Init_utf_16le();
+  void Init_utf_32be();
+  void Init_utf_32le();
+  void Init_windows_1250();
+  void Init_windows_1251();
+  void Init_windows_1252();
+  void Init_windows_1253();
+  void Init_windows_1254();
+  void Init_windows_1257();
+  void Init_windows_31j();
+
+  void Init_transdb();
+  void Init_trans_big5();
+  void Init_trans_cesu_8();
+  void Init_trans_chinese();
+  void Init_trans_ebcdic();
+  void Init_trans_emoji();
+  void Init_trans_emoji_iso2022_kddi();
+  void Init_trans_emoji_sjis_docomo();
+  void Init_trans_emoji_sjis_kddi();
+  void Init_trans_emoji_sjis_softbank();
+  void Init_trans_escape();
+  void Init_trans_gb18030();
+  void Init_trans_gbk();
+  void Init_trans_iso2022();
+  void Init_trans_japanese();
+  void Init_trans_japanese_euc();
+  void Init_trans_japanese_sjis();
+  void Init_trans_korean();
+  void Init_trans_single_byte();
+  void Init_trans_utf8_mac();
+  void Init_trans_utf_16_32();
+
+  void Init_bigdecimal();
+  void Init_bigdecimal(void);
+  void Init_continuation(void);
+  void Init_coverage(void);
+  void Init_cparse(void);
+  void Init_date_core(void);
+  void Init_digest(void);
+  void Init_escape(void);
+  void Init_etc(void);
+  void Init_fcntl(void);
+  void Init_fiber(void);
+  void Init_fiddle(void);
+  void Init_generator(void);
+  void Init_md5(void);
+  void Init_monitor(void);
+  void Init_nkf(void);
+  void Init_nonblock(void);
+  void Init_objspace(void);
+  void Init_parser(void);
+  void Init_pathname(void);
+  void Init_psych(void);
+  void Init_ripper(void);
+  void Init_rmd160(void);
+  void Init_sdbm(void);
+  void Init_sha1(void);
+  void Init_sha2(void);
+  void Init_sizeof(void);
+  void Init_socket(void);
+  void Init_stringio(void);
+  void Init_strscan(void);
+  void Init_wait(void);
+  void Init_zlib(void);
+
+  void Init_openssl(void);
+  void Init_ruby_description(void);
+
+#ifndef _WIN32
+  void Init_console(void);
+  void Init_dbm(void);
+  void Init_gdbm(void);
+  void Init_pty(void);
+  void Init_readline(void);
+  void Init_syslog(void);
+#endif
+}
diff --git a/ruby/interpreter/RubyEval.hpp b/ruby/interpreter/RubyEval.hpp
new file mode 100644
index 00000000000..881a36d3b6f
--- /dev/null
+++ b/ruby/interpreter/RubyEval.hpp
@@ -0,0 +1,33 @@
+#ifndef RUBYEVAL_included
+#define RUBYEVAL_included
+
+#include "ruby.h"
+#include "./RubyException.hpp"
+
+namespace openstudio {
+
+inline VALUE evaluateSimpleImpl(VALUE arg) {
+  return rb_eval_string(StringValuePtr(arg));
+}
+
+inline 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 RubyException(err, loc);
+  }
+
+  return result;
+}
+
+} // openstudio
+
+#endif // RUBYEVAL_included
diff --git a/src/cli/RubyException.hpp b/ruby/interpreter/RubyException.hpp
similarity index 100%
rename from src/cli/RubyException.hpp
rename to ruby/interpreter/RubyException.hpp
diff --git a/src/cli/RubyInterpreter.hpp b/ruby/interpreter/RubyInterpreter.hpp
similarity index 94%
rename from src/cli/RubyInterpreter.hpp
rename to ruby/interpreter/RubyInterpreter.hpp
index b50575a2770..84d0175bdf8 100644
--- a/src/cli/RubyInterpreter.hpp
+++ b/ruby/interpreter/RubyInterpreter.hpp
@@ -27,11 +27,10 @@
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************************************************/
 
-#ifndef CLI_RUBYINTERPRETER_HPP
-#define CLI_RUBYINTERPRETER_HPP
-
-#include "RubyException.hpp"
+#ifndef RUBYINTERPRETER_HPP
+#define RUBYINTERPRETER_HPP
 
+#include "RubyEval.hpp"
 #include <vector>
 #include <string>
 #include <map>
@@ -686,16 +685,12 @@
 
 #include <ruby.h>
 #include <ruby/encoding.h>
+#include <SWIGRubyRuntime.hxx>
 
-#include <cli/SWIGRubyRuntime.hxx>
-
+// RubyInterpreter predates RubyEngine. It is possible that the two things should be merged into one.
 class RubyInterpreter
 {
  private:
-  // dummy arguments passed to ruby_options
-  // int m_argc;
-  // char** m_argv;
-
   static void addIncludePath(std::vector<std::string>& includePaths, const std::string& includePath) {
     includePaths.push_back("-I");
     includePaths.push_back(includePath);
@@ -703,33 +698,6 @@ class RubyInterpreter
 
  public:
   explicit RubyInterpreter(const std::vector<std::string>& t_includePaths) {
-
-    //// set load paths
-    //std::vector<std::string> rubyArgs;
-
-    //rubyArgs.emplace_back("-EUTF-8");
-
-    //for (const auto &p : t_includePaths) {
-    //  addIncludePath(rubyArgs, p);
-    //}
-
-    //// and now give the interpreter something to parse, so that it doesn't sit
-    //// waiting on stdin from us
-    //rubyArgs.emplace_back("-e");
-    //rubyArgs.emplace_back("");
-
-    //m_argc = static_cast<int>(rubyArgs.size());
-    //m_argv = new char*[m_argc];
-
-    //for (int i = 0; i < m_argc; ++i){
-    //  m_argv[i] = new char[rubyArgs[i].size() + 1];
-    //  strcpy(m_argv[i], rubyArgs[i].c_str());
-    //}
-
-    //// the return value of ruby_options is the parsed node of our "script"
-    //// from the -e "" we passed in. This could be used to actually parse / eval something if we wanted
-    //ruby_options(m_argc, m_argv);
-
     // register some default types that we want to pass in / out of the ruby system
     registerType<int>("int");
     registerType<long>("long");
@@ -737,17 +705,9 @@ class RubyInterpreter
     registerType<double>("double");
     registerType<std::string>("std::string");
     registerType<float>("float");
-
-    // set the script name if desired
-    //ruby_script("script_name");
   }
 
-  ~RubyInterpreter() {
-    //for (int i = 0; i < m_argc; i++){
-    //  delete[] m_argv[i];
-    //}
-    //delete[] m_argv;
-  }
+  ~RubyInterpreter() {}
 
   /// Register a type along with its vector versions so that it can be used
   /// from within the Ruby wrapper. These are necessary because there's no way to divine
@@ -765,26 +725,7 @@ class RubyInterpreter
   // TODO: should this be static?
   // cppcheck-suppress functionStatic
   void 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
-    //      const auto cwd = openstudio::filesystem::current_dir();
-    rb_protect(evaluateSimpleImpl, val, &error);
-    //      openstudio::filesystem::current_dir(cwd);
-
-    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 RubyException(err, loc);
-    }
+    openstudio::evalString(t_str);
   }
 
   /// Execute a function by name with 0 parameters and no return value
@@ -880,11 +821,6 @@ class RubyInterpreter
 
   std::map<std::string, std::string> m_types;
 
-  // Used for our rb_protect calls.
-  static VALUE evaluateSimpleImpl(VALUE arg) {
-    return rb_eval_string(StringValuePtr(arg));
-  }
-
   // Returns the swig_type_info for the templated type if it's been registered previously
   // with registerType
   template <typename Type>
@@ -1518,4 +1454,4 @@ class RubyInterpreter
 #  endif
 #endif
 
-#endif  // CLI_RUBYINTERPRETER_HPP
+#endif  // RUBYINTERPRETER_HPP
diff --git a/ruby/module/CMakeLists.txt b/ruby/module/CMakeLists.txt
new file mode 100644
index 00000000000..540de9b7a82
--- /dev/null
+++ b/ruby/module/CMakeLists.txt
@@ -0,0 +1,99 @@
+if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(Apple)?Clang$")
+  # using Clang
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -undefined dynamic_lookup")
+elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+  # using GCC
+  #set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --unresolved-symbols=ignore-all")
+  #set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --unresolved-symbols=ignore-all")
+endif()
+
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} )
+
+# openstudio ruby bindings to load into normal ruby
+add_library(
+  openstudio_rb
+  MODULE
+  RubyAPI.hpp
+  openstudio_rb.cpp
+)
+set_target_properties(openstudio_rb PROPERTIES PREFIX "")
+set_target_properties(openstudio_rb PROPERTIES OUTPUT_NAME openstudio)
+target_include_directories(openstudio_rb SYSTEM PRIVATE ${RUBY_INCLUDE_DIRS})
+
+# change output directory so the openstudio.pdb file does not conflict between this target and the CLI
+set_target_properties(openstudio_rb PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/ruby/")
+set_target_properties(openstudio_rb PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ruby/")
+set_target_properties(openstudio_rb PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ruby/")
+
+if(APPLE)
+  set_target_properties(openstudio_rb PROPERTIES SUFFIX ".bundle" )
+else()
+  set_target_properties(openstudio_rb PROPERTIES SUFFIX ".so" )
+endif()
+
+if(MSVC)
+  set_target_properties(openstudio_rb PROPERTIES COMPILE_FLAGS "/bigobj /wd4996 /wd5033")
+endif()
+
+add_custom_command(TARGET openstudio_rb
+  POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/openstudio.rb $<TARGET_FILE_DIR:openstudio_rb>/openstudio.rb
+)
+
+file(GLOB_RECURSE OPENSTUDIO_FILES FOLLOW_SYMLINKS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/" "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/**/*.*")
+foreach(OPENSTUDIO_FILE ${OPENSTUDIO_FILES})
+  add_custom_command(TARGET openstudio_rb
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/${OPENSTUDIO_FILE}" "$<TARGET_FILE_DIR:openstudio_rb>/openstudio/${OPENSTUDIO_FILE}"
+  )
+endforeach()
+
+if( WIN32 )
+  # Dynamically link to openstudiolib.dll on Windows only
+  target_link_libraries(
+    openstudio_rb
+    PUBLIC
+    openstudiolib
+    PRIVATE
+    rubybindings
+    ${ALL_RUBY_BINDING_TARGETS}
+  )
+  target_link_libraries(openstudio_rb PRIVATE ${RUBY_MINGW_STUB_LIB})
+else()
+  # statically link everywhere else
+  target_link_libraries(
+    openstudio_rb
+    PRIVATE
+    openstudio_utilities
+    openstudio_airflow
+    openstudio_model
+    openstudio_energyplus
+    openstudio_epjson
+    openstudio_measure
+    openstudio_osversion
+    openstudio_sdd
+    openstudio_isomodel
+    openstudio_gbxml
+    openstudio_gltf
+    openstudio_radiance
+    rubybindings
+    ${ALL_RUBY_BINDING_TARGETS}
+  )
+endif()
+
+if (APPLE)
+  target_link_libraries(openstudio_rb
+    PRIVATE
+    ${OPENSSL_CRYPTO_LIBRARY}
+    ${OPENSSL_SSL_LIBRARY}
+  )
+endif()
+
+install(TARGETS openstudio_rb
+  EXPORT openstudio
+  DESTINATION Ruby
+  COMPONENT "RubyAPI"
+)
+install(FILES openstudio.rb DESTINATION Ruby COMPONENT "RubyAPI")
+install(DIRECTORY openstudio DESTINATION Ruby COMPONENT "RubyAPI")
diff --git a/ruby/RubyAPI.hpp b/ruby/module/RubyAPI.hpp
similarity index 100%
rename from ruby/RubyAPI.hpp
rename to ruby/module/RubyAPI.hpp
diff --git a/ruby/openstudio.rb b/ruby/module/openstudio.rb
similarity index 68%
rename from ruby/openstudio.rb
rename to ruby/module/openstudio.rb
index 9d83720ead7..15699e71004 100644
--- a/ruby/openstudio.rb
+++ b/ruby/module/openstudio.rb
@@ -32,27 +32,40 @@
 if /mswin/.match(RUBY_PLATFORM) or /mingw/.match(RUBY_PLATFORM)
 
   if defined?(RubyInstaller::Runtime)
-
-    cur_location = File.dirname(__FILE__)
-
-    build_type = File.basename(cur_location)
-
+    require 'pathname'
+    # This is an absolute path
+    cur_location = Pathname.new(__dir__)
+    build_type = cur_location.basename.to_s
     if build_type == "Debug" or build_type == "Release" or build_type == "RelWithDebugInfo" or build_type == "RelMinSize"
-      # in build dir execution
-      relative_dll_path = File.join(File.dirname(File.dirname(cur_location)), build_type)
-      RubyInstaller::Runtime.add_dll_directory(relative_dll_path)
+      # Multi configuration build dir:
+      # We're at `build/Products/ruby/Release/openstudio.rb`
+      # and we want openstudiolib.dll in `Products/Release`
+      abs_dll_path = cur_location.parent.parent.join(build_type)
+      # First thing is done in there is to foward to DllDirectory, which does
+      # File.expand_path anyways in the initialize method
+      # puts "build_type=#{build_type}, abs_dll_path=#{abs_dll_path}"
+      RubyInstaller::Runtime.add_dll_directory(abs_dll_path)
+    elsif cur_location.parent.basename.to_s == "Products"
+      # This is a build dir execution, but without a multi configuration
+      # generator such as Ninja.
+      # We're at `build/Products/ruby` and we want `build/Products/`
+      abs_dll_path =  cur_location.parent
+      # puts "cur_location.parent.basename=#{cur_location.parent.basename}, abs_dll_path=#{abs_dll_path}"
+      RubyInstaller::Runtime.add_dll_directory(abs_dll_path)
     else
-      # install dir execution
+      # install dir execution. We at `install/Ruby/openstudio.rb'
+      # and we want `install/bin`
       ['bin', 'lib'].each do |p|
-        if File.exists?(File.join(File.dirname(cur_location), p))
-          RubyInstaller::Runtime.add_dll_directory(File.join(File.dirname(cur_location), p))
+        d = cur_location.join('..', p)
+        if d.exist?
+          RubyInstaller::Runtime.add_dll_directory(d)
         end
       end
     end
   end
 else
   # Do something here for Mac OSX environments
-  ENV['PATH'] = "#{File.dirname(__FILE__)}:#{original_path}"
+  ENV['PATH'] = "#{__dir__}:#{original_path}"
 end
 
 # require openstudio.so
@@ -62,4 +75,4 @@
 ENV['PATH'] = original_path
 
 # add this directory to Ruby load path
-$:.unshift(File.expand_path(File.dirname(__FILE__)))
+$:.unshift(__dir__)
diff --git a/ruby/openstudio/energyplus/find_energyplus.rb b/ruby/module/openstudio/energyplus/find_energyplus.rb
similarity index 100%
rename from ruby/openstudio/energyplus/find_energyplus.rb
rename to ruby/module/openstudio/energyplus/find_energyplus.rb
diff --git a/ruby/openstudio/measure/ShowRunnerOutput.rb b/ruby/module/openstudio/measure/ShowRunnerOutput.rb
similarity index 100%
rename from ruby/openstudio/measure/ShowRunnerOutput.rb
rename to ruby/module/openstudio/measure/ShowRunnerOutput.rb
diff --git a/ruby/openstudio/ruleset/ShowRunnerOutput.rb b/ruby/module/openstudio/ruleset/ShowRunnerOutput.rb
similarity index 100%
rename from ruby/openstudio/ruleset/ShowRunnerOutput.rb
rename to ruby/module/openstudio/ruleset/ShowRunnerOutput.rb
diff --git a/ruby/openstudio_rb.cpp b/ruby/module/openstudio_rb.cpp
similarity index 97%
rename from ruby/openstudio_rb.cpp
rename to ruby/module/openstudio_rb.cpp
index fb45f2c763a..bec1f527823 100644
--- a/ruby/openstudio_rb.cpp
+++ b/ruby/module/openstudio_rb.cpp
@@ -27,7 +27,7 @@
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************************************************/
 
-#include "init_openstudio.hpp"
+#include "InitRubyBindings.hpp"
 #include <RubyAPI.hpp>
 #include <iostream>
 #include <ruby.h>
@@ -36,9 +36,8 @@ extern "C" {
 
 RUBY_API void Init_openstudio(void) {
 
-  init_openstudio_internal();
+  openstudio::ruby::initRubyBindings();
 
 }
 
 }
-
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index d01b0e817e7..8720415cf47 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -2,235 +2,8 @@ if (APPLE)
    find_library(COREFOUNDATION_LIBRARY CoreFoundation )
 endif (APPLE)
 
-set(INIT_CALLS "")
-set(INIT_DECLARATIONS "")
-foreach(LIB ${EXTENSION_LIBS_LIST})
-  get_filename_component(LIB_NAME ${LIB} NAME_WE)
-  list (FIND LIBS "${LIB_NAME}" _index)
-  if( ${_index} GREATER -1 )
-    set(INIT_DECLARATIONS "${INIT_DECLARATIONS}  void Init_${LIB_NAME}();\\\n" )
-    set(INIT_CALLS "${INIT_CALLS}  Init_${LIB_NAME}();\\\n" )
-    set(INIT_CALLS "${INIT_CALLS}  rb_provide(\"${LIB_NAME}.so\");\\\n" )
-  endif()
-endforeach()
-
-CONFIGURE_FILE_WITH_CHECKSUM("InitMacros.hxx.in" "${CMAKE_CURRENT_BINARY_DIR}/InitMacros.hxx")
-
-set(MODULE_ROOT "${CONAN_OPENSTUDIO_RUBY_ROOT}")
-if ("${MODULE_ROOT}" STREQUAL "")
-  set(MODULE_ROOT "${CONAN_OPENSTUDIO_RUBY_ROOT_DEBUG}")
-endif()
-
-message("Searching for ruby modules in '${MODULE_ROOT}/lib/**/*.rb'")
-file(GLOB_RECURSE  EXTENSION_RB  FOLLOW_SYMLINKS "${MODULE_ROOT}/lib/**/*.rb")
-
-foreach( _FILE ${EXTENSION_RB} )
-  file(RELATIVE_PATH LOCATION ${MODULE_ROOT}/lib ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-set(GEMFILE_FILES "${OPENSTUDIO_GEMS_DIR}/openstudio-gems.gemspec" "${PROJECT_BINARY_DIR}/openstudio-gems/Gemfile" "${PROJECT_BINARY_DIR}/openstudio-gems/Gemfile.lock")
-file(GLOB_RECURSE RB_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.rb")
-file(GLOB_RECURSE ERB_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.erb")
-file(GLOB_RECURSE JS_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.js")
-file(GLOB_RECURSE CSS_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.css")
-file(GLOB_RECURSE GIF_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gif")
-file(GLOB_RECURSE PNG_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.png")
-file(GLOB_RECURSE HTML_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.html")
-file(GLOB_RECURSE IDF_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.idf")
-file(GLOB_RECURSE OSM_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.osm")
-file(GLOB_RECURSE EPW_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.epw")
-file(GLOB_RECURSE DDY_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.ddy")
-file(GLOB_RECURSE STAT_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.stat")
-file(GLOB_RECURSE CSV_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.csv")
-file(GLOB_RECURSE JSON_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.json")
-file(GLOB_RECURSE SPEC_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gemspec")
-file(GLOB_RECURSE GZ_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.gz")
-file(GLOB_RECURSE YML_FILES FOLLOW_SYMLINKS "${OPENSTUDIO_GEMS_DIR}/**/*.yml")
-file(GLOB_RECURSE OS_FILES FOLLOW_SYMLINKS "${CMAKE_CURRENT_SOURCE_DIR}/openstudio/**/*.rb") # DLM: this is temporary, these files should live somewhere else and be included in the shared Ruby lib
-
-foreach( _FILE ${GEMFILE_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${RB_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${ERB_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${JS_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${CSS_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${GIF_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${PNG_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${HTML_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${IDF_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${OSM_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${EPW_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${DDY_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${STAT_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${CSV_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${JSON_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${SPEC_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${GZ_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-foreach( _FILE ${YML_FILES} )
-  file(RELATIVE_PATH LOCATION "${PROJECT_BINARY_DIR}/openstudio-gems" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-# DLM: this is temporary, these files should live somewhere else and be included in the shared Ruby lib
-foreach( _FILE ${OS_FILES} )
-  file(RELATIVE_PATH LOCATION "${CMAKE_CURRENT_SOURCE_DIR}" ${_FILE})
-  list(APPEND FILES ${_FILE})
-  list(APPEND EMBEDDED_PATHS ${LOCATION})
-endforeach()
-
-list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/embedded_help.rb")
-list(APPEND EMBEDDED_PATHS "embedded_help.rb")
-
-list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/openstudio_cli.rb")
-list(APPEND EMBEDDED_PATHS "openstudio_cli.rb")
-
-list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/openstudio_init_extended.rb")
-list(APPEND EMBEDDED_PATHS "openstudio_init_extended.rb")
-
-
-list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/measure_manager.rb")
-list(APPEND EMBEDDED_PATHS "measure_manager.rb")
-
-list(APPEND FILES "${CMAKE_CURRENT_SOURCE_DIR}/measure_manager_server.rb")
-list(APPEND EMBEDDED_PATHS "measure_manager_server.rb")
-
-embed_files("${FILES}" "${EMBEDDED_PATHS}" OUTPUT)
-
-set_source_files_properties(EmbeddedScripting.i
-  PROPERTIES CPLUSPLUS true
-)
-
-include_directories(${CMAKE_CURRENT_BINARY_DIR}
-  ${PROJECT_BINARY_DIR}
-  ${PROJECT_SOURCE_DIR}
-  ${CMAKE_CURRENT_SOURCE_DIR}
-  ${OPENSTUDIO_GEMS_DIR}
-)
-
-# 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}/SWIGRubyRuntime.hxx"
-  COMMAND ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_LIB}"
-          "${SWIG_EXECUTABLE}"
-          "-ruby"
-          -external-runtime "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
-)
-
-add_custom_command(
-  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
-  COMMAND ${CMAKE_COMMAND} -E env SWIG_LIB="${SWIG_LIB}"
-          "${SWIG_EXECUTABLE}"
-          "-ruby"
-          "-c++"
-          -o "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
-          "-fvirtual"
-          "-I${PROJECT_SOURCE_DIR}/src"
-          "-I${PROJECT_BINARY_DIR}/src"
-          "-I${OPENSTUDIO_GEMS_DIR}"
-          "-D_WINDOWS"
-          "-Fmicrosoft"
-          "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedScripting.i"
-  DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedScripting.i"
-          "${CMAKE_CURRENT_BINARY_DIR}/embedded_files.hxx"
-          "EmbeddedHelp.hpp"
-)
-
-set_source_files_properties(${EMBED_SOURCE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE)
-
-source_group(embedded_files FILES ${OUTPUT})
-
 if(WIN32)
   CONFIGURE_FILE_WITH_CHECKSUM(openstudio.rc.in "${CMAKE_CURRENT_BINARY_DIR}/openstudio.rc")
-endif()
-
-if(WIN32)
 
   # This is really just about RC but instead of spending hours trying to figure how to disable /bigobj just for that part,
   # I removed the definition altogether and didn't hit any issue.
@@ -238,85 +11,31 @@ if(WIN32)
 
   add_executable(openstudio
     main.cpp
-    "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
-    RubyException.hpp
-    RubyInterpreter.hpp
-    "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
-    ${OUTPUT}
+    ScriptEngineGlobals.hpp
+    ScriptEngineGlobals.cpp
     "${CMAKE_CURRENT_BINARY_DIR}/openstudio.rc"
   )
 else()
   add_executable(openstudio
     main.cpp
-    "${CMAKE_CURRENT_BINARY_DIR}/SWIGRubyRuntime.hxx"
-    RubyException.hpp
-    RubyInterpreter.hpp
-    "${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx"
-    ${OUTPUT}
+    ScriptEngineGlobals.hpp
+    ScriptEngineGlobals.cpp
   )
 endif()
 
-
-# -Wno-deprecated-declaration, /wd4996: suppresses deprecated warning
-# -Wno-register, /wd5033: ISO C++1z does not allow �register� storage class specifier
-if(MSVC)
-  set_target_properties(openstudio PROPERTIES COMPILE_FLAGS "/bigobj /wd4996 /wd5033")
-  set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx" PROPERTIES COMPILE_FLAGS "-DRUBY_EMBEDDED" )
-else()
-  set_target_properties(openstudio PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-register")
-  set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/embedded_scripting_wrap.cxx" PROPERTIES COMPILE_FLAGS "-DRUBY_EMBEDDED -Wno-unused-variable" )
-endif()
-
-# We are setting up a set of artificial dependencies here so that
-# cmake knows how to order the libraries properly
-#
-# Better: properly specifying these libs in the conan recipe
-# At the moment this is not possible due to some of them coming from the OS and some
-# being built by this project
-# TODO: Continue to make the conan deps more robust and get this sorted out
-add_library(additional_ruby_libs empty.cpp)
-add_library(ruby_libs empty.cpp)
-target_link_libraries(ruby_libs PUBLIC CONAN_PKG::openstudio_ruby)
-target_link_libraries(ruby_libs PUBLIC additional_ruby_libs)
-
-target_link_libraries(openstudio PRIVATE init_openstudio ruby_libs ${ALL_RUBY_BINDING_TARGETS})
-
-include("${OPENSTUDIO_GEMS_DIR}/export-extensions.cmake")
-
 target_link_libraries(openstudio
  PRIVATE
- openstudio_utilities
- openstudio_airflow
- openstudio_model
- openstudio_energyplus
- openstudio_epjson
- openstudio_measure
- openstudio_osversion
- openstudio_sdd
- openstudio_isomodel
- openstudio_gbxml
- openstudio_gltf
- openstudio_radiance
- ${ruby_extension_libs}
+ openstudio_utilities_minimal
+ CONAN_PKG::cli11
 )
 
-if (UNIX AND NOT APPLE)
-  target_link_libraries(additional_ruby_libs dl crypt anl)
-  target_link_libraries(openstudio PRIVATE "icui18n" "icuuc" "gmp")
-elseif(WIN32)
-  target_link_libraries(openstudio PRIVATE wsock32 ws2_32 Dbghelp Shlwapi Iphlpapi)
-  set_target_properties(openstudio PROPERTIES LINK_FLAGS_DEBUG "/NODEFAULTLIB:MSVCRT")
-else()
-endif()
+add_dependencies(openstudio rubyengine)
 
 if( APPLE )
   target_link_libraries(openstudio PRIVATE ${COREFOUNDATION_LIBRARY})
 endif()
 
-install(TARGETS openstudio EXPORT openstudio DESTINATION bin COMPONENT "CLI")
-install(FILES openstudio_cli.rb DESTINATION Ruby COMPONENT "RubyAPI")
-install(FILES measure_manager.rb DESTINATION Ruby COMPONENT "RubyAPI")
-install(FILES measure_manager_server.rb DESTINATION Ruby COMPONENT "RubyAPI")
+install(TARGETS openstudio EXPORT openstudio COMPONENT "CLI")
 
 if( BUILD_PAT )
   if( APPLE )
diff --git a/src/cli/ScriptEngineGlobals.cpp b/src/cli/ScriptEngineGlobals.cpp
new file mode 100644
index 00000000000..17f8238cd86
--- /dev/null
+++ b/src/cli/ScriptEngineGlobals.cpp
@@ -0,0 +1,9 @@
+#include "./ScriptEngineGlobals.hpp"
+#include "../scriptengine/ScriptEngine.hpp"
+
+namespace openstudio {
+
+std::unique_ptr<openstudio::ScriptEngine> rubyEngine;
+std::unique_ptr<openstudio::ScriptEngine> pythonEngine;
+
+}  // namespace openstudio
diff --git a/src/cli/ScriptEngineGlobals.hpp b/src/cli/ScriptEngineGlobals.hpp
new file mode 100644
index 00000000000..2fe25d5b066
--- /dev/null
+++ b/src/cli/ScriptEngineGlobals.hpp
@@ -0,0 +1,14 @@
+#ifndef ScriptEngineGlobals_Included
+#define ScriptEngineGlobals_Included
+#include <memory>
+
+namespace openstudio {
+
+class ScriptEngine;
+
+extern std::unique_ptr<openstudio::ScriptEngine> rubyEngine;
+extern std::unique_ptr<openstudio::ScriptEngine> pythonEngine;
+
+}  // namespace openstudio
+
+#endif
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index c17243fbd2f..5ab61a53c1f 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -1,577 +1,14 @@
-/***********************************************************************************************************************
-*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
-*
-*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
-*  following conditions are met:
-*
-*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
-*  disclaimer.
-*
-*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
-*  disclaimer in the documentation and/or other materials provided with the distribution.
-*
-*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
-*  derived from this software without specific prior written permission from the respective party.
-*
-*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
-*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
-*  written permission from Alliance for Sustainable Energy, LLC.
-*
-*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
-*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
-*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-***********************************************************************************************************************/
-
-#include "RubyInterpreter.hpp"
-#include "GC_Value.hpp"
-#include "InitMacros.hxx"
-#include "../../ruby/init_openstudio.hpp"
-#include <embedded_files.hxx>
-
+#include "./ScriptEngineGlobals.hpp"
+#include "../scriptengine/ScriptEngine.hpp"
+#include <CLI/CLI.hpp>
 #include <iostream>
 
-#ifndef _MSC_VER
-#  include <dlfcn.h>
-#  include <dirent.h>
-#else
-#  include <windows.h>
-#  pragma warning(disable : 4930)
-#  pragma warning(disable : 4101)
-#endif
-
-extern "C"
-{
-  void Init_EmbeddedScripting(void);
-  INIT_DECLARATIONS;
-
-  void Init_encdb();
-
-  //void Init_ascii(); // this is not included in libenc
-  void Init_big5();
-  void Init_cesu_8();
-  void Init_cp949();
-  void Init_emacs_mule();
-  void Init_euc_jp();
-  void Init_euc_kr();
-  void Init_euc_tw();
-  void Init_gb18030();
-  void Init_gb2312();
-  void Init_gbk();
-  void Init_iso_8859_1();
-  void Init_iso_8859_10();
-  void Init_iso_8859_11();
-  void Init_iso_8859_13();
-  void Init_iso_8859_14();
-  void Init_iso_8859_15();
-  void Init_iso_8859_16();
-  void Init_iso_8859_2();
-  void Init_iso_8859_3();
-  void Init_iso_8859_4();
-  void Init_iso_8859_5();
-  void Init_iso_8859_6();
-  void Init_iso_8859_7();
-  void Init_iso_8859_8();
-  void Init_iso_8859_9();
-  void Init_koi8_r();
-  void Init_koi8_u();
-  void Init_shift_jis();
-  //void Init_unicode(); // this is not included in libenc
-  //void Init_us_ascii(); // this is not included in libenc
-  void Init_utf_16be();
-  void Init_utf_16le();
-  void Init_utf_32be();
-  void Init_utf_32le();
-  //void Init_utf_8(); // this is not included in libenc
-  void Init_windows_1250();
-  void Init_windows_1251();
-  void Init_windows_1252();
-  void Init_windows_1253();
-  void Init_windows_1254();
-  void Init_windows_1257();
-  void Init_windows_31j();
-
-  void Init_transdb();
-
-  void Init_trans_big5();
-  void Init_trans_cesu_8();
-  void Init_trans_chinese();
-  void Init_trans_ebcdic();
-  void Init_trans_emoji();
-  void Init_trans_emoji_iso2022_kddi();
-  void Init_trans_emoji_sjis_docomo();
-  void Init_trans_emoji_sjis_kddi();
-  void Init_trans_emoji_sjis_softbank();
-  void Init_trans_escape();
-  void Init_trans_gb18030();
-  void Init_trans_gbk();
-  void Init_trans_iso2022();
-  void Init_trans_japanese();
-  void Init_trans_japanese_euc();
-  void Init_trans_japanese_sjis();
-  void Init_trans_korean();
-  void Init_trans_single_byte();
-  void Init_trans_utf8_mac();
-  void Init_trans_utf_16_32();
-
-  void Init_bigdecimal();
-  void Init_bigdecimal(void);
-  void Init_continuation(void);
-  void Init_coverage(void);
-  void Init_cparse(void);
-  void Init_date_core(void);
-  void Init_digest(void);
-  void Init_escape(void);
-  void Init_etc(void);
-  void Init_fcntl(void);
-  void Init_fiber(void);
-  void Init_fiddle(void);
-  void Init_generator(void);
-  void Init_md5(void);
-  void Init_monitor(void);
-  void Init_nkf(void);
-  void Init_nonblock(void);
-  void Init_objspace(void);
-  void Init_parser(void);
-  void Init_pathname(void);
-  void Init_psych(void);
-  void Init_ripper(void);
-  void Init_rmd160(void);
-  void Init_sdbm(void);
-  void Init_sha1(void);
-  void Init_sha2(void);
-  void Init_sizeof(void);
-  void Init_socket(void);
-  void Init_stringio(void);
-  void Init_strscan(void);
-  void Init_wait(void);
-  void Init_zlib(void);
-
-  void Init_openssl(void);
-  void Init_ruby_description(void);
-
-#ifndef _WIN32
-  void Init_console(void);
-  void Init_dbm(void);
-  void Init_gdbm(void);
-  void Init_pty(void);
-  void Init_readline(void);
-  void Init_syslog(void);
-#endif
-
-  VALUE init_rest_of_openstudio(...) {
-    init_openstudio_internal_extended();
-    return Qtrue;
-  }
-}
-
-std::vector<std::string> paths;
-static RubyInterpreter rubyInterpreter(paths);  //(paths);
-
-int main(int argc, char* argv[]) {
-  ruby_sysinit(&argc, &argv);
-  {
-    RUBY_INIT_STACK;
-    ruby_init();
-
-    swig::GC_VALUE::hash_id = rb_intern("hash");
-    swig::GC_VALUE::lt_id = rb_intern("<");
-    swig::GC_VALUE::gt_id = rb_intern(">");
-    swig::GC_VALUE::eq_id = rb_intern("==");
-    swig::GC_VALUE::le_id = rb_intern("<=");
-    swig::GC_VALUE::ge_id = rb_intern(">=");
-
-    swig::GC_VALUE::pos_id = rb_intern("+@");
-    swig::GC_VALUE::neg_id = rb_intern("-@");
-    swig::GC_VALUE::inv_id = rb_intern("~");
-
-    swig::GC_VALUE::add_id = rb_intern("+");
-    swig::GC_VALUE::sub_id = rb_intern("-");
-    swig::GC_VALUE::mul_id = rb_intern("*");
-    swig::GC_VALUE::div_id = rb_intern("/");
-    swig::GC_VALUE::mod_id = rb_intern("%");
-
-    swig::GC_VALUE::and_id = rb_intern("&");
-    swig::GC_VALUE::or_id = rb_intern("|");
-    swig::GC_VALUE::xor_id = rb_intern("^");
-
-    swig::GC_VALUE::lshift_id = rb_intern("<<");
-    swig::GC_VALUE::rshift_id = rb_intern(">>");
-
-    INIT_CALLS;
-
-    // in case any further init methods try to require files, init this first
-    Init_EmbeddedScripting();
-
-    // Need embedded_help for requiring files out of the embedded system
-    auto embedded_extensions_string = embedded_files::getFileAsString(":/embedded_help.rb");
-
-    try {
-      rubyInterpreter.evalString(embedded_extensions_string);
-    } catch (const std::exception& e) {
-      rubyInterpreter.evalString(R"(STDOUT.flush)");
-      std::cout << "Exception in embedded_help: " << e.what() << std::endl;  // endl will flush
-      return ruby_cleanup(1);
-    } catch (...) {
-      rubyInterpreter.evalString(R"(STDOUT.flush)");
-      std::cout << "Unknown Exception in embedded_help" << std::endl;  // endl will flush
-      return ruby_cleanup(1);
-    }
-
-    //// encodings
-    /// Get the symbols from: `DUMPBIN /ARCHIVEMEMBERS  C:\Users\julien\.conan\data\openstudio_ruby\2.7.2\nrel\testing\package\2fa339f0e9f8e459bd56b19a37be69734f2745f4\lib\enc\libenc.lib`
-    Init_encdb();
-    rb_provide("enc/encdb.so");
-    //Init_ascii();
-    //rb_provide("enc/ascii.so");
-    Init_big5();
-    rb_provide("enc/big5.so");
-    Init_cesu_8();
-    rb_provide("enc/cesu_8.so");
-    Init_cp949();
-    rb_provide("enc/cp949.so");
-    Init_emacs_mule();
-    rb_provide("enc/emacs_mule.so");
-    Init_euc_jp();
-    rb_provide("enc/euc_jp.so");
-    Init_euc_kr();
-    rb_provide("enc/euc_kr.so");
-    Init_euc_tw();
-    rb_provide("enc/euc_tw.so");
-    Init_gb18030();
-    rb_provide("enc/gb18030.so");
-    Init_gb2312();
-    rb_provide("enc/gb2312.so");
-    Init_gbk();
-    rb_provide("enc/gbk.so");
-    Init_iso_8859_1();
-    rb_provide("enc/iso_8859_1.so");
-    Init_iso_8859_10();
-    rb_provide("enc/iso_8859_10.so");
-    Init_iso_8859_11();
-    rb_provide("enc/iso_8859_11.so");
-    Init_iso_8859_13();
-    rb_provide("enc/iso_8859_13.so");
-    Init_iso_8859_14();
-    rb_provide("enc/iso_8859_14.so");
-    Init_iso_8859_15();
-    rb_provide("enc/iso_8859_15.so");
-    Init_iso_8859_16();
-    rb_provide("enc/iso_8859_16.so");
-    Init_iso_8859_2();
-    rb_provide("enc/iso_8859_2.so");
-    Init_iso_8859_3();
-    rb_provide("enc/iso_8859_3.so");
-    Init_iso_8859_4();
-    rb_provide("enc/iso_8859_4.so");
-    Init_iso_8859_5();
-    rb_provide("enc/iso_8859_5.so");
-    Init_iso_8859_6();
-    rb_provide("enc/iso_8859_6.so");
-    Init_iso_8859_7();
-    rb_provide("enc/iso_8859_7.so");
-    Init_iso_8859_8();
-    rb_provide("enc/iso_8859_8.so");
-    Init_iso_8859_9();
-    rb_provide("enc/iso_8859_9.so");
-    Init_koi8_r();
-    rb_provide("enc/koi8_r.so");
-    Init_koi8_u();
-    rb_provide("enc/koi8_u.so");
-    Init_shift_jis();
-    rb_provide("enc/shift_jis.so");
-    //Init_unicode();
-    //rb_provide("enc/unicode.so");
-    //Init_us_ascii();
-    //rb_provide("enc/us_ascii.so");
-    Init_utf_16be();
-    rb_provide("enc/utf_16be.so");
-    Init_utf_16le();
-    rb_provide("enc/utf_16le.so");
-    Init_utf_32be();
-    rb_provide("enc/utf_32be.so");
-    Init_utf_32le();
-    rb_provide("enc/utf_32le.so");
-    //Init_utf_8();
-    //rb_provide("enc/utf_8.so");
-    Init_windows_1250();
-    rb_provide("enc/windows_1250.so");
-    Init_windows_1251();
-    rb_provide("enc/windows_1251.so");
-    Init_windows_1252();
-    rb_provide("enc/windows_1252.so");
-    Init_windows_1253();
-    rb_provide("enc/windows_1253.so");
-    Init_windows_1254();
-    rb_provide("enc/windows_1254.so");
-    Init_windows_1257();
-    rb_provide("enc/windows_1257.so");
-    Init_windows_31j();
-    rb_provide("enc/windows_31j.so");
-
-    /// Get the symbols from: `DUMPBIN /ARCHIVEMEMBERS  C:\Users\julien\.conan\data\openstudio_ruby\2.7.2\nrel\testing\package\2fa339f0e9f8e459bd56b19a37be69734f2745f4\lib\enc\libtrans.lib`
-    Init_transdb();
-    rb_provide("enc/trans/transdb.so");
-
-    //Init_trans_big5();
-    //rb_provide("enc/trans/big5.so");
-    Init_trans_big5();
-    rb_provide("enc/trans/big5.so");
-
-    Init_trans_cesu_8();
-    rb_provide("enc/trans/cesu_8.so");
-
-    Init_trans_chinese();
-    rb_provide("enc/trans/chinese.so");
-
-    Init_trans_ebcdic();
-    rb_provide("enc/trans/ebcdic.so");
-
-    Init_trans_emoji();
-    rb_provide("enc/trans/emoji.so");
-
-    Init_trans_emoji_iso2022_kddi();
-    rb_provide("enc/trans/emoji_iso2022_kddi.so");
-
-    Init_trans_emoji_sjis_docomo();
-    rb_provide("enc/trans/emoji_sjis_docomo.so");
-
-    Init_trans_emoji_sjis_kddi();
-    rb_provide("enc/trans/emoji_sjis_kddi.so");
-
-    Init_trans_emoji_sjis_softbank();
-    rb_provide("enc/trans/emoji_sjis_softbank.so");
-
-    Init_trans_escape();
-    rb_provide("enc/trans/escape.so");
-
-    Init_trans_gb18030();
-    rb_provide("enc/trans/gb18030.so");
-
-    Init_trans_gbk();
-    rb_provide("enc/trans/gbk.so");
-
-    Init_trans_iso2022();
-    rb_provide("enc/trans/iso2022.so");
-
-    Init_trans_japanese();
-    rb_provide("enc/trans/japanese.so");
-
-    Init_trans_japanese_euc();
-    rb_provide("enc/trans/japanese_euc.so");
-
-    Init_trans_japanese_sjis();
-    rb_provide("enc/trans/japanese_sjis.so");
-
-    Init_trans_korean();
-    rb_provide("enc/trans/korean.so");
-
-    Init_trans_single_byte();
-    rb_provide("enc/trans/single_byte.so");
-
-    Init_trans_utf8_mac();
-    rb_provide("enc/trans/utf8_mac.so");
-
-    Init_trans_utf_16_32();
-    rb_provide("enc/trans/utf_16_32.so");
-
-    Init_bigdecimal();
-    rb_provide("bigdecimal");
-    rb_provide("bigdecimal.so");
-
-    Init_continuation();
-    rb_provide("continuation");
-    rb_provide("continuation.so");
-
-    Init_coverage();
-    rb_provide("coverage");
-    rb_provide("coverage.so");
-
-    Init_cparse();
-    rb_provide("cparse");
-    rb_provide("racc/cparse");
-    rb_provide("cparse.so");
-    rb_provide("racc/cparse.so");
-
-    Init_date_core();
-    rb_provide("date_core");
-    rb_provide("date_core.so");
-
-    Init_digest();
-    rb_provide("digest");
-    rb_provide("digest.so");
-
-    Init_escape();
-    rb_provide("escape");
-    rb_provide("escape.so");
-
-    Init_etc();
-    rb_provide("etc");
-    rb_provide("etc.so");
-
-    Init_fcntl();
-    rb_provide("fcntl");
-    rb_provide("fcntl.so");
-
-    Init_fiber();
-    rb_provide("fiber");
-    rb_provide("fiber.so");
-
-    Init_fiddle();
-    rb_provide("fiddle");
-    rb_provide("fiddle.so");
-
-    Init_generator();
-    rb_provide("json/ext/generator");
-    rb_provide("json/ext/generator.so");
-
-    Init_md5();
-    rb_provide("md5");
-    rb_provide("digest/md5");
-    rb_provide("md5.so");
-    rb_provide("digest/md5.so");
-
-    Init_monitor();
-    rb_provide("monitor");
-    rb_provide("monitor.so");
-
-    Init_nkf();
-    rb_provide("nkf");
-    rb_provide("nkf.so");
-
-    Init_nonblock();
-    rb_provide("nonblock");
-    rb_provide("nonblock.so");
-    rb_provide("io/nonblock");
-    rb_provide("io/nonblock.so");
-
-    Init_ruby_description();
-
-    Init_objspace();
-    rb_provide("objspace");
-    rb_provide("objspace.so");
-
-    Init_parser();
-    rb_provide("json/ext/parser");
-    rb_provide("json/ext/parser.so");
-
-    Init_pathname();
-    rb_provide("pathname");
-    rb_provide("pathname.so");
-
-    Init_psych();
-    rb_provide("psych");
-    rb_provide("psych.so");
-
-    Init_ripper();
-    rb_provide("ripper");
-    rb_provide("ripper.so");
-
-    Init_rmd160();
-    rb_provide("rmd160");
-    rb_provide("digest/rmd160");
-    rb_provide("rmd160.so");
-    rb_provide("digest/rmd160.so");
-
-    Init_sdbm();
-    rb_provide("sdbm");
-    rb_provide("sdbm.so");
-
-    Init_sha1();
-    rb_provide("sha1");
-    rb_provide("digest/sha1");
-    rb_provide("sha1.so");
-    rb_provide("digest/sha1.so");
-
-    Init_sha2();
-    rb_provide("sha2");
-    rb_provide("digest/sha2");
-    rb_provide("sha2.so");
-    rb_provide("digest/sha2.so");
-
-    Init_sizeof();
-    rb_provide("sizeof");
-    rb_provide("sizeof.so");
-
-    Init_socket();
-    rb_provide("socket");
-    rb_provide("socket.so");
-
-    Init_stringio();
-    rb_provide("stringio");
-    rb_provide("stringio.so");
-
-    Init_strscan();
-    rb_provide("strscan");
-    rb_provide("strscan.so");
-
-    Init_wait();
-    rb_provide("wait");
-    rb_provide("io/wait");
-    rb_provide("wait.so");
-    rb_provide("io/wait.so");
-
-    Init_zlib();
-    rb_provide("zlib");
-    rb_provide("zlib.so");
-
-    Init_openssl();
-    rb_provide("openssl");
-    rb_provide("openssl.so");
-
-    Init_nonblock();
-    rb_provide("io/nonblock");
-    rb_provide("io/nonblock.so");
-
-#ifndef _WIN32
-
-    // DLM: we have Init_console on Windows but crashes when try to init it, fails to load openssl
-    Init_console();
-    rb_provide("console");
-    rb_provide("console.so");
-
-    Init_dbm();
-    rb_provide("dbm");
-    rb_provide("dbm.so");
-
-    Init_gdbm();
-    rb_provide("gdbm");
-    rb_provide("gdbm.so");
-
-    Init_pty();
-    rb_provide("pty");
-    rb_provide("pty.so");
-
-    Init_readline();
-    rb_provide("readline");
-    rb_provide("readline.so");
-
-    Init_syslog();
-    rb_provide("syslog");
-    rb_provide("syslog.so");
-#endif
-
-    // openstudio
-    init_openstudio_internal_basic();
-
-    auto module = rb_define_module("OpenStudio");
-    rb_define_module_function(module, "init_rest_of_openstudio", init_rest_of_openstudio, 0);
-  }
-
-  // DLM: this will interpret any strings passed on the command line as UTF-8
-  // can we be smarter and detect the correct encoding? use wmain on windows to get utf-16?
-  // or we might want to follow ruby and allow '--external-encoding=UTF-8' as an input argument?
-  rb_enc_set_default_external(rb_enc_from_encoding(rb_utf8_encoding()));
-
+int rubyCLI(int argc, char* argv[]) {
   // chop off the first argument which is the exe path/name
-  ruby_set_argv(argc - 1, argv + 1);
+  openstudio::rubyEngine = openstudio::loadScriptEngine("rubyengine", argc - 1, argv + 1);
 
   try {
-    rubyInterpreter.evalString(R"(
+    openstudio::rubyEngine->exec(R"(
        begin
          require 'openstudio_cli'
        rescue Exception => e
@@ -582,30 +19,36 @@ int main(int argc, char* argv[]) {
        end
      )");
   } catch (const std::exception& e) {
-    rubyInterpreter.evalString(R"(STDOUT.flush)");
+    openstudio::rubyEngine->exec(R"(STDOUT.flush)");
     std::cout << "Exception: " << e.what() << std::endl;  // endl will flush
-    return ruby_cleanup(1);
+    return 1;
   } catch (...) {
-    rubyInterpreter.evalString(R"(STDOUT.flush)");
+    openstudio::rubyEngine->exec(R"(STDOUT.flush)");
     std::cout << "Unknown Exception" << std::endl;  // endl will flush
-    return ruby_cleanup(1);
+    return 1;
   }
-  rubyInterpreter.evalString(R"(STDOUT.flush)");
+
+  openstudio::rubyEngine->exec(R"(STDOUT.flush)");
   std::cout << std::flush;
-  return ruby_cleanup(0);
+  return 0;
 }
 
-extern "C"
-{
-  int rb_hasFile(const char* t_filename) {
-    // TODO Consider expanding this to use the path which we have artificially defined in embedded_help.rb
-    std::string expandedName = std::string(":/ruby/2.7.0/") + std::string(t_filename) + ".rb";
-    return embedded_files::hasFile(expandedName);
-  }
+int main(int argc, char* argv[]) {
+  int result = 0;
 
-  int rb_require_embedded(const char* t_filename) {
-    std::string require_script = R"(require ')" + std::string(t_filename) + R"(')";
-    rubyInterpreter.evalString(require_script);
-    return 0;
+  if ((argc > 1) && (std::string_view(argv[1]) == "experimental")) {
+    CLI::App app{"openstudio"};
+
+    const auto experimentalApp = app.add_subcommand("experimental");
+    [[maybe_unused]] const auto runCommand = experimentalApp->add_subcommand("run");
+
+    CLI11_PARSE(app, argc, argv);
+  } else {
+    result = rubyCLI(argc, argv);
   }
+
+  // Important to destroy RubyEngine and finalize Ruby at the right time
+  // After the main function returns it is (apparently) too late
+  openstudio::rubyEngine.reset();
+  return result;
 }
diff --git a/src/scriptengine/CMakeLists.txt b/src/scriptengine/CMakeLists.txt
new file mode 100644
index 00000000000..c53d2b7ecdd
--- /dev/null
+++ b/src/scriptengine/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Script Engine is a base for scripting in OpenStudio
+# See implementations in ruby/engine and python/engine
+
+add_library(scriptengine INTERFACE)
+target_include_directories(scriptengine INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/scriptengine/ScriptEngine.hpp b/src/scriptengine/ScriptEngine.hpp
new file mode 100644
index 00000000000..ec236d00fbd
--- /dev/null
+++ b/src/scriptengine/ScriptEngine.hpp
@@ -0,0 +1,99 @@
+#ifndef SCRIPTENGINE_included
+#define SCRIPTENGINE_included
+
+#include "../utilities/core/ApplicationPathHelpers.hpp"
+#include "../utilities/core/DynamicLibrary.hpp"
+#include <any>
+#include <functional>
+#include <map>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <typeinfo>
+
+namespace openstudio {
+class Measure;
+class ScriptEngine;
+
+struct ScriptObject
+{
+  std::any object;
+};
+}  // namespace openstudio
+
+using ScriptEngineFactoryType = openstudio::ScriptEngine*(int, char**);
+
+namespace openstudio {
+
+class ScriptEngine
+{
+ public:
+  ScriptEngine([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
+    registerType<openstudio::Measure*>("openstudio::Measure *");
+  }
+
+  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;
+
+  template <typename T>
+  T getAs(ScriptObject& obj) {
+    void* result = getAs_impl(obj, typeid(T));
+    if (result) {
+      return static_cast<T>(result);
+    } else {
+      throw std::bad_cast();
+    }
+  }
+
+  template <typename T>
+  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<const std::type_info>& lhs, const std::reference_wrapper<const std::type_info>& rhs) const {
+      return lhs.get().before(rhs.get());
+    }
+  };
+
+  std::map<std::reference_wrapper<const std::type_info>, std::string, Compare> types;
+};
+
+inline std::unique_ptr<openstudio::ScriptEngine> loadScriptEngine(std::string_view libraryBaseName, int argc, char* argv[]) {
+  auto enginePath = openstudio::getOpenStudioModuleDirectory() / openstudio::getSharedLibraryName(libraryBaseName);
+  openstudio::DynamicLibrary engineLib(enginePath);
+
+  const std::function<ScriptEngineFactoryType> factory = engineLib.load_symbol<ScriptEngineFactoryType>("makeScriptEngine");
+
+  return std::unique_ptr<openstudio::ScriptEngine>(factory(argc, argv));
+}
+
+}  // namespace openstudio
+
+#endif
diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt
index 672b5d2c657..e74dc9fd967 100644
--- a/src/utilities/CMakeLists.txt
+++ b/src/utilities/CMakeLists.txt
@@ -23,6 +23,9 @@ set(core_src
   core/Containers.hpp
   core/Containers.cpp
   core/Deprecated.hpp
+  core/DynamicLibrary.hpp
+  core/DynamicLibraryPOSIX.hpp
+  core/DynamicLibraryWindows.hpp
   core/Enum.hpp
   core/EnumHelpers.hpp
   core/Exception.hpp
@@ -223,6 +226,13 @@ foreach(_FILE ${XLST_TRANSFORM_FILES})
   list(APPEND E_PATHS ${LOCATION})
 endforeach()
 
+file(GLOB_RECURSE XSD_SCHEMA_FILES  FOLLOW_SYMLINKS "${CMAKE_CURRENT_SOURCE_DIR}/xml/resources/*.xsd")
+foreach(_FILE ${XSD_SCHEMA_FILES})
+  file(RELATIVE_PATH LOCATION "${CMAKE_CURRENT_SOURCE_DIR}" ${_FILE})
+  list(APPEND E_FILES ${_FILE})
+  list(APPEND E_PATHS ${LOCATION})
+endforeach()
+
 include("${PROJECT_SOURCE_DIR}/embedded/EmbedFiles.cmake")
 embed_files("${E_FILES}" "${E_PATHS}" EMBEDDED_OUTPUT openstudio)
 
@@ -452,6 +462,51 @@ target_compile_definitions(${target_name} PRIVATE openstudio_utilities_EXPORTS S
 target_include_directories(${target_name} PUBLIC ${PROJECT_BINARY_DIR})
 target_include_directories(${target_name} PUBLIC ${PROJECT_BINARY_DIR}/src)
 
+include(CheckIncludeFile)
+check_include_file("unistd.h" HAVE_UNISTD_H)
+if(HAVE_UNISTD_H)
+  target_compile_definitions(${target_name} PUBLIC "-DHAVE_UNISTD_H")
+endif()
+
+# To be used by the main cli program, this target
+# is the most minimal thing that provides Path, and Filesystem
+# capabilities, which the cli requires.
+add_library(openstudio_utilities_minimal
+  OBJECT
+  core/ApplicationPathHelpers.hpp
+  ${CMAKE_CURRENT_BINARY_DIR}/core/ApplicationPathHelpers.cxx
+  core/DynamicLibrary.hpp
+  core/DynamicLibraryPOSIX.hpp
+  core/DynamicLibraryWindows.hpp
+  core/Filesystem.hpp
+  core/Filesystem.cpp
+  core/FilesystemHelpers.hpp
+  core/FilesystemHelpers.cpp
+  core/Logger.hpp
+  core/Logger.cpp
+  core/LogMessage.hpp
+  core/LogMessage.cpp
+  core/LogSink.hpp
+  core/LogSink_Impl.hpp
+  core/LogSink.cpp
+  core/Path.hpp
+  core/Path.cpp
+  core/PathHelpers.hpp
+  core/PathHelpers.cpp
+  core/String.hpp
+  core/String.cpp
+  core/StringHelpers.hpp
+  core/StringHelpers.cpp
+  core/StringStreamLogSink.hpp
+  core/StringStreamLogSink_Impl.hpp
+  core/StringStreamLogSink.cpp
+)
+target_link_libraries(openstudio_utilities_minimal PUBLIC ${${target_name}_depends})
+
+if(HAVE_UNISTD_H)
+  target_compile_definitions(openstudio_utilities_minimal PUBLIC "-DHAVE_UNISTD_H")
+endif()
+
 if(BUILD_TESTING)
   CREATE_TEST_TARGETS(${target_name} "${${target_name}_test_src}" openstudiolib)
   add_dependencies(${target_name}_tests openstudio_model_resources)
diff --git a/src/utilities/core/ApplicationPathHelpers.cxx.in b/src/utilities/core/ApplicationPathHelpers.cxx.in
index 6003a75ae3b..7db55d0b2aa 100644
--- a/src/utilities/core/ApplicationPathHelpers.cxx.in
+++ b/src/utilities/core/ApplicationPathHelpers.cxx.in
@@ -33,6 +33,7 @@
 
 #include <OpenStudio.hxx>
 
+#include <fmt/format.h>
 #include <sstream>
 #include <boost/regex.hpp>
 #include <boost/algorithm/string.hpp>
@@ -467,4 +468,16 @@ namespace openstudio {
     return openstudio::path();
   }
 
+  std::string_view getSharedLibrarySuffix() {
+    return "${CMAKE_SHARED_LIBRARY_SUFFIX}";
+  }
+  
+  std::string_view getSharedLibraryPrefix() {
+    return "${CMAKE_SHARED_LIBRARY_PREFIX}";
+  }
+
+  std::string getSharedLibraryName(std::string_view basename) {
+    return fmt::format("{}{}{}", getSharedLibraryPrefix(), basename, getSharedLibrarySuffix());
+  }
+
 } // openstudio
diff --git a/src/utilities/core/ApplicationPathHelpers.hpp b/src/utilities/core/ApplicationPathHelpers.hpp
index aff99a35cd3..547c20aaaf7 100644
--- a/src/utilities/core/ApplicationPathHelpers.hpp
+++ b/src/utilities/core/ApplicationPathHelpers.hpp
@@ -75,6 +75,18 @@ UTILITIES_API openstudio::path getRadianceDirectory();
 /// \returns The path to the Perl executable if it exists.
 UTILITIES_API openstudio::path getPerlExecutable();
 
+/// \returns The platform specific suffix for shared libraries.
+/// "dll" on Windows, ".so" on Linux, etc.
+UTILITIES_API std::string_view getSharedLibrarySuffix();
+
+/// \returns The platform specific file prefix for shared libraries.
+/// "lib" on Linux.
+UTILITIES_API std::string_view getSharedLibraryPrefix();
+
+/// \returns The platform specific shared library name,
+/// concatenates the prefix and suffix to the given basename
+UTILITIES_API std::string getSharedLibraryName(std::string_view basename);
+
 }  // namespace openstudio
 
 #endif  //UTILITIES_CORE_PATHHELPERS_HPP
diff --git a/src/utilities/core/DynamicLibrary.hpp b/src/utilities/core/DynamicLibrary.hpp
new file mode 100644
index 00000000000..1afe3292ca6
--- /dev/null
+++ b/src/utilities/core/DynamicLibrary.hpp
@@ -0,0 +1,5 @@
+#if defined(_MSC_VER)
+#  include "DynamicLibraryWindows.hpp"
+#elif defined(HAVE_UNISTD_H)
+#  include "DynamicLibraryPOSIX.hpp"
+#endif
diff --git a/src/utilities/core/DynamicLibraryPOSIX.hpp b/src/utilities/core/DynamicLibraryPOSIX.hpp
new file mode 100644
index 00000000000..b3c91f0fa2f
--- /dev/null
+++ b/src/utilities/core/DynamicLibraryPOSIX.hpp
@@ -0,0 +1,47 @@
+#ifndef dynamiclibrary_posix_hpp_INCLUDED
+#define dynamiclibrary_posix_hpp_INCLUDED
+
+#include "Path.hpp"
+
+#include <stdexcept>
+#include <string>
+
+#include <dlfcn.h>
+
+#include <fmt/format.h>
+
+namespace openstudio {
+
+struct DynamicLibrary
+{
+  template <typename Signature>
+  [[nodiscard]] Signature* load_symbol(const std::string& name) {
+    // reinterpret_cast is necessary here
+    const auto symbol = reinterpret_cast<Signature*>(dlsym(m_handle.get(), name.c_str()));  // NOLINT
+
+    if (symbol == nullptr) {
+      throw std::runtime_error(fmt::format("Unable to load symbol: '{}', reason: '{}'", name, dlerror()));
+    }
+
+    return symbol;
+  }
+
+  explicit DynamicLibrary(openstudio::path location)
+    : m_location{std::move(location)}, m_handle{dlopen(m_location.c_str(), RTLD_LAZY), m_handle_deleter} {
+    if (!m_handle) {
+      throw std::runtime_error(fmt::format("Unable to load library '{}', reason: '{}'", m_location.string(), dlerror()));
+    }
+  }
+
+  static void m_handle_deleter(void* h) {
+    if (h) {
+      dlclose(h);
+    }
+  }
+
+  openstudio::path m_location{};
+  std::unique_ptr<void, decltype(&m_handle_deleter)> m_handle{nullptr, m_handle_deleter};
+};
+}  // namespace openstudio
+
+#endif  // dynamiclibrary_posix_hpp_INCLUDED
diff --git a/src/utilities/core/DynamicLibraryWindows.hpp b/src/utilities/core/DynamicLibraryWindows.hpp
new file mode 100644
index 00000000000..be9f7391da1
--- /dev/null
+++ b/src/utilities/core/DynamicLibraryWindows.hpp
@@ -0,0 +1,113 @@
+#ifndef dynamiclibrary_windows_hpp_INCLUDED
+#define dynamiclibrary_windows_hpp_INCLUDED
+
+#include "Path.hpp"
+
+#include <stdexcept>
+#include <filesystem>
+#include <string>
+
+#if !defined(WIN32_LEAN_AND_MEAN)
+#  define WIN32_LEAN_AND_MEAN
+#  include <windows.h>
+#  undef WIN32_LEAN_AND_MEAN
+#else
+#  include <windows.h>
+#endif
+
+#include <fmt/format.h>
+
+namespace openstudio {
+
+struct DynamicLibrary
+{
+
+  template <typename T>
+  static [[nodiscard]] std::wstring to_wstring(const T& t_str) {
+    return std::wstring(t_str.begin(), t_str.end());
+  }
+
+  template <typename T>
+  static [[nodiscard]] std::string to_string(const T& t_str) {
+    return std::string(t_str.begin(), t_str.end());
+  }
+
+#if defined(_UNICODE) || defined(UNICODE)
+  template <typename T>
+  static std::wstring to_proper_string(const T& t_str) {
+    return to_wstring(t_str);
+  }
+#else
+  template <typename T>
+  static std::string [[nodiscard]] to_proper_string(const T& t_str) {
+    return to_string(t_str);
+  }
+#endif
+
+  static std::string [[nodiscard]] get_error_message(DWORD t_err) {
+    typedef LPTSTR StringType;
+
+#if defined(_UNICODE) || defined(UNICODE)
+    std::wstring retval = L"Unknown Error";
+#else
+    std::string retval = "Unknown Error";
+#endif
+    StringType lpMsgBuf = nullptr;
+
+    // TODO this reinterpret_cast is suspicious (implementation borrowed from ChaiScript)
+    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, t_err,
+                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<StringType>(&lpMsgBuf), 0, nullptr)
+          != 0
+        && lpMsgBuf) {
+      retval = lpMsgBuf;
+      LocalFree(lpMsgBuf);
+    }
+
+    return to_string(retval);
+  }
+
+  template <typename Signature>
+  [[nodiscard]] Signature* load_symbol(const std::string& name) {
+    // reinterpret_cast is necessary here
+    const auto symbol = reinterpret_cast<Signature*>(GetProcAddress(m_handle, name.c_str()));
+
+    if (symbol == nullptr) {
+      throw std::runtime_error(fmt::format("Unable to load symbol: '{}', reason: '{}'", name, get_error_message(GetLastError())));
+    }
+
+    return symbol;
+  }
+
+  explicit DynamicLibrary(openstudio::path location)
+    : m_location{std::move(location)}, m_handle{LoadLibrary(to_proper_string(m_location.string()).c_str())} {
+    if (!m_handle) {
+      throw std::runtime_error(fmt::format("Unable to load library '{}', reason: '{}'", m_location.string(), get_error_message(GetLastError())));
+    }
+  }
+
+  DynamicLibrary() = delete;
+  DynamicLibrary(DynamicLibrary&& other) noexcept : m_handle{other.m_handle} {
+    other.m_handle = HMODULE{};
+  }
+  DynamicLibrary(const DynamicLibrary&) = delete;
+  DynamicLibrary& operator=(DynamicLibrary&& other) noexcept {
+    if (m_handle) {
+      FreeLibrary(m_handle);
+    }
+    m_handle = std::exchange(other.m_handle, HMODULE{});
+    return *this;
+  }
+  DynamicLibrary& operator=(const DynamicLibrary&) = delete;
+
+  ~DynamicLibrary() noexcept {
+    if (m_handle) {
+      FreeLibrary(m_handle);
+    }
+  }
+
+  openstudio::path m_location{};
+  HMODULE m_handle{};
+};
+
+}  // namespace openstudio
+#endif  // dynamiclibrary_windows_hpp_INCLUDED


Test patch:
diff --git a/src/cli/measure_manager_test.rb b/ruby/engine/measure_manager_test.rb
similarity index 100%
rename from src/cli/measure_manager_test.rb
rename to ruby/engine/measure_manager_test.rb
diff --git a/ruby/test/CMakeLists.txt b/ruby/test/CMakeLists.txt
new file mode 100644
index 00000000000..b86dd40991b
--- /dev/null
+++ b/ruby/test/CMakeLists.txt
@@ -0,0 +1,61 @@
+# find all tests
+file(GLOB RUBY_TEST_SRC "./*.rb")
+
+# TODO: It doesn't not work with this executable that's in build/Ruby-install/bin
+# message("RUBY_EXECUTABLE=${RUBY_EXECUTABLE}")
+
+set(_RUBY_POSSIBLE_EXECUTABLE_NAMES
+  ruby
+  ruby2.7
+  ruby2.7.2
+  ruby27
+  ruby272)
+
+# TODO: this isn't great but I haven't found a better way to locate the system ruby (and avoid the one in build/Ruby-install/)
+find_program(SYSTEM_RUBY_EXECUTABLE NAMES ${_RUBY_POSSIBLE_EXECUTABLE_NAMES}
+  HINTS
+    "/usr/local/rvm/rubies/ruby-2.7.2/bin/"
+    "/usr/local/rvm/rubies/ruby-2.7.2/bin/ruby"
+    "/usr/share/rvm/rubies/ruby-2.7.2/bin/"
+    "$ENV{HOME}/.rvm/rubies/ruby-2.7.2/bin/"
+
+    "C:/Ruby27-x64/bin/"
+
+     "/usr/local/ruby272/bin/"
+     "/usr/local/ruby27/bin/"
+     "/usr/bin/"
+     "/usr/local/bin/"
+
+
+  NO_DEFAULT_PATH)
+
+if(NOT SYSTEM_RUBY_EXECUTABLE)
+  message(WARNING "Your system ruby wasn't found, you won't be able to run the `ctest -R RubyTest` command and the tests won't be created at all.")
+else()
+  message(STATUS "Found SYSTEM_RUBY_EXECUTABLE=${SYSTEM_RUBY_EXECUTABLE}")
+
+  # add a test for each unit test
+  set(RUBY_TEST_REQUIRES "#include test files")
+  foreach(f ${RUBY_TEST_SRC})
+
+    file(READ "${f}" CONTENTS)
+    string(REGEX MATCHALL "def +test_([A-Za-z_0-9 ]+)" FOUND_TESTS ${CONTENTS})
+
+    foreach(HIT ${FOUND_TESTS})
+      string(REGEX REPLACE "def +test_([A-Za-z_0-9]+)" "\\1" TEST_NAME ${HIT})
+      string(REGEX MATCH "/?([A-Za-z_0-9 ]+)\\.rb" FILE_NAME ${f})
+      string(REGEX REPLACE "/?([A-Za-z_0-9 ]+)\\.rb" "\\1" FILE_NAME ${FILE_NAME})
+      if(BUILD_TESTING)
+
+        # Call with Ruby itself
+        add_test(NAME "RubyTest-${FILE_NAME}-${TEST_NAME}"
+          COMMAND "${CMAKE_COMMAND}" -E chdir "${CMAKE_CURRENT_BINARY_DIR}"
+          "${SYSTEM_RUBY_EXECUTABLE}" "-I" "$<TARGET_FILE_DIR:openstudio_rb>" "${f}" "--name=test_${TEST_NAME}"
+        )
+
+        set_tests_properties("RubyTest-${FILE_NAME}-${TEST_NAME}" PROPERTIES TIMEOUT 660 )
+      endif()
+    endforeach()
+  endforeach()
+
+endif()
diff --git a/ruby/openstudio_test.in b/ruby/test/openstudio_test.in
similarity index 100%
rename from ruby/openstudio_test.in
rename to ruby/test/openstudio_test.in