# 機能設計書 60-パッケージ読み込み

## 概要

本ドキュメントは、Julia の `include` / `include_string` / `evalfile` / `require` によるソースコードの読み込みと評価機能の設計を記述する。

### 本機能の処理概要

本機能は、Julia のパッケージ・ソースファイルの読み込みと評価を提供する。`include` はファイルの内容を指定モジュールのグローバルスコープで評価し、`include_string` は文字列コードを同様に評価する。`require` は `using` / `import` 文の内部実装であり、パッケージの識別・検索・プリコンパイルキャッシュのロード・フォールバックとしてのソースロードを行う。`evalfile` は匿名モジュール内でファイルを評価する。

**業務上の目的・背景**: Julia のパッケージエコシステムの中核を担う。`using PackageName` / `import PackageName` という構文は内部的に `require` を呼び出し、パッケージの検索・ロード・プリコンパイルキャッシュの利用を自動化する。ソースファイルの分割管理（`include`）、REPL からのファイル実行（`evalfile`）、テスト・スクリプトの実行など、Julia プログラミングの基本的なワークフローを支える。

**機能の利用シーン**: `using LinearAlgebra` によるパッケージの利用、`include("src/utils.jl")` によるソースファイルの分割読み込み、`include_string(mod, code)` による動的コードの評価、`evalfile("script.jl", ["arg1"])` によるスクリプトの独立実行。

**主要な処理内容**:
1. `include(mod, path)` によるファイル読み込みと評価（`_include` 関数で実装）
2. `include_string(mod, code, filename)` による文字列コードの評価
3. `evalfile(path, args)` による匿名モジュール内でのファイル評価
4. `require(into, mod)` による `using` / `import` のパッケージロード
5. `identify_package_env` によるパッケージの識別（LOAD_PATH 検索）
6. `locate_package_env` によるパッケージファイルの所在特定
7. `_require_prelocked` によるプリコンパイルキャッシュからのロードまたはソースロード

**関連システム・外部連携**: LOAD_PATH（パッケージ検索パス）、DEPOT_PATH（depot ディレクトリ）、Project.toml / Manifest.toml（プロジェクト環境）、プリコンパイルシステム（`.ji` キャッシュファイル）、パッケージイメージ（`.so` / `.dylib`）、Pkg パッケージマネージャ。

**権限による制御**: ファイルシステムのアクセス権に依存。ケースセンシティブなファイルシステムチェック（`isfile_casesensitive`）を実施。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | Juliaプロンプト（julia>) | 補助機能 | REPL から using/import/include を実行してパッケージやファイルを読み込み |

## 機能種別

インフラストラクチャ（パッケージ読み込み・コードロード）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| mod | Module | Yes（include/include_string） | ファイル・コードを評価するモジュール | 有効な Module |
| path | AbstractString | Yes（include/evalfile） | 読み込むファイルのパス | 有効なファイルパス |
| code | AbstractString | Yes（include_string） | 評価する Julia コード文字列 | 有効な Julia コード |
| filename | AbstractString | No（include_string） | ソース位置情報に使うファイル名（デフォルト "string"） | - |
| mapexpr | Function | No | 各式の変換関数（デフォルト identity） | Callable |
| args | Vector{String} | No（evalfile） | スクリプトの ARGS（デフォルト空） | - |
| into | Module | 暗黙（require） | import/using の呼び出し元モジュール | 有効な Module |
| mod_name | Symbol | 暗黙（require） | ロードするパッケージ名 | 有効な Julia 識別子 |

### 入力データソース

- Julia ソースコード（`.jl` ファイル）
- 文字列として提供されるコード
- LOAD_PATH で定義された環境（Project.toml / Manifest.toml）
- プリコンパイルキャッシュ（`.ji` ファイル、パッケージイメージ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 評価結果 | Any | include / include_string / evalfile の場合、最後の式の評価結果 |
| Module | Module | require の場合、ロードされたモジュール |

### 出力先

- include / include_string: 関数の戻り値として呼び出し元に返却。副作用としてモジュール内にバインディングが追加される
- require: loaded_modules テーブルにモジュールを登録し、モジュールを返却

## 処理フロー

### 処理シーケンス

```
1. include(mod, path) の処理
   └─ _include(identity, mod, path)
       └─ _include_dependency で絶対パス解決・依存関係追跡
       └─ include_callbacks の呼び出し
       └─ read(path, String) でファイル内容を読み込み
       └─ task_local_storage に SOURCE_PATH を設定
       └─ include_string(identity, mod, code, path) で評価

2. include_string(mapexpr, mod, code, filename) の処理
   └─ Meta.parseall(code) でソースコード全体をパース
   └─ for ex in ast.args で各式をイテレート
       └─ mapexpr(ex) でオプショナルな式変換
       └─ Expr(:toplevel, loc, ex) でラッピング
       └─ TRACE_EVAL によるトレース出力（設定時）
       └─ Core.eval(mod, line_and_ex) で各式を評価
   └─ エラー時は LoadError でラップ

3. evalfile(path, args) の処理
   └─ Module(:__anon__) で匿名モジュールを生成
   └─ Core.eval で ARGS, include, eval を定義
   └─ include(path) を実行

4. require(into, mod) の処理
   └─ __require(into, mod) を invoke_in_world で呼び出し
       └─ identify_package_env(into, mod) でパッケージを識別
       └─ _require_prelocked(uuidkey, env)
           └─ start_loading で並行ロードの制御
           └─ __require_prelocked(uuidkey, env)
               └─ locate_package_env で .jl ファイルの場所を特定
               └─ プリコンパイルキャッシュからのロードを試行
               └─ 失敗時: compilecache でプリコンパイルを実行
               └─ 最終フォールバック: include でソースを直接ロード
           └─ end_loading でロード完了通知
           └─ insert_extension_triggers でエクステンション処理
           └─ run_package_callbacks で通知
```

### フローチャート

```mermaid
flowchart TD
    A["using PackageName"] --> B["require(into, :PackageName)"]
    B --> C["__require(into, mod)"]
    C --> D["identify_package_env(into, mod)"]
    D --> E{"パッケージ発見?"}
    E -->|No| F["ArgumentError: Package not found"]
    E -->|Yes| G["_require_prelocked(uuidkey, env)"]
    G --> H["start_loading(uuidkey)"]
    H --> I{"既にロード済み?"}
    I -->|Yes| J["既存Module返却"]
    I -->|No| K["__require_prelocked(uuidkey, env)"]
    K --> L["locate_package_env でファイル特定"]
    L --> M{"プリコンパイルキャッシュあり?"}
    M -->|Yes| N["_require_search_from_serialized"]
    N --> O{"ロード成功?"}
    O -->|Yes| P["Module返却"]
    O -->|No| Q["compilecache でプリコンパイル"]
    M -->|No| Q
    Q --> R{"プリコンパイル成功?"}
    R -->|Yes| S["キャッシュからロード"]
    R -->|No| T["include でソース直接ロード"]

    U["include(mod, path)"] --> V["_include(identity, mod, path)"]
    V --> W["_include_dependency でパス解決"]
    W --> X["read(path, String)"]
    X --> Y["include_string(mod, code, path)"]
    Y --> Z["Meta.parseall → Core.eval loop"]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-60-1 | LOAD_PATH 検索 | パッケージは LOAD_PATH の環境を順に検索して特定する。デフォルトは `["@", "@v#.#", "@stdlib"]` | require 使用時 |
| BR-60-2 | プリコンパイル優先 | require はまずプリコンパイルキャッシュ（.ji ファイル）からのロードを試み、失敗時にソースロードにフォールバックする | JLOptions().use_compiled_modules != 0 |
| BR-60-3 | require_lock | パッケージロードは ReentrantLock（require_lock）で保護され、並行ロードの一貫性を保証する | 常時 |
| BR-60-4 | ケースセンシティブチェック | ファイルパスのケースセンシティブ判定をOS別に実装（Unix: そのまま、macOS: getattrlist、Windows: GetLongPathName） | ファイル検索時 |
| BR-60-5 | 相対パス解決 | include のパスは現在の SOURCE_PATH からの相対パスとして解決される | include 使用時 |
| BR-60-6 | LoadError ラップ | include_string でのエラーは LoadError(filename, line, exc) でラップされる | include_string エラー時 |
| BR-60-7 | mapexpr 変換 | include / include_string にオプションの mapexpr 関数を渡すと、各式が評価前に変換される | mapexpr 指定時 |
| BR-60-8 | evalfile 隔離 | evalfile は匿名モジュール内で実行されるため、定義がグローバルスコープに漏れない | evalfile 使用時 |
| BR-60-9 | toplevel ラッピング | include_string は各式を Expr(:toplevel, loc, ex) でラップして Core.eval に渡す | 常時 |
| BR-60-10 | 自己参照の最適化 | require で自身のモジュールをロードしようとした場合、即座に moduleroot を返す | nameof(topmod) === mod |

### 計算ロジック

特別な計算ロジックは存在しない。パッケージの識別（UUID + 名前）、ファイルパスの解決、キャッシュの有効性検証が主要な処理。

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

該当なし。ただし、以下のインメモリテーブルを使用:

| テーブル名 | 型 | 説明 |
|-----------|-----|------|
| loaded_modules | Dict{PkgId, Module} | ロード済みモジュールのレジストリ |
| loaded_precompiles | Dict{PkgId, Vector{Module}} | プリコンパイル済みモジュールの拡張リスト |
| loaded_modules_order | Vector{Module} | ロード順序の記録 |
| pkgorigins | Dict{PkgId, PkgOrigin} | パッケージの起源情報（パス、キャッシュパス、バージョン） |
| package_locks | Dict{PkgId, Tuple} | 並行ロード制御用ロック |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| - | ArgumentError | パッケージが LOAD_PATH に見つからない（"Package X not found in current path"） | `Pkg.add` でインストール、または相対パス `import .X` を使用 |
| - | ArgumentError | 依存関係にないパッケージをインポート（"Cannot load module X into module Y"） | `Pkg.resolve()` または `Pkg.instantiate()` を実行 |
| - | ArgumentError | locate_package_env でパッケージファイルが見つからない | `Pkg.instantiate()` を実行 |
| - | LoadError | include_string 内でエラー発生（ファイル名・行番号付き） | ソースコードを修正 |
| - | SystemError | include のパスが存在しない（ENOENT） | ファイルパスを確認 |
| - | PrecompilableError | プリコンパイル不可（`__precompile__(false)`） | パッケージの設計を確認 |
| - | ErrorException | パッケージが期待するモジュールを定義しなかった | パッケージのソースコードを確認 |

### リトライ仕様

- プリコンパイルキャッシュのロード失敗時、`compilecache` でプリコンパイルを再実行する
- `maybe_cachefile_lock` で他プロセスによるキャッシュ生成を待機し、完了後に再検索（`@goto load_from_cache`）
- 並行パッケージプリコンパイル（`Precompilation.precompilepkgs`）による自動リトライ

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

- `start_loading` / `end_loading` でパッケージロードの排他制御を実施
- `package_locks` を使い、同一パッケージの並行ロードをConditionで待機させる
- ロード失敗時、`end_loading` で待機中のタスクにエラーを通知
- `require_lock`（ReentrantLock）でロード操作全体をシリアライズ

## パフォーマンス要件

- プリコンパイルキャッシュ（`.ji` ファイル）の利用により、2回目以降のパッケージロードが高速化
- パッケージイメージ（`.so` / `.dylib`）による更なる高速化（`use_pkgimages` オプション）
- `LOADING_CACHE` による TOML パース結果・パッケージ検索結果のキャッシュ
- `require_lock` はリエントラントであり、再帰的な require 呼び出しに対応

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

- `include` は任意のファイルを読み込み・実行するため、信頼できないファイルの include は避けるべき
- `require` は LOAD_PATH 上のパッケージのみをロードするため、LOAD_PATH の管理が重要
- `__precompile__(false)` によりプリコンパイルを禁止して、プリコンパイル時の副作用を回避可能

## 備考

- `include` の mapexpr 引数は Julia 1.5 で追加
- `TRACE_EVAL` グローバル変数（または `--trace-eval` コマンドラインオプション）で include_string の評価トレースが可能
- `__toplevel__` は特殊な baremodule で、パッケージのトップレベルロードに使用される。このモジュールで評価されたモジュールは `loaded_modules` テーブルに自動登録される
- `PkgId` 構造体は UUID とパッケージ名のペアで、パッケージの一意識別子として機能する

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | pkgid.jl | `base/pkgid.jl` | PkgId 構造体（3-9行目）: uuid（UUID または nothing）と name のペア。パッケージの一意識別子 |
| 1-2 | loading.jl | `base/loading.jl` | PkgOrigin 構造体（2707-2712行目）: path, cachepath, version を持つ。パッケージの起源情報 |
| 1-3 | loading.jl | `base/loading.jl` | loaded_modules / loaded_precompiles / loaded_modules_order（2715-2717行目）: ロード済みモジュールのグローバルレジストリ |
| 1-4 | initdefs.jl | `base/initdefs.jl` | LOAD_PATH（194行目）: パッケージ検索パスの定義。デフォルトは `["@", "@v#.#", "@stdlib"]` |

**読解のコツ**: Julia のパッケージロードは PkgId（UUID + 名前）でパッケージを識別する。LOAD_PATH の各エントリは `load_path_expand` で具体的なディレクトリパスに展開される。`@` は現在のアクティブプロジェクト、`@v#.#` はバージョン固有の環境、`@stdlib` は標準ライブラリ。

#### Step 2: include / include_string の評価ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | loading.jl | `base/loading.jl` | include_string（3085-3138行目）: Meta.parseall でパース後、各式を Core.eval で評価 |
| 2-2 | loading.jl | `base/loading.jl` | _include（3180-3198行目）: ファイル読み込み→include_string 呼び出し。SOURCE_PATH の管理 |
| 2-3 | loading.jl | `base/loading.jl` | _include_dependency（2440-2470行目）: パスの絶対パス解決と依存関係追跡 |
| 2-4 | loading.jl | `base/loading.jl` | evalfile（3225-3234行目）: 匿名モジュール内で include を実行 |

**主要処理フロー（include_string）**:
- **3089-3090行目**: `Meta.parser_for_module(mod)` でパーサーを取得し、`Meta.parseall(code)` でソースコード全体をパース
- **3098行目**: `for ex in ast.args` で各式をイテレート
- **3104行目**: `ex = mapexpr(ex)` でオプショナルな式変換を適用
- **3107行目**: `line_and_ex.args[2] = ex` で式を `:toplevel` ラッパーに設定
- **3109-3124行目**: TRACE_EVAL の設定確認（グローバル変数 → コマンドラインオプションのフォールバック）
- **3130行目**: `result = Core.eval(mod, line_and_ex)` で各式を評価
- **3136行目**: エラー時は `LoadError(filename, loc.line, exc)` でラップ

**主要処理フロー（_include）**:
- **3182行目**: `_include_dependency(mod, _path)` で絶対パス解決
- **3183-3185行目**: `include_callbacks` の呼び出し（Revise 等で利用）
- **3186行目**: `read(path, String)` でファイル内容を文字列として読み込み
- **3188行目**: `tls[:SOURCE_PATH] = path` で現在のソースパスを設定
- **3190行目**: `include_string(mapexpr, mod, code, path)` で評価

#### Step 3: require のパッケージロードフローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | loading.jl | `base/loading.jl` | require（2556-2562行目）: ワールドエイジを取得し、__require を invoke_in_world で呼び出し |
| 3-2 | loading.jl | `base/loading.jl` | __require（2576-2626行目）: identify_package_env でパッケージ識別、_require_prelocked でロード |
| 3-3 | loading.jl | `base/loading.jl` | _require_prelocked（2687-2705行目）: start_loading で排他制御、__require_prelocked でロード、end_loading で完了通知 |
| 3-4 | loading.jl | `base/loading.jl` | __require_prelocked（2813-2949行目）: locate_package_env → プリコンパイルキャッシュロード → フォールバック include |

**主要処理フロー（require）**:
- **2556-2562行目**: `require(into, mod)` はワールドエイジを決定し、`__require` を呼び出す
- **2581-2583行目**: 自己参照の最適化 — `nameof(topmod) === mod` なら `topmod` を即座に返す
- **2588行目**: `identify_package_env(into, String(mod))` で LOAD_PATH を検索してパッケージを特定
- **2590-2614行目**: パッケージ未発見時の詳細なエラーメッセージ生成（ヒント付き）
- **2621行目**: `_require_prelocked(uuidkey, env)` でロード実行
- **2689行目**: `start_loading(uuidkey, UInt128(0), true)` で並行ロードの排他制御
- **2694行目**: `__require_prelocked(uuidkey, env)` で実際のロード処理
- **2831-2837行目**: プリコンパイルキャッシュからのロード試行
- **2856-2929行目**: キャッシュ失敗時の `compilecache` によるプリコンパイル実行
- **2931-2949行目**: 最終フォールバックとしての `include(__toplevel__, path)` によるソースロード

#### Step 4: パッケージ識別・検索の詳細を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | loading.jl | `base/loading.jl` | identify_package_env（356-400行目 / 402-430行目）: LOAD_PATH の環境を順に検索 |
| 4-2 | loading.jl | `base/loading.jl` | locate_package_env（477-548行目）: PkgId からファイルパスを特定 |
| 4-3 | loading.jl | `base/loading.jl` | start_loading / end_loading（2404-2424行目）: 並行ロードの排他制御 |
| 4-4 | loading.jl | `base/loading.jl` | register_root_module（2736-2758行目）: ロード済みモジュールのレジストリ登録 |

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

```
using PackageName
    │
    └─ require(Main, :PackageName)                    [loading.jl:2556]
           │
           └─ invoke_in_world(world, __require, Main, :PackageName)
                  │
                  └─ __require(Main, :PackageName)    [loading.jl:2576]
                         │
                         ├─ identify_package_env(Main, "PackageName")  [loading.jl:356]
                         │      └─ load_path() → for env in paths
                         │             └─ environment_deps_get(env, nothing, name)
                         │
                         └─ _require_prelocked(uuidkey, env)  [loading.jl:2687]
                                │
                                ├─ start_loading(uuidkey, 0, true)  [loading.jl:2404]
                                │
                                └─ __require_prelocked(uuidkey, env)  [loading.jl:2813]
                                       │
                                       ├─ locate_package_env(pkg, env)  [loading.jl:477]
                                       │
                                       ├─ _require_search_from_serialized(pkg, spec, ...)
                                       │      └─ プリコンパイルキャッシュからロード
                                       │
                                       ├─ compilecache(pkg, spec)  [キャッシュ失敗時]
                                       │
                                       └─ include(__toplevel__, path)  [最終フォールバック]
                                              └─ _include(identity, __toplevel__, path)

include(mod, "file.jl")
    │
    └─ _include(identity, mod, "file.jl")             [loading.jl:3180]
           │
           ├─ _include_dependency(mod, "file.jl")     [loading.jl:2440]
           │      └─ パス解決・依存関係追跡
           │
           ├─ include_callbacks の呼び出し
           │
           ├─ read(path, String)
           │
           └─ include_string(identity, mod, code, path)  [loading.jl:3085]
                  │
                  ├─ Meta.parseall(code) → AST
                  │
                  └─ for ex in ast.args
                         ├─ mapexpr(ex)
                         └─ Core.eval(mod, Expr(:toplevel, loc, ex))

evalfile("script.jl", ["arg1"])
    │
    └─ Module(:__anon__)                              [loading.jl:3225]
           └─ Core.eval(m, Expr(:toplevel,
                  :(const ARGS = args),
                  :(const include = IncludeInto(m)),
                  :(const eval = EvalInto(m)),
                  :(include(path))))
```

### データフロー図

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

using PackageName               → require(Main, :PackageName)         → Module PackageName
                                    identify_package_env → PkgId
                                    locate_package_env → path
                                    _require_search_from_serialized
                                    または include(__toplevel__, path)
                                    → loaded_modules[PkgId] = Module

include(mod, "file.jl")         → _include(identity, mod, path)      → 最後の式の結果
                                    _include_dependency → abs_path
                                    read(path) → code::String
                                    include_string(mod, code, path)
                                    Meta.parseall → AST
                                    Core.eval loop

include_string(mod, code)       → Meta.parseall(code) → AST          → 最後の式の結果
                                    for ex: Core.eval(mod, ex)

evalfile("script.jl", args)     → Module(:__anon__)                   → 最後の式の結果
                                    include(path) in anonymous module
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| loading.jl | `base/loading.jl` | ソース | require, include, include_string, evalfile, identify_package_env, locate_package_env 等の主要実装 |
| pkgid.jl | `base/pkgid.jl` | ソース | PkgId 構造体の定義。パッケージの UUID + 名前による識別 |
| initdefs.jl | `base/initdefs.jl` | ソース | LOAD_PATH, DEPOT_PATH の定義と初期化。load_path_expand 関数 |
| Base.jl | `base/Base.jl` | ソース | Base モジュールの定義。IncludeInto / EvalInto の定義 |
| module.jl | `base/module.jl` | ソース | _eval_import / _eval_using から require を呼び出すインタフェース |
| expr.jl | `base/expr.jl` | ソース | Meta.parseall の定義（include_string で使用） |
