diff --git a/CMakeLists.txt b/CMakeLists.txt index 335b000ca159..895f2dd26fce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,12 @@ if(USE_R35) endif(USE_R35) if(BUILD_FOR_R) + list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") + find_package(LibR REQUIRED) + message(STATUS "LIBR_EXECUTABLE: ${LIBR_EXECUTABLE}") + message(STATUS "LIBR_INCLUDE_DIRS: ${LIBR_INCLUDE_DIRS}") + message(STATUS "LIBR_CORE_LIBRARY: ${LIBR_CORE_LIBRARY}") + include_directories(${LIBR_INCLUDE_DIRS}) ADD_DEFINITIONS(-DLGB_R_BUILD) endif(BUILD_FOR_R) @@ -301,6 +307,11 @@ if(WIN32 AND (MINGW OR CYGWIN)) TARGET_LINK_LIBRARIES(_lightgbm IPHLPAPI) endif() +if(BUILD_FOR_R) + TARGET_LINK_LIBRARIES(lightgbm ${LIBR_CORE_LIBRARY}) + TARGET_LINK_LIBRARIES(_lightgbm ${LIBR_CORE_LIBRARY}) +endif(BUILD_FOR_R) + install(TARGETS lightgbm _lightgbm RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib diff --git a/R-package/src/cmake/modules/FindLibR.cmake b/R-package/src/cmake/modules/FindLibR.cmake new file mode 100644 index 000000000000..20c0a974e1ff --- /dev/null +++ b/R-package/src/cmake/modules/FindLibR.cmake @@ -0,0 +1,208 @@ +# CMake module used to find the location of R's +# dll and header files. +# +# Borrows heavily from xgboost's R package: +# +# * https://github.com/dmlc/xgboost/blob/master/cmake/modules/FindLibR.cmake +# +# Defines the following: +# LIBR_FOUND +# LIBR_HOME +# LIBR_EXECUTABLE +# LIBR_INCLUDE_DIRS +# LIBR_LIB_DIR +# LIBR_CORE_LIBRARY +# and a CMake function to create R.lib for MSVC + +if(NOT R_ARCH) + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(R_ARCH "i386") + else() + set(R_ARCH "x64") + endif() +endif() + +if(NOT ("${R_ARCH}" STREQUAL "x64")) + message(FATAL_ERROR "LightGBM's R package currently only supports 64-bit operating systems") +endif() + +# Creates R.lib and R.def in the build directory for linking with MSVC +function(create_rlib_for_msvc) + + message("Creating R.lib and R.def") + + # various checks and warnings + if(NOT WIN32 OR NOT MSVC) + message(FATAL_ERROR "create_rlib_for_msvc() can only be used with MSVC") + endif() + + if(NOT EXISTS "${LIBR_LIB_DIR}") + message(FATAL_ERROR "LIBR_LIB_DIR, '${LIBR_LIB_DIR}', not found") + endif() + + find_program(GENDEF_EXE gendef) + find_program(DLLTOOL_EXE dlltool) + + if(NOT GENDEF_EXE OR NOT DLLTOOL_EXE) + message(FATAL_ERROR "Either gendef.exe or dlltool.exe not found!\ + \nDo you have Rtools installed with its MinGW's bin/ in PATH?") + endif() + + # extract symbols from R.dll into R.def and R.lib import library + execute_process(COMMAND ${GENDEF_EXE} + "-" "${LIBR_LIB_DIR}/R.dll" + OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/R.def" + ) + execute_process(COMMAND ${DLLTOOL_EXE} + "--input-def" "${CMAKE_CURRENT_BINARY_DIR}/R.def" + "--output-lib" "${CMAKE_CURRENT_BINARY_DIR}/R.lib" + ) +endfunction(create_rlib_for_msvc) + +# R version information is used to search for R's libraries in +# the registry on Windows. Since this code is orchestrated by +# an R script (src/install.libs.R), that script uses R's built-ins to +# find the version of R and pass it through as a CMake variable +if(CMAKE_R_VERSION) + message("R version passed into FindLibR.cmake: ${CMAKE_R_VERSION}") +elseif(WIN32) + message(FATAL_ERROR "Expected CMAKE_R_VERSION to be passed in on Windows but none was provided. Check src/install.libs.R") +endif() + + +if (NOT LIBR_EXECUTABLE) + find_program( + LIBR_EXECUTABLE + NAMES R R.exe + ) + + # CRAN may run RD CMD CHECK instead of R CMD CHECK, + # which can lead to this infamous error: + # 'R' should not be used without a path -- see par. 1.6 of the manual + if(LIBR_EXECUTABLE MATCHES ".*\\.Rcheck.*") + unset(LIBR_EXECUTABLE CACHE) + endif() + + # ignore the R bundled with R.app on Mac, since that is GUI-only + if(LIBR_EXECUTABLE MATCHES ".+R\\.app.*") + unset(LIBR_EXECUTABLE CACHE) + endif() +endif() + +# Find R executable unless it has been provided directly or already found +if (NOT LIBR_EXECUTABLE) + if(APPLE) + + find_library(LIBR_LIBRARIES R) + + if(LIBR_LIBRARIES MATCHES ".*\\.framework") + set(LIBR_HOME "${LIBR_LIBRARIES}/Resources") + set(LIBR_EXECUTABLE "${LIBR_HOME}/R") + else() + get_filename_component(_LIBR_LIBRARIES "${LIBR_LIBRARIES}" REALPATH) + get_filename_component(_LIBR_LIBRARIES_DIR "${_LIBR_LIBRARIES}" DIRECTORY) + set(LIBR_EXECUTABLE "${_LIBR_LIBRARIES_DIR}/../bin/R") + endif() + + elseif(UNIX) + + # attempt to find R executable + if(NOT LIBR_EXECUTABLE) + find_program( + LIBR_EXECUTABLE + NO_DEFAULT_PATH + HINTS "${CMAKE_CURRENT_BINARY_DIR}" "/usr/bin" "/usr/lib/" "/usr/local/bin/" + NAMES R + ) + endif() + + # Windows + else() + + # if R executable not available, query R_HOME path from registry + if(NOT LIBR_HOME) + + # Try to find R's location in the registry + # ref: https://cran.r-project.org/bin/windows/base/rw-FAQ.html#Does-R-use-the-Registry_003f + get_filename_component( + LIBR_HOME + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\R-core\\R\\${CMAKE_R_VERSION};InstallPath]" + ABSOLUTE + ) + endif() + + if(NOT LIBR_HOME) + get_filename_component( + LIBR_HOME + "[HKEY_CURRENT_USER\\SOFTWARE\\R-core\\R\\${CMAKE_R_VERSION};InstallPath]" + ABSOLUTE + ) + endif() + + if(NOT LIBR_HOME) + message(FATAL_ERROR "Unable to locate R executable.\ + \nEither add its location to PATH or provide it through the LIBR_EXECUTABLE CMake variable") + endif() + + # set exe location based on R_ARCH + set(LIBR_EXECUTABLE "${LIBR_HOME}/bin/${R_ARCH}/R.exe") + + endif() + + if(NOT LIBR_EXECUTABLE) + message(FATAL_ERROR "Unable to locate R executable.\ + \nEither add its location to PATH or provide it through the LIBR_EXECUTABLE CMake variable") + endif() + +endif() + +# ask R for the home path +execute_process( + COMMAND ${LIBR_EXECUTABLE} "--slave" "--vanilla" "-e" "cat(normalizePath(R.home(), winslash='/'))" + OUTPUT_VARIABLE LIBR_HOME +) + +# ask R for the include dir +execute_process( + COMMAND ${LIBR_EXECUTABLE} "--slave" "--vanilla" "-e" "cat(normalizePath(R.home('include'), winslash='/'))" + OUTPUT_VARIABLE LIBR_INCLUDE_DIRS +) + +# ask R for the lib dir +execute_process( + COMMAND ${LIBR_EXECUTABLE} "--slave" "--vanilla" "-e" "cat(normalizePath(R.home('lib'), winslash='/'))" + OUTPUT_VARIABLE LIBR_LIB_DIR +) + +# look for the core R library +find_library( + LIBR_CORE_LIBRARY + NAMES R + HINTS "${CMAKE_CURRENT_BINARY_DIR}" "${LIBR_LIB_DIR}" "${LIBR_HOME}/bin" "${LIBR_LIBRARIES}" +) + +set(LIBR_HOME ${LIBR_HOME} CACHE PATH "R home directory") +set(LIBR_EXECUTABLE ${LIBR_EXECUTABLE} CACHE PATH "R executable") +set(LIBR_INCLUDE_DIRS ${LIBR_INCLUDE_DIRS} CACHE PATH "R include directory") +set(LIBR_LIB_DIR ${LIBR_LIB_DIR} CACHE PATH "R shared libraries directory") +set(LIBR_CORE_LIBRARY ${LIBR_CORE_LIBRARY} CACHE PATH "R core shared library") + +if(WIN32 AND MSVC) + + # create a local R.lib import library for R.dll if it doesn't exist + if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/R.lib") + create_rlib_for_msvc() + endif() + +endif() + +# define find requirements +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(LibR DEFAULT_MSG + LIBR_HOME + LIBR_EXECUTABLE + LIBR_INCLUDE_DIRS + LIBR_LIB_DIR + LIBR_CORE_LIBRARY +) diff --git a/R-package/src/install.libs.R b/R-package/src/install.libs.R index 2f8f36c449b2..f6d2335b2cd8 100644 --- a/R-package/src/install.libs.R +++ b/R-package/src/install.libs.R @@ -53,6 +53,17 @@ if (!use_precompile) { } cmake_cmd <- paste0(cmake_cmd, " -DBUILD_FOR_R=ON ") + # Pass in R version, used to help find R executable for linking + R_version_string <- paste( + R.Version()[["major"]] + , R.Version()[["minor"]] + , sep = "." + ) + cmake_cmd <- sprintf( + paste0(cmake_cmd, " -DCMAKE_R_VERSION='%s' ") + , R_version_string + ) + # Check if Windows installation (for gcc vs Visual Studio) if (WINDOWS) { if (use_mingw) { diff --git a/include/LightGBM/R_object_helper.h b/include/LightGBM/R_object_helper.h index 8321011260cc..1464c0a7083f 100644 --- a/include/LightGBM/R_object_helper.h +++ b/include/LightGBM/R_object_helper.h @@ -39,9 +39,9 @@ // 64bit pointer #if INTPTR_MAX == INT64_MAX - typedef int64_t R_xlen_t; + typedef int64_t xlen_t; #else - typedef int R_xlen_t; + typedef int xlen_t; #endif #else @@ -58,7 +58,7 @@ unsigned int gccls : 3; }; - typedef int R_xlen_t; + typedef int xlen_t; #endif // R_VER_ABOVE_35 struct lgbm_primsxp { @@ -110,8 +110,8 @@ typedef struct LGBM_SER { } LGBM_SER, *LGBM_SE; struct lgbm_vecsxp { - R_xlen_t length; - R_xlen_t truelength; + xlen_t length; + xlen_t truelength; }; typedef struct VECTOR_SER { diff --git a/include/LightGBM/utils/log.h b/include/LightGBM/utils/log.h index 75809c6a19cb..ee3ff453ace5 100644 --- a/include/LightGBM/utils/log.h +++ b/include/LightGBM/utils/log.h @@ -14,6 +14,13 @@ #include #include +#ifdef LGB_R_BUILD + #define R_NO_REMAP + #define R_USE_C99_IN_CXX + #include + #include +#endif + namespace LightGBM { #if defined(_MSC_VER) @@ -57,7 +64,6 @@ namespace LightGBM { if ((pointer) == nullptr) LightGBM::Log::Fatal(#pointer " Can't be NULL at %s, line %d .\n", __FILE__, __LINE__); #endif - enum class LogLevel: int { Fatal = -1, Warning = 0, @@ -65,7 +71,6 @@ enum class LogLevel: int { Debug = 2, }; - /*! * \brief A static Log class */ @@ -107,19 +112,33 @@ class Log { vsprintf(str_buf, format, val); #endif va_end(val); - fprintf(stderr, "[LightGBM] [Fatal] %s\n", str_buf); - fflush(stderr); - throw std::runtime_error(std::string(str_buf)); + + // R code should write back to R's error stream, + // otherwise to stderr + #ifndef LGB_R_BUILD + fprintf(stderr, "[LightGBM] [Fatal] %s\n", str_buf); + fflush(stderr); + throw std::runtime_error(std::string(str_buf)); + #else + Rf_error("[LightGBM] [Fatal] %s\n", str_buf); + #endif } private: static void Write(LogLevel level, const char* level_str, const char *format, va_list val) { if (level <= GetLevel()) { // omit the message with low level - // write to STDOUT - printf("[LightGBM] [%s] ", level_str); - vprintf(format, val); - printf("\n"); - fflush(stdout); + // R code should write back to R's output stream, + // otherwise to stdout + #ifndef LGB_R_BUILD + printf("[LightGBM] [%s] ", level_str); + vprintf(format, val); + printf("\n"); + fflush(stdout); + #else + Rprintf("[LightGBM] [%s] ", level_str); + Rvprintf(format, val); + Rprintf("\n"); + #endif } }