# 機能設計書 90-gemパッケージマネージャ

## 概要

本ドキュメントは、mrubyのgemパッケージマネージャ機能の設計を記述する。MRuby::Gem::Specification（gem仕様定義）、MRuby::Gem::List（gem一覧管理・依存解決・トポロジカルソート）、MRuby::GemBox（gemグループ定義）、MRuby::Gem::Version（セマンティックバージョニング比較）の各クラスと、gembox定義ファイル群を含む。

### 本機能の処理概要

**業務上の目的・背景**：mrubyはモジュール式のアーキテクチャを採用しており、各機能はmrbgem（gem）として独立したパッケージで提供される。gemパッケージマネージャは、これらのgemの仕様定義、依存関係解決、バージョン検証、コンフリクト検出、コンパイルルール設定、初期化コード生成を統合的に管理する。gemboxにより複数のgemをグループ化し、ビルド構成から簡潔に参照できる。

**機能の利用シーン**：build_config.rbでの`conf.gem`/`conf.gembox`によるgem指定、ビルド時の依存解決、gem_init.c自動生成、テストコード生成。

**主要な処理内容**：
1. Gem::Specification - gem仕様の定義と管理（名前、バージョン、依存関係、ソースファイル、コンパイラ設定）
2. Gem::List - gemの一覧管理、依存解決、トポロジカルソート、コンフリクト検出
3. Gem::Version - セマンティックバージョニング比較（=, !=, >, <, >=, <=, ~>）
4. GemBox - gemグループの定義（gembox DSL）
5. gembox定義ファイル群 - default, stdlib, stdlib-ext, stdlib-io, math, metaprog等
6. gem_init.c生成 - 各gemのGENERATED_TMP_mrb_*_gem_init/final関数の生成
7. インクルードパス伝播 - 依存gem間のヘッダパス共有

**関連システム・外部連携**：mrubyビルドシステム（MRuby::Build）、git（外部gemのクローン）、mgem-list（mruby gem registory）

**権限による制御**：特になし

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI | ビルドサマリー | print_build_summaryでgem一覧表示 |

## 機能種別

パッケージ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| gem spec (name) | String | Yes | gem名 | 一意であること |
| gem spec (licenses) | String/Array | Yes | ライセンス | 必須 |
| gem spec (authors) | String/Array | Yes | 著者 | 必須 |
| gem spec (version) | String | No | バージョン文字列（デフォルト: "0.0.0"） | セマンティックバージョニング形式 |
| add_dependency (name) | String | Yes | 依存先gem名 | - |
| add_dependency (requirements) | String/Array | No | バージョン要件（デフォルト: ">= 0.0.0"） | 比較演算子+バージョン形式 |
| gembox (gemboxfile) | String | Yes | gemboxファイル名（拡張子なし） | mrbgems/内に.gemboxとして存在すること |

### 入力データソース

- 各gemの`mrbgem.rake`ファイル
- `mrbgems/*.gembox`ファイル
- `build_config/*.rb`ファイル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| gem_init.c | 生成コード | 各gemの初期化・終了関数の宣言とテーブル |
| gem_test.c | 生成コード | テスト用のirep読込コード |
| active_gems.txt | テキスト | アクティブgem名の一覧 |

### 出力先

`build/{target_name}/mrbgems/` ディレクトリ以下

## 処理フロー

### 処理シーケンス

```
1. gem仕様読み込み
   ├─ build_config内のconf.gem/conf.gembox呼出
   │   ├─ gem(): GemLoader.fetch!でソース取得
   │   │   ├─ core: mrbgems/{name}を参照
   │   │   ├─ gemdir: ローカルパスを参照
   │   │   ├─ github/bitbucket/git: リポジトリをクローン
   │   │   └─ mgem: mgem-listから情報取得後にgit clone
   │   ├─ mrbgem.rake load → Gem::Specification生成
   │   └─ gems << current で一覧に追加
   └─ gembox(): gemboxファイルをinstance_evalで実行

2. gemセットアップ (gems.setup)
   ├─ each(&:setup)
   │   ├─ rbfiles = Dir.glob("mrblib/**/*.rb")
   │   ├─ objs = srcs_to_objs("src")
   │   ├─ @initializer実行（mrbgem.rakeのブロック）
   │   ├─ generate_functions判定
   │   └─ build.libmruby_objs << @objs
   └─ setup_dependencies
       ├─ 各gemの依存関係を収集
       ├─ 未登録の依存gemをdefault_gem_paramsで自動追加
       └─ 再帰的に全依存を解決（fixpoint）

3. gem検証 (gems.check)
   ├─ generate_gem_table
   │   ├─ バージョン検証（version_ok?）
   │   └─ コンフリクト検出
   ├─ tsort_dependencies（TSort）
   │   └─ 依存順のトポロジカルソート
   ├─ setup_compilers
   │   ├─ コンパイルルール定義
   │   └─ バージョンdefine設定
   └─ import_include_paths
       └─ 依存gemのinclude/パスを再帰的に伝播

4. gem_init.c生成（generate_gem_init）
   ├─ ヘッダ生成
   ├─ mrbcでrbfilesをコンパイル（cdump or irep）
   ├─ GENERATED_TMP_mrb_{funcname}_gem_init関数生成
   │   ├─ シンボル初期化（cdump時）
   │   ├─ mrb_{funcname}_gem_init呼出（Cソースある場合）
   │   └─ mrb_load_proc/mrb_load_irep呼出（rbfilesある場合）
   └─ GENERATED_TMP_mrb_{funcname}_gem_final関数生成
```

### フローチャート

```mermaid
flowchart TD
    A[conf.gem / conf.gembox] --> B{ソース種別?}
    B -->|core| C[mrbgems/{name}参照]
    B -->|gemdir| D[ローカルパス参照]
    B -->|github/git| E[git clone]
    B -->|mgem| F[mgem-list参照→git clone]
    C --> G[mrbgem.rake load]
    D --> G
    E --> G
    F --> G
    G --> H[Gem::Specification生成]
    H --> I[gems << current]
    I --> J[gems.setup]
    J --> K[各gem setup]
    K --> L[setup_dependencies]
    L --> M{未解決の依存?}
    M -->|Yes| N[default_gem_paramsで自動追加]
    N --> L
    M -->|No| O[gems.check]
    O --> P[バージョン検証]
    P --> Q[コンフリクト検出]
    Q --> R[tsort_dependencies]
    R --> S[setup_compilers]
    S --> T[import_include_paths]
    T --> U[gem_init.c生成]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-90-1 | 必須フィールド | name, licenses, authorsの3つは全gemで必須 | gem setup時 |
| BR-90-2 | バージョン比較 | セマンティックバージョニング。~>演算子はtwiddle-wakka方式（~> 2.2.2は>= 2.2.2かつ< 2.3.0） | 依存解決時 |
| BR-90-3 | 依存自動解決 | 未登録の依存gemはcore→mgemの優先順で自動追加 | setup_dependencies時 |
| BR-90-4 | 循環依存禁止 | TSort::Cyclicが発生した場合はビルドエラー | tsort_dependencies時 |
| BR-90-5 | コンフリクト検出 | 同一ビルドに含まれるgemのadd_conflictで指定されたgemがある場合エラー | generate_gem_table時 |
| BR-90-6 | gem重複排除 | 同じdirのgemは一度のみ追加（Gem::List#<<で重複チェック） | gem追加時 |
| BR-90-7 | canonical gem | canonical指定されたgemは後続の同名gem定義を無視 | GemLoader#skip_this?時 |
| BR-90-8 | cdump | デフォルトでCダンプ方式（cdump: true）でRubyコードをコンパイル。disable_cdumpでirep方式に切替 | gem_init.c生成時 |
| BR-90-9 | インクルードパス伝播 | 依存先gemのinclude/ディレクトリとexport_include_pathsを再帰的にコンパイラに追加 | import_include_paths時 |
| BR-90-10 | テスト依存 | add_test_dependencyはtest_enabled?またはbintest_enabled?の場合のみ有効 | gem setup時 |

### 計算ロジック

**Version比較**：
- バージョン文字列をドット区切りで整数配列化（例: "1.2.3" → [1, 2, 3]）
- 配列要素を先頭から順次比較（Comparable#<=>）
- ~>演算子: `self >= other && self < other.skip_major_or_minor`
  - skip_major_or_minor: 最後の要素を削除し、新しい最後の要素をsucc（例: "2.2.2" → "2.3"）

## データベース操作仕様

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| - | RuntimeError | 必須フィールド（name/licenses/authors）未設定 | mrbgem.rakeにspec設定を追加 |
| - | RuntimeError | 依存gemが見つからない | gemの追加またはdefaultパラメータの設定 |
| - | RuntimeError | バージョン不一致 | 要件を満たすバージョンのgemを使用 |
| - | RuntimeError | 循環依存 | 依存関係を修正 |
| - | RuntimeError | gemコンフリクト | 競合するgemの一方を除外 |
| - | RuntimeError | canonical重複宣言 | canonical指定を最初の宣言のみに限定 |
| - | RuntimeError | gemboxファイル未発見 | gemboxファイル名を確認 |
| - | RuntimeError | mrbgem.rakeファイル未発見 | gemディレクトリ構造を確認 |
| - | RuntimeError | GemLoaderのソース種別が不正 | git/github/bitbucket/mgem/core/gemdirのいずれか1つを指定 |

### リトライ仕様

該当なし

## トランザクション仕様

該当なし

## パフォーマンス要件

特になし

## セキュリティ考慮事項

- mrbgem.rakeファイルはRubyコードとしてloadされるため、信頼できるソースのgemのみを使用すること
- mgem-listからのgem取得はGitHub上のリポジトリをクローンするため、ネットワークアクセスが発生する
- lockファイルによるcommitハッシュ固定で再現性と安全性を確保

## 備考

- gemboxは入れ子にできる（例: defaultはstdlib, stdlib-ext, stdlib-io, math, metaprogを内包）
- full-core.gemboxはmrbgems/mruby-*/を動的にグロブして全gemを含める（mruby-bin-debugger, mruby-test, mruby-sleepを除外）
- default-no-stdio.gemboxはstdlibとmathのみ（stdio不要環境向け）
- default-no-fpu.gemboxはstdlibのみ（FPU不要環境向け）
- pkg-configによる外部ライブラリ検索がsearch_packageメソッドで利用可能
- funcnameはgem名のハイフンをアンダースコアに変換（例: mruby-array-ext → mruby_array_ext）
- gem_init.cの生成方式としてcdump方式（Cソースとして直接埋め込み）とirep方式（バイナリirep埋め込み）がある

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: Gem仕様定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | gem.rb | `lib/mruby/gem.rb` | Gem::Specification#initialize（43-50行目）：name, @initializer, @version, @dependenciesの初期設定 |
| 1-2 | gem.rb | `lib/mruby/gem.rb` | Gem::Specification#setup（52-91行目）：rbfiles/objsの収集（59-60行目）、@initializerブロック実行（76行目）、generate_functions判定（78行目）、gem_initオブジェクトの追加（79行目） |

**読解のコツ**: Specification#setupが各gemの実質的な初期化。mrbgem.rakeで`MRuby::Gem::Specification.new`ブロック内に定義された内容が`@initializer`として保存され、setup時にinstance_evalで実行される。`generate_functions`がtrueのgemにはgem_init.cが生成される。

#### Step 2: 依存関係解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | gem.rb | `lib/mruby/gem.rb` | Gem::List#setup（425-432行目）：fixpoint方式で依存解決をループ |
| 2-2 | gem.rb | `lib/mruby/gem.rb` | Gem::List#setup_dependencies（439-463行目）：未登録依存gemの自動追加ロジック |
| 2-3 | gem.rb | `lib/mruby/gem.rb` | Gem::List#generate_gem_table（465-491行目）：バージョン検証とコンフリクト検出 |
| 2-4 | gem.rb | `lib/mruby/gem.rb` | Gem::List#tsort_dependencies（493-525行目）：TSort利用のトポロジカルソート |

**主要処理フロー**:
- **425-432行目**: `setup`はbegin-untilループで`gemset == gemset_prev`になるまで繰り返す。新しい依存gemが追加されなくなるまでループ。
- **439-463行目**: `setup_dependencies`は全gemの@dependenciesを走査し、gem_tableに未登録のgemを`build.gem(def_gem[:default])`で追加。core gemかmgemかを`default_gem_params`で判定（416-423行目）。
- **493-525行目**: `tsort_dependencies`はTSortモジュールをgem_tableにミックスインし、tsort_each_node/tsort_each_childを動的定義してトポロジカルソートを実行。TSort::Cyclic例外を循環依存エラーに変換。

#### Step 3: バージョン比較を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | gem.rb | `lib/mruby/gem.rb` | Gem::Version#initialize（370-373行目）：バージョン文字列を整数配列化 |
| 3-2 | gem.rb | `lib/mruby/gem.rb` | Gem::Version#<=>（333-348行目）：配列要素ごとの比較 |
| 3-3 | gem.rb | `lib/mruby/gem.rb` | Gem::Version#twiddle_wakka_ok?（356-359行目）：~>演算子の実装 |
| 3-4 | gem.rb | `lib/mruby/gem.rb` | Gem::Specification#version_ok?（289-306行目）：比較演算子ディスパッチ（=, !=, >, <, >=, <=, ~>） |

#### Step 4: gem_init.cの生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | gem.rb | `lib/mruby/gem.rb` | Gem::Specification#generate_gem_init（216-247行目）：初期化関数/終了関数の生成。cdump/irep方式の分岐（222-239行目） |
| 4-2 | gem.rb | `lib/mruby/gem.rb` | Gem::Specification#setup_compilers（97-106行目）：コンパイルルール定義、バージョンdefine追加 |

**読解のコツ**: `generate_gem_init`はopenでCファイルを生成。cdump方式では`mrb_load_proc`、irep方式では`mrb_load_irep`を使用。`gem_mrblib_{funcname}_proc_init_syms`はcdump時のシンボルテーブル初期化。

#### Step 5: GemBoxを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | gem.rb | `lib/mruby/gem.rb` | GemBox定義（576-584行目）：Object.newベースのシンプルなDSLオブジェクト |
| 5-2 | load_gems.rb | `lib/mruby/build/load_gems.rb` | gembox()メソッド（6-16行目）：gemboxファイルをinstance_evalで実行 |
| 5-3 | default.gembox | `mrbgems/default.gembox` | 代表的なgembox定義例：stdlib系gemboxの入れ子参照＋binツール群 |

#### Step 6: インクルードパス伝播を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | gem.rb | `lib/mruby/gem.rb` | Gem::List#import_include_paths（539-566行目）：依存gemのinclude/とexport_include_pathsを再帰伝播 |

### プログラム呼び出し階層図

```
conf.gem / conf.gembox (build_config.rb)
    |
    +-- LoadGems#gembox(gemboxfile)
    |     +-- GemBox.config = self
    |     +-- instance_eval(gembox_file)
    |           +-- conf.gem (再帰的)
    |           +-- conf.gembox (再帰的)
    |
    +-- LoadGems#gem(gem_src)
          +-- GemLoader.new.fetch!
          |     +-- fromCore!   → GemCheckout(mrbgems/{name})
          |     +-- fromGemdir! → GemCheckout(local_path)
          |     +-- fromGitHub! → fromGit!(https://github.com/...)
          |     +-- fromBitBucket! → fromGit!(https://bitbucket.org/...)
          |     +-- fromMGem!   → fetchMGem → fromGit!
          |     +-- fromGit!    → git_clone + git_checkout → GemCheckout
          |
          +-- load mrbgem.rake
          |     +-- Gem::Specification.new(name, &block)
          |
          +-- gems << current

gems.setup(build)
    +-- each(&:setup)    [Gem::Specification#setup]
    +-- setup_dependencies
    |     +-- default_gem_params → build.gem(default)
    |     +-- (再帰: 新規gemの依存も解決)
    +-- gems.check(build)
          +-- generate_gem_table
          |     +-- version_ok? → Version#<=>
          |     +-- conflict検出
          +-- tsort_dependencies → TSort#tsort
          +-- each(&:setup_compilers)
          |     +-- define_rules
          |     +-- define_gem_init_builder
          +-- import_include_paths (再帰)
```

### データフロー図

```
[入力]                          [処理]                              [出力]

build_config.rb ──────────▶ gem()/gembox() ──────────────▶ Gem::Specification
                                  |
mrbgem.rake ──────────────▶ load → Specification.new ──▶ @gems (Gem::List)
                                  |
                           gems.setup()
                                  |
                    ┌─────────────┼─────────────────┐
                    ▼             ▼                   ▼
            setup_dependencies  generate_gem_table  tsort_dependencies
                    |             |                   |
                    ▼             ▼                   ▼
            依存gem自動追加   バージョン検証      トポロジカルソート
                                                     |
                                              setup_compilers
                                                     |
                                              ┌──────┴──────┐
                                              ▼             ▼
                                       gem_init.c      define_rules
                                       生成            (コンパイル設定)
                                              |
mrblib/**/*.rb ──mrbc──▶ gem_mrblib_*_proc ──┘
                                              |
                                    GENERATED_TMP_mrb_*
                                    _gem_init / _gem_final
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| gem.rb | `lib/mruby/gem.rb` | ライブラリ | Gem::Specification, List, Version, GemBox定義 |
| load_gems.rb | `lib/mruby/build/load_gems.rb` | ライブラリ | gem/gembox読込、GemLoader, GemCheckout |
| mrbgems.rake | `tasks/mrbgems.rake` | タスク | gem_init.c/LEGAL自動生成 |
| default.gembox | `mrbgems/default.gembox` | 設定 | デフォルトgemグループ（stdlib系+bin系+metaprog） |
| stdlib.gembox | `mrbgems/stdlib.gembox` | 設定 | 標準ライブラリgemグループ（18 gem） |
| stdlib-ext.gembox | `mrbgems/stdlib-ext.gembox` | 設定 | 標準拡張gemグループ（pack, sprintf, time, struct, data, random） |
| stdlib-io.gembox | `mrbgems/stdlib-io.gembox` | 設定 | IO系gemグループ（io, socket, errno, dir） |
| math.gembox | `mrbgems/math.gembox` | 設定 | 数学gemグループ（math, rational, complex, bigint） |
| metaprog.gembox | `mrbgems/metaprog.gembox` | 設定 | メタプログラミングgemグループ（metaprog, method, eval, binding, compiler） |
| full-core.gembox | `mrbgems/full-core.gembox` | 設定 | 全コアgemの動的グロブ（一部除外） |
| default-no-stdio.gembox | `mrbgems/default-no-stdio.gembox` | 設定 | stdio不要環境向け（stdlib+math） |
| default-no-fpu.gembox | `mrbgems/default-no-fpu.gembox` | 設定 | FPU不要環境向け（stdlibのみ） |
