make-r-def.R 2.91 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# [description]
#     Create a definition file (.def) from a .dll file, using objdump.
#
# [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({
27
        require("processx")  # nolint: undesirable_function
28
29
30
31
32
33
34
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
67
68
69
70
71
72
73
      })
    })
    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."
        ))
        # shQuote() is necessary here since one of the arguments
        # is a file-path to R.dll, which may have spaces. processx
        # does such quoting but system2() does not
        exit_code <- system2(
            command = command
            , args = shoQuote(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)
invisible(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(
74
        pattern = "[Ordinal/Name Pointer] Table"  # nolint: non_portable_path
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
        , 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))