[R-pkg-devel] Best practices for built version checking in packages LinkingTo others?

Pavel Krivitsky p@kr|v|t@ky @end|ng |rom un@w@edu@@u
Fri Jan 17 11:21:27 CET 2025


Dear All,

I maintain a package ('ergm') that exports a C API via /inst/include/
that includes header files with data structures, macros, and inline
functions, as well as a number of functions exported in a stubs file
(that use R_FindSymbol() and/or R_GetCCallable() to locate the
functions in 'ergm') that the linking package #includes to access them.

I've recently had an update stall because a source-compatible change in
one of the structs changed the ABI---that is, packages LinkingTo 'ergm'
needs to be rebuilt, and one of them was Suggested by 'ergm' precisely
in order to test the exported C API. As
https://cloud.r-project.org/doc/manuals/r-devel/R-exts.html#Linking-to-native-routines-in-other-packages
suggests, LinkingTo packages should either depend on an exact version
(which is impractical) or test whether the version matches at runtime.

Thus, the questions are:

   1. What is the best way to save this information? I have a
      rudimentary implementation [1], but R already has a mechanism to
      store the build-time R version since it warns about that; can it
      be used for packages as well?
      
   2. How to best test the C API without making updates impossible?
      Obviously, I have my own continuous integration setups, so I
      could simply disable the tests on CRAN, but there is some benefit
      to having tests on CRAN as well [2]. That is, should the testing
      be conditional ABI versions not mismatching, or is this more
      trouble than it's worth?
      
   3. What should be done in case of a mismatch?
      
         1. Is there a way for 'ergm' to detect when a package LinkingTo it
            is being loaded? Checking and handling can be put into .onLoad()
            of the LinkingTo packages, but there is some benefit to putting
            as little code into the LinkingTo package. However, as far as I
            can tell, hooks can only be set for specific packages. Is this
            correct?
            
         2. What should happen if a mismatch is detected? Should the loading
            be blocked (e.g., by having .onLoad() throw an error), or should
            it lead to a "use at your own risk" type warning?

Thanks in advance,
Pavel

[1]

This can be implemented by defining macros with the ABI version in
'ergm', e.g.,

ergm_version.h:

#define ERGM_API_MAJOR 4
#define ERGM_API_MINOR 8

typedef struct {unsigned int major, minor;} APIVersion;

then

ergm_stubs.c (#included from every LinkingTo package into a C file):

#include "ergm_version.h"

APIVersion GetBuiltErgmAPIVersion(void){
  return (APIVersion) {ERGM_API_MAJOR, ERGM_API_MINOR};
}

Then, at compile time, the macros will be resolved so
GetBuiltErgmAPIVersion() will return the build-time 'ergm' version.
R_FindSymbol() could be used to obtain a pointer to
GetBuiltErgmAPIVersion() from a given package and hence its 'ergm'
build version, which can then be returned to R.

[2]

For example (and this actually happened several versions ago) the
package might do something with computational cost that depends on the
number of installed packages.



More information about the R-package-devel mailing list