build_r.R 7 KB
Newer Older
1
# For macOS users who have decided to use gcc
2
# (replace 8 with version of gcc installed on your machine)
James Lamb's avatar
James Lamb committed
3
4
5
6
7
# NOTE: your gcc / g++ from Homebrew is probably in /usr/local/bin
#export CXX=/usr/local/bin/g++-8 CC=/usr/local/bin/gcc-8
# Sys.setenv("CXX" = "/usr/local/bin/g++-8")
# Sys.setenv("CC" = "/usr/local/bin/gcc-8")

8
9
args <- commandArgs(trailingOnly = TRUE)
INSTALL_AFTER_BUILD <- !("--skip-install" %in% args)
10
11
TEMP_R_DIR <- file.path(getwd(), "lightgbm_r")
TEMP_SOURCE_DIR <- file.path(TEMP_R_DIR, "src")
12

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
USING_GPU <- "--use-gpu" %in% args
USING_MINGW <- "--use-mingw" %in% args
USING_MSYS2 <- "--use-msys2" %in% args

recognized_args <- c(
  "--skip-install"
  , "--use-gpu"
  , "--use-mingw"
  , "--use-msys2"
)
unrecognized_args <- setdiff(args, recognized_args)
if (length(unrecognized_args) > 0L) {
  msg <- paste0(
    "Unrecognized arguments: "
    , paste0(unrecognized_args, collapse = ", ")
  )
  stop(msg)
}

# [description] Replace statements in install.libs.R code based on
#               command-line flags
.replace_flag <- function(variable_name, value, content) {
  out <- gsub(
    pattern = paste0(variable_name, " <-.*")
    , replacement = paste0(variable_name, " <- ", as.character(value))
    , x = content
  )
  return(out)
}

43
44
45
install_libs_content <- readLines(
  file.path("R-package", "src", "install.libs.R")
)
46
47
48
install_libs_content <- .replace_flag("use_gpu", USING_GPU, install_libs_content)
install_libs_content <- .replace_flag("use_mingw", USING_MINGW, install_libs_content)
install_libs_content <- .replace_flag("use_msys2", USING_MSYS2, install_libs_content)
49

James Lamb's avatar
James Lamb committed
50
51
# R returns FALSE (not a non-zero exit code) if a file copy operation
# breaks. Let's fix that
52
.handle_result <- function(res) {
53
  if (!all(res)) {
54
55
    stop("Copying files failed!")
  }
56
  return(invisible(NULL))
James Lamb's avatar
James Lamb committed
57
58
}

59
# system() will not raise an R exception if the process called
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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({
        require("processx")  # nolint
      })
    })
    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)) {
93
94
        stop(paste0("Command failed with exit code: ", exit_code))
    }
95
    return(invisible(exit_code))
96
97
}

James Lamb's avatar
James Lamb committed
98
# Make a new temporary folder to work in
99
100
unlink(x = TEMP_R_DIR, recursive = TRUE)
dir.create(TEMP_R_DIR)
James Lamb's avatar
James Lamb committed
101
102

# copy in the relevant files
103
104
result <- file.copy(
  from = "R-package/./"
105
  , to = sprintf("%s/", TEMP_R_DIR)
106
107
108
  , recursive = TRUE
  , overwrite = TRUE
)
James Lamb's avatar
James Lamb committed
109
110
.handle_result(result)

111
112
113
114
115
116
# overwrite src/install.libs.R with new content based on command-line flags
writeLines(
  text = install_libs_content
  , con = file.path(TEMP_SOURCE_DIR, "install.libs.R")
)

117
118
119
120
121
122
123
124
125
126
127
128
129
130
# Add blank Makevars files
result <- file.copy(
  from = file.path(TEMP_R_DIR, "inst", "Makevars")
  , to = file.path(TEMP_SOURCE_DIR, "Makevars")
  , overwrite = TRUE
)
.handle_result(result)
result <- file.copy(
  from = file.path(TEMP_R_DIR, "inst", "Makevars.win")
  , to = file.path(TEMP_SOURCE_DIR, "Makevars.win")
  , overwrite = TRUE
)
.handle_result(result)

131
132
result <- file.copy(
  from = "include/"
133
  , to =  sprintf("%s/", TEMP_SOURCE_DIR)
134
135
136
  , recursive = TRUE
  , overwrite = TRUE
)
James Lamb's avatar
James Lamb committed
137
138
.handle_result(result)

139
140
result <- file.copy(
  from = "src/"
141
  , to = sprintf("%s/", TEMP_SOURCE_DIR)
142
143
144
  , recursive = TRUE
  , overwrite = TRUE
)
Gao Tao's avatar
Gao Tao committed
145
146
.handle_result(result)

147
148
149
150
151
152
153
154
155
156
157
# compute/ is a submodule with boost, only needed if
# building the R package with GPU support
if (USING_GPU) {
  result <- file.copy(
    from = "compute/"
    , to = sprintf("%s/", TEMP_SOURCE_DIR)
    , recursive = TRUE
    , overwrite = TRUE
  )
  .handle_result(result)
}
James Lamb's avatar
James Lamb committed
158

159
160
result <- file.copy(
  from = "CMakeLists.txt"
161
  , to = file.path(TEMP_R_DIR, "inst", "bin/")
162
163
  , overwrite = TRUE
)
James Lamb's avatar
James Lamb committed
164
165
.handle_result(result)

166
167
# remove CRAN-specific files
result <- file.remove(
168
169
  file.path(TEMP_R_DIR, "cleanup")
  , file.path(TEMP_R_DIR, "configure")
170
171
172
173
174
175
176
  , file.path(TEMP_R_DIR, "configure.ac")
  , file.path(TEMP_R_DIR, "configure.win")
  , file.path(TEMP_SOURCE_DIR, "Makevars.in")
  , file.path(TEMP_SOURCE_DIR, "Makevars.win.in")
)
.handle_result(result)

177
178
179
180
181
182
183
184
185
186
187
#------------#
# submodules #
#------------#
result <- file.copy(
  from = "external_libs/"
  , to = sprintf("%s/", TEMP_SOURCE_DIR)
  , recursive = TRUE
  , overwrite = TRUE
)
.handle_result(result)

188
189
190
191
192
193
194
195
196
197
198
199
200
201
# copy files into the place CMake expects
for (src_file in c("lightgbm_R.cpp", "lightgbm_R.h", "R_object_helper.h")) {
  result <- file.copy(
    from = file.path(TEMP_SOURCE_DIR, src_file)
    , to = file.path(TEMP_SOURCE_DIR, "src", src_file)
    , overwrite = TRUE
  )
  .handle_result(result)
  result <- file.remove(
    file.path(TEMP_SOURCE_DIR, src_file)
  )
  .handle_result(result)
}

202
203
204
205
206
207
208
result <- file.copy(
  from = file.path("R-package", "inst", "make-r-def.R")
  , to = file.path(TEMP_R_DIR, "inst", "bin/")
  , overwrite = TRUE
)
.handle_result(result)

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# R packages cannot have versions like 3.0.0rc1, but
# 3.0.0-1 is acceptable
LGB_VERSION <- readLines("VERSION.txt")[1L]
LGB_VERSION <- gsub(
  pattern = "rc"
  , replacement = "-"
  , x = LGB_VERSION
)

# DESCRIPTION has placeholders for version
# and date so it doesn't have to be updated manually
DESCRIPTION_FILE <- file.path(TEMP_R_DIR, "DESCRIPTION")
description_contents <- readLines(DESCRIPTION_FILE)
description_contents <- gsub(
  pattern = "~~VERSION~~"
  , replacement = LGB_VERSION
  , x = description_contents
)
description_contents <- gsub(
  pattern = "~~DATE~~"
  , replacement = as.character(Sys.Date())
  , x = description_contents
)
writeLines(description_contents, DESCRIPTION_FILE)

James Lamb's avatar
James Lamb committed
234
235
236
# NOTE: --keep-empty-dirs is necessary to keep the deep paths expected
#       by CMake while also meeting the CRAN req to create object files
#       on demand
237
.run_shell_command("R", c("CMD", "build", TEMP_R_DIR, "--keep-empty-dirs"))
James Lamb's avatar
James Lamb committed
238
239
240

# Install the package
version <- gsub(
241
242
243
  "Version: ",
  "",
  grep(
244
    "Version: "
245
    , readLines(con = file.path(TEMP_R_DIR, "DESCRIPTION"))
246
    , value = TRUE
247
  )
James Lamb's avatar
James Lamb committed
248
249
250
)
tarball <- file.path(getwd(), sprintf("lightgbm_%s.tar.gz", version))

251
252
install_cmd <- "R"
install_args <- c("CMD", "INSTALL", "--no-multiarch", "--with-keep.source", tarball)
253
if (INSTALL_AFTER_BUILD) {
254
  .run_shell_command(install_cmd, install_args)
255
} else {
256
  cmd <- paste0(install_cmd, " ", paste0(install_args, collapse = " "))
257
258
  print(sprintf("Skipping installation. Install the package with command '%s'", cmd))
}