Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[R] Remove dependency on gendef for Visual Studio builds (fixes #5608) #5764

Merged
merged 7 commits into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ metastore_db
# files from R-package source install
**/config.status
R-package/src/Makevars
*.lib

# Visual Studio Code
/.vscode/
Expand Down
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions R-package/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
96 changes: 96 additions & 0 deletions R-package/inst/make-r-def.R
Original file line number Diff line number Diff line change
@@ -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))
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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')"
)

Expand Down
34 changes: 34 additions & 0 deletions cmake/RPackageInstall.cmake.in
Original file line number Diff line number Diff line change
@@ -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}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, CMake would run commands like R CMD INSTALL but would not check if the command actually succeeded or not. Now CMake will check the result of the command. This change will help us debug install issues in the coming future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh great!

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")
16 changes: 16 additions & 0 deletions cmake/RPackageInstallTargetSetup.cmake
Original file line number Diff line number Diff line change
@@ -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()
32 changes: 0 additions & 32 deletions cmake/Utils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
26 changes: 18 additions & 8 deletions cmake/modules/FindLibR.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
77 changes: 54 additions & 23 deletions doc/build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://discuss.xgboost.ai>`_.

**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:

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:

Expand Down