Actual question
Seems like devtools::test() does not make sure that package dependencies as stated in a package's DESCRIPTION file are loaded prior to running the unit tests. How can I change that?
Details
I'm writing a package (B) that imports another one of my packages (A).
When I try to run my unit tests via devtools::test(), or, to be more precise via the shortcut SHFT + CRTL + T in RStudio, certain tests fail as the imported package seems to be disregarded/not loaded and thus a certain function (isPackageInstalled) can't be found.
Trying to load the imported package A manually before running devtools::test() didn't help either. I guess that's due to the fact that devtools (or testthat) "simulates" a fresh workspace state? Running the unit tests "one by one" works just fine after manually loading package A beforehand, though.
I thought that devtools would look up package dependencies in the DESCRIPTION file of B and thus load them as would be the case when running require("B"), but apparently not.
Here's my DESCRIPTION file:
Package: B
Type: Package
Title: What the package does (short line)
Version: 0.1.0.1
Date: 2014-08-05
Author: Who wrote it
Maintainer: Who to complain to <yourfault#somewhere.net>
Description: More about what it does (maybe more than one line)
License: What license is it under?
Imports: A
Here's the code I ran:
devtools::load_all() # or SHFT + CTRL + L in RStudio
devtools::test() # or SHFT + CTRL + T in RStudio
That's what RStudio's build pane gave me:
==> devtools::test()
Loading required package: testthat
Testing B
Loading B
Creating a new generic function for 'signalCondition' in package 'B'
package : 1
package : ......
1. Error: getPackageDescription ------------------------------------------------
could not find function "isPackageInstalled"
1: expect_is(res <- getPackageDescription(), expected) at test-getPackageDescription.r:13
2: expect_that(object, is_a(class), info, label)
3: condition(object)
4: paste0(class(x), collapse = ", ")
5: getPackageDescription()
6: getPackageDescription() at Q:\home\wsp\rapp2\B/R/getPackageDescription.r:37
7: getPackageDescription(from = from, fields = fields, drop = drop, encoding = encoding,
...) at Q:\home\wsp\rapp2\B/R/getPackageDescription.r:154
8: getPackageDescription(from = from, fields = fields, drop = drop, encoding = encoding,
...) at Q:\home\wsp\rapp2\B/R/getPackageDescription.r:37
Am I missing something here?
Screenshot of build tools dialogue:
The usual approach would be to use roxygen2 to automatically generate your NAMESPACE file from special comments in your source code, but maintain your DESCRIPTION file manually. There's no special stuff that I'm aware of to keep them in sync, but R CMD CHECK will tell you if there's something missing/extra in your DESCRIPTION.
Related
I'm building a package that imports {sf}, and more specifically I use st_length() in one of my functions.
I initially added only {sf} to my package "Imports", but when I checked it I got a few {lwgeom} related errors:
Running examples in 'gtfstools-Ex.R' failed
The error most likely occurred in:
> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: get_trip_speed
> ### Title: Get trip speed
> ### Aliases: get_trip_speed
>
> ### ** Examples
>
> data_path <- system.file("extdata/spo_gtfs.zip", package = "gtfstools")
>
> gtfs <- read_gtfs(data_path)
>
> trip_speed <- get_trip_speed(gtfs)
Error in sf::st_length(trips_geometries) :
package lwgeom required, please install it first
This error happens when the examples are running, but some similar errors happen with the tests.
Then I added {lwgeom} to Imports. The check runs fine, but in the end I get a note: NOTE: Namespaces in Imports field not imported from: 'lwgeom'
What's the best practice when dealing with cases like this? Should I just keep track of this note and send it as a comment to CRAN during the package submission process?
You can consider adding the {lwgeom} package in Suggests field of your package DESCRIPTION file. It should do the trick.
The Suggests != Depends article by Dirk Eddelbuettel refers to a relevant bit of Writing R Extensions (WRE) that might be useful to this case.
Section 1.1.3.1 (suggested packages) reads (as of 2021-03-12):
Note that someone wanting to run the examples/tests/vignettes may not have a suggested package available (and it may not even be possible to install it for that platform). The recommendation used to be to make their use conditional via if(require("pkgname")): this is OK if that conditioning is done in examples/tests/vignettes, although using if(requireNamespace("pkgname")) is preferred, if possible.
However, using require for conditioning in package code is not good practice as it alters the search path for the rest of the session and relies on functions in that package not being masked by other require or library calls. It is better practice to use code like
if (requireNamespace("rgl", quietly = TRUE)) {
rgl::plot3d(...)
} else {
## do something else not involving rgl.
}
So while just adding {lwgeom} to Suggests works, we may stumble upon the issue where someone that runs a "lean installation" (i.e. without suggested packages) of my package won't be able to use the functions that rely on {lwgeom}.
More importantly, if an author of a package that I am importing decides to run a reverse dependency check on my package while not installing suggested packages, the check would fail because I'd have a few examples, tests and vignettes bits failing due to not having {lwgeom} available.
Thus, in addition to listing it in Suggests, I added some checks on examples and vignettes like suggested by WRE:
*examples/vignette context*
# the examples below require the 'lwgeom' package to be installed
if (requireNamespace("lwgeom", quietly = TRUE)) {
... do something ...
}
In the functions that require {lwgeom} I added:
if (!requireNamespace("lwgeom", quietly = TRUE))
stop(
"The 'lwgeom' package is required to run this function. ",
"Please install it first."
)
And added this bit to the tests of such functions (using {testthat}):
if (!requireNamespace("lwgeom", quietly = TRUE)) {
expect_error(
set_trip_speed(gtfs, "CPTM L07-0", 50),
regexp = paste0(
"The \\'lwgeom\\' package is required to run this function\\. ",
"Please install it first\\."
)
)
skip("'lwgeom' package required to run set_trip_speed() tests.")
}
Assume an R package (myPackage) that imports the R package RCircos via the DESCRIPTION file and the NAMESPACE file.
$ cat DESCRIPTION
Package: myPackage
Imports: RCircos (>= 1.2.0)
...
$ cat NAMESPACE
import(RCircos)
...
One of the perks of RCircos is that it defines a custom environment (called RCircos.Env) and reads/writes variables to this environment from various of its functions. For example, function RCircos.Initialize.Plot.Parameters reads and writes to this environment.
...
RCircosEnvironment <- NULL;
RCircosEnvironment <- get("RCircos.Env", envir = globalenv());
RCircosEnvironment[["RCircos.PlotPar"]] <- plot.param;
(This peculiar behavior has also been recognized by other R packages; see, for example, lines 247-249 of this package).
Unfortunately, it seems that the environment RCircos.Env is not recognized out of the box in myPackage when I simply import RCircos via the DESCRIPTION file and the NAMESPACE file.
So what can be done?
There seem to be two options of making the environment RCircos.Env accessible to functions like RCircos.Initialize.Plot.Parameters. However, both of these options cause the CRAN check (R CMD check myPackage --as-cran) to issue WARNINGs or NOTEs during the mandatory evaluation of myPackage prior to the submission to CRAN, thus preventing its acceptance on CRAN.
Option 1: I include the following line directly prior to the function demanding the object:
# my code here #
assign("RCircos.Env", RCircos::RCircos.Env, .GlobalEnv)
RCircos.Set.Core.Components(...)
# my code here #
However, the CRAN check highlights this line with a NOTE, thus preventing the acceptance of myPackage on CRAN.
* checking R code for possible problems ... NOTE
Found the following assignments to the global environment:
File ‘PACViR/R/visualizeWithRCircos.R’:
assign("RCircos.Env", RCircos::RCircos.Env, .GlobalEnv)
Option 2: I load the entire RCircos library prior to the function demanding the object:
# my code here #
library(RCircos)
RCircos.Set.Core.Components(...)
# my code here #
However, the CRAN check highlights this option with a WARNING, again preventing the acceptance of myPackage on CRAN.
* checking dependencies in R code ... WARNING
'library' or 'require' call not declared from: ‘RCircos’
'library' or 'require' call to ‘RCircos’ in package code.
Please use :: or requireNamespace() instead.
See section 'Suggested packages' in the 'Writing R Extensions' manual.
Surely, there must be an easy and CRAN-compatible way of making the environment RCircos.Env accessible to functions such as RCircos.Set.Core.Components within myPackage! Can someone name and explain such a way?
Apparently the normal re-export does not work with environments as it does with functions. But this does work:
RCircos.Env <- RCircos::RCircos.Env
#' test
#'
#' #param ... data
#'
#' #export
test_fun <- function(...) {
RCircos::RCircos.Set.Core.Components(...)
}
With DESCRIPTION:
Package: test
Type: Package
Title: test
Description: This is a description.
Version: 0.1.0
Authors#R: person("Wouter", "van der Bijl",
email = "redacted#redacted.com",
role = c("aut", "cre"))
Maintainer: Wouter van der Bijl <redacted#redacted.com>
License: GPL-3
Encoding: UTF-8
LazyData: true
Imports: RCircos
RoxygenNote: 6.1.1
And this NAMESPACE:
# Generated by roxygen2: do not edit by hand
export(test_fun)
Test with:
library(test)
data(UCSC.HG19.Human.CytoBandIdeogram, package = 'RCircos')
test_fun(UCSC.HG19.Human.CytoBandIdeogram)
Basically, when RCircos runs get("RCircos.Env", envir = globalenv()), it will traverse up the search path until it finds the RCircos.Env from your package instead.
When running R CMD Check I get 0 errors, 0 warnings, 0 notes.
Note that this strategy that RCircos uses, with an environment that gets looked up by using get(.., envir = globalenv()) is really unorthodox and generally not a good idea. R functions should generally not have side-effects, such as editing unseen environments. Setting default values etc. is typically done using options(). The whole package is probably not something you'd want to emulate, but at least now you can use it as a dependency.
UPDATE
I have completed the package and it is hosted online at https://github.com/iembry-USGS/ie2misc.
Since the error message in the original post was not helpful, I attempted to roxygenize the package to see if that would work or not. Below are the commands and the error message.
library(roxygen2)
roxygenize(".", roclets = "rd")
# First time using roxygen2. Upgrading automatically...
# Error in parse(n = -1, file = file, srcfile = NULL,
# keep.source = FALSE) :
# 1:1: unexpected input
# 1: �
^
I am assuming that the unexpected input is referring to a character, but I don't know which file has the character in question.
Any assistance would be helpful.
Thank you.
UPDATE End
I am working on creating a package that contains 3 functions. I've been successful at creating 3 other packages using 1 function, but not with this package.
I have included the contents of the DESCRIPTION file below. Below that content is the code and the error that I am receiving when attempting to document this package.
Thank you.
Package: ie2misc
Title: Irucka Embry's Miscellaneous functions created while he was a
CNTS USGS Contractor.
Version: 1.0.0
Authors#R: person("Irucka", "Embry", , "", c("aut", "cre"))
Depends: R (>= 3.0.0), tcltk, data.table (>= 1.9.4)
Imports: openxlsx, gWidgets2, gWidgets2tcltk, stringi, qdap
Suggests: Rcpp (>= 0.11.5)
Maintainer: Irucka Embry <iembry#usgs.gov>
Description: Irucka Embry's Miscellaneous functions (processing exp files,
psf files, etc.) created while he was a Cherokee Nation Technology Solutions
(CNTS) USGS Contractor.
URL: https://gitlab.com/iembry/ie2misc
BugReports: https://gitlab.com/iembry/ie2misc/issues
License: CC0
Collate:
'ie2misc.R'
'psfFileChangeBATCH.R'
'psfFileChange.R'
'expFileOutput.R'
LazyData: true
Encoding: UTF-8
Then I run:
setwd("ie2misc"); library(devtools); document();
Updating documentation
Loading
Error in if (pkg$package == "devtools") { : argument is of length zero
I still don't know what the problem is, but I created an empty package with library(devtools). Then, I copied most of the ie2misc files into the newly created package. I also rewrote the DESCRIPTION file in RStudio. (Usually I use the Kate text editor for working on all files for R packages.) Once those steps were completed, I was able to document, check, and build the package.
I'm creating a package using devtools and roxygen2 (in RStudio), however after I've built the package my function no longer works as intended. Yet, if I load the function's .R file and run the function from there in RStudio, it works perfectly. I've created another package using this method before and it worked fine (13 functions all working as intended from my other package), yet I cant seem to get this new one to work.
To start creating the package I start with:
library("devtools")
devtools::install_github("klutometis/roxygen")
library(roxygen2)
setwd("my parent directory")
create("triale")
All is working fine so far. So I put my .R file containing my function in the R folder under the triale folder. The .R file looks like this:
#' Trial Z Function
#'
#' This function counts the values in the columns
#' #param x is the number
#' #keywords x
#' #export
#' #examples
#' trialz()
trialz = function(x) {w_id= c(25,x,25,25,25,1,1,1,1,1);
wcenter= c(rep("BYSTAR-1",10));
df1 <<- data.frame(w_id, wcenter);
countit <<- data.table(df1);
view <<- countit[, .N, by = list(w_id, wcenter)];
View(view)}
Again if I were to just run the code from the .R file, and test the function it works fine. But to continue, next I enter:
setwd("./triale")
document()
The triale documentation is updated, triale is loaded, and the NAMESPACE and trialz.Rd are both written so that trialz.Rd is under the man folder, and NAMESPACE is under the triale folder as intended. Next I install triale:
setwd("..")
install("triale")
Which I know works because I get the following:
Installing triale
"C:/PROGRA~1/R/R-31~1.3/bin/x64/R" --vanilla CMD INSTALL \
"C:/Users/grice/Documents/R/triale" \
--library="C:/Users/grice/Documents/R/win-library/3.1" --install-tests
* installing *source* package 'triale' ...
** R
** preparing package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded
*** arch - i386
*** arch - x64
* DONE (triale)
Reloading installed triale
Package is now built, so I do the following:
library("triale")
library("data.table")
Note whenever I load the package data.table I get the following error message:
data.table 1.9.4 For help type: ?data.table
*** NB: by=.EACHI is now explicit. See README to restore previous behaviour.
However it doesnt seem to affect my function. So now its time to test my function from my package:
trialz(25)
This goes through, and I of course get a populated df1, and countit, but for whatever reason view is always empty (as in 0 obs. of 0 variables).
So I test my work using the dummy code below:
>trialy = function(x) {wid= c(25,x,25,25,25,1,1,1,1,1);
wc= c(rep("BYSTAR-1",10));
df2 <<- data.frame(wid, wc);
countitt <<- data.table(df2);
viewer <<- countitt[, .N, by = list(wid, wc)];
View(viewer)}
>trialy(25)
Even though this is the same exact code with just the names changed around it works. Dumbfounded I open trialz.R and copy the function from there and run it as below, and that works:
> trialz = function(x) {w_id= c(25,x,25,25,25,1,1,1,1,1);
wcenter= c(rep("BYSTAR-1",10));
df1 <<- data.frame(w_id, wcenter);
countit <<- data.table(df1);
view <<- countit[, .N, by = list(w_id, wcenter)];
View(view)}
> trialz(25)
Since I've created a package before I know my method is solid (that package had 13 dif. functions, all of which worked). I just don't understand how a function can work fine as written, yet when I package it, the function no longer works.
Again here is where it stops working as intended when using my package:
view <<- countit[, .N, by = list(w_id, wcenter)];
View(view)}
And my end result should look something like this, if my package worked:
wid wc N
1 25 BYSTAR-1 5
2 1 BYSTAR-1 5
Can anyone explain why view is never populated after I package my function? I've tested it as much as I know how, and my results should be reproducible for anyone thats willing to try it for themselves.
Thanks, I appreciate any feedback.
Your problem here is that "<<-" does not create variables in the global environment but rather in the parent environment. (See help("<<-").)
The parent environment of a function is the environment in which it has been defined. In the case where you defined your function directly in your workspace, this parent environment actually is the same as your workspace environment (namely: .GlobalEnv), which is why your variables are assigned values as you expect them to. In the case where your function is packaged, however, the parent environment is the package environment and not the .GlobalEnv! This is why you do not see your variables being assigned values in your workspace.
Refer to the chapter on environments in Hadley's book and How R Searches and Finds Stuff for more details on environments in R.
Note that doing this would not be considered a proper debugging technique, to say the least. In general, you never want to use the "<<-" operator.
For options on debugging R code, see, e.g., this question. I, in particular, like the debugonce function very well. See ?debugonce.
I forgot one important part when editing my description file in that I for got to add
Imports: data.table
Also the NAMESPACE file needed to include the data.table package as an import as well, like so:
import(data.table)
export(Z)
export(AS) .... etc.
Doing this ensures that whenever a function within your package uses a function from another package, that (second) package is called up before your code is executed.
How can I get the version number for a specific package?
The obvious way is to get the dictionary with all installed packages, and then filter for the one of interest:
pkgs = Pkg.installed();
pkgs["Datetime"]
Getting the list of all installed packages is very slow though, especially if there are many packages.
EDIT: For Julia version 1.1+
Use the Pkg REPL notation:
] status # Show every installed package version
] status pkgName # Show the specific version of the package
] status pkgName1 pkgName2 # Show the named packages. You can continue the list.
The ] enters the Pkg REPL, so you basically write status ...
So in your case, write after entering the Pkg REPL:
status DataFrame
Or use the object-oriented approach (NB: Here you don't enter the Pkg REPL, i.e. DON'T use ]:
Pkg.status("DataFrame")
EDIT: For Julia version 1.0
Pkg.installed seems to have "regressed" with the new package system. There are no arguments for Pkg.installed. So, the OP's original method seems to be about the best you can do at the moment.
pkgs = Pkg.installed();
pkgs["Datetime"]
EDIT: For Julia version upto 0.6.4
You can pass a string to Pkg.installed. For example:
pkgs = Pkg.installed("JuMP")
I often check available calling arguments with methods. For example:
julia> methods(Pkg.installed)
# 2 methods for generic function "installed":
installed() at pkg/pkg.jl:122
installed(pkg::AbstractString) at pkg/pkg.jl:129
or
julia> Pkg.installed |> methods
# 2 methods for generic function "installed":
installed() at pkg/pkg.jl:122
installed(pkg::AbstractString) at pkg/pkg.jl:129
I would try Pkg.status("PackageName")
This will print out a little blurb giving the package name.
Here is an example
julia> Pkg.status("QuantEcon")
- QuantEcon 0.0.1 master
In Julia 1.1 you can use
(v1.1) pkg> status "name_of_the_package"
to find the version of any package in a given environment.
In order to look of a version of an indirectly included package (e.g. top-level project includes Module A which depends on Module B, where you need to know info about Module B), you have to pull the info either from the Manifest.toml directly, or you have to bring in the Context object in from Pkg.
Below is done with Julia 1.3.1 ... there may be changes to Pkg's internals since then.
using Pkg
ctx = Pkg.Operations.Context()
# Get the version of CSV.jl
version = ctx.env.manifest[UUID("336ed68f-0bac-5ca0-87d4-7b16caf5d00b")].version
if version <= v"0.5.24"
# handle some uniqueness about the specific version of CSV.jl here
end
UPDATE: Or without a UUID and just the package name (thanks #HHFox):
using Pkg
pkg_name = "Observables"
m = Pkg.Operations.Context().env.manifest
v = m[findfirst(v->v.name == pkg_name, m)].version
or to do the same with the Manifest.toml
using Pkg
# given the path to the Manifest.toml file...
manifest_dict = Pkg.TOML.parsefile(manifest_path)
# look for a named package like `CSV`
package_dict = manifest_dict[package_name][1]
#show package_dict
Well this didn't print well in the comment section...
Here is a version that matches the name rather than the UUID
using Pkg
m = Pkg.Operations.Context().env.manifest
v = m[findfirst(v -> v.name == "CSV", m)].version
For packages which are dependencies of the specified packages in the project file one can use status -m <packageName> or shorter st -m <packageName> in package mode (After ]`).
For a full list, just use st -m.
This is an extension to https://stackoverflow.com/a/25641957.