From the R and RStudio in a Secured Environment on CentOS 7 and Improving the secured computer-based testing environment for R on CentOS 7 using Rprofile and Renviron posts, there was a need to override commands that required internet access in an internetless environment. One huge part of the puzzle that wasn’t quite solved was the ability to disable the install.packages()
command within RStudio. In particular, the outcome from running install.packages()
looked like:
In reality, the desired outcome was to provide a custom message:
This resulted in an issue (4498) being filed that asked for a specific point during RStudio’s startup of R. Turns out that Jonathan McPherson already added it ~4+ years ago in commit fc86699#diff-9825eb7f38c890c5ed3121779b9f993e
. He provided a sample use case of it in an earlier ticket requesting the feature. Therefore, the hook will work with RStudio 0.99.* and later versions.
With this being said, the post is broken down into four sections that aim to cover RStudio’s hook process, overriding a base function, establishing a hook, and setting up hooks to run automatically in startup with .Rprofile
.
RStudio Hook
The injection point uses R’s setHook()
function to register a value for the session initialization in RStudio. The example below can be found on RStudio’s rstudioapi
R package website.
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
message("Welcome to RStudio! ", rstudioapi::getVersion())
action = "append") },
Overriding a base command
Next, we need to be able to override or replace an existing function in an R package. Functions are able to be overriden by modifying the R package environment they reside in. Overwriting a function within a package requires unlocking the environment (“bind”), modifying the functions, and then relocking the environment. The environment write status can be modified with base::unlockBinding()
and base::lockBinding()
while the assignment is handled by base::assign()
.
Note: The approach is only for private use. CRAN will not permit any package modifying content in this way.
## Override an R function in a package.
= function(name, pkgname, value) {
shim_pkg_func
## Ensure package is loaded.
## If the package is not on the search path, we cannot modify it!
= paste0("package:", pkgname)
pkg_env_name if (!pkg_env_name %in% search()) {
# Load the library
library(pkgname, character.only = TRUE)
}
# Retrieve package environment
= as.environment(pkg_env_name)
env
# Unlock environment where the function/variable is found.
::unlockBinding(name, env)
base
# Make the assignment into the environment with the new value
::assign(name, value, envir = env)
base
# Close the environment
::lockBinding(name, env)
base }
Establishing a Hook
The next part is to establish the hook regardless of whether R was being used in RStudio, RGUI, or in shell. Differentiating between applications is key. One way to do so is to check whether the RSTUDIO
environment variable is set. Environment variable can be searched for using Sys.getenv()
function.
## Provide an alternative install.packages(...) routine
= function(...) { message("Done!") }
install_packages_shim
## Setup a shim to override the install.packages() function
if(Sys.getenv("RSTUDIO") == "1") {
## Establishes a delayed registration of the shim after
## RStudio's startup procedure occurs. This procedure overwrites settings
## sometimes established in .Rprofile. Thus, we need a delayed
## component to ensure we're preventing
## install.packages() from being delayed
## c.f. https://github.com/rstudio/rstudio/issues/1579#issuecomment-495706255
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
shim_pkg_func("install.packages", "utils", install_packages_shim)
action = "append")
}, else {
} ## Establish the shim for _R_ terminal sessions or R GUI.
## Attempting to establish the hook with RStudio would result
## in its initialization procedure overwrite this shim.
shim_pkg_func("install.packages", "utils", install_packages_shim)
}
Full Code
Combining each portion of the above code inside the .Rprofile
gives the ideal startup override. That said, the code should be placed inside the .First()
function, which runs when R is first loaded. For the full context, consider checking out the .Rprofile
on GitHub.
To create an .Rprofile
file, use:
touch ~/.Rprofile
vi ~/.Rprofile
Full code discussed previously with the .First()
modification to be placed inside ~/.Rprofile
.
= function() {
.First
## Disable install.packages ----
## Override an R function in a package.
= function(name, pkgname, value) {
shim_pkg_func
## Ensure package is loaded.
## If the package is not on the search path, we cannot modify it!
= paste0("package:", pkgname)
pkg_env_name if (!pkg_env_name %in% search()) {
# Load the library
library(pkgname, character.only = TRUE)
}
# Retrieve package environment
= as.environment(pkg_env_name)
env #pkg_ns_env = asNamespace(pkgname)
# Unlock environment where the function/variable is found.
::unlockBinding(name, env)
base
# Make the assignment into the environment with the new value
::assign(name, value, envir = env)
base
# Close the environment
::lockBinding(name, env)
base
}
## Provide an alternative install.packages(...) routine
= function(...) { message("Done!") }
install_packages_shim
## Setup a shim to override the install.packages() function
if(Sys.getenv("RSTUDIO") == "1") {
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
shim_pkg_func("install.packages", "utils", install_packages_shim)
action = "append")
}, else {
} ## Establish the shim for _R_ terminal sessions or R GUI.
shim_pkg_func("install.packages", "utils", install_packages_shim)
} }
Fin
In conclusion, the hooks RStudio setups can be overriden with just a bit more code. Hopefully, this helps out either with auto-launching content, setting up new pranks, or just trying to avoid internet-facing functions from triggering.