diff --git a/.github/workflows/matlab.yml b/.github/workflows/matlab.yml index b403fbc3366..1bf273df1ce 100644 --- a/.github/workflows/matlab.yml +++ b/.github/workflows/matlab.yml @@ -60,6 +60,10 @@ jobs: # errors will occur. To work around this issue, we can explicitly # force MATLAB to use the system libstdc++.so via LD_PRELOAD. LD_PRELOAD: /usr/lib/x86_64-linux-gnu/libstdc++.so.6 + + # Add the installation directory to the MATLAB Search Path by + # setting the MATLABPATH environment variable. + MATLABPATH: matlab/install/arrow_matlab uses: matlab-actions/run-tests@v1 with: select-by-folder: matlab/test diff --git a/ci/scripts/matlab_build.sh b/ci/scripts/matlab_build.sh index 5e9bdd2a91a..656805275ed 100755 --- a/ci/scripts/matlab_build.sh +++ b/ci/scripts/matlab_build.sh @@ -23,7 +23,8 @@ set -ex base_dir=${1} source_dir=${base_dir}/matlab build_dir=${base_dir}/matlab/build +install_dir=${base_dir}/matlab/install -cmake -S ${source_dir} -B ${build_dir} -G Ninja -D MATLAB_BUILD_TESTS=ON -cmake --build ${build_dir} --config Release +cmake -S ${source_dir} -B ${build_dir} -G Ninja -D MATLAB_BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX=${install_dir} -D MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH=OFF +cmake --build ${build_dir} --config Release --target install ctest --test-dir ${build_dir} diff --git a/matlab/CMakeLists.txt b/matlab/CMakeLists.txt index e667500c466..18f98988a14 100644 --- a/matlab/CMakeLists.txt +++ b/matlab/CMakeLists.txt @@ -22,6 +22,7 @@ function(build_arrow) set(options BUILD_GTEST) set(one_value_args) set(multi_value_args) + cmake_parse_arguments(ARG "${options}" "${one_value_args}" @@ -31,23 +32,40 @@ function(build_arrow) message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") endif() + # If Arrow needs to be built, the default location will be within the build tree. + set(ARROW_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/arrow_ep-prefix") + if(WIN32) - set(ARROW_IMPORTED_TYPE IMPORTED_IMPLIB) - set(ARROW_LIBRARY_SUFFIX ${CMAKE_IMPORT_LIBRARY_SUFFIX}) + # The shared library is located in the "bin" directory. + set(ARROW_SHARED_LIBRARY_DIR "${ARROW_PREFIX}/bin") + + # Imported libraries are used + set(ARROW_IMPORT_LIB_FILENAME + "${CMAKE_IMPORT_LIBRARY_PREFIX}arrow${CMAKE_IMPORT_LIBRARY_SUFFIX}") + set(ARROW_IMPORT_LIB "${ARROW_PREFIX}/lib/${ARROW_IMPORT_LIB_FILENAME}") else() - set(ARROW_IMPORTED_TYPE IMPORTED_LOCATION) - set(ARROW_LIBRARY_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + # The shared library is located in the "lib" directory. + set(ARROW_SHARED_LIBRARY_DIR "${ARROW_PREFIX}/lib") endif() - set(ARROW_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/arrow_ep-prefix") - set(ARROW_INCLUDE_DIR "${ARROW_PREFIX}/include") - set(ARROW_LIBRARY_DIR "${ARROW_PREFIX}/lib") - set(ARROW_SHARED_LIB - "${ARROW_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}arrow${ARROW_LIBRARY_SUFFIX}") + set(ARROW_SHARED_LIB_FILENAME + "${CMAKE_SHARED_LIBRARY_PREFIX}arrow${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(ARROW_SHARED_LIB "${ARROW_SHARED_LIBRARY_DIR}/${ARROW_SHARED_LIB_FILENAME}") + set(ARROW_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/arrow_ep-build") set(ARROW_CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${ARROW_PREFIX}" "-DCMAKE_INSTALL_LIBDIR=lib" "-DARROW_BUILD_STATIC=OFF") - set(ARROW_BUILD_BYPRODUCTS "${ARROW_SHARED_LIB}") + set(ARROW_INCLUDE_DIR "${ARROW_PREFIX}/include") + + # The output libraries need to be guaranteed to be available for linking the test + # executables. + if(WIN32) + # On Windows, add the Arrow link library as a BUILD_BYPRODUCTS for arrow_ep. + set(ARROW_BUILD_BYPRODUCTS "${ARROW_IMPORT_LIB}") + else() + # On Linux and macOS, add the Arrow shared library as a BUILD_BYPRODUCTS for arrow_ep. + set(ARROW_BUILD_BYPRODUCTS "${ARROW_SHARED_LIB}") + endif() # Building the Arrow C++ libraries and bundled GoogleTest binaries requires ExternalProject. include(ExternalProject) @@ -59,14 +77,14 @@ function(build_arrow) externalproject_add(arrow_ep SOURCE_DIR "${CMAKE_SOURCE_DIR}/../cpp" BINARY_DIR "${ARROW_BINARY_DIR}" - CMAKE_ARGS ${ARROW_CMAKE_ARGS} - BUILD_BYPRODUCTS ${ARROW_BUILD_BYPRODUCTS}) + CMAKE_ARGS "${ARROW_CMAKE_ARGS}" + BUILD_BYPRODUCTS "${ARROW_BUILD_BYPRODUCTS}") set(ARROW_LIBRARY_TARGET arrow_shared) # If find_package has already found a valid Arrow installation, then # we don't want to link against the newly built arrow_shared library. - # However, we still need create a library target to trigger building + # However, we still need to create a library target to trigger building # of the arrow_ep target, which will ultimately build the bundled # GoogleTest binaries. if(Arrow_FOUND) @@ -76,108 +94,96 @@ function(build_arrow) file(MAKE_DIRECTORY "${ARROW_INCLUDE_DIR}") add_library(${ARROW_LIBRARY_TARGET} SHARED IMPORTED) set_target_properties(${ARROW_LIBRARY_TARGET} - PROPERTIES ${ARROW_IMPORTED_TYPE} ${ARROW_SHARED_LIB} - INTERFACE_INCLUDE_DIRECTORIES ${ARROW_INCLUDE_DIR}) + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${ARROW_INCLUDE_DIR} + IMPORTED_LOCATION ${ARROW_SHARED_LIB}) + if(WIN32) + # On Windows, IMPORTED_IMPLIB is set to the location of arrow.lib, which is + # for linking arrow_matlab against the Arrow C++ library. + set_target_properties(${ARROW_LIBRARY_TARGET} PROPERTIES IMPORTED_IMPLIB + ${ARROW_IMPORT_LIB}) + endif() add_dependencies(${ARROW_LIBRARY_TARGET} arrow_ep) if(ARG_BUILD_GTEST) build_gtest() endif() - endfunction() macro(enable_gtest) + set(ARROW_GTEST_PREFIX "${ARROW_BINARY_DIR}/googletest_ep-prefix") + set(ARROW_GTEST_MAIN_PREFIX "${ARROW_BINARY_DIR}/googletest_ep-prefix") + if(WIN32) - set(ARROW_GTEST_IMPORTED_TYPE IMPORTED_IMPLIB) - set(ARROW_GTEST_MAIN_IMPORTED_TYPE IMPORTED_IMPLIB) + set(ARROW_GTEST_SHARED_LIB_DIR "${ARROW_GTEST_PREFIX}/bin") + set(ARROW_GTEST_MAIN_SHARED_LIB_DIR "${ARROW_GTEST_MAIN_PREFIX}/bin") - set(ARROW_GTEST_LIBRARY_SUFFIX ${CMAKE_IMPORT_LIBRARY_SUFFIX}) - set(ARROW_GTEST_MAIN_LIBRARY_SUFFIX ${CMAKE_IMPORT_LIBRARY_SUFFIX}) - else() - set(ARROW_GTEST_IMPORTED_TYPE IMPORTED_LOCATION) - set(ARROW_GTEST_MAIN_IMPORTED_TYPE IMPORTED_LOCATION) + set(ARROW_GTEST_LINK_LIB_DIR "${ARROW_GTEST_PREFIX}/lib") + set(ARROW_GTEST_LINK_LIB + "${ARROW_GTEST_LINK_LIB_DIR}/${CMAKE_IMPORT_LIBRARY_PREFIX}gtest${CMAKE_IMPORT_LIBRARY_SUFFIX}" + ) - set(ARROW_GTEST_LIBRARY_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(ARROW_GTEST_MAIN_LIBRARY_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(ARROW_GTEST_MAIN_LINK_LIB_DIR "${ARROW_GTEST_MAIN_PREFIX}/lib") + set(ARROW_GTEST_MAIN_LINK_LIB + "${ARROW_GTEST_MAIN_LINK_LIB_DIR}/${CMAKE_IMPORT_LIBRARY_PREFIX}gtest_main${CMAKE_IMPORT_LIBRARY_SUFFIX}" + ) + else() + set(ARROW_GTEST_SHARED_LIB_DIR "${ARROW_GTEST_PREFIX}/lib") + set(ARROW_GTEST_MAIN_SHARED_LIB_DIR "${ARROW_GTEST_MAIN_PREFIX}/lib") endif() - set(ARROW_GTEST_PREFIX "${ARROW_BINARY_DIR}/googletest_ep-prefix") set(ARROW_GTEST_INCLUDE_DIR "${ARROW_GTEST_PREFIX}/include") - set(ARROW_GTEST_LIBRARY_DIR "${ARROW_GTEST_PREFIX}/lib") set(ARROW_GTEST_SHARED_LIB - "${ARROW_GTEST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest${ARROW_GTEST_LIBRARY_SUFFIX}" + "${ARROW_GTEST_SHARED_LIB_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest${CMAKE_SHARED_LIBRARY_SUFFIX}" ) - set(ARROW_GTEST_MAIN_PREFIX "${ARROW_BINARY_DIR}/googletest_ep-prefix") set(ARROW_GTEST_MAIN_INCLUDE_DIR "${ARROW_GTEST_MAIN_PREFIX}/include") - set(ARROW_GTEST_MAIN_LIBRARY_DIR "${ARROW_GTEST_MAIN_PREFIX}/lib") set(ARROW_GTEST_MAIN_SHARED_LIB - "${ARROW_GTEST_MAIN_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest_main${ARROW_GTEST_MAIN_LIBRARY_SUFFIX}" + "${ARROW_GTEST_MAIN_SHARED_LIB_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest_main${CMAKE_SHARED_LIBRARY_SUFFIX}" ) list(APPEND ARROW_CMAKE_ARGS "-DARROW_BUILD_TESTS=ON") - list(APPEND ARROW_BUILD_BYPRODUCTS "${ARROW_GTEST_SHARED_LIB}" - "${ARROW_GTEST_MAIN_SHARED_LIB}") + + # The appropriate libraries need to be guaranteed to be available when linking the test + # executables. + if(WIN32) + # On Windows, add the gtest link libraries as BUILD_BYPRODUCTS for arrow_ep. + list(APPEND ARROW_BUILD_BYPRODUCTS "${ARROW_GTEST_LINK_LIB}" + "${ARROW_GTEST_MAIN_LINK_LIB}") + else() + # On Linux and macOS, add the gtest shared libraries as BUILD_BYPRODUCTS for arrow_ep. + list(APPEND ARROW_BUILD_BYPRODUCTS "${ARROW_GTEST_SHARED_LIB}" + "${ARROW_GTEST_MAIN_SHARED_LIB}") + endif() endmacro() # Build the GoogleTest binaries that are bundled with the Arrow C++ libraries. macro(build_gtest) - set(ARROW_GTEST_INCLUDE_DIR "${ARROW_GTEST_PREFIX}/include") - set(ARROW_GTEST_MAIN_INCLUDE_DIR "${ARROW_GTEST_MAIN_PREFIX}/include") - file(MAKE_DIRECTORY "${ARROW_GTEST_INCLUDE_DIR}") - if(WIN32) - set(ARROW_GTEST_RUNTIME_DIR "${ARROW_GTEST_PREFIX}/bin") - set(ARROW_GTEST_MAIN_RUNTIME_DIR "${ARROW_GTEST_MAIN_PREFIX}/bin") - set(ARROW_GTEST_RUNTIME_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}") - set(ARROW_GTEST_MAIN_RUNTIME_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}") - set(ARROW_GTEST_RUNTIME_LIB - "${ARROW_GTEST_RUNTIME_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest${ARROW_GTEST_RUNTIME_SUFFIX}" - ) - set(ARROW_GTEST_MAIN_RUNTIME_LIB - "${ARROW_GTEST_MAIN_RUNTIME_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}gtest_main${ARROW_GTEST_MAIN_RUNTIME_SUFFIX}" - ) - - # Multi-Configuration generators (e.g. Visual Studio or XCode) place their build artifacts - # in a subdirectory named ${CMAKE_BUILD_TYPE} by default, where ${CMAKE_BUILD_TYPE} varies - # depending on the chosen build configuration (e.g. Release or Debug). - get_property(GENERATOR_IS_MULTI_CONFIG_VALUE GLOBAL - PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(GENERATOR_IS_MULTI_CONFIG_VALUE) - set(MATLAB_TESTS_DIR "${CMAKE_BINARY_DIR}/$") - else() - set(MATLAB_TESTS_DIR "${CMAKE_BINARY_DIR}") - endif() - - # We need to copy the gtest and gtest_main runtime DLLs into the directory where the - # MATLAB C++ tests reside, since Windows requires that runtime DLLs are in the same - # directory as the executables that depend on them (or on the %PATH%). - externalproject_add_step(arrow_ep copy - COMMAND ${CMAKE_COMMAND} -E make_directory - ${MATLAB_TESTS_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${ARROW_GTEST_RUNTIME_LIB} - ${MATLAB_TESTS_DIR} - COMMAND ${CMAKE_COMMAND} -E copy - ${ARROW_GTEST_MAIN_RUNTIME_LIB} ${MATLAB_TESTS_DIR} - DEPENDEES install) - endif() - + # Create target GTest::gtest add_library(GTest::gtest SHARED IMPORTED) set_target_properties(GTest::gtest - PROPERTIES ${ARROW_GTEST_IMPORTED_TYPE} ${ARROW_GTEST_SHARED_LIB} + PROPERTIES IMPORTED_LOCATION ${ARROW_GTEST_SHARED_LIB} INTERFACE_INCLUDE_DIRECTORIES ${ARROW_GTEST_INCLUDE_DIR}) + if(WIN32) + set_target_properties(GTest::gtest PROPERTIES IMPORTED_IMPLIB ${ARROW_GTEST_LINK_LIB}) + endif() + add_dependencies(GTest::gtest arrow_ep) + + # Create target GTest::gtest_main add_library(GTest::gtest_main SHARED IMPORTED) set_target_properties(GTest::gtest_main - PROPERTIES ${ARROW_GTEST_MAIN_IMPORTED_TYPE} - ${ARROW_GTEST_MAIN_SHARED_LIB} + PROPERTIES IMPORTED_LOCATION ${ARROW_GTEST_MAIN_SHARED_LIB} INTERFACE_INCLUDE_DIRECTORIES ${ARROW_GTEST_MAIN_INCLUDE_DIR}) + if(WIN32) + set_target_properties(GTest::gtest_main PROPERTIES IMPORTED_IMPLIB + ${ARROW_GTEST_MAIN_LINK_LIB}) + endif() - add_dependencies(GTest::gtest arrow_ep) add_dependencies(GTest::gtest_main arrow_ep) endmacro() @@ -188,9 +194,15 @@ string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" MLARROW_BASE_VERSION "${MLARROW_V project(mlarrow VERSION "${MLARROW_BASE_VERSION}") +# On Windows, set the global variable that determines the MSVC runtime library that is +# used by targets created in this CMakeLists.txt file. +if(WIN32) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") +endif() + option(MATLAB_BUILD_TESTS "Build the C++ tests for the MATLAB interface" OFF) -# Grab CMAKE Modules from the CPP interface +# Grab CMAKE Modules from the CPP interface. set(CPP_CMAKE_MODULES "${CMAKE_SOURCE_DIR}/../cpp/cmake_modules") if(EXISTS "${CPP_CMAKE_MODULES}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CPP_CMAKE_MODULES}) @@ -198,10 +210,21 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake_modules) +# Multi-Configuration generators (e.g. Visual Studio or XCode) place their build artifacts +# in a subdirectory named ${CMAKE_BUILD_TYPE} by default, where ${CMAKE_BUILD_TYPE} varies +# depending on the chosen build configuration (e.g. Release or Debug). +get_property(GENERATOR_IS_MULTI_CONFIG_VALUE GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(GENERATOR_IS_MULTI_CONFIG_VALUE) + set(MATLAB_BUILD_OUTPUT_DIR "${CMAKE_BINARY_DIR}/$") +else() + set(MATLAB_BUILD_OUTPUT_DIR "${CMAKE_BINARY_DIR}") +endif() + # Only build the MATLAB interface C++ tests if MATLAB_BUILD_TESTS=ON. if(MATLAB_BUILD_TESTS) # find_package(GTest) supports custom GTEST_ROOT as well as package managers. find_package(GTest) + if(NOT GTest_FOUND) # find_package(Arrow) supports custom ARROW_HOME as well as package # managers. @@ -212,12 +235,36 @@ if(MATLAB_BUILD_TESTS) # C++ libraries that are built from source. build_arrow(BUILD_GTEST) else() + # On Windows, IMPORTED_LOCATION needs to be set to indicate where the shared + # libraries live when GTest is found. + if(WIN32) + set(GTEST_SHARED_LIB_DIR "${GTEST_ROOT}/bin") + set(GTEST_SHARED_LIBRARY_FILENAME + "${CMAKE_SHARED_LIBRARY_PREFIX}gtest${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(GTEST_SHARED_LIBRARY_LIB + "${GTEST_SHARED_LIB_DIR}/${GTEST_SHARED_LIBRARY_FILENAME}") + + set(GTEST_MAIN_SHARED_LIB_DIR "${GTEST_ROOT}/bin") + set(GTEST_MAIN_SHARED_LIBRARY_FILENAME + "${CMAKE_SHARED_LIBRARY_PREFIX}gtest_main${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(GTEST_MAIN_SHARED_LIBRARY_LIB + "${GTEST_MAIN_SHARED_LIB_DIR}/${GTEST_MAIN_SHARED_LIBRARY_FILENAME}") + + set_target_properties(GTest::gtest PROPERTIES IMPORTED_LOCATION + "${GTEST_SHARED_LIBRARY_LIB}") + + set_target_properties(GTest::gtest_main + PROPERTIES IMPORTED_LOCATION + "${GTEST_MAIN_SHARED_LIBRARY_LIB}") + endif() + find_package(Arrow) if(NOT Arrow_FOUND) # Trigger an automatic build of the Arrow C++ libraries. build_arrow() endif() endif() + else() find_package(Arrow) if(NOT Arrow_FOUND) @@ -226,42 +273,48 @@ else() endif() # MATLAB is Required -find_package(Matlab REQUIRED) +find_package(Matlab REQUIRED COMPONENTS MAIN_PROGRAM) -# Construct the absolute path to featherread's source files -set(featherread_sources featherreadmex.cc feather_reader.cc util/handle_status.cc - util/unicode_conversion.cc) -list(TRANSFORM featherread_sources PREPEND ${CMAKE_SOURCE_DIR}/src/) +message(STATUS "Mex Library: ${Matlab_MEX_LIBRARY}") +message(STATUS "Mex Include Folder: ${Matlab_INCLUDE_DIRS}") -# Build featherreadmex MEX binary -matlab_add_mex(R2018a - NAME featherreadmex - SRC ${featherread_sources} - LINK_TO arrow_shared) +set(CPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/cpp) +set(MATLAB_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/cpp/arrow/matlab) + +set(arrow_matlab_sources + mex/mex_util.cc + feather/feather_reader.cc + feather/feather_writer.cc + feather/feather_functions.cc + feather/util/handle_status.cc + feather/util/unicode_conversion.cc) +list(TRANSFORM arrow_matlab_sources PREPEND ${CPP_SOURCE_DIR}/arrow/matlab/) + +add_library(arrow_matlab SHARED ${arrow_matlab_sources}) + +# Declare a dependency on arrow_shared (libarrow.so/dylib/dll). +target_link_libraries(arrow_matlab arrow_shared) + +# Declare a dependency on the MEX shared library (libmex.so/dylib/dll). +target_link_libraries(arrow_matlab ${Matlab_MX_LIBRARY}) +target_link_libraries(arrow_matlab ${Matlab_MEX_LIBRARY}) -# Construct the absolute path to featherwrite's source files -set(featherwrite_sources featherwritemex.cc feather_writer.cc util/handle_status.cc - util/unicode_conversion.cc) -list(TRANSFORM featherwrite_sources PREPEND ${CMAKE_SOURCE_DIR}/src/) +# Include the MATLAB MEX headers. +target_include_directories(arrow_matlab PRIVATE ${Matlab_INCLUDE_DIRS}) +target_include_directories(arrow_matlab PRIVATE ${CPP_SOURCE_DIR}) +target_include_directories(arrow_matlab PRIVATE ${ARROW_INCLUDE_DIR}) +target_compile_definitions(arrow_matlab PRIVATE ARROW_MATLAB_EXPORTING) -# Build featherwritemex MEX binary +set(mexfcn_sources mex/mexfcn.cc) +list(TRANSFORM mexfcn_sources PREPEND ${CPP_SOURCE_DIR}/arrow/matlab/) + +# Build mexfcn MEX binary. matlab_add_mex(R2018a - NAME featherwritemex - SRC ${featherwrite_sources} - LINK_TO arrow_shared) + NAME mexfcn + SRC ${mexfcn_sources} + LINK_TO arrow_matlab) -# Ensure the MEX binaries are placed in the src directory on all platforms -if(WIN32) - set_target_properties(featherreadmex PROPERTIES RUNTIME_OUTPUT_DIRECTORY - $<1:${CMAKE_SOURCE_DIR}/src>) - set_target_properties(featherwritemex PROPERTIES RUNTIME_OUTPUT_DIRECTORY - $<1:${CMAKE_SOURCE_DIR}/src>) -else() - set_target_properties(featherreadmex PROPERTIES LIBRARY_OUTPUT_DIRECTORY - $<1:${CMAKE_SOURCE_DIR}/src>) - set_target_properties(featherwritemex PROPERTIES LIBRARY_OUTPUT_DIRECTORY - $<1:${CMAKE_SOURCE_DIR}/src>) -endif() +target_include_directories(mexfcn PRIVATE ${CPP_SOURCE_DIR}) # ############################################################################## # C++ Tests @@ -273,10 +326,203 @@ if(MATLAB_BUILD_TESTS) # Define a test executable target. TODO: Remove the placeholder test. This is # just for testing GoogleTest integration. add_executable(placeholder_test ${CMAKE_SOURCE_DIR}/src/placeholder_test.cc) + add_executable(mex_util_test ${CPP_SOURCE_DIR}/arrow/matlab/mex/mex_util_test.cc) + # Declare a dependency on the GTest::gtest and GTest::gtest_main IMPORTED # targets. target_link_libraries(placeholder_test GTest::gtest GTest::gtest_main) - # Add a test target. + # Declare a dependency on the GTest::gtest and GTest::gtest_main IMPORTED + # targets. + target_link_libraries(mex_util_test GTest::gtest GTest::gtest_main) + target_link_libraries(mex_util_test arrow_matlab) + + # Include the MATLAB MEX headers. + target_include_directories(mex_util_test PRIVATE ${Matlab_INCLUDE_DIRS}) + # Include the C++ source headers. + target_include_directories(mex_util_test PRIVATE ${CPP_SOURCE_DIR}) + + # Add test targets for C++ tests. add_test(PlaceholderTestTarget placeholder_test) + add_test(CheckNumArgsTestTarget mex_util_test) + + # On macOS, add the directory of libarrow.dylib to the $DYLD_LIBRARY_PATH for + # running CheckNumArgsTestTarget. + if(APPLE) + get_target_property(ARROW_SHARED_LIB arrow_shared IMPORTED_LOCATION) + get_filename_component(ARROW_SHARED_LIB_DIR ${ARROW_SHARED_LIB} DIRECTORY) + + set_tests_properties(CheckNumArgsTestTarget + PROPERTIES ENVIRONMENT + "DYLD_LIBRARY_PATH=${ARROW_SHARED_LIB_DIR}") + endif() + + # On Windows: + # Add the directory of gtest.dll and gtest_main.dll to the %PATH% for running + # all tests. + # Add the directory of libmx.dll, libmex.dll, and libarrow.dll to the %PATH% for running + # CheckNumArgsTestTarget. + # Note: When appending to the path using set_test_properties' ENVIRONMENT property, + # make sure that we escape ';' to prevent CMake from interpreting the input as + # a list of strings. + if(WIN32) + get_target_property(GTEST_SHARED_LIB GTest::gtest IMPORTED_LOCATION) + get_filename_component(GTEST_SHARED_LIB_DIR ${GTEST_SHARED_LIB} DIRECTORY) + + get_target_property(GTEST_MAIN_SHARED_LIB GTest::gtest_main IMPORTED_LOCATION) + get_filename_component(GTEST_MAIN_SHARED_LIB_DIR ${GTEST_MAIN_SHARED_LIB} DIRECTORY) + + set_tests_properties(PlaceholderTestTarget + PROPERTIES ENVIRONMENT + "PATH=${GTEST_SHARED_LIB_DIR}\;${GTEST_MAIN_SHARED_LIB_DIR}\;$ENV{PATH}" + ) + + get_target_property(ARROW_SHARED_LIB arrow_shared IMPORTED_LOCATION) + get_filename_component(ARROW_SHARED_LIB_DIR ${ARROW_SHARED_LIB} DIRECTORY) + + set(MATLAB_DLL_DEPENDENCIES_DIR "${Matlab_ROOT_DIR}/bin/win64") + + set_tests_properties(CheckNumArgsTestTarget + PROPERTIES ENVIRONMENT + "PATH=${ARROW_SHARED_LIB_DIR}\;${MATLAB_DLL_DEPENDENCIES_DIR}\;${GTEST_SHARED_LIB_DIR}\;${GTEST_MAIN_SHARED_LIB_DIR}\;$ENV{PATH}" + ) + endif() +endif() + +# ############################################################################## +# Install +# ############################################################################## +# Create a subdirectory at CMAKE_INSTALL_PREFIX to install the interface. +set(CMAKE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/arrow_matlab") + +# Install MATLAB source files. +# On macOS, exclude '.DS_Store' files in the source tree from installation. +install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/matlab/" + DESTINATION ${CMAKE_INSTALL_DIR} + PATTERN ".DS_Store" EXCLUDE) + +# Install arrow_matlab and mexfcn. +# Use the RUNTIME output artifact keyword for Windows. +# Use the LIBRARY output artifact keyword for macOS and Linux. +install(TARGETS arrow_matlab mexfcn + RUNTIME DESTINATION ${CMAKE_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_DIR}) + +get_target_property(ARROW_SHARED_LIB arrow_shared IMPORTED_LOCATION) +get_filename_component(ARROW_SHARED_LIB_DIR ${ARROW_SHARED_LIB} DIRECTORY) +get_filename_component(ARROW_SHARED_LIB_FILENAME ${ARROW_SHARED_LIB} NAME_WE) + +if(WIN32) + # On Windows, arrow.dll must be installed to to CMAKE_INSTALL_DIR regardless of whether + # Arrow_FOUND is true or false. + install(FILES ${ARROW_SHARED_LIB} DESTINATION "${CMAKE_INSTALL_DIR}") +endif() + +# On macOS, use the RPATH values below for runtime dependency resolution. This enables +# relocation of the installation directory. +if(APPLE) + # Setting INSTALL_RPATH_USE_LINK_PATH to true will add the paths to external dependencies + # to the RPATH of arrow_matlab and mexfcn, including the MATLAB dependencies. + # If Arrow_FOUND is true, this also includes the path to arrow_shared. + set_target_properties(arrow_matlab mexfcn PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) + + # Add @loader_path to the RPATH of mexfcn so that libarrow_matlab.dylib can be found + # at runtime. + set_target_properties(mexfcn PROPERTIES INSTALL_RPATH "@loader_path") + + if(NOT Arrow_FOUND) + # If Arrow_FOUND is false, Arrow is built by the arrow_shared target and needs + # to be copied to CMAKE_INSTALL_DIR. The DIRECTORY install command is used to + # install libarrow.dylib (symlink) and the real files it points to. + # + # The subfolders cmake and pkgconfig are excluded as they will be empty. + # Note: The following CMake Issue suggests enabling an option to exclude all + # folders that would be empty after installation: + # https://gitlab.kitware.com/cmake/cmake/-/issues/17122 + install(DIRECTORY "${ARROW_SHARED_LIB_DIR}/" + DESTINATION ${CMAKE_INSTALL_DIR} + FILES_MATCHING + REGEX "${ARROW_SHARED_LIB_FILENAME}\\..*dylib" + PATTERN "cmake" EXCLUDE + PATTERN "pkgconfig" EXCLUDE) + + # Add @loader_path to the RPATH of arrow_matlab so that libarrow.dylib can be found + # at runtime. + set_target_properties(arrow_matlab PROPERTIES INSTALL_RPATH "@loader_path") + endif() +endif() + +# On Linux, use the RUNPATH values below for runtime dependency resolution. This enables +# relocation of the installation directory. +if(UNIX + AND NOT APPLE + AND NOT CYGWIN) + # Setting INSTALL_RPATH_USE_LINK_PATH to true will add the paths to external dependencies + # to the RUNPATH of arrow_matlab and mexfcn, including the MATLAB dependencies. + # If Arrow_FOUND is true, this also includes the path to arrow_shared. + set_target_properties(arrow_matlab mexfcn PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) + + # Add $ORIGIN to the RUNPATH of mexfcn so that libarrow_matlab.so can be found + # at runtime. + set_target_properties(mexfcn PROPERTIES INSTALL_RPATH $ORIGIN) + + if(NOT Arrow_FOUND) + # If Arrow_FOUND is false, Arrow is built by the arrow_shared target and needs + # to be copied to CMAKE_INSTALL_DIR. The DIRECTORY install command is used to + # install libarrow.so (symlink) and the real files it points to. + # + # The subfolders cmake and pkgconfig are excluded as they will be empty. + # Note: The following CMake Issue suggests enabling an option to exclude all + # folders that would be empty after installation: + # https://gitlab.kitware.com/cmake/cmake/-/issues/17122 + install(DIRECTORY "${ARROW_SHARED_LIB_DIR}/" + DESTINATION ${CMAKE_INSTALL_DIR} + FILES_MATCHING + REGEX "${ARROW_SHARED_LIB_FILENAME}\\.so.*" + PATTERN "cmake" EXCLUDE + PATTERN "pkgconfig" EXCLUDE) + + # Add $ORIGIN to the RUNPATH of arrow_matlab so that libarrow.so can be found + # at runtime. + set_target_properties(arrow_matlab PROPERTIES INSTALL_RPATH $ORIGIN) + endif() +endif() + +# MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE toggles whether an addpath command to add the install +# directory path to the MATLAB Search Path is added to the startup.m file located in the MATLAB +# userpath directory. +option(MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE + "Sets whether the path to the install directory should be added to the startup.m file located at the MATLAB userpath" + OFF) + +# If MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE is specified ON and MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH +# is not specified, then set MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH=OFF. +if(MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE AND NOT DEFINED + MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH) + set(MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH OFF) +endif() + +# MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH toggles whether the path to the install directory should +# be directly added to the MATLAB Search Path. +option(MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH + "Sets whether the path to the install directory should be directly added to the MATLAB Search Path" + ON) + +if(MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH OR MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE) + set(TOOLS_DIR "${CMAKE_SOURCE_DIR}/tools") + + # Pass Matlab_MAIN_PROGRAM, TOOLS_DIR, and INSTALL_DIR to the install step + # code/script execution scope. + install(CODE "set(Matlab_MAIN_PROGRAM \"${Matlab_MAIN_PROGRAM}\")") + install(CODE "set(TOOLS_DIR \"${TOOLS_DIR}\")") + install(CODE "set(INSTALL_DIR \"${CMAKE_INSTALL_DIR}\")") + install(CODE "set(MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE \"${MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE}\")" + ) + install(CODE "set(MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH \"${MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH}\")" + ) + + # Call the CMake script that runs the MATLAB function to add the install directory + # to the MATLAB Search Path or add a command to the MATLAB startup file to add the + # install directory to the MATLAB Search Path. + install(SCRIPT "${TOOLS_DIR}/UpdateMatlabSearchPath.cmake") endif() diff --git a/matlab/src/cpp/arrow/matlab/api/visibility.h b/matlab/src/cpp/arrow/matlab/api/visibility.h new file mode 100644 index 00000000000..51efac972ee --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/api/visibility.h @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) +#ifdef ARROW_MATLAB_EXPORTING +#define ARROW_MATLAB_EXPORT __declspec(dllexport) +#else +#define ARROW_MATLAB_EXPORT __declspec(dllimport) +#endif +#else // Not Windows +#define ARROW_MATLAB_EXPORT __attribute__((visibility("default"))) +#endif diff --git a/matlab/src/cpp/arrow/matlab/feather/feather_functions.cc b/matlab/src/cpp/arrow/matlab/feather/feather_functions.cc new file mode 100644 index 00000000000..f5b5a394083 --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/feather/feather_functions.cc @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "feather_functions.h" + +#include + +#include "feather_reader.h" +#include "feather_writer.h" +#include "util/handle_status.h" + +namespace arrow { +namespace matlab { +namespace feather { + +void featherwrite(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { + const std::string filename{mxArrayToUTF8String(prhs[0])}; + + // Open a Feather file at the provided file path for writing. + std::shared_ptr feather_writer{nullptr}; + util::HandleStatus(FeatherWriter::Open(filename, &feather_writer)); + + // Write the Feather file table variables and table metadata from MATLAB. + util::HandleStatus(feather_writer->WriteVariables(prhs[1], prhs[2])); +} + +void featherread(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { + const std::string filename{mxArrayToUTF8String(prhs[0])}; + + // Read the given Feather file into memory. + std::shared_ptr feather_reader{nullptr}; + util::HandleStatus(FeatherReader::Open(filename, &feather_reader)); + + // Return the Feather file table variables and table metadata to MATLAB. + plhs[0] = feather_reader->ReadVariables(); + plhs[1] = feather_reader->ReadMetadata(); +} + +} // namespace feather +} // namespace matlab +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/cpp/arrow/matlab/feather/feather_functions.h b/matlab/src/cpp/arrow/matlab/feather/feather_functions.h new file mode 100644 index 00000000000..c055e9582c0 --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/feather/feather_functions.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#pragma once + +#include + +#include "arrow/matlab/api/visibility.h" + +namespace arrow { +namespace matlab { +namespace feather { + +ARROW_MATLAB_EXPORT void featherwrite(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]); + +ARROW_MATLAB_EXPORT void featherread(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]); + +} // namespace feather +} // namespace matlab +} // namespace arrow diff --git a/matlab/src/feather_reader.cc b/matlab/src/cpp/arrow/matlab/feather/feather_reader.cc similarity index 98% rename from matlab/src/feather_reader.cc rename to matlab/src/cpp/arrow/matlab/feather/feather_reader.cc index 1cbb50541e7..413b7f153e9 100644 --- a/matlab/src/feather_reader.cc +++ b/matlab/src/cpp/arrow/matlab/feather/feather_reader.cc @@ -15,9 +15,6 @@ // specific language governing permissions and limitations // under the License. -#include -#include - #include "feather_reader.h" #include @@ -33,17 +30,21 @@ #include #include +#include +#include + #include "matlab_traits.h" #include "util/handle_status.h" #include "util/unicode_conversion.h" namespace arrow { namespace matlab { +namespace feather { namespace internal { // Read the name of variable i from the Feather file as a mxArray*. mxArray* ReadVariableName(const std::string& column_name) { - return matlab::util::ConvertUTF8StringToUTF16CharMatrix(column_name); + return util::ConvertUTF8StringToUTF16CharMatrix(column_name); } template @@ -57,8 +58,7 @@ mxArray* ReadNumericVariableData(const std::shared_ptr& column) { mxArray* variable_data = mxCreateNumericMatrix(column->length(), 1, matlab_class_id, mxREAL); - auto arrow_numeric_array = - std::static_pointer_cast(column); + auto arrow_numeric_array = std::static_pointer_cast(column); // Get a raw pointer to the Arrow array data. const MatlabType* source = arrow_numeric_array->raw_values(); @@ -182,10 +182,10 @@ Status FeatherReader::Open(const std::string& filename, // Open file with given filename as a ReadableFile. ARROW_ASSIGN_OR_RAISE(auto readable_file, io::ReadableFile::Open(filename)); - + // Open the Feather file for reading with a TableReader. ARROW_ASSIGN_OR_RAISE(auto reader, ipc::feather::Reader::Open(readable_file)); - + // Set the internal reader_ object. (*feather_reader)->reader_ = reader; @@ -273,5 +273,6 @@ mxArray* FeatherReader::ReadVariables() { return variables; } +} // namespace feather } // namespace matlab -} // namespace arrow +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/feather_reader.h b/matlab/src/cpp/arrow/matlab/feather/feather_reader.h similarity index 97% rename from matlab/src/feather_reader.h rename to matlab/src/cpp/arrow/matlab/feather/feather_reader.h index 197e470bf6e..0e2089d3672 100644 --- a/matlab/src/feather_reader.h +++ b/matlab/src/cpp/arrow/matlab/feather/feather_reader.h @@ -17,16 +17,17 @@ #pragma once -#include -#include - #include #include #include #include +#include +#include + namespace arrow { namespace matlab { +namespace feather { class FeatherReader { public: @@ -71,5 +72,6 @@ class FeatherReader { std::string description_; }; +} // namespace feather } // namespace matlab -} // namespace arrow +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/feather_writer.cc b/matlab/src/cpp/arrow/matlab/feather/feather_writer.cc similarity index 98% rename from matlab/src/feather_writer.cc rename to matlab/src/cpp/arrow/matlab/feather/feather_writer.cc index 1a76ada1995..f67d8dc2fba 100644 --- a/matlab/src/feather_writer.cc +++ b/matlab/src/cpp/arrow/matlab/feather/feather_writer.cc @@ -15,10 +15,6 @@ // specific language governing permissions and limitations // under the License. -#include -#include /* for std::multiplies */ -#include /* for std::accumulate */ - #include "feather_writer.h" #include @@ -33,11 +29,16 @@ #include #include +#include +#include /* for std::multiplies */ +#include /* for std::accumulate */ + #include "matlab_traits.h" #include "util/handle_status.h" namespace arrow { namespace matlab { +namespace feather { namespace internal { // Returns the arrow::DataType that corresponds to the input type string @@ -279,7 +280,8 @@ Status FeatherWriter::Open(const std::string& filename, *feather_writer = std::shared_ptr(new FeatherWriter()); // Open a FileOutputStream corresponding to the provided filename. - ARROW_ASSIGN_OR_RAISE((*feather_writer)->file_output_stream_, + ARROW_ASSIGN_OR_RAISE( + (*feather_writer)->file_output_stream_, io::FileOutputStream::Open(filename, &((*feather_writer)->file_output_stream_))); return Status::OK(); } @@ -331,15 +333,15 @@ Status FeatherWriter::WriteVariables(const mxArray* variables, const mxArray* me auto datatype = internal::ConvertMatlabTypeStringToArrowDataType(type_str); auto field = std::make_shared(name_str, datatype); - ARROW_ASSIGN_OR_RAISE(std::shared_ptr validity_bitmap, + ARROW_ASSIGN_OR_RAISE( + std::shared_ptr validity_bitmap, arrow::AllocateResizableBuffer(internal::BitPackedLength(num_rows_))); // Populate bit-packed arrow::Buffer using validity data in the mxArray*. internal::BitPackBuffer(valid, validity_bitmap); // Wrap mxArray data in an arrow::Array of the equivalent type. - auto array = - internal::WriteVariableData(data, type_str, validity_bitmap); + auto array = internal::WriteVariableData(data, type_str, validity_bitmap); // Verify that the arrow::Array has the right number of elements. internal::ValidateNumRows(array->length(), num_rows_); @@ -362,5 +364,6 @@ Status FeatherWriter::WriteVariables(const mxArray* variables, const mxArray* me return ipc::feather::WriteTable(*table, file_output_stream_.get(), write_props); } +} // namespace feather } // namespace matlab } // namespace arrow diff --git a/matlab/src/feather_writer.h b/matlab/src/cpp/arrow/matlab/feather/feather_writer.h similarity index 97% rename from matlab/src/feather_writer.h rename to matlab/src/cpp/arrow/matlab/feather/feather_writer.h index a35b1434340..0042d4ac694 100644 --- a/matlab/src/feather_writer.h +++ b/matlab/src/cpp/arrow/matlab/feather/feather_writer.h @@ -17,16 +17,17 @@ #pragma once -#include -#include - #include #include #include #include +#include +#include + namespace arrow { namespace matlab { +namespace feather { class FeatherWriter { public: @@ -64,5 +65,6 @@ class FeatherWriter { std::shared_ptr file_output_stream_; }; +} // namespace feather } // namespace matlab -} // namespace arrow +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/matlab_traits.h b/matlab/src/cpp/arrow/matlab/feather/matlab_traits.h similarity index 99% rename from matlab/src/matlab_traits.h rename to matlab/src/cpp/arrow/matlab/feather/matlab_traits.h index a76539fa7a9..8ef5aea933a 100644 --- a/matlab/src/matlab_traits.h +++ b/matlab/src/cpp/arrow/matlab/feather/matlab_traits.h @@ -18,7 +18,6 @@ #pragma once #include - #include namespace arrow { @@ -100,4 +99,3 @@ struct MatlabTraits { } // namespace matlab } // namespace arrow - diff --git a/matlab/src/util/handle_status.cc b/matlab/src/cpp/arrow/matlab/feather/util/handle_status.cc similarity index 98% rename from matlab/src/util/handle_status.cc rename to matlab/src/cpp/arrow/matlab/feather/util/handle_status.cc index f1c3b7f2598..86d6c1c79cf 100644 --- a/matlab/src/util/handle_status.cc +++ b/matlab/src/cpp/arrow/matlab/feather/util/handle_status.cc @@ -16,11 +16,11 @@ // under the License. #include - #include namespace arrow { namespace matlab { +namespace feather { namespace util { void HandleStatus(const Status& status) { @@ -86,6 +86,8 @@ void HandleStatus(const Status& status) { } } } + } // namespace util +} // namespace feather } // namespace matlab } // namespace arrow diff --git a/matlab/src/util/handle_status.h b/matlab/src/cpp/arrow/matlab/feather/util/handle_status.h similarity index 96% rename from matlab/src/util/handle_status.h rename to matlab/src/cpp/arrow/matlab/feather/util/handle_status.h index 7212114a1d1..3025b3b2080 100644 --- a/matlab/src/util/handle_status.h +++ b/matlab/src/cpp/arrow/matlab/feather/util/handle_status.h @@ -21,12 +21,15 @@ namespace arrow { namespace matlab { +namespace feather { namespace util { + // Terminates execution and returns to the MATLAB prompt, // displaying an error message if the given status // indicates that an error has occurred. void HandleStatus(const Status& status); + } // namespace util +} // namespace feather } // namespace matlab } // namespace arrow - diff --git a/matlab/src/util/unicode_conversion.cc b/matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.cc similarity index 91% rename from matlab/src/util/unicode_conversion.cc rename to matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.cc index 01c2e4b9451..623943d0ca6 100644 --- a/matlab/src/util/unicode_conversion.cc +++ b/matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.cc @@ -15,13 +15,14 @@ // specific language governing permissions and limitations // under the License. -#include /* for std::wstring_convert */ -#include /* for std::codecvt_utf8_utf16 */ - #include "unicode_conversion.h" +#include /* for std::codecvt_utf8_utf16 */ +#include /* for std::wstring_convert */ + namespace arrow { namespace matlab { +namespace feather { namespace util { mxArray* ConvertUTF8StringToUTF16CharMatrix(const std::string& utf8_string) { @@ -29,7 +30,7 @@ mxArray* ConvertUTF8StringToUTF16CharMatrix(const std::string& utf8_string) { const char* string_start = utf8_string.c_str(); const char* string_end = string_start + utf8_string.length(); - // Due to this issue on MSVC: https://stackoverflow.com/q/32055357 we cannot + // Due to this issue on MSVC: https://stackoverflow.com/q/32055357 we cannot // directly use a destination type of char16_t. #if _MSC_VER >= 1900 using CharType = int16_t; @@ -43,7 +44,7 @@ mxArray* ConvertUTF8StringToUTF16CharMatrix(const std::string& utf8_string) { try { utf16_string = code_converter.from_bytes(string_start, string_end); } catch (...) { - // In the case that any error occurs, just try returning a string in the + // In the case that any error occurs, just try returning a string in the // user's current locale instead. return mxCreateString(string_start); } @@ -52,12 +53,13 @@ mxArray* ConvertUTF8StringToUTF16CharMatrix(const std::string& utf8_string) { const mwSize dimensions[2] = {1, utf16_string.size()}; mxArray* character_matrix = mxCreateCharArray(2, dimensions); mxChar* character_matrix_pointer = mxGetChars(character_matrix); - std::copy(utf16_string.data(), utf16_string.data() + utf16_string.size(), - character_matrix_pointer); + std::copy(utf16_string.data(), utf16_string.data() + utf16_string.size(), + character_matrix_pointer); return character_matrix; } } // namespace util +} // namespace feather } // namespace matlab -} // namespace arrow +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/util/unicode_conversion.h b/matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.h similarity index 96% rename from matlab/src/util/unicode_conversion.h rename to matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.h index fa905cbf0ba..8a41f08605a 100644 --- a/matlab/src/util/unicode_conversion.h +++ b/matlab/src/cpp/arrow/matlab/feather/util/unicode_conversion.h @@ -17,16 +17,20 @@ #pragma once -#include #include +#include + namespace arrow { namespace matlab { +namespace feather { namespace util { + // Converts a UTF-8 encoded std::string to a heap-allocated UTF-16 encoded // mxCharArray. mxArray* ConvertUTF8StringToUTF16CharMatrix(const std::string& utf8_string); + } // namespace util +} // namespace feather } // namespace matlab } // namespace arrow - diff --git a/matlab/src/featherwritemex.cc b/matlab/src/cpp/arrow/matlab/mex/mex_functions.h similarity index 55% rename from matlab/src/featherwritemex.cc rename to matlab/src/cpp/arrow/matlab/mex/mex_functions.h index d8f90baafc5..aa09fc863b2 100644 --- a/matlab/src/featherwritemex.cc +++ b/matlab/src/cpp/arrow/matlab/mex/mex_functions.h @@ -16,21 +16,25 @@ // under the License. #include +#include +#include #include -#include "feather_writer.h" -#include "util/handle_status.h" +#include "arrow/matlab/feather/feather_functions.h" -// MEX gateway function. This is the entry point for featherwritemex.cc. -void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { - const std::string filename{mxArrayToUTF8String(prhs[0])}; +namespace arrow { +namespace matlab { +namespace mex { + +using namespace arrow::matlab::feather; - // Open a Feather file at the provided file path for writing. - std::shared_ptr feather_writer{nullptr}; - arrow::matlab::util::HandleStatus( - arrow::matlab::FeatherWriter::Open(filename, &feather_writer)); +using mex_fcn_t = + std::function; - // Write the Feather file table variables and table metadata from MATLAB. - arrow::matlab::util::HandleStatus(feather_writer->WriteVariables(prhs[1], prhs[2])); -} +static const std::unordered_map FUNCTION_MAP = { + {"featherread", featherread}, {"featherwrite", featherwrite}}; + +} // namespace mex +} // namespace matlab +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/cpp/arrow/matlab/mex/mex_util.cc b/matlab/src/cpp/arrow/matlab/mex/mex_util.cc new file mode 100644 index 00000000000..ba6e87c483e --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/mex/mex_util.cc @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "mex_util.h" + +namespace arrow { +namespace matlab { +namespace mex { + +void checkNumArgs(int nrhs) { + if (nrhs < 1) { + mexErrMsgIdAndTxt("MATLAB:arrow:minrhs", + "'mexfcn' requires at least one input argument, which must be the " + "name of the C++ MEX to invoke."); + } +} + +std::string get_function_name(const mxArray* input) { + std::string opname; + if (!mxIsChar(input)) { + mexErrMsgIdAndTxt("MATLAB:arrow:FunctionNameDataType", + "The first input argument to 'mexfcn' must be a character vector."); + } + const char* c_str = mxArrayToUTF8String(input); + return std::string{c_str}; +} + +mex_fcn_t lookup_function(const std::string& function_name) { + auto kv_pair = FUNCTION_MAP.find(function_name); + if (kv_pair == FUNCTION_MAP.end()) { + mexErrMsgIdAndTxt("MATLAB:arrow:UnknownMEXFunction", "Unrecognized MEX function '%s'", + function_name.c_str()); + } + return kv_pair->second; +} + +} // namespace mex +} // namespace matlab +} // namespace arrow \ No newline at end of file diff --git a/matlab/src/cpp/arrow/matlab/mex/mex_util.h b/matlab/src/cpp/arrow/matlab/mex/mex_util.h new file mode 100644 index 00000000000..8e02084a288 --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/mex/mex_util.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#pragma once + +#include +#include +#include + +#include + +#include "mex_functions.h" + +#include "arrow/matlab/api/visibility.h" + +namespace arrow { +namespace matlab { +namespace mex { + +ARROW_MATLAB_EXPORT void checkNumArgs(int nrhs); + +ARROW_MATLAB_EXPORT std::string get_function_name(const mxArray* input); + +ARROW_MATLAB_EXPORT mex_fcn_t lookup_function(const std::string& function_name); + +} // namespace mex +} // namespace matlab +} // namespace arrow diff --git a/matlab/src/cpp/arrow/matlab/mex/mex_util_test.cc b/matlab/src/cpp/arrow/matlab/mex/mex_util_test.cc new file mode 100644 index 00000000000..8f2c65c75f3 --- /dev/null +++ b/matlab/src/cpp/arrow/matlab/mex/mex_util_test.cc @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include + +#include "mex_util.h" + +namespace arrow { +namespace matlab { +namespace mex { +TEST(CheckNumArgsTests, TooFewArgsError) { + EXPECT_THROW(checkNumArgs(0), std::exception); +} +} // namespace mex +} // namespace matlab +} // namespace arrow diff --git a/matlab/src/featherreadmex.cc b/matlab/src/cpp/arrow/matlab/mex/mexfcn.cc similarity index 59% rename from matlab/src/featherreadmex.cc rename to matlab/src/cpp/arrow/matlab/mex/mexfcn.cc index b52b8a98f1d..c1205b071c1 100644 --- a/matlab/src/featherreadmex.cc +++ b/matlab/src/cpp/arrow/matlab/mex/mexfcn.cc @@ -15,23 +15,14 @@ // specific language governing permissions and limitations // under the License. -#include - #include -#include "feather_reader.h" -#include "util/handle_status.h" +#include "mex_util.h" -// MEX gateway function. This is the entry point for featherreadmex.cpp. void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { - const std::string filename{mxArrayToUTF8String(prhs[0])}; - - // Read the given Feather file into memory. - std::shared_ptr feather_reader{nullptr}; - arrow::matlab::util::HandleStatus( - arrow::matlab::FeatherReader::Open(filename, &feather_reader)); + using namespace arrow::matlab::mex; - // Return the Feather file table variables and table metadata to MATLAB. - plhs[0] = feather_reader->ReadVariables(); - plhs[1] = feather_reader->ReadMetadata(); + checkNumArgs(nrhs); + auto fcn = lookup_function(get_function_name(prhs[0])); + fcn(nlhs, plhs, nrhs - 1, ++prhs); } diff --git a/matlab/src/+mlarrow/+util/createMetadataStruct.m b/matlab/src/matlab/+arrow/+util/createMetadataStruct.m similarity index 100% rename from matlab/src/+mlarrow/+util/createMetadataStruct.m rename to matlab/src/matlab/+arrow/+util/createMetadataStruct.m diff --git a/matlab/src/+mlarrow/+util/createVariableStruct.m b/matlab/src/matlab/+arrow/+util/createVariableStruct.m similarity index 100% rename from matlab/src/+mlarrow/+util/createVariableStruct.m rename to matlab/src/matlab/+arrow/+util/createVariableStruct.m diff --git a/matlab/src/+mlarrow/+util/makeValidMATLABTableVariableNames.m b/matlab/src/matlab/+arrow/+util/makeValidMATLABTableVariableNames.m similarity index 100% rename from matlab/src/+mlarrow/+util/makeValidMATLABTableVariableNames.m rename to matlab/src/matlab/+arrow/+util/makeValidMATLABTableVariableNames.m diff --git a/matlab/src/+mlarrow/+util/table2mlarrow.m b/matlab/src/matlab/+arrow/+util/table2mlarrow.m similarity index 99% rename from matlab/src/+mlarrow/+util/table2mlarrow.m rename to matlab/src/matlab/+arrow/+util/table2mlarrow.m index 36e4d1d15a9..391b0603ea2 100644 --- a/matlab/src/+mlarrow/+util/table2mlarrow.m +++ b/matlab/src/matlab/+arrow/+util/table2mlarrow.m @@ -43,7 +43,7 @@ % implied. See the License for the specific language governing % permissions and limitations under the License. -import mlarrow.util.*; +import arrow.util.*; % Struct array representing the underlying data of each variable % in the given table. diff --git a/matlab/src/featherread.m b/matlab/src/matlab/featherread.m similarity index 97% rename from matlab/src/featherread.m rename to matlab/src/matlab/featherread.m index 31bc426b877..1d77cd30ed0 100644 --- a/matlab/src/featherread.m +++ b/matlab/src/matlab/featherread.m @@ -23,7 +23,7 @@ % specific language governing permissions and limitations % under the License. -import mlarrow.util.*; +import arrow.util.*; % Validate input arguments. narginchk(1, 1); @@ -46,7 +46,7 @@ % Read table variables and metadata from the given Feather file using % libarrow. -[variables, metadata] = featherreadmex(filename); +[variables, metadata] = mexfcn('featherread', filename); % Make valid MATLAB table variable names out of any of the % Feather table column names that are not valid MATLAB table diff --git a/matlab/src/featherwrite.m b/matlab/src/matlab/featherwrite.m similarity index 94% rename from matlab/src/featherwrite.m rename to matlab/src/matlab/featherwrite.m index eeedf26d0be..361d7b62180 100644 --- a/matlab/src/featherwrite.m +++ b/matlab/src/matlab/featherwrite.m @@ -23,7 +23,7 @@ function featherwrite(filename, t) % specific language governing permissions and limitations % under the License. -import mlarrow.util.table2mlarrow; +import arrow.util.table2mlarrow; % Validate input arguments. narginchk(2, 2); @@ -39,6 +39,5 @@ function featherwrite(filename, t) [variables, metadata] = table2mlarrow(t); % Write the table to a Feather file. -featherwritemex(filename, variables, metadata); - +mexfcn('featherwrite', filename, variables, metadata); end diff --git a/matlab/test/tfeather.m b/matlab/test/tfeather.m index 625a3a52544..7d871762583 100755 --- a/matlab/test/tfeather.m +++ b/matlab/test/tfeather.m @@ -17,21 +17,14 @@ % permissions and limitations under the License. methods(TestClassSetup) - function addFeatherFunctionsToMATLABPath(testCase) import matlab.unittest.fixtures.PathFixture % Add Feather test utilities to the MATLAB path. testCase.applyFixture(PathFixture('util')); - % Add featherread and featherwrite to the MATLAB path. - testCase.applyFixture(PathFixture(fullfile('..', 'src'))); - % featherreadmex must be on the MATLAB path. - testCase.assertTrue(~isempty(which('featherreadmex')), ... - '''featherreadmex'' must be on the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); - % featherwritemex must be on the MATLAB path. - testCase.assertTrue(~isempty(which('featherwritemex')), ... - '''featherwritemex'' must be on to the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); + % mexfcn must be on the MATLAB path. + testCase.assertTrue(~isempty(which('mexfcn')), ... + '''mexfcn'' must be on the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); end - end methods(TestMethodSetup) @@ -226,7 +219,5 @@ function NumericComplexUnsupported(testCase) expectedTable = featherRoundTrip(filename, actualTable); testCase.verifyNotEqual(actualTable, expectedTable); end - end - end diff --git a/matlab/test/tfeathermex.m b/matlab/test/tfeathermex.m index 77070ad1421..f502b05c949 100644 --- a/matlab/test/tfeathermex.m +++ b/matlab/test/tfeathermex.m @@ -22,16 +22,10 @@ function addFeatherFunctionsToMATLABPath(testCase) import matlab.unittest.fixtures.PathFixture % Add Feather test utilities to the MATLAB path. testCase.applyFixture(PathFixture('util')); - % Add featherread and featherwrite to the MATLAB path. - testCase.applyFixture(PathFixture(fullfile('..', 'src'))); - % featherreadmex must be on the MATLAB path. - testCase.assertTrue(~isempty(which('featherreadmex')), ... - '''featherreadmex'' must be on the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); - % featherwritemex must be on the MATLAB path. - testCase.assertTrue(~isempty(which('featherwritemex')), ... - '''featherwritemex'' must be on to the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); + % mexfcn must be on the MATLAB path. + testCase.assertTrue(~isempty(which('mexfcn')), ... + '''mexfcn'' must be on the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); end - end methods(TestMethodSetup) @@ -40,7 +34,6 @@ function setupTempWorkingDirectory(testCase) import matlab.unittest.fixtures.WorkingFolderFixture; testCase.applyFixture(WorkingFolderFixture); end - end methods(Test) @@ -57,11 +50,11 @@ function InvalidMATLABTableVariableNames(testCase) filename = fullfile(pwd, 'temp.feather'); % Create a table with an invalid MATLAB table variable name. - invalidVariable = mlarrow.util.createVariableStruct('double', 1, true, '@'); - validVariable = mlarrow.util.createVariableStruct('double', 1, true, 'Valid'); + invalidVariable = arrow.util.createVariableStruct('double', 1, true, '@'); + validVariable = arrow.util.createVariableStruct('double', 1, true, 'Valid'); variables = [invalidVariable, validVariable]; - metadata = mlarrow.util.createMetadataStruct(1, 2); - featherwritemex(filename, variables, metadata); + metadata = arrow.util.createMetadataStruct(1, 2); + mexfcn('featherwrite', filename, variables, metadata); t = featherread(filename); testCase.verifyEqual(t.Properties.VariableNames{1}, 'x_'); @@ -69,8 +62,6 @@ function InvalidMATLABTableVariableNames(testCase) testCase.verifyEqual(t.Properties.VariableDescriptions{1}, 'Original variable name: ''@'''); testCase.verifyEqual(t.Properties.VariableDescriptions{2}, ''); - end - + end end - end diff --git a/matlab/test/tmexfcn.m b/matlab/test/tmexfcn.m new file mode 100644 index 00000000000..ebaec650e1d --- /dev/null +++ b/matlab/test/tmexfcn.m @@ -0,0 +1,61 @@ +classdef tmexfcn < matlab.unittest.TestCase + % Tests for mexfcn. + + % Licensed to the Apache Software Foundation (ASF) under one or more + % contributor license agreements. See the NOTICE file distributed with + % this work for additional information regarding copyright ownership. + % The ASF licenses this file to you under the Apache License, Version + % 2.0 (the "License"); you may not use this file except in compliance + % with the License. You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + % implied. See the License for the specific language governing + % permissions and limitations under the License. + + methods(TestClassSetup) + function addFeatherFunctionsToMATLABPath(testCase) + import matlab.unittest.fixtures.PathFixture + % Add Feather test utilities to the MATLAB path. + testCase.applyFixture(PathFixture('util')); + % mexfcn must be on the MATLAB path. + testCase.assertTrue(~isempty(which('mexfcn')), ... + '''mexfcn'' must be on the MATLAB path. Use ''addpath'' to add folders to the MATLAB path.'); + end + end + + methods(Test) + function UnknownMEXFunctionError(testCase) + % Verifies mexfcn throws an error if an unkown MEX function name + % is passed to it. + errID = "MATLAB:arrow:UnknownMEXFunction"; + fcn = @()mexfcn('NotAFunction'); + testCase.verifyError(fcn, errID); + end + + function TooFewInputArguementsError(testCase) + % Verifies mexfcn throws an error if zero input arguments are + % passed it. + errID = "MATLAB:arrow:minrhs"; + fcn = @()mexfcn; + testCase.verifyError(fcn, errID); + end + + function InvalidFunctionNameDataTypeError(testCase) + % Verifies mexfcn throws an error if the first input argument is + % not a character vector. + errID = "MATLAB:arrow:FunctionNameDataType"; + fcn = @()mexfcn(1); + testCase.verifyError(fcn, errID); + + fcn = @()mexfcn(categorical); + testCase.verifyError(fcn, errID); + + fcn = @()mexfcn(datetime(2021, 9, 10)); + testCase.verifyError(fcn, errID); + end + end +end diff --git a/matlab/test/util/createVariablesAndMetadataStructs.m b/matlab/test/util/createVariablesAndMetadataStructs.m index 0c60cbfbbcc..a26bfe45a3e 100644 --- a/matlab/test/util/createVariablesAndMetadataStructs.m +++ b/matlab/test/util/createVariablesAndMetadataStructs.m @@ -17,7 +17,7 @@ % implied. See the License for the specific language governing % permissions and limitations under the License. -import mlarrow.util.*; +import arrow.util.*; type = 'uint8'; data = uint8([1; 2; 3]); diff --git a/matlab/test/util/featherMEXRoundTrip.m b/matlab/test/util/featherMEXRoundTrip.m index 49ab183edab..021e0f6e2cf 100644 --- a/matlab/test/util/featherMEXRoundTrip.m +++ b/matlab/test/util/featherMEXRoundTrip.m @@ -17,6 +17,6 @@ % implied. See the License for the specific language governing % permissions and limitations under the License. -featherwritemex(filename, variablesIn, metadataIn); -[variablesOut, metadataOut] = featherreadmex(filename); +mexfcn('featherwrite', filename, variablesIn, metadataIn); +[variablesOut, metadataOut] = mexfcn('featherread', filename); end \ No newline at end of file diff --git a/matlab/tools/UpdateMatlabSearchPath.cmake b/matlab/tools/UpdateMatlabSearchPath.cmake new file mode 100644 index 00000000000..178bb48ab5d --- /dev/null +++ b/matlab/tools/UpdateMatlabSearchPath.cmake @@ -0,0 +1,54 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Convert Matlab_MAIN_PROGRAM, INSTALL_DIR and TOOLS_DIR to use OS native path notation. +file(TO_NATIVE_PATH ${Matlab_MAIN_PROGRAM} NATIVE_MATLAB_MAIN_PROGRAM) +file(TO_NATIVE_PATH ${INSTALL_DIR} NATIVE_INSTALL_DIR) +file(TO_NATIVE_PATH ${TOOLS_DIR} NATIVE_TOOLS_DIR) + +# Initialize an instance of MATLAB and call the MATLAB function, addInstallDirToSearchPath, +# defined in ${NATIVE_TOOLS_DIR}. +# Flags to pass to MATLAB: +# -sd: startup directory for the MATLAB +# -batch: non-interactive script execution +execute_process(COMMAND "${NATIVE_MATLAB_MAIN_PROGRAM}" -sd "${NATIVE_TOOLS_DIR}" -batch + "addInstallDirToSearchPath('${INSTALL_DIR}', '${MATLAB_ADD_INSTALL_DIR_TO_SEARCH_PATH}', '${MATLAB_ADD_INSTALL_DIR_TO_STARTUP_FILE}')" + RESULT_VARIABLE MATLAB_EXIT_CODE) + +if(MATLAB_EXIT_CODE EQUAL "1") + # Get path to the default MATLAB pathdef.m file. + set(MATLAB_PATHDEF_FILE "${Matlab_MAIN_PROGRAM}/toolbox/local/pathdef.m") + file(TO_NATIVE_PATH ${MATLAB_PATHDEF_FILE} NATIVE_MATLAB_PATHDEF_FILE) + + message(FATAL_ERROR "Failed to add the installation directory, ${NATIVE_INSTALL_DIR}, to the MATLAB Search Path. This may be due to the current user lacking the necessary filesystem permissions to modify ${NATIVE_MATLAB_PATHDEF_FILE}. In order to complete the installation process, ${NATIVE_INSTALL_DIR} must be added to the MATLAB Search Path using the \"addpath\" and \"savepath\" MATLAB commands or by resolving the permissions issues and re-running the CMake install target." + ) +endif() + +if(MATLAB_EXIT_CODE EQUAL "2") + message(FATAL_ERROR "Failed to append to the MATLAB startup.m file located at the MATLAB userpath directory. fopen() failed to open the file. In order to complete the installation process, ${NATIVE_INSTALL_DIR} must be added to the MATLAB Search Path using the \"addpath\" and \"savepath\" MATLAB commands." + ) +endif() + +if(MATLAB_EXIT_CODE EQUAL "3") + message(FATAL_ERROR "Failed to append to the MATLAB startup.m file located at the MATLAB userpath directory. fwrite() failed to write to the file. In order to complete the installation process, ${NATIVE_INSTALL_DIR} must be added to the MATLAB Search Path using the \"addpath\" and \"savepath\" MATLAB commands." + ) +endif() + +if(MATLAB_EXIT_CODE EQUAL "4") + message(FATAL_ERROR "Failed to append to the MATLAB startup.m file located at the MATLAB userpath directory. fclose() failed to close the file. In order to complete the installation process, ${NATIVE_INSTALL_DIR} must be added to the MATLAB Search Path using the \"addpath\" and \"savepath\" MATLAB commands." + ) +endif() diff --git a/matlab/tools/addInstallDirToSearchPath.m b/matlab/tools/addInstallDirToSearchPath.m new file mode 100644 index 00000000000..67253439549 --- /dev/null +++ b/matlab/tools/addInstallDirToSearchPath.m @@ -0,0 +1,54 @@ +function addInstallDirToSearchPath(installDirPath, addInstallDirToSearchPath, addInstallDirToStartupFile) + % addInstallDirToSearchPath Add the input path, INSTALLDIRPATH, to the + % MATLAB Search Path and save. + % + % Licensed to the Apache Software Foundation (ASF) under one or more + % contributor license agreements. See the NOTICE file distributed with + % this work for additional information regarding copyright ownership. + % The ASF licenses this file to you under the Apache License, Version + % 2.0 (the "License"); you may not use this file except in compliance + % with the License. You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + % implied. See the License for the specific language governing + % permissions and limitations under the License. + + if addInstallDirToSearchPath == "ON" + addpath(installDirPath); + status = savepath(fullfile(matlabroot, "toolbox", "local", "pathdef.m")); + + % Return exit code 1 to indicate savepath failure and 0 to indicate the path has + % been saved successfully. + if status == 0 + disp("Sucessfully added installation directory to the MATLAB Search Path: " + installDirPath); + quit(0); + else + quit(1); + end + end + + if addInstallDirToStartupFile == "ON" + fid = fopen(fullfile(userpath, "startup.m"), "a"); + if fid > 2 + count = fwrite(fid, "addpath(""" + installDirPath + """);"); + if count == 0 + % fwrite failed. + quit(3); + end + status = fclose(fid); + if status ~= 0 + % fclose failed. + quit(4); + end + else + % fopen failed. + quit(2); + end + disp("Sucessfully appended an addpath command to the MATLAB startup.m file located at the userpath to add installation directory to the MATLAB Search Path: " + installDirPath); + quit(0); + end +end