install.libs.R 7.47 KB
Newer Older
Laurae's avatar
Laurae committed
1
# User options
2
use_gpu <- FALSE
3
make_args_from_build_script <- character(0L)
4
5
6

# For Windows, the package will be built with Visual Studio
# unless you set one of these to TRUE
7
use_mingw <- FALSE
8
9
10
11
12
use_msys2 <- FALSE

if (use_mingw && use_msys2) {
  stop("Cannot use both MinGW and MSYS2. Please choose only one.")
}
Laurae's avatar
Laurae committed
13

14
if (.Machine$sizeof.pointer != 8L) {
15
  stop("LightGBM only supports 64-bit R, please check the version of R and Rtools.")
Guolin Ke's avatar
Guolin Ke committed
16
17
}

18
R_ver <- as.double(R.Version()$major) + as.double(R.Version()$minor) / 10.0
19

20
21
22
23
24
# Get some paths
source_dir <- file.path(R_PACKAGE_SOURCE, "src", fsep = "/")
build_dir <- file.path(source_dir, "build", fsep = "/")
inst_dir <- file.path(R_PACKAGE_SOURCE, "inst", fsep = "/")

25
26
27
28
29
30
31
32
33
# 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
.run_shell_command <- function(cmd, args, strict = TRUE) {
    on_windows <- .Platform$OS.type == "windows"
    has_processx <- suppressMessages({
      suppressWarnings({
34
        require("processx")  # nolint: undesirable_function
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
      })
    })
    if (has_processx && on_windows) {
      result <- processx::run(
        command = cmd
        , args = args
        , windows_verbatim_args = TRUE
        , error_on_status = FALSE
        , echo = TRUE
      )
      exit_code <- result$status
    } else {
      if (on_windows) {
        message(paste0(
          "Using system() to run shell commands. Installing "
          , "'processx' with install.packages('processx') might "
          , "make this faster."
        ))
      }
      cmd <- paste0(cmd, " ", paste0(args, collapse = " "))
      exit_code <- system(cmd)
    }

    if (exit_code != 0L && isTRUE(strict)) {
        stop(paste0("Command failed with exit code: ", exit_code))
    }
    return(invisible(exit_code))
}

# try to generate Visual Studio build files
.generate_vs_makefiles <- function(cmake_args) {
  vs_versions <- c(
67
68
    "Visual Studio 17 2022"
    , "Visual Studio 16 2019"
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    , "Visual Studio 15 2017"
    , "Visual Studio 14 2015"
  )
  working_vs_version <- NULL
  for (vs_version in vs_versions) {
    message(sprintf("Trying '%s'", vs_version))
    # if the build directory is not empty, clean it
    if (file.exists("CMakeCache.txt")) {
      file.remove("CMakeCache.txt")
    }
    vs_cmake_args <- c(
      cmake_args
      , "-G"
      , shQuote(vs_version)
      , "-A"
      , "x64"
    )
    exit_code <- .run_shell_command("cmake", c(vs_cmake_args, ".."), strict = FALSE)
    if (exit_code == 0L) {
      message(sprintf("Successfully created build files for '%s'", vs_version))
      return(invisible(TRUE))
    }

  }
  return(invisible(FALSE))
94
}
James Lamb's avatar
James Lamb committed
95
96

# Move in CMakeLists.txt
97
write_succeeded <- file.copy(
98
  file.path(inst_dir, "bin", "CMakeLists.txt")
99
100
101
102
103
  , "CMakeLists.txt"
  , overwrite = TRUE
)
if (!write_succeeded) {
  stop("Copying CMakeLists.txt failed")
James Lamb's avatar
James Lamb committed
104
105
}

106
107
108
109
110
111
112
# Prepare building package
dir.create(
  build_dir
  , recursive = TRUE
  , showWarnings = FALSE
)
setwd(build_dir)
113

114
use_visual_studio <- !(use_mingw || use_msys2)
115

116
117
118
119
# If using MSVC to build, pull in the script used
# to create R.def from R.dll
if (WINDOWS && use_visual_studio) {
  write_succeeded <- file.copy(
120
    file.path(inst_dir, "make-r-def.R")
121
122
123
124
125
    , file.path(build_dir, "make-r-def.R")
    , overwrite = TRUE
  )
  if (!write_succeeded) {
    stop("Copying make-r-def.R failed")
126
  }
127
}
128

129
# Prepare installation steps
130
131
132
133
134
135
136
cmake_args <- c(
  "-D__BUILD_FOR_R=ON"
  # pass in R version, to help FindLibR find the R library
  , sprintf("-DCMAKE_R_VERSION='%s.%s'", R.Version()[["major"]], R.Version()[["minor"]])
  # ensure CMake build respects how R is configured (`R CMD config SHLIB_EXT`)
  , sprintf("-DCMAKE_SHARED_LIBRARY_SUFFIX_CXX='%s'", SHLIB_EXT)
)
137
build_cmd <- "make"
138
build_args <- c("_lightgbm", make_args_from_build_script)
139
lib_folder <- file.path(source_dir, fsep = "/")
140

141
142
143
144
145
# add in command-line arguments
# NOTE: build_r.R replaces the line below
command_line_args <- NULL
cmake_args <- c(cmake_args, command_line_args)

146
147
148
149
150
151
152
153
WINDOWS_BUILD_TOOLS <- list(
  "MinGW" = c(
    build_tool = "mingw32-make.exe"
    , makefile_generator = "MinGW Makefiles"
  )
  , "MSYS2" = c(
    build_tool = "make.exe"
    , makefile_generator = "MSYS Makefiles"
154
  )
155
)
156

157
158
159
160
161
162
163
164
165
if (use_mingw) {
  windows_toolchain <- "MinGW"
} else if (use_msys2) {
  windows_toolchain <- "MSYS2"
} else {
  # Rtools 4.0 moved from MinGW to MSYS toolchain. If user tries
  # Visual Studio install but that fails, fall back to the toolchain
  # supported in Rtools
  if (R_ver >= 4.0) {
166
167
    windows_toolchain <- "MSYS2"
  } else {
168
    windows_toolchain <- "MinGW"
169
  }
170
171
172
}
windows_build_tool <- WINDOWS_BUILD_TOOLS[[windows_toolchain]][["build_tool"]]
windows_makefile_generator <- WINDOWS_BUILD_TOOLS[[windows_toolchain]][["makefile_generator"]]
173

174
175
176
if (use_gpu) {
  cmake_args <- c(cmake_args, "-DUSE_GPU=ON")
}
177

178
179
180
# the checks below might already run `cmake -G`. If they do, set this flag
# to TRUE to avoid re-running it later
makefiles_already_generated <- FALSE
181

182
183
184
185
186
187
188
189
# Check if Windows installation (for gcc vs Visual Studio)
if (WINDOWS) {
  if (!use_visual_studio) {
    message(sprintf("Trying to build with %s", windows_toolchain))
    # Must build twice for Windows due sh.exe in Rtools
    cmake_args <- c(cmake_args, "-G", shQuote(windows_makefile_generator))
    .run_shell_command("cmake", c(cmake_args, ".."), strict = FALSE)
    build_cmd <- windows_build_tool
190
    build_args <- c("_lightgbm", make_args_from_build_script)
191
192
193
194
  } else {
    visual_studio_succeeded <- .generate_vs_makefiles(cmake_args)
    if (!isTRUE(visual_studio_succeeded)) {
      warning(sprintf("Building with Visual Studio failed. Attempting with %s", windows_toolchain))
195
      # Must build twice for Windows due sh.exe in Rtools
196
      cmake_args <- c(cmake_args, "-G", shQuote(windows_makefile_generator))
197
      .run_shell_command("cmake", c(cmake_args, ".."), strict = FALSE)
198
      build_cmd <- windows_build_tool
199
      build_args <- c("_lightgbm", make_args_from_build_script)
Laurae's avatar
Laurae committed
200
    } else {
201
202
203
      build_cmd <- "cmake"
      build_args <- c("--build", ".", "--target", "_lightgbm", "--config", "Release")
      lib_folder <- file.path(source_dir, "Release", fsep = "/")
204
      makefiles_already_generated <- TRUE
205
    }
206
  }
207
} else {
208
    .run_shell_command("cmake", c(cmake_args, ".."))
209
210
    makefiles_already_generated <- TRUE
}
211

212
213
214
215
# generate build files
if (!makefiles_already_generated) {
  .run_shell_command("cmake", c(cmake_args, ".."))
}
216

217
# build the library
218
message(paste0("Building lightgbm", SHLIB_EXT))
219
.run_shell_command(build_cmd, build_args)
220
src <- file.path(lib_folder, paste0("lightgbm", SHLIB_EXT), fsep = "/")
221

222
223
224
225
# Packages with install.libs.R need to copy some artifacts into the
# expected places in the package structure.
# see https://cran.r-project.org/doc/manuals/r-devel/R-exts.html#Package-subdirectories,
# especially the paragraph on install.libs.R
226
dest <- file.path(R_PACKAGE_DIR, paste0("libs", R_ARCH), fsep = "/")
227
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
Laurae's avatar
Laurae committed
228
if (file.exists(src)) {
229
  message(paste0("Found library file: ", src, " to move to ", dest))
230
  file.copy(src, dest, overwrite = TRUE)
231
232
233
234
235
236

  symbols_file <- file.path(source_dir, "symbols.rds")
  if (file.exists(symbols_file)) {
    file.copy(symbols_file, dest, overwrite = TRUE)
  }

237
} else {
238
  stop(paste0("Cannot find lightgbm", SHLIB_EXT))
239
}
240
241
242

# clean up the "build" directory
if (dir.exists(build_dir)) {
243
  message("Removing 'build/' directory")
244
  unlink(
245
    x = build_dir
246
247
248
249
    , recursive = TRUE
    , force = TRUE
  )
}