install.libs.R 7.7 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
186
# 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)
cmake_args <- c(cmake_args, r_version_arg)
187

188
189
190
# 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
191

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

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

227
228
229
230
231
# 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 = "/")

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

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

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

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