diff --git a/.gitignore b/.gitignore index d0328b187227..bd2b50f88f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,7 @@ metastore_db # files from R-package source install **/config.status R-package/src/Makevars +*.lib # Visual Studio Code /.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b31732844471..c355a6896721 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,11 +226,12 @@ add_dependencies(xgboost runxgboost) #-- Installing XGBoost if (R_LIB) + include(cmake/RPackageInstallTargetSetup.cmake) set_target_properties(xgboost PROPERTIES PREFIX "") if (APPLE) set_target_properties(xgboost PROPERTIES SUFFIX ".so") endif (APPLE) - setup_rpackage_install_target(xgboost ${CMAKE_CURRENT_BINARY_DIR}) + setup_rpackage_install_target(xgboost "${CMAKE_CURRENT_BINARY_DIR}/R-package-install") set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/dummy_inst") endif (R_LIB) if (MINGW) diff --git a/R-package/CMakeLists.txt b/R-package/CMakeLists.txt index 96776a0458b6..ee89e7d6fc55 100644 --- a/R-package/CMakeLists.txt +++ b/R-package/CMakeLists.txt @@ -36,3 +36,6 @@ set(LINKED_LIBRARIES_PRIVATE ${LINKED_LIBRARIES_PRIVATE} ${LIBR_CORE_LIBRARY} PA if (USE_OPENMP) target_link_libraries(xgboost-r PRIVATE OpenMP::OpenMP_CXX) endif () + +set(LIBR_HOME "${LIBR_HOME}" PARENT_SCOPE) +set(LIBR_EXECUTABLE "${LIBR_EXECUTABLE}" PARENT_SCOPE) \ No newline at end of file diff --git a/R-package/inst/make-r-def.R b/R-package/inst/make-r-def.R new file mode 100644 index 000000000000..ef5c06cac88e --- /dev/null +++ b/R-package/inst/make-r-def.R @@ -0,0 +1,96 @@ +# [description] +# Create a definition file (.def) from a .dll file, using objdump. This +# is used by FindLibR.cmake when building the R package with MSVC. +# +# [usage] +# +# Rscript make-r-def.R something.dll something.def +# +# [references] +# * https://www.cs.colorado.edu/~main/cs1300/doc/mingwfaq.html + +args <- commandArgs(trailingOnly = TRUE) + +IN_DLL_FILE <- args[[1L]] +OUT_DEF_FILE <- args[[2L]] +DLL_BASE_NAME <- basename(IN_DLL_FILE) + +message(sprintf("Creating '%s' from '%s'", OUT_DEF_FILE, IN_DLL_FILE)) + +# system() will not raise an R exception if the process called +# fails. Wrapping it here to get that behavior. +# +# system() introduces a lot of overhead, at least on Windows, +# so trying processx if it is available +.pipe_shell_command_to_stdout <- function(command, args, out_file) { + has_processx <- suppressMessages({ + suppressWarnings({ + require("processx") # nolint + }) + }) + if (has_processx) { + p <- processx::process$new( + command = command + , args = args + , stdout = out_file + , windows_verbatim_args = FALSE + ) + invisible(p$wait()) + } else { + message(paste0( + "Using system2() to run shell commands. Installing " + , "'processx' with install.packages('processx') might " + , "make this faster." + )) + exit_code <- system2( + command = command + , args = shQuote(args) + , stdout = out_file + ) + if (exit_code != 0L) { + stop(paste0("Command failed with exit code: ", exit_code)) + } + } + return(invisible(NULL)) +} + +# use objdump to dump all the symbols +OBJDUMP_FILE <- "objdump-out.txt" +.pipe_shell_command_to_stdout( + command = "objdump" + , args = c("-p", IN_DLL_FILE) + , out_file = OBJDUMP_FILE +) + +objdump_results <- readLines(OBJDUMP_FILE) +result <- file.remove(OBJDUMP_FILE) + +# Only one table in the objdump results matters for our purposes, +# see https://www.cs.colorado.edu/~main/cs1300/doc/mingwfaq.html +start_index <- which( + grepl( + pattern = "[Ordinal/Name Pointer] Table" + , x = objdump_results + , fixed = TRUE + ) +) +empty_lines <- which(objdump_results == "") +end_of_table <- empty_lines[empty_lines > start_index][1L] + +# Read the contents of the table +exported_symbols <- objdump_results[(start_index + 1L):end_of_table] +exported_symbols <- gsub("\t", "", exported_symbols) +exported_symbols <- gsub(".*\\] ", "", exported_symbols) +exported_symbols <- gsub(" ", "", exported_symbols) + +# Write R.def file +writeLines( + text = c( + paste0("LIBRARY \"", DLL_BASE_NAME, "\"") + , "EXPORTS" + , exported_symbols + ) + , con = OUT_DEF_FILE + , sep = "\n" +) +message(sprintf("Successfully created '%s'", OUT_DEF_FILE)) diff --git a/appveyor.yml b/appveyor.yml index f3d676314473..cfdd5cbfd11e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,7 +107,7 @@ test_script: ) # MSVC R package: run only the unit tests - if /i "%target%" == "rmsvc" ( - cd build_rmsvc%ver%\R-package && + cd R-package && R.exe -q -e "library(testthat); setwd('tests'); source('testthat.R')" ) diff --git a/cmake/RPackageInstall.cmake.in b/cmake/RPackageInstall.cmake.in new file mode 100644 index 000000000000..3701518fb339 --- /dev/null +++ b/cmake/RPackageInstall.cmake.in @@ -0,0 +1,34 @@ +# Commands to install the R package as a CMake install target + +function(check_call) + set(cmd COMMAND) + cmake_parse_arguments( + PARSE_ARGV 0 + CALL_ARG "" "" "${cmd}" + ) + string(REPLACE ";" " " commands "${CALL_ARG_COMMAND}") + message("Command: ${commands}") + execute_process(COMMAND ${CALL_ARG_COMMAND} + OUTPUT_VARIABLE _out + ERROR_VARIABLE _err + RESULT_VARIABLE _res) + if(NOT "${_res}" EQUAL "0") + message(FATAL_ERROR "out: ${_out}, err: ${_err}, res: ${_res}") + endif() +endfunction() + +# Important paths +set(build_dir "@build_dir@") +set(LIBR_EXECUTABLE "@LIBR_EXECUTABLE@") + +# Back up cmake_install.cmake +file(WRITE "${build_dir}/R-package/src/Makevars" "all:") +file(WRITE "${build_dir}/R-package/src/Makevars.win" "all:") + +# Install dependencies +set(XGB_DEPS_SCRIPT + "deps = setdiff(c('data.table', 'magrittr', 'stringi'), rownames(installed.packages())); if(length(deps)>0) install.packages(deps, repo = 'https://cloud.r-project.org/')") +check_call(COMMAND "${LIBR_EXECUTABLE}" -q -e "${XGB_DEPS_SCRIPT}") + +# Install the XGBoost R package +check_call(COMMAND "${LIBR_EXECUTABLE}" CMD INSTALL --no-multiarch --build "${build_dir}/R-package") \ No newline at end of file diff --git a/cmake/RPackageInstallTargetSetup.cmake b/cmake/RPackageInstallTargetSetup.cmake new file mode 100644 index 000000000000..8912a0402e2c --- /dev/null +++ b/cmake/RPackageInstallTargetSetup.cmake @@ -0,0 +1,16 @@ +# Assembles the R-package files in build_dir; +# if necessary, installs the main R package dependencies; +# runs R CMD INSTALL. +function(setup_rpackage_install_target rlib_target build_dir) + configure_file(${PROJECT_SOURCE_DIR}/cmake/RPackageInstall.cmake.in ${PROJECT_BINARY_DIR}/RPackageInstall.cmake @ONLY) + install( + DIRECTORY "${xgboost_SOURCE_DIR}/R-package" + DESTINATION "${build_dir}" + REGEX "src/*" EXCLUDE + REGEX "R-package/configure" EXCLUDE + ) + install(TARGETS ${rlib_target} + LIBRARY DESTINATION "${build_dir}/R-package/src/" + RUNTIME DESTINATION "${build_dir}/R-package/src/") + install(SCRIPT ${PROJECT_BINARY_DIR}/RPackageInstall.cmake) +endfunction() \ No newline at end of file diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake index 6105330b11f6..141be6f57b7a 100644 --- a/cmake/Utils.cmake +++ b/cmake/Utils.cmake @@ -110,38 +110,6 @@ function(format_gencode_flags flags out) set(${out} "${${out}}" PARENT_SCOPE) endfunction(format_gencode_flags flags) -# Assembles the R-package files in build_dir; -# if necessary, installs the main R package dependencies; -# runs R CMD INSTALL. -function(setup_rpackage_install_target rlib_target build_dir) - # backup cmake_install.cmake - install(CODE "file(COPY \"${build_dir}/R-package/cmake_install.cmake\" -DESTINATION \"${build_dir}/bak\")") - - install(CODE "file(REMOVE_RECURSE \"${build_dir}/R-package\")") - install( - DIRECTORY "${xgboost_SOURCE_DIR}/R-package" - DESTINATION "${build_dir}" - REGEX "src/*" EXCLUDE - REGEX "R-package/configure" EXCLUDE - ) - install(TARGETS ${rlib_target} - LIBRARY DESTINATION "${build_dir}/R-package/src/" - RUNTIME DESTINATION "${build_dir}/R-package/src/") - install(CODE "file(WRITE \"${build_dir}/R-package/src/Makevars\" \"all:\")") - install(CODE "file(WRITE \"${build_dir}/R-package/src/Makevars.win\" \"all:\")") - set(XGB_DEPS_SCRIPT - "deps = setdiff(c('data.table', 'magrittr', 'stringi'), rownames(installed.packages()));\ - if(length(deps)>0) install.packages(deps, repo = 'https://cloud.r-project.org/')") - install(CODE "execute_process(COMMAND \"${LIBR_EXECUTABLE}\" \"-q\" \"-e\" \"${XGB_DEPS_SCRIPT}\")") - install(CODE "execute_process(COMMAND \"${LIBR_EXECUTABLE}\" CMD INSTALL\ - \"--no-multiarch\" \"--build\" \"${build_dir}/R-package\")") - - # restore cmake_install.cmake - install(CODE "file(RENAME \"${build_dir}/bak/cmake_install.cmake\" - \"${build_dir}/R-package/cmake_install.cmake\")") -endfunction(setup_rpackage_install_target) - macro(enable_nvtx target) find_package(NVTX REQUIRED) target_include_directories(${target} PRIVATE "${NVTX_INCLUDE_DIR}") diff --git a/cmake/modules/FindLibR.cmake b/cmake/modules/FindLibR.cmake index 01104392bb7e..a775feb28550 100644 --- a/cmake/modules/FindLibR.cmake +++ b/cmake/modules/FindLibR.cmake @@ -23,7 +23,7 @@ # Windows users might want to change this to their R version: if(NOT R_VERSION) - set(R_VERSION "3.4.1") + set(R_VERSION "4.0.0") endif() if(NOT R_ARCH) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") @@ -43,16 +43,26 @@ function(create_rlib_for_msvc) if(NOT EXISTS "${LIBR_LIB_DIR}") message(FATAL_ERROR "LIBR_LIB_DIR was not set!") endif() - find_program(GENDEF_EXE gendef) find_program(DLLTOOL_EXE dlltool) - if(NOT GENDEF_EXE OR NOT DLLTOOL_EXE) - message(FATAL_ERROR "\nEither gendef.exe or dlltool.exe not found!\ + if(NOT DLLTOOL_EXE) + message(FATAL_ERROR "\ndlltool.exe not found!\ \nDo you have Rtools installed with its MinGW's bin/ in PATH?") - endif() + 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") + get_filename_component( + LIBR_RSCRIPT_EXECUTABLE_DIR + ${LIBR_EXECUTABLE} + DIRECTORY + ) + set(LIBR_RSCRIPT_EXECUTABLE "${LIBR_RSCRIPT_EXECUTABLE_DIR}/Rscript") + + execute_process( + COMMAND ${LIBR_RSCRIPT_EXECUTABLE} + "${CMAKE_CURRENT_BINARY_DIR}/../../R-package/inst/make-r-def.R" + "${LIBR_LIB_DIR}/R.dll" "${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") diff --git a/doc/build.rst b/doc/build.rst index e2a91b740189..a6ab2ce2579e 100644 --- a/doc/build.rst +++ b/doc/build.rst @@ -49,19 +49,7 @@ Please refer to `Trouble Shooting`_ section first if you have any problem during installation. If the instructions do not work for you, please feel free to ask questions at `the user forum `_. -**Contents** - -* `Building the Shared Library`_ - - - `Building on Linux Distributions`_ - - `Building on OSX`_ - - `Building on Windows`_ - - `Building with GPU support`_ - -* `Python Package Installation`_ -* `R Package Installation`_ -* `Trouble Shooting`_ -* `Building the documentation`_ +.. contents:: Contents .. _build_shared_lib: @@ -365,12 +353,11 @@ You can install XGBoost from CRAN just like any other R package: and then run ``install.packages("xgboost")``. Without OpenMP, XGBoost will only use a single CPU core, leading to suboptimal training speed. -Installing the development version ----------------------------------- +Installing the development version (Linux / Mac OSX) +---------------------------------------------------- Make sure you have installed git and a recent C++ compiler supporting C++11 (See above -sections for requirements of building C++ core). On Windows, Rtools must be installed, -and its bin directory has to be added to ``PATH`` during the installation. +sections for requirements of building C++ core). Due to the use of git-submodules, ``devtools::install_github`` can no longer be used to install the latest version of R package. Thus, one has to run git to check out the code first: @@ -390,6 +377,37 @@ Thus, one has to run git to check out the code first: If all fails, try `Building the shared library`_ to see whether a problem is specific to R package or not. Notice that the R package is installed by CMake directly. +Installing the development version with Visual Studio +----------------------------------------------------- + +On Windows, CMake with Visual C++ Build Tools (or Visual Studio) can be used to build the R package. + +While not required, this build can be faster if you install the R package ``processx`` with ``install.packages("processx")``. + +.. note:: Setting correct PATH environment variable on Windows + + If you are using Windows, make sure to include the right directories in the PATH environment variable. + + * If you are using R 4.x with RTools 4.0: + - ``C:\rtools40\usr\bin`` + - ``C:\rtools40\mingw64\bin`` + + * If you are using R 3.x with RTools 3.x: + + - ``C:\Rtools\bin`` + - ``C:\Rtools\mingw_64\bin`` + +Open the Command Prompt and navigate to the XGBoost directory, and then run the following commands. Make sure to specify the correct R version. + +.. code-block:: bash + + cd C:\path\to\xgboost + mkdir build + cd build + cmake .. -G"Visual Studio 16 2019" -A x64 -DR_LIB=ON -DR_VERSION=4.0.0 + cmake --build . --target install --config Release + + .. _r_gpu_support: Installing R package with GPU support @@ -409,19 +427,32 @@ On Linux, starting from the XGBoost directory type: When default target is used, an R package shared library would be built in the ``build`` area. The ``install`` target, in addition, assembles the package files with this shared library under ``build/R-package`` and runs ``R CMD INSTALL``. -On Windows, CMake with Visual C++ Build Tools (or Visual Studio) has to be used to build an R package with GPU support. Rtools must also be installed (perhaps, some other MinGW distributions with ``gendef.exe`` and ``dlltool.exe`` would work, but that was not tested). +On Windows, CMake with Visual Studio has to be used to build an R package with GPU support. Rtools must also be installed. + +.. note:: Setting correct PATH environment variable on Windows + + If you are using Windows, make sure to include the right directories in the PATH environment variable. + + * If you are using R 4.x with RTools 4.0: + + - ``C:\rtools40\usr\bin`` + - ``C:\rtools40\mingw64\bin`` + * If you are using R 3.x with RTools 3.x: + + - ``C:\Rtools\bin`` + - ``C:\Rtools\mingw_64\bin`` + +Open the Command Prompt and navigate to the XGBoost directory, and then run the following commands. Make sure to specify the correct R version. .. code-block:: bash + cd C:\path\to\xgboost mkdir build cd build - cmake .. -G"Visual Studio 14 2015 Win64" -DUSE_CUDA=ON -DR_LIB=ON + cmake .. -G"Visual Studio 16 2019" -A x64 -DUSE_CUDA=ON -DR_LIB=ON -DR_VERSION=4.0.0 cmake --build . --target install --config Release -When ``--target xgboost`` is used, an R package DLL would be built under ``build/Release``. -The ``--target install``, in addition, assembles the package files with this dll under ``build/R-package`` and runs ``R CMD INSTALL``. - -If cmake can't find your R during the configuration step, you might provide the location of its executable to cmake like this: ``-DLIBR_EXECUTABLE="C:/Program Files/R/R-3.4.1/bin/x64/R.exe"``. +If CMake can't find your R during the configuration step, you might provide the location of R to CMake like this: ``-DLIBR_HOME="C:\Program Files\R\R-4.0.0"``. If on Windows you get a "permission denied" error when trying to write to ...Program Files/R/... during the package installation, create a ``.Rprofile`` file in your personal home directory (if you don't already have one in there), and add a line to it which specifies the location of your R packages user library, like the following: