install.libs.R 7.87 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_int_UUID <- .Internal(internalsID())
19
R_ver <- as.double(R.Version()$major) + as.double(R.Version()$minor) / 10.0
20

21
if (!(R_int_UUID == "0310d4b8-ccb1-4bb8-ba94-d36a55f60262"
22
    || R_int_UUID == "2fdf6c18-697a-4ba7-b8ef-11c0d92f1327")) {
23
24
25
  warning("Warning: unmatched R_INTERNALS_UUID, may not run normally.")
}

26
27
28
29
30
# 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 = "/")

31
32
33
34
35
36
37
38
39
# 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({
40
        require("processx")  # nolint: undesirable_function
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
67
68
69
70
71
72
      })
    })
    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(
73
74
    "Visual Studio 17 2022"
    , "Visual Studio 16 2019"
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
    , "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))
100
}
James Lamb's avatar
James Lamb committed
101
102

# Move in CMakeLists.txt
103
write_succeeded <- file.copy(
104
  file.path(inst_dir, "bin", "CMakeLists.txt")
105
106
107
108
109
  , "CMakeLists.txt"
  , overwrite = TRUE
)
if (!write_succeeded) {
  stop("Copying CMakeLists.txt failed")
James Lamb's avatar
James Lamb committed
110
111
}

112
113
114
115
116
117
118
# Prepare building package
dir.create(
  build_dir
  , recursive = TRUE
  , showWarnings = FALSE
)
setwd(build_dir)
119

120
use_visual_studio <- !(use_mingw || use_msys2)
121

122
123
124
125
# 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(
126
    file.path(inst_dir, "make-r-def.R")
127
128
129
130
131
    , file.path(build_dir, "make-r-def.R")
    , overwrite = TRUE
  )
  if (!write_succeeded) {
    stop("Copying make-r-def.R failed")
132
  }
133
}
134

135
136
137
# Prepare installation steps
cmake_args <- NULL
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
177
if (use_gpu) {
  cmake_args <- c(cmake_args, "-DUSE_GPU=ON")
}
cmake_args <- c(cmake_args, "-D__BUILD_FOR_R=ON")
Guolin Ke's avatar
Guolin Ke committed
178

179
180
181
182
183
184
185
# Pass in R version, used to help find R executable for linking
R_version_string <- paste(
  R.Version()[["major"]]
  , R.Version()[["minor"]]
  , sep = "."
)
r_version_arg <- sprintf("-DCMAKE_R_VERSION='%s'", R_version_string)
186
187
188
# ensure CMake build respects how R is configured (`R CMD config SHLIB_EXT`)
shlib_ext_arg <- sprintf("-DCMAKE_SHARED_LIBRARY_SUFFIX_CXX='%s'", SHLIB_EXT)
cmake_args <- c(cmake_args, r_version_arg, shlib_ext_arg)
189

190
191
192
# 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
193

194
195
196
197
198
199
200
201
# 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
202
    build_args <- c("_lightgbm", make_args_from_build_script)
203
204
205
206
  } 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))
207
      # Must build twice for Windows due sh.exe in Rtools
208
      cmake_args <- c(cmake_args, "-G", shQuote(windows_makefile_generator))
209
      .run_shell_command("cmake", c(cmake_args, ".."), strict = FALSE)
210
      build_cmd <- windows_build_tool
211
      build_args <- c("_lightgbm", make_args_from_build_script)
Laurae's avatar
Laurae committed
212
    } else {
213
214
215
      build_cmd <- "cmake"
      build_args <- c("--build", ".", "--target", "_lightgbm", "--config", "Release")
      lib_folder <- file.path(source_dir, "Release", fsep = "/")
216
      makefiles_already_generated <- TRUE
217
    }
218
  }
219
} else {
220
    .run_shell_command("cmake", c(cmake_args, ".."))
221
222
    makefiles_already_generated <- TRUE
}
223

224
225
226
227
# generate build files
if (!makefiles_already_generated) {
  .run_shell_command("cmake", c(cmake_args, ".."))
}
228

229
230
231
232
233
# build the library
message("Building lib_lightgbm")
.run_shell_command(build_cmd, build_args)
src <- file.path(lib_folder, paste0("lib_lightgbm", SHLIB_EXT), fsep = "/")

234
235
236
237
# 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
238
dest <- file.path(R_PACKAGE_DIR, paste0("libs", R_ARCH), fsep = "/")
239
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
Laurae's avatar
Laurae committed
240
if (file.exists(src)) {
241
  message(paste0("Found library file: ", src, " to move to ", dest))
242
  file.copy(src, dest, overwrite = TRUE)
243
244
245
246
247
248

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

249
} else {
Laurae's avatar
Laurae committed
250
  stop(paste0("Cannot find lib_lightgbm", SHLIB_EXT))
251
}
252
253
254

# clean up the "build" directory
if (dir.exists(build_dir)) {
255
  message("Removing 'build/' directory")
256
  unlink(
257
    x = build_dir
258
259
260
261
    , recursive = TRUE
    , force = TRUE
  )
}