Rで解析:ローカル環境でパッケージを構築する「bigbang」パッケージ
社内ネットワークや研究機関の環境でRを運用する場合、セキュリティ状況によっては必要なパッケージをローカルでまとめて導入・管理できる仕組みがあると便利です。しかし、ローカルに置いたアーカイブファイルから依存関係を追跡し、DESCRIPTIONやNAMESPACEを整備するのは手間です。
本パッケージは、ローカルに保存したパッケージアーカイブからtidyverseスタイルのメタパッケージを簡単に構築できます。依存関係グラフの構築とトポロジカル順序の決定、循環参照の検出、ローカル依存とリポジトリ依存の分類をおこなうコマンドが収録されています。
また、ソースコードを走査し、暗黙の依存関係を検出することや、ローカルアーカイブからの依存解決付きインストール、生成したメタパッケージの点検も可能です。本パッケージの利用で、リポジトリへ接続できない環境でも、自作パッケージ群を一貫した手順で配布・管理できるのではないかと考えます。
パッケージバージョンは0.1.0。Windows 11 x64 (build 26200)のR version 4.6.1で確認しています。
パッケージのインストール
下記コマンドを実行してください。
# パッケージのインストール
install.packages("bigbang")
# パッケージの読み込み
library("bigbang")
サンプル用ローカルパッケージの作成
本パッケージはローカルに保存されたアーカイブファイルを入力とするため、まず題材となるパッケージを自作します。ここでは北海道3地点(札幌・岩見沢・倶知安)の12月と1月の積雪深を題材に、下記3本のパッケージを作成します。
- sekisetsudata:観測データと集計関数を提供、依存なし
- sekisetsutool:sekisetsudataに依存、積雪深の指数化をおこなう
- sekisetsumodel:sekisetsudataに依存、DESCRIPTIONに書かれていないMatrixとclassをソース中で利用
sekisetsumodelには、あえて宣言されていない依存関係を含めています。これは後述のdiagnose_dependenciesコマンドの検出対象になります。
# 作業用の一時ディレクトリのパスを生成
sagyou_dir <- tempfile("sekisetsu-")
# パッケージソースを置くディレクトリのパスを生成
src_dir <- file.path(sagyou_dir, "src")
# アーカイブファイルを置くディレクトリのパスを生成
archives <- file.path(sagyou_dir, "archives")
# ソース用ディレクトリを階層ごと作成
dir.create(src_dir, recursive = TRUE)
# アーカイブ用ディレクトリを作成
dir.create(archives)
# 最小構成のパッケージを作成してtar.gz化する自作関数を定義
pkg_sakusei <- function(name, version, depends = NULL, exports, code) {
# パッケージのルートディレクトリのパスを生成
root <- file.path(src_dir, name)
# Rソース用のサブディレクトリまで一括作成
dir.create(file.path(root, "R"), recursive = TRUE)
# DESCRIPTIONの各行を組み立て
dcf <- c(paste0("Package: ", name),
paste0("Title: Sekisetsu Local Package ", name),
paste0("Version: ", version),
"Authors@R: person('Masaki', 'Karada', email = 'masaki@example.com', role = c('aut', 'cre'))",
"Description: Original local package for the sekisetsu metapackage example.",
"License: MIT + file LICENSE",
"Encoding: UTF-8")
# 依存パッケージが指定された場合のみDependsフィールドを追加
if (!is.null(depends)) dcf <- c(dcf, paste0("Depends: ", depends))
# DESCRIPTIONを書き出し
writeLines(dcf, file.path(root, "DESCRIPTION"))
# LICENSEファイルを書き出し
writeLines(c("YEAR: 2026", "COPYRIGHT HOLDER: Masaki Karada"), file.path(root, "LICENSE"))
# NAMESPACEにエクスポート宣言を書き出し
writeLines(paste0("export(", exports, ")"), file.path(root, "NAMESPACE"))
# Rのソースファイルを書き出し
writeLines(code, file.path(root, "R", paste0(name, ".R")))
# 出力するアーカイブファイルのパスを生成
tar_path <- file.path(archives, paste0(name, "_", version, ".tar.gz"))
# 相対パスでアーカイブ化するため作業ディレクトリを一時的に移動
old <- setwd(src_dir)
# 関数終了時に元の作業ディレクトリへ復帰するよう予約
on.exit(setwd(old))
# tar.gz形式のアーカイブを作成
utils::tar(tar_path, files = name, compression = "gzip", tar = "internal")
# アーカイブのパスを非表示で返却
invisible(tar_path)}
# 観測データ本体を提供するパッケージを作成
pkg_sakusei(
name = "sekisetsudata", version = "0.1.0",
exports = c("sekisetsu_kansoku", "sekisetsu_youyaku"),
code = c(
"sekisetsu_kansoku <- function() {",
" data.frame(",
" chiten = c('Sapporo', 'Sapporo', 'Iwamizawa', 'Iwamizawa', 'Kutchan', 'Kutchan'),",
" tsuki = c(12L, 1L, 12L, 1L, 12L, 1L),",
" sekisetsu_cm = c(38, 76, 61, 118, 93, 152),",
" stringsAsFactors = FALSE",
" )",
"}",
"sekisetsu_youyaku <- function(x = sekisetsu_kansoku()) {",
" stats::aggregate(sekisetsu_cm ~ chiten, data = x, FUN = mean)",
"}"))
# 積雪深を最大値100として指数化するパッケージを作成
pkg_sakusei(
name = "sekisetsutool", version = "0.2.0", depends = "sekisetsudata",
exports = "sekisetsu_shisuu",
code = c(
"sekisetsu_shisuu <- function(x = sekisetsudata::sekisetsu_kansoku()) {",
" x$shisuu <- round(x$sekisetsu_cm / max(x$sekisetsu_cm) * 100, 1)",
" x",
"}"))
# 暗黙の依存関係を含む分類処理のパッケージを作成
pkg_sakusei(
name = "sekisetsumodel", version = "0.1.0", depends = "sekisetsudata",
exports = c("sekisetsu_bunrui", "sekisetsu_gyouretsu"),
code = c(
"sekisetsu_bunrui <- function(train, test, label) {",
" class::knn(train = train, test = test, cl = label, k = 1L)",
"}",
"sekisetsu_gyouretsu <- function(i, j, v) {",
" Matrix::sparseMatrix(i = i, j = j, x = v)",
"}"))
# 作成されたアーカイブファイルの一覧を表示
list.files(archives)
[1] "sekisetsudata_0.1.0.tar.gz" "sekisetsumodel_0.1.0.tar.gz" "sekisetsutool_0.2.0.tar.gz" コマンド例
詳細はコメント、パッケージのヘルプを確認してください。
ローカルメタパッケージの構築:create_metapackageコマンド
複数のローカルパッケージを単一のメタパッケージとして統合し、依存関係の解決と配布を容易にする場面で利用します。生成されたメタパッケージには、構成パッケージを導入する<name>_install、読み込む<name>_attach、解除する<name>_detach、一覧を返す<name>_packagesの各関数が用意されます。
| オプション | 意味 | 初期値 |
|---|---|---|
| name | 作成するメタパッケージ名(アンダースコア不可) | なし |
| packages | 含めるローカルパッケージ名(バージョン付き) | なし |
| pkg_dir | ローカルアーカイブファイルの保存ディレクトリ | なし |
| ext | アーカイブ拡張子 | “.tar.gz” |
| version | メタパッケージのバージョン | “0.1.0” |
| dest_dir | 出力先ディレクトリ(既定パスなし、指定必須) | なし |
| reexport | 構成パッケージの関数を再エクスポートするかの論理値 | FALSE |
| document | devtools::document()を自動実行するかの論理値 | TRUE |
| verbose | 詳細メッセージを表示するかの論理値 | getOption(“bigbang.verbose”, interactive()) |
| authors | DESCRIPTIONのAuthors@Rフィールドの内容 | “person(‘First’, ‘Last’, email = ‘first.last@example.com’, role = c(‘aut’, ‘cre’))” |
| description | メタパッケージの説明文字列 | “Local Package Metapackage” |
| license | メタパッケージのライセンス | “MIT + file LICENSE” |
| additional_deps | 自動検出の結果に追加する依存関係のベクトル | NULL |
| ignore_deps | 検出されても無視する依存関係のベクトル | NULL |
| import_deps | DependsではなくImportsに配置するパッケージ名のベクトル | c(“data.table”, “dplyr”, “ggplot2”, “readr”, “tibble”, “tidyr”, “xts”, “zoo”) |
| force_deps | 自動検出を無視して使用する依存パッケージ名のベクトル | NULL |
| debug | デバッグメッセージを出力するかの論理値 | FALSE |
# メタパッケージの出力先ディレクトリのパスを生成
dest_dir <- file.path(sagyou_dir, "meta")
# 出力先ディレクトリを作成
dir.create(dest_dir)
# 3本のローカルパッケージからメタパッケージを構築
meta <- create_metapackage(
# 作成するメタパッケージ名を指定
name = "sekisetsuverse",
# 構成パッケージをバージョン付きの名前で指定
packages = c("sekisetsudata_0.1.0", "sekisetsutool_0.2.0", "sekisetsumodel_0.1.0"),
# アーカイブの保存ディレクトリを指定
pkg_dir = archives,
# 出力先ディレクトリを指定
dest_dir = dest_dir,
# メタパッケージのバージョンを指定
version = "1.0.0",
# DESCRIPTIONに書き込む説明文を指定
description = "Snow depth local metapackage",
# devtoolsによる自動ドキュメント生成を無効化
document = FALSE,
# 詳細メッセージの表示を無効化
verbose = FALSE,
# Imports行きの既定パッケージ群を空にしてDependsへ集約
import_deps = character())
# 構築結果の要約を表示
meta
# 生成されたメタパッケージ直下のファイル一覧を表示
list.files(meta$path)
<bigbang metapackage>
Package: sekisetsuverse
Path: C:\Users\AppData\Local\Temp\RtmpGsiUYh\sekisetsu-36ec78527bbc\meta\sekisetsuverse
Components: sekisetsudata, sekisetsutool, sekisetsumodel
Non-local dependencies: Matrix, class
# リポジトリから取得が必要な依存パッケージを表示
meta$cran_dependencies
[1] "Matrix" "class"
# ローカルアーカイブで解決できた依存パッケージを表示
meta$local_dependencies
[1] "sekisetsudata"sekisetsudataは構成パッケージ内で依存関係が解決、ソースコードからのみ検出されたMatrixとclassはリポジトリ由来の依存として分類されました。
暗黙の依存関係の診断:diagnose_dependenciesコマンド
DESCRIPTIONに宣言されないままソースコードでMatrixやclassを参照している箇所を、ファイル名と行番号付きで抽出します。R CMD checkで「there is no package called ‘Matrix’」といったエラーが出る場合の原因特定に利用できます。
| オプション | 意味 | 初期値 |
|---|---|---|
| packages | 診断対象のローカルパッケージ名(バージョン付き)のベクトル | なし |
| pkg_dir | ローカルアーカイブファイルの保存ディレクトリ | なし |
| ext | アーカイブ拡張子 | “.tar.gz” |
# 作成した3本のローカルパッケージの暗黙依存を診断
shindan <- diagnose_dependencies(
# 診断対象をバージョン付きの名前で指定
packages = c("sekisetsudata_0.1.0", "sekisetsutool_0.2.0", "sekisetsumodel_0.1.0"),
# アーカイブの保存ディレクトリを指定
pkg_dir = archives)
# 暗黙依存を仕込んだパッケージの診断結果を表示
shindan[["sekisetsumodel_0.1.0"]]
$matrix_refs
[1] "sekisetsumodel.R:5 - Matrix::sparseMatrix(i = i, j = j, x = v)"
$class_refs
[1] "sekisetsumodel.R:2 - class::knn(train = train, test = test, cl = label, k = 1L)"
# パッケージごとの検出件数を集計して表示
vapply(shindan, function(x) length(x$matrix_refs) + length(x$class_refs), integer(1))
sekisetsudata_0.1.0 sekisetsutool_0.2.0 sekisetsumodel_0.1.0
0 0 2 宣言のないMatrixとclassの参照が、該当ファイルの行番号と該当行の内容付きで検出されました。診断結果はパッケージ名を要素名とするリストで返るため、集計して全体像を把握できます。
依存解決付きのローカルインストール:install_local_pkgコマンド
ローカルからパッケージを導入し、依存先にも同じディレクトリにアーカイブがあれば再帰的に導入します。cran_depsの初期値”skip”ではネットワークへ一切アクセスしないため利用が可能です。
| オプション | 意味 | 初期値 |
|---|---|---|
| package | 拡張子を除いたアーカイブファイル名 | なし |
| pkg_dir | ローカルアーカイブファイルの保存ディレクトリ | なし |
| ext | アーカイブ拡張子 | “.tar.gz” |
| repos | cran_depsが”install”のときのみ使用するリポジトリ | getOption(“repos”) |
| cran_deps | ローカルにない依存の扱い(“skip”は無視、”error”は中断、”install”はリポジトリから導入) | c(“skip”, “error”, “install”) |
| verbose | 進捗と要約メッセージを表示するかの論理値 | getOption(“bigbang.verbose”, interactive()) |
下記例では、sekisetsutoolのみを指定しましたが、Dependsのsekisetsudataがローカルアーカイブに存在するため、R CMD INSTALLで2ライブラリが導入されます。
# 検証用ライブラリディレクトリのパスを生成
lib_dir <- file.path(sagyou_dir, "lib")
# ライブラリディレクトリを作成
dir.create(lib_dir)
# 作成したディレクトリをライブラリ検索パスの先頭に追加
.libPaths(c(lib_dir, .libPaths()))
# 依存先を含めてローカルアーカイブからインストール
# 結果出力は省略します
setuzoku <- install_local_pkg(
# 導入するアーカイブ名を拡張子なしで指定
package = "sekisetsutool_0.2.0",
# アーカイブの保存ディレクトリを指定
pkg_dir = archives,
# ローカルにない依存はネットワークへ接続せず無視
cran_deps = "skip",
# 進捗メッセージの表示を無効化
verbose = FALSE)
# インストール結果の要約を表示
setuzoku
<bigbang local installation>
Installed: 1
Failed: 0
Skipped: 0
# 実際に導入されたパッケージ名を表示
names(setuzoku$installed)
[1] "sekisetsutool_0.2.0"生成物の削除痕跡のスキャン:scan_bigbang_artifactコマンド
生成されたメタパッケージを点検し、開発初期に存在した削除処理の痕跡(V1、V2、V3、V7の各シグネチャ)が残っていないかを確認します。ソースディレクトリのほか、tar.gzやzipのアーカイブ、インストール済みディレクトリも対象しています。
| オプション | 意味 | 初期値 |
|---|---|---|
| path | ソースディレクトリ、アーカイブ、インストール済みディレクトリのパス | なし |
| dry_run | TRUE固定の論理値(自動修正は意図的に未実装) | TRUE |
# 生成したメタパッケージの点検
scan_bigbang_artifact(meta$path)
<bigbang artifact scan>
Path: C:\Users\AppData\Local\Temp\RtmpGsiUYh\sekisetsu-36ec78527bbc\meta\sekisetsuverse
Type: source
Result: no deletion signatures found
# 作業用の一時ディレクトリを削除
unlink(sagyou_dir, recursive = TRUE)
この記事が誰かの役に立ちますように。