tmpfs が noexec だと、Rパッケージのインストールに失敗することがある

[R Advent Calendar 2021](https://qiita.com/advent-calendar/2021/rlang) の7日目の記事です。

Rのパッケージをインストールできないのは、 /tmp にマウントした tmpfs が noexec なのが原因だった、という話です。エラーメッセージからは原因が分かりにくいので、状況を再現しました。

## 実行環境

Dockerイメージを、以下の Dockerfile ファイルから作成します。あえて rocker/tidyverse を使っていません。本文書の実行環境は、 Windows 10 + WSL2 + R 3.6.3 です。

```
FROM ubuntu:focal

ENV TZ=Asia/Tokyo
RUN apt-get update && apt-get install -y tzdata
RUN apt-get install -y r-base
RUN apt-get install -y wget gdebi-core
RUN wget https://download2.rstudio.org/server/bionic/amd64/rstudio-server-2021.09.1-372-amd64.deb
RUN gdebi --non-interactive rstudio-server-2021.09.1-372-amd64.deb

RUN apt-get install -y libssl-dev libcurl4-openssl-dev libxml2-dev
RUN R -e 'install.packages("remotes")'
RUN Rscript -e 'remotes::install_version("devtools")'

RUN adduser --disabled-password --gecos "" rstudio
RUN echo "rstudio:rstudio" | chpasswd
CMD rstudio-server start && tail -f < /dev/null
```

docker-compose.yml を以下のように書きます。最初は /tmp の tmpfs を exec にしておきます。

```
version: '3'
services:
  installation_failed:
    build: .
    image: installation_failed
    ports:
      - 8787:8787
    tmpfs:
      - /tmp:exec
    volumes:
      - .:/home/rstudio/work
```

この Docker イメージをビルドして起動すると(必要なら sudo をつけてください)、 http://example.com:8787/ の RStudio Server にログインできます。ホスト名は localhost などに適宜読み替えてください。先の Dockerfile に書いた通り、ユーザとパスワードは rstudio です。

``` bash
docker-compose -f docker-compose.yml build
docker-compose -f docker-compose.yml up -d
```

## tmpfs が exec のとき

data.table パッケージをインストールしましょう。 HTTP proxy などに止められなければ、無事インストールできるはずです。

``` r
install.packages("data.table")
library(data.table)
```

pkgbuild を使って、パッケージをビルドできるかどうか調べます。ビルドできますね。

``` r
pkgbuild::check_build_tools(debug = TRUE)
## Your system is ready to build packages!

pkgbuild::check_compiler(debug = TRUE)
## Trying to compile a simple C file
## Running /usr/lib/R/bin/R CMD SHLIB foo.c
## gcc -std=gnu99 -I"/usr/share/R/include" -DNDEBUG -fpic -g -O2 -fdebug-prefix-map=/build/r-base-jbaK_j/r-base-3.6.3=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -g -c foo.c -o foo.o
## gcc -std=gnu99 -shared -L/usr/lib/R/lib -Wl,-Bsymbolic-functions -Wl,-z,relro -o foo.so foo.o -L/usr/lib/R/lib -lR
## [1] TRUE
```

## tmpfs が noexec のとき

先ほどの docker-compose.yml の、 /tmp:exec を /tmp:noexec に変えます。これを docker-compose-noexec.yml として保存して、

```
version: '3'
services:
  installation_failed:
    build: .
    image: installation_failed
    ports:
      - 8787:8787
    tmpfs:
      - /tmp:noexec
    volumes:
      - .:/home/rstudio/work
```

先ほどと同じ Docker イメージを docker-compose-noexec.yml で起動します(必要なら sudo をつけて実行してください)。

``` bash
docker-compose -f docker-compose.yml down
docker-compose -f docker-compose-noexec.yml up -d
```

pkgbuild を使って、パッケージをビルドできるかどうか調べます。ビルドツールはありますが、コンパイルはできないようです。

``` r
pkgbuild::check_build_tools(debug = TRUE)
## Your system is ready to build packages!

pkgbuild::check_compiler(debug = TRUE)
## Trying to compile a simple C file
## Running /usr/lib/R/bin/R CMD SHLIB foo.c
## gcc -std=gnu99 -I"/usr/share/R/include" -DNDEBUG -fpic -g -O2 -fdebug-prefix-map=/build/r-base-jbaK_j/r-base-3.6.3=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -g -c foo.c -o foo.o
## gcc -std=gnu99 -shared -L/usr/lib/R/lib -Wl,-Bsymbolic-functions -Wl,-z,relro -o foo.so foo.o -L/usr/lib/R/lib -lR
## Error: Failed to compile C code
```

data.table パッケージをインストールしましょう。 configure が実行可能でない、と言われてインストールに失敗します。

``` r
install.packages("data.table")
## Installing package into '/home/rstudio/R/x86_64-pc-linux-gnu-library/3.6'
## (as 'lib' is unspecified)
## trying URL 'https://cloud.r-project.org/src/contrib/data.table_1.14.2.tar.gz'
## Content type 'application/x-gzip' length 5301817 bytes (5.1 MB)
## ==================================================
## downloaded 5.1 MB
##
## * installing *source* package 'data.table' ...
## ** package 'data.table' successfully unpacked and MD5 sums checked
## ** using staged installation
## ERROR: 'configure' exists but is not executable -- see the 'R Installation and Administration Manual'
## * removing '/home/rstudio/R/x86_64-pc-linux-gnu-library/3.6/data.table'
## Warning in install.packages :
##   installation of package 'data.table' had non-zero exit status
##
## The downloaded source packages are in
## 	'/tmp/RtmpatoM4r/downloaded_packages'
```

エラーメッセージから /tmp で data.table パッケージをビルドしようとして失敗したことが分かります。しかしこのエラーメッセージだと、原因が分かりにくいですね。実際にこのようなやり取りが [GitHub の Issues](https://github.com/r-lib/devtools/issues/32) でありました。

/tmp が noexec かどうかは、 findmnt コマンドで確認できます。

``` bash
findmnt /tmp
## TARGET SOURCE FSTYPE OPTIONS
## /tmp   tmpfs  tmpfs  rw,nosuid,nodev,noexec,relatime
```

## 自作したRパッケージをインストールする

自作したRパッケージが依存する、外部のRパッケージは、 DESCRIPTION ファイルの Imports: に書きます。ここに data.table があると、やはりパッケージのビルドでエラーになります。

### tmpfs が exec のとき

1. `install.packages("data.table")` で data.table をインストールします
1. RStudio の Build タブを開きます
1. Check を押してパッケージを検査すると、最後まで実行されて結果が出ます
1. Install and Restart を押してインストールする処理も上手くいきます

### tmpfs が noexec のとき

1. `install.packages("data.table")` は失敗するので、結果を無視します
1. RStudio の Build タブを開きます
1. RStudio の Build タブを開き、 Check を押してパッケージを検査すると、以下のメッセージが出ます

```
==> devtools::check(document = FALSE)

-- Building ------------------------------- installationfailedsample --
Setting env vars:
* CFLAGS    : -Wall -pedantic
* CXXFLAGS  : -Wall -pedantic
* CXX11FLAGS: -Wall -pedantic
-----------------------------------------------------------------------
v  checking for file '/home/rstudio/work/sample/DESCRIPTION' ...
-  preparing 'installationfailedsample':
v  checking DESCRIPTION meta-information ... OK
-  checking for LF line-endings in source and make files and shell scripts
-  checking for empty or unneeded directories
-  building 'installationfailedsample_0.1.0.tar.gz'

-- Checking ------------------------------- installationfailedsample --
Setting env vars:
* _R_CHECK_CRAN_INCOMING_REMOTE_: FALSE
* _R_CHECK_CRAN_INCOMING_       : FALSE
* _R_CHECK_FORCE_SUGGESTS_      : FALSE
* NOT_CRAN                      : true
Error: Could not find tools necessary to compile a package
Call `pkgbuild::check_build_tools(debug = TRUE)` to diagnose the problem.
Execution halted

Exited with status 1.
```

"Could not find tools necessary to compile a package" というエラーメッセージから、 tmpfs が原因であることを突き止めるのは大変です。gcc と g++ はインストールされているので、 `pkgbuild::check_build_tools(debug = TRUE)` は正常終了します。そういう理由からこの記事を書きました。

## サンプルコード

コード一式を [GitHub](https://github.com/zettsu-t/installationFailed) に置きました。お役に立てれば幸いです。