Rcpp包调用GSL库的方法

前段时间做 R 包 [**hgwrr**](https://cran.r-project.org/web/packages/hgwrr/index.html) 需要用到 GSL 库,就涉及到如何让R在编译的时候能够链接到GSL的 `libgsl.so` 和 `libgslcblas.so` 两个库。虽然有 **RcppGSL** 包,但是这个包只是提供了R与GSL数据转换的功能,并不能解决链接时的问题。CRAN的编译环境中是包含有GSL库的,所以我们可以通过编译配置文件解决。

# Linux和macOS

在Linux和macOS上,我们需要编写 `configure` 文件来链接GSL。然后将配置的参数传递到 `Makevars.in` 文件来生成 `Makevars` 文件,然后让 R 去编译。

## configure

我们可以先写 `configure.ac` 文件,放在R包代码根目录中。

```shell
# configure.ac

AC_INIT([name], 0.2-2)

AC_LANG(C++)
AC_REQUIRE_CPP

AC_PATH_PROG([GSL_CONFIG], [gsl-config])

if test "${GSL_CONFIG}" != ""; then
    GSL_CFLAGS=`${GSL_CONFIG} --cflags`
    GSL_LIBS=`${GSL_CONFIG} --libs`
else
    AC_MSG_ERROR([gsl-config not found, is GSL installed?])
fi

AC_SUBST(GSL_CFLAGS)
AC_SUBST(GSL_LIBS)

AC_CONFIG_FILES([src/Makevars])
AC_OUTPUT
```

我个人也不是很懂 GNU 的 autoconf 配置,大意就是检测 `gsl-config` 是否存在,存在的话就运行下面两行命令

```bash
gsl-config --cflags
gsl-config --libs
```

并将第一行命令输出结果作为 C++ 编译参数,第二行的结果作为链接的参数。注意这里和 **RcppGSL** 包给出的配置示例不同,该包文档中给出的示例是

```diff
if test "${GSL_CONFIG}" != ""; then
    GSL_CFLAGS=`${GSL_CONFIG} --cflags`
-   GSL_LIBS=`${GSL_CONFIG} --libs`
+   GSL_LIBS=`${GSL_CONFIG} --libs-without-cblas`
else
    AC_MSG_ERROR([gsl-config not found, is GSL installed?])
fi
```

这样会导致 `libgslcblas.so` 在Fedora系统上链接不到的问题。本人也是在提交的包被打回来之后才意识到这个问题。

然后使用下面的命令生成 `configure` 文件

```bash
autoconf -o configure configure.ac
```

然后将生成的cache删除即可。

## Makevars

在目录 `src` 中编写 `Makevars` 文件是R的要求,其实就是一个 Makefile 文件。但是我们在 `configure.ac` 文件中指定了让 `configure` 去配置 `src/Makevars` 文件,所以只需要在 `src` 中写一个“模板”,叫做 `Makevars.in` 。

```makefile
# Makevars.in

CXX_STD = CXX11

GSL_LIBS = @GSL_LIBS@
GSL_CFLAGS = @GSL_CFLAGS@

PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) $(GSL_CFLAGS)
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(GSL_LIBS)
```

这里面变量 `PKG_CXXFLAGS` 是编译标志,`PKG_LIBS` 是链接标志。以 `PKG_CXXFLAGS` 为例,这个变量将 `GSL_CFLAGS` 作为自己的一部分,而 `GSL_CFLAGS` 被设置为了 `@GSL_CFLGAS@` ,这个用一对 `@` 符号包围起来的,就是 configure 要替换下来的字符串。

也就是说,configure 在调用 gsl-config 之后,将后者返回值保存在 `GSL_CFLAGS` 中,然后它根据 `Makevars.in` 去生成 `Makevars` 文件,期间把 `Makevars.in` 文件中的字符串 `@GSL_FLAGS@` 替换成自己的 `GSL_CFLAGS` 的值,这个值就顺势传导到 `Makevars` 中的 `GSL_CFLAGS` 变量,并最终作为编译参数传递给编译器。

# Windows

在 Windows 上就比较简单了。由于 Windows 上不需要运行 configure ,而且也不能运行,我们只需要编写一个 `Makevars.win` 就可以了,而且路径也是固定的,变化没有那么多。

```makefile
# Makevars.win

CXX_STD = CXX11

GSL_LIBS = -L$(R_HOME)/lib -lgsl -lgslcblas

PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) $(LIBHGWR_CXXFLAGS)
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(GSL_LIBS)
```

也就是默认 GSL 安装在 R 家目录的 `lib` 目录中,这也是CRAN的配置。包含目录我们默认就是 R 家目录的 `include` 目录,必然已经指定过了,所以指定不指定都无所谓。