Writing R package functions to create an API to command-line tools

#1

Hi,

I am currently developing an R package and I want one of the functions to wrap a command-line tool to create an API to this tool. I am somewhat new to R package dev so I am running into a few issues that may be trivially easy for some of you to solve.

  1. When I am using the terminal on my Mac and type 'which toolname' I am able to locate the path to the executable for a given tool. However, when I invoke the system() function in R, for example, system('which toolname'), the path is not found. I am assuming has to do with the PATH variable in my bash_profile which R does not seem to be finding/searching. Any idea how to get R to search PATH to find the CLI tool?

  2. As a possible circumvention of this issue, I am considering having users of my package provide the path to the CLI tool manually, just once, and storing that path internally in the package for future use. Is it possible to have a function that takes a path to an executable as an argument and then stores the path internally so that it can be called each time the package is loaded?

Thank you for your help.

0 Likes

#2

Sys.which("toolname") should do what you want, even on a Windows system (which uses where toolname on the command line).

In my opinion, it's better to use existing solutions for user-specific values:

  • Expect the command toolname ... to just work; if it doesn't, print an informative error message suggesting the user update their PATH.

  • Possibly offering another way with a package option. So, if your package is named toolnamer, let the user provide a path to the executable with the toolnamer.exe option. Then one of your function parameters could be:

    exe_path = getOption("toolnamer.exe", default = "toolname")
    

    This lets them control it with an .Rprofile script. If they don't set the option, then it falls back to looking for toolname along the PATH.

3 Likes

#3

@nwerth Thanks for the reply. When I invoke Sys.which("toolname"), it still does not find the tool path. Does Sys.which() search for the tool in the PATH variable specified in my .bash_profile?

Also, just to confirm I understand your suggestion, it sounds like you recommend that I write the wrapper function such that it expects the tool will be found when it is called. And if it is not found, put the onus on the user to ensure that this is resolved through existing methods.

Your second point is one I'm still trying to grasp. I know that packages can set/retrieve options using options() and getOption(), so are you suggesting that I offer the ability to set an executable path (toolnamer.exe) as an option that can be retrieved? The user would be able to set it in their .Rprofile? Am I understanding this correctly?

0 Likes

#4

Sys.which() is only an operating-system-agnostic version of the which command. If R does not read environment variables from your bash profile, it can't help with that.

Correct. It may sound mean and user-unfriendly, but it's a sane option. It's better the user learns how to set up his/her environment. It'll help them in the long term. And often, what's "friendly guidance" for some becomes "unfriendly forcing" for others. For example, Sys.which doesn't respect aliases.

Yes. This is a good candidate for an R option. It's very user-specific and possibly holds private information. If you want to know more about options and .Rprofile, check out ?options and ?Rprofile.

4 Likes

#5

One more option: you could bundle the executable with the package, and then get the path to it with the system.file() function. However, I know CRAN's maintainers don't like packages to include binary executables. If the tool is open source, you could include its source files; RSQLite does this with the SQLite code.

1 Like

#6

@nwerth Thanks for all of your advice. I like the idea of suggesting the user add the path to the executable as an option in their .Rprofile so that this option can be retrieved with getOption().

I am thinking about going with a function that wraps usethis::edit_r_profile(scope = c('user')) to open their .Rprofile for them, and also print an informative message to the console to tell them what line of code to add to their .Rprofile, options(exe.path = 'path/to/exe'. This should guide the user toward a proper config, without being too forceful, I think.

0 Likes