Environment Management in R with VerR
VerR.Rmd
In this vignette we will use VerR to compare two different version of
an R package. We will use the dummyPkg
package, which is a
dummy package that was created for this purpose. The package has two
versions, 0.1.1
and 0.1.2
, and we will compare
them using VerR. This package is available through GitHub, via
leopoldguyot/dummyPkg
.
1. Setup your environments
Create a first environment
To start using VerR, we need to create a new environment. To do so,
we will use the envCreate
function. This function will
create a new environment in the current working directory, and will
populate it with the specified packages. Note that a new environment is
just a new directory within .envs/
that contains a
renv
project.
To specify the packages that we want to install in the environment, we have multiple options:
-
pkg
: Install the latest version of the package from CRAN. -
pkg@version
: Install a specific version of the package from CRAN. -
username/repo
: Install the package from GitHub.
You can also specify the commit withusername/repo@commitId
. -
bioc::pkg
: Install the package from Bioconductor.
Note that to install a specific version of a Bioconductor package, it is recommended to install it via GitHub.
In our case we will use the GitHub option to install the
dummyPkg
package using his sleep1
branch.
library(VerR)
envCreate("sleep1Env", "leopoldguyot/dummyPkg@sleep1")
#> - The project is out-of-sync -- use `renv::status()` for details.
#> # Downloading packages -------------------------------------------------------
#> - Downloading dummyPkg from GitHub ... OK [1 Kb in 0.41s]
#> Successfully downloaded 1 package in 0.47 seconds.
#>
#> The following package(s) will be installed:
#> - dummyPkg [leopoldguyot/dummyPkg@sleep1]
#> These packages will be installed into "~/work/VerR/VerR/vignettes/.envs/sleep1Env/renv/library/linux-ubuntu-noble/R-4.5/x86_64-pc-linux-gnu".
#>
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ... OK [built from source and cached in 1.0s]
#> Successfully installed 1 package in 1 second.
#> Installed packages in environment: /home/runner/work/VerR/VerR/vignettes/.envs/sleep1Env
#> - The project is out-of-sync -- use `renv::status()` for details.
#> The following package(s) will be updated in the lockfile:
#>
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg [* -> leopoldguyot/dummyPkg@sleep1]
#>
#> # RSPM -----------------------------------------------------------------------
#> - renv [* -> 1.1.5]
#>
#> The version of R recorded in the lockfile will be updated:
#> - R [* -> 4.5.1]
#>
#> - Lockfile written to "~/work/VerR/VerR/vignettes/.envs/sleep1Env/renv.lock".
#> Updated lockfile in environment: .envs/sleep1Env
Once the environment is created, we can verify that it was correctly
initialized. To do so, we can use the envList
function. It
will list all the environments present in the .envs/
directory.
envList()
#> [1] "sleep1Env"
To obtain more details about this environment you can also use the
envInfo
function which will display extra information, like
the installed packages in the environment.
envInfo("sleep1Env", pkgInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#> - dummyPkg (Version: 0.1.1, Source: GitHub)
#> - renv (Version: 1.1.5, Source: Repository)
Extract the environment’s lockFile
To save your environment configuration you can export his lockFile to
a target location, this will allow to restore this environment later.
See ?renv::snapshot
for more information on lockFiles. To
export this lockFile you can use the lockFileExport
function. By default it will export the lockFiles of all available
environments and store these files within the
exportedLockFiles/
directory inside your current working
directory. You can also choose to select specific environments and
target paths for the lockFiles.
lockFileExport()
#> Exported lockfile from sleep1Env to exportedLockFiles/sleep1Env_lockFile.lock
The lockFile is now available in the exportedLockFiles/
directory:
list.files("exportedLockFiles")
#> [1] "sleep1Env_lockFile.lock"
Create an environment from a lockFile
We will now use the exported lockFile to create a new environment.
This environment will therefore be a clone of the sleep1Env
environment. To do so, we will again use the envCreate
function, but this time with the lockFile
argument.
envCreate("sleep2Env", lockFile = "exportedLockFiles/sleep1Env_lockFile.lock")
#> - None of the packages recorded in the lockfile are currently installed.
#> The following package(s) will be updated:
#>
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg [* -> leopoldguyot/dummyPkg@sleep1]
#>
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ... OK [linked from cache]
#> Restored environment from lockfile: exportedLockFiles/sleep1Env_lockFile.lock
#> - The lockfile is already up to date.
#> Updated lockfile in environment: .envs/sleep2Env
Again we can check, that the environment was correctly created.
envInfo("sleep2Env", pkgInfo = TRUE)
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#> - dummyPkg (Version: 0.1.1, Source: GitHub)
#> - renv (Version: 1.1.5, Source: Repository)
Update your environment
Currently we have two environments, sleep1Env
and
sleep2Env
, that contain the same version of the
dummyPkg
package. We will now update the
sleep2Env
environment to use another version of the
dummyPkg
package. To do so, we will use the
envInstallPackage
function.
envInstallPackage(
package = "leopoldguyot/dummyPkg@sleep2",
envName = "sleep2Env"
)
#> # Downloading packages -------------------------------------------------------
#> - Downloading dummyPkg from GitHub ... OK [1 Kb in 0.38s]
#> Successfully downloaded 1 package in 0.44 seconds.
#>
#> The following package(s) will be installed:
#> - dummyPkg [leopoldguyot/dummyPkg@sleep2]
#> These packages will be installed into "~/work/VerR/VerR/vignettes/.envs/sleep2Env/renv/library/linux-ubuntu-noble/R-4.5/x86_64-pc-linux-gnu".
#>
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ... OK [built from source and cached in 1.0s]
#> Successfully installed 1 package in 1 second.
#> - The project is out-of-sync -- use `renv::status()` for details.
#> The following package(s) will be updated in the lockfile:
#>
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg [ver: 0.1.1 -> 0.1.2; ref: sleep1 -> sleep2; sha: 2916e807 -> 08efd32f]
#>
#> - Lockfile written to "~/work/VerR/VerR/vignettes/.envs/sleep2Env/renv.lock".
#> Updated lockfile in environment: .envs/sleep2Env
To be sure that the package was correctly updated, we can check the installed packages for both environments.
envInfo(pkgInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#> - dummyPkg (Version: 0.1.1, Source: GitHub)
#> - renv (Version: 1.1.5, Source: Repository)
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#> - dummyPkg (Version: 0.1.2, Source: GitHub)
#> - renv (Version: 1.1.5, Source: Repository)
As expected, the sleep2Env
environment now contains the
dummyPkg
package version 0.1.2
while the
sleep1Env
environment stills contain the version
0.1.1
.
2. Run expression within the environments
Now that we have two environments with different versions of the
dummyPkg
package, we can compare them using the
runInEnv
function. This function allows us to run an
expression in a specific environment.
We will compare the sleepy
function, which is a simple
function that prints a message and sleeps for a certain amount of time.
In the sleep1Env
environment, the function sleeps for 1
second, while in the sleep2Env
environment, it sleeps for 2
seconds.
# version 0.1.1, x = 1
# version 0.1.2, x = 2
sleepy <- function() {
message("Sleeping for x seconds")
Sys.sleep(x)
}
runInEnv(
expr = {
library(dummyPkg)
sleepy()
return(packageVersion("dummyPkg"))
},
envName = envList()
)
#> Running expression in environment: sleep1Env
#> Warning in normalizePath(result_file, winslash = "/"):
#> path[1]="/tmp/Rtmp74nUvJ/result.rds": No such file or directory
#> Warning in normalizePath(result_file, winslash = "/"):
#> path[1]="/tmp/Rtmp74nUvJ/result.rds": No such file or directory
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> $sleep1Env
#> [1] '0.1.1'
#>
#> $sleep2Env
#> [1] '0.1.2'
The first thing to note is that any text output generated during the
evaluation of the expression is displayed in the console. In this case,
for the sleep1Env
environment, the function prints
Sleeping for 1 seconds
, while for the
sleep2Env
environment, it prints
Sleeping for 2 seconds
.
The runInEnv
function when called on multiple
environments returns a named list, where the names correspond to the
environment names, and the values are the return values of the evaluated
expression. As expected, the sleep1Env
environment returns
the version 0.1.1
, while the sleep2Env
environment returns the version 0.1.2
.
Run expression in parallel
noPar <- system.time(runInEnv(
expr = {
library(dummyPkg)
sleepy()
return(packageVersion("dummyPkg"))
},
envName = envList(),
parallel = FALSE
))
#> Running expression in environment: sleep1Env
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
par <- system.time(runInEnv(
expr = {
library(dummyPkg)
sleepy()
return(packageVersion("dummyPkg"))
},
envName = envList(),
parallel = TRUE
))
noPar
#> user system elapsed
#> 0.928 0.777 4.551
par
#> user system elapsed
#> 0.008 0.006 4.415
4. Manipulate file through the environments
We will now explore how to send files to the environments, and also how to remove them. For this example we will use an R script that contains code to measure execution time of the sleepy function. Here is the code contained in the script:
library("dummyPkg")
executionTimes <- replicate(3, {
startTime <- Sys.time()
sleepy()
endTime <- Sys.time()
as.numeric(difftime(endTime, startTime, units = "secs"))
})
executionTimes
We would want to run this script in both environments to compare the
execution times. First, we need to copy this script in each environment.
We use the envCopyTo
function to copy the script to the
environments.
scriptPath <- system.file("scripts",
"sleepyBenchmark.R",
package = "VerR"
)
envCopyTo(
sourcePath = scriptPath,
envName = envList(),
targetPath = "scripts/sleepyBenchmark.R"
)
#> Copied /home/runner/work/_temp/Library/VerR/scripts/sleepyBenchmark.R to .envs/sleep1Env/scripts/sleepyBenchmark.R
#> Copied /home/runner/work/_temp/Library/VerR/scripts/sleepyBenchmark.R to .envs/sleep2Env/scripts/sleepyBenchmark.R
To verify that the copy was successful, we can use the
fileInfo
parameter from the envInfo
function.
This will display a file tree of each environment:
envInfo(fileInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> File Tree:
#> .envs/sleep1Env
#> └── scripts
#> └── sleepyBenchmark.R
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> File Tree:
#> .envs/sleep2Env
#> └── scripts
#> └── sleepyBenchmark.R
5. Run a script in the environments
We will now run the script in both environments using the
runInEnv
function. Using source()
to run the
script will return the last value of the script. In this case, it will
return the execution times of the sleepy
function.
result <- runInEnv(
expr = {
result <- source("scripts/sleepyBenchmark.R")
result$value
}
)
#> Running expression in environment: sleep1Env
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
Lets now check the results:
result
#> $sleep1Env
#> [1] 1.001483 1.001204 1.001260
#>
#> $sleep2Env
#> [1] 2.002466 2.002234 2.002316
lapply(result, mean)
#> $sleep1Env
#> [1] 1.001316
#>
#> $sleep2Env
#> [1] 2.002339
As expected, the average execution time for the
sleep1Env
environment is approximately 1 second, while for
the sleep2Env
environment, it is approximately 2 seconds.
This confirms that the sleepy function behaves as intended in both
versions of the dummyPkg
package.
6. Benchmark in environments
VerR
provides an easy way to benchmark the execution
time of an expression in multiple environments. The
benchInEnv
function takes an expression and measures the
execution time of this expression in each environment. The
setup
parameter allow to evaluate R code before the
evaluation of the benchmarked expression, this is particularly useful to
load packages or to retrieve data. You can also provide the
rep
argument to specify the number of replications to
perform. The function will return a named list with the execution times
for each environment.
res <- benchInEnv(
expr = {
sleepy()
},
envName = envList(),
rep = 3,
setup = {
library(dummyPkg)
}
)
#>
#> Benchmarking expression in environment: sleep1Env
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#>
#> Benchmarking expression in environment: sleep2Env
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
boxplot(time ~ envName, data = res)