fpm (Fortran Package Manager)とVSCodeの連携


# 概要
本記事では，fpmとVSCodeを連携させて，コマンド入力を簡略化する方法を説明します．
[過去の記事](https://qiita.com/implicit_none/items/d755d34e319b3bee78de)の更新版です．

fpmの基本的な使い方は下記の記事を参考にしてください．

- [fpm (Fortran Package Manager)の使い方](https://qiita.com/implicit_none/items/6a65ac1b83efefc782db)
- [fpm (Fortran Package Manager)の設定ファイルの解説](https://qiita.com/implicit_none/items/99c5d12f5c18733c66fd)
- [fpm (Fortran Package Manager)のプロジェクトの作成例](https://qiita.com/implicit_none/items/08a2d26f8288dda7b4f5)

# 環境
- Windows 10
- fpm 0.5.0
- gfortran 10.3.0 (TDM-GCC-64)
- VSCode 1.62.3
    - Modern Fortran拡張 2.6.2

Windowsで実行し，動作確認をしています．パスの区切りは`\`になっています．Unix系OSで動かす場合には，ドライブレターを無視し，パスの区切りを`/`に置き換えてください．

# 題材
fpmでビルドするプロジェクトとして，[fpm (Fortran Package Manager)のプロジェクトの作成例](https://qiita.com/implicit_none/items/08a2d26f8288dda7b4f5)で作成した，`non_number`プロジェクトを少し改変して利用します．

## non_numberプロジェクトの構造
`non_number`プロジェクトのうち，`src`, `test`, `example`がfpmプロジェクトのディレクトリです．それ以外に依存プロジェクトである`check`プロジェクトが`util`ディレクトリに，外部ライブラリであるstdlibが`lib`, `include`ディレクトリに置かれています．

`non_number`ライブラリにアプリケーションを追加しているところが改変点です．

```console
.
├── README.md
├── app
│   └── main.f90
├── example
│   └── demo_is_nan.f90
├── fpm.toml
├── include
│   └── stdlib_stats.mod
├── lib
│   └── libstdlib.a
├── src
│   └── non_number.f90
├── test
│   ├── test_is_nan_real32.f90
│   └── test_is_nan_real64.f90
└── util
    └── check
        ├── README.md
        ├── fpm.toml
        └── src
            └── check.f90
```

アプリケーション（`app\main.f90`の中）では，4要素の配列の中に`NaN`を一つ代入し，`is_nan`関数でそれを検出しています．

```Fortran:app/main.f90
program main
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    use :: non_number
    implicit none

    real(real32) :: f(4) = [1., 2., 3., 4.]
    logical :: f_nan(4)

    f(3) = ieee_value(f(3), ieee_quiet_nan)

    block
        integer(int32) :: i
        do i = 1, size(f)
            f_nan(i) = is_nan(f(i))
        end do
    end block

    if (all(f_nan .eqv. .true.)) then
        print *, "f does not have nan"
    else
        print *, "f has nan"
    end if
end program main
```

## プロジェクトのビルドとテスト
`fpm build`，`fpm run`および`fpm test`コマンドを実行し，プロジェクトが正しく実行できるのかを確認します．

```console
> fpm build --profile debug --flag "-Iinclude" --link-flag "-Llib -lstdlib"
> fpm run --profile debug --flag "-Iinclude" --link-flag "-Llib -lstdlib"
 f has nan
```

```console
> fpm test --profile debug --flag "-Iinclude" --link-flag "-Llib -lstdlib"
is_nan returns true for fp32 number that is nan
        True
is_nan returns false for fp32 number that is not nan
        True
is_nan returns true for fp64 number that is nan
        True
is_nan returns false for fp64 number that is not nan
        True
```

アプリケーションでは，`f`の中にある`NaN`を検出できています．テストも全てPassしており，アプリケーション，テスト共に正しく実行できています．

しかし，実行コマンドを見て判るように，fpmでは`build`, `run`, `test`の際にすべて同じコンパイラオプションを付ける必要があります．これをコマンド入力するのは煩わしいので，VSCodeと連携して簡略化します．

# VSCodeからfpmを使う
VSCodeのタスクを登録し，タスクを呼び出すことでfpmのコマンド入力を簡略化します．

いくつかのタスクはショートカットキーで呼び出せますし，タスク呼出し時に未保存のソースファイルを自動で保存してくれるなど，タスクとして登録することの利点は地味ですが無視できないと感じています．

## Modern Fortran拡張のエラー表示への対処
VSCodeを使うので，VSCodeのFortran拡張についても少し触れておきます．

`non_number`プロジェクトのディレクトリをVSCodeでを開くと，サブディレクトリやファイルにアクセスできるようになります．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/3006ec14-0f72-f2d9-33b6-2ae1aa0d2c98.png)

ここで，ファイル名が赤く表示されているのは，Modern Fortran拡張がファイル内でエラーを検出している状態です．`app\main.f90`内で，`non_number`モジュールを`use`するところで，`non_number.mod`が見つからないというエラーが生じています．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/44fbf480-b73a-6342-39b1-ee1393465438.png)

Modern Fortran拡張を用いると，モジュールとなるソースファイルを開いた時点で，`モジュール名.mod`というファイルが自動的に作成されます．`src`ディレクトリや`util\check\src`ディレクトリに表示されているモジュールファイルがそれらに相当します．Modern Fortran拡張は，それを利用してモジュールが存在するか，存在していればモジュール内で定義されている手続などを確認しています．fpmのプロジェクトではディレクトリ構造が定められているので，`.mod`ファイルが必ずしも同じ場所にあるとは限りません．

そこで，Modern Fortran拡張の設定を利用して，`.mod`ファイルが出力されるディレクトリを設定します．

fpmプロジェクトディレクトリ直下に`.mod`ディレクトリを作成し，そこに`.mod`ファイルが出力されるよう設定します．Modern Fortran拡張をプロジェクトごとに設定するには，fpmのプロジェクトディレクトリ下に，`.vscode\settings.json`ファイルを作成し，設定を記述します．既に存在している場合には既存の`settings.json`に追記します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/f072e4df-742f-b760-d390-81c62bdb4216.png)

```json:settings.json
{
    "fortran.linterModOutput": ".modディレクトリへのフルパス"
}
```

`fortran.linterModOutput`に，`.mod`ファイルを出力したいディレクトリを指定します．例えば，プロジェクトディレクトリが`D:\non_number`の場合，下記の様になります．Windowsでは，パスの区切りが`\`であるため，エスケープして`\\`と書きます．Unix系OSの場合はパスの区切りを`/`に置き換えるだけで問題ありません．

```json
{
    "fortran.linterModOutput": "D:\\non_number\\.mod"
}
```

相対パスは受け付けてくれません．VSCodeを使っているのであれば，ディレクトリを右クリックし，パスのコピーを選択すれば，簡単にフルパスを取得できます．

既に`.mod`ファイルがソースファイルと同じ場所に作られてしまっている場合，それらのファイルを`.mod`ディレクトリに移動します．あるいは，既に作られた`.mod`ファイルを削除した後に，モジュールとなるソースファイルを開くと，`.mod`ディレクトリにモジュールファイルが出力されることが確認できます．
参照されるモジュールファイルが全て作られた後，エラーが表示されていたソースファイル（`app\main.f90`）を開き，保存するとエラーが消えます．このとき，ファイルを変更する必要はなく，`ctrl`+`s`を入力する等，保存する動作を行うだけで構いません．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/cc1f2f04-be44-7050-07b1-ea4e6d25489e.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/1f268e22-ee79-6c86-7016-783546055ca0.png)

## ビルドタスクの設定
VSCodeのビルドタスクを設定し，ショートカットキーでビルドできるようにします．設定の方法は何通りかありますが，`.vscode\tasks.json`に設定を記述します．

[ターミナル]→[既定のビルドタスクの構成...]→[テンプレートからtasks.jsonを生成]→[Others]を選択し，tasks.jsonを作成します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/7e9c900e-8825-396f-6da1-2817a6425054.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/791485b4-8a61-db72-bc89-a1aa7183f50b.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/8ca9c2fd-ebfa-6f89-c23e-2b0d2f9741f8.png)

```jsonc:tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "echo",
            "type": "shell",
            "command": "echo Hello"
        }
    ]
}
```

`"label": "echo"`から始まる3行を消して書き換えます．

```jsonc:tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "fpm build",
            "type": "shell",
            "options": {
                "cwd": "${workspaceRoot}",
                "shell": {
                    "executable": "${env:windir}\\system32\\cmd.exe",
                    "args": [
                        "/d",
                        "/c"
                    ]
                }
            },
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "command": "fpm",
            "args": [
                "build",
                "--profile",
                "debug",
                "--flag",
                "\"-Iinclude\"",
                "--link-flag",
                "\"-Llib -lstdlib\""
            ]
        }
    ]
}
```

ここでは，重要な設定が4個あります．

- `"command": "fpm"`で，ビルドタスクとして実行されるコマンドを指定します．ここでは`fpm`を指定しています．
- `"args": [...]`で，`fpm`に渡すサブコマンドおよびオプションを指定します．[過去の記事](https://qiita.com/implicit_none/items/d755d34e319b3bee78de)では，`"command"`に`fpm build`を書いていましたが，VSCodeの仕様かfpmの仕様が変更になったため，サブコマンドは`args`に書くことになりました．
- `"options": {"cwd": "${workspaceRoot}"}`で，ビルドタスクが実行されるディレクトリが，必ずfpmプロジェクトのルートディレクトリとなるように指定しています．
- `"group": {"kind": "build","isDefault": true}`で，標準のビルドタスクであることを設定しています．この設定を行うことで，キーボードショートカット（`ctrl`+`shift`+`b`）を利用してこのタスクを起動できるようになります．

なお，`"options":`に`shell:`の項目はWindowsのみで必要で，Unix系OSの場合は必要ありません．この設定がないと，下記のようなメッセージが出力され，ビルドタスクが実行されたまま戻ってこなくなります．

```console
> Executing task: fpm build --profile debug --flag "-Iinclude" --link-flag "-Llib -lstdlib" <

パラメーターの書式が違います - /d
```

`ctrl`+`shift`+`b`でビルドタスクを実行すると，ターミナルが表示され，`fpm build`を実行した時の結果が表示されます．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/bccf1f30-b2e4-784c-f002-eeb0ca8e65af.png)

## テストタスクの設定
ビルドタスクと同様にして，テストタスクも構成できます．
ビルドタスクをコピペして，`"label"`，`args`のサブコマンド，`"group"`の`"kind"`を編集します．

```jsonc:tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "fpm build",
            中略
        },
        {
            "label": "fpm test",
            "type": "shell",
            "options": {
                "cwd": "${workspaceRoot}",
                "shell": {
                    "executable": "${env:windir}\\system32\\cmd.exe",
                    "args": [
                        "/d",
                        "/c"
                    ]
                }
            },
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "command": "fpm",
            "args": [
                "test",
                "--profile",
                "debug",
                "--flag",
                "\"-Iinclude\"",
                "--link-flag",
                "\"-Llib -lstdlib\""
            ]
        }
    ]
}
```

`"label"`を`"fpm test"`に，`"group"`の`"kind"`を`"test"`に，`"args"`のサブコマンドを`"test"`に変更しました．

デフォルトのテストタスクを実行するには，`ctrl`+`shift`+`p`を入力してコマンドパレットを呼び出し，`タスク: テスト タスクの実行`を選択します．`タスク: テスト タスクの実行`は，コマンドパレットに"run test task"と入力すると絞り込まれます．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/e00338a7-e8da-5dae-4d88-41fc89c788a6.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/07c99134-dc90-0e2b-bdbe-ede819476a28.png)

テストタスクを実行するショートカットキーは定められていないので，各自で設定する必要があります．設定の方法は[過去の記事](https://qiita.com/implicit_none/items/d755d34e319b3bee78de#テストタスクの設定)を参考にしてください．

## その他のタスクの設定
### fpm run
ビルドしたアプリケーションを実行する場合もビルドと同じオプションが必要になるので，設定しておいた方が楽はできます．ビルドタスクをコピペしてテストタスクを作成したように，コピペして設定を若干変更します．

変更点は，下記の通りです．

- `"label"`を`"fpm run"`に変更
- `"group"`を`: "none"`に変更
    - 特定のタスクグループには属さないタスクとして登録します．
- `"args"`のサブコマンドを`"run"`に変更

```jsonc:tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "fpm build",
            中略
        },
        {
            "label": "fpm test",
            中略
        },
        {
            "label": "fpm run",
            "type": "shell",
            "options": {
                "cwd": "${workspaceRoot}",
                "shell": {
                    "executable": "${env:windir}\\system32\\cmd.exe",
                    "args": [
                        "/d",
                        "/c"
                    ]
                }
            },
            "group": "none",
            "command": "fpm",
            "args": [
                "run",
                "--profile",
                "debug",
                "--flag",
                "\"-Iinclude\"",
                "--link-flag",
                "\"-Llib -lstdlib\""
            ],
            "problemMatcher": []
        }
    ]
}
```

タスクを実行するには，`ctrl`+`shift`+`p`を入力してコマンドパレットを呼び出し，実行するタスクを選択します．コマンドパレットに"run task"と入力すると，タスクの一覧が表示されるので，実行したいタスク（fpm run）を選択します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/06ae6b89-1654-270c-9305-09989b672014.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/49a534f9-c1ee-8265-b600-b204edc8299f.png)

タスクを実行する際に，`スキャンするタスク出力のエラーと警告の種類を選択`というメニューが表示された場合は，`タスクの出力をスキャンせずに続行`を選択します．このメニューを表示しないようにするには，タスクの設定に`"problemMatcher": []`を追加します．

### fpm install
[fpm (Fortran Package Manager)の使い方](https://qiita.com/implicit_none/items/6a65ac1b83efefc782db)で解説していますが，fpmはコンパイラやビルドオプションに応じて異なるビルドディレクトリ名を作成し，その中でビルドを実行します．そのため，オプションやコンパイラを切り替えてビルドしていると，デバッグするバイナリがどこにあるのか判らなくなります．デバッグ用の設定ファイル（"launch.json"）に実行するバイナリを記述したくても，パスが判らないという事態に陥ります．

そこで，`.\build\bin`にビルドしたバイナリをコピーし，それをデバッガに渡すことで設定の共通化を図ります．`.\build\bin`にビルドしたバイナリをコピーするために，`fpm install`をタスクとして登録します．

`fpm run`をコピペし，若干変更します．変更点は，下記の通りです．

- `"label"`を`"fpm install"`に変更
- `"args"`のサブコマンドを`"install"`に変更
- `"args"`にインストールディレクトリを指定するオプション`"--prefix", "./build"`を追加

```jsonc:tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "fpm build",
            中略
        },
        {
            "label": "fpm test",
            中略
        },
        {
            "label": "fpm run",
            中略
        },
        {
            "label": "fpm install",
            "type": "shell",
            "options": {
                "cwd": "${workspaceRoot}",
                "shell": {
                    "executable": "${env:windir}\\system32\\cmd.exe",
                    "args": [
                        "/d",
                        "/c"
                    ]
                }
            },
            "group": "none",
            "command": "fpm",
            "args": [
                "install",
                "--profile",
                "debug",
                "--flag",
                "\"-Iinclude\"",
                "--link-flag",
                "\"-Llib -lstdlib\"",
                "--prefix",
                "./build"
            ],
            "problemMatcher": []
        }
    ]
}
```

タスクを実行するには，`ctrl`+`shift`+`p`を入力してコマンドパレットを呼び出し，実行するタスクを選択します．コマンドパレットに"run task"と入力すると，タスクの一覧が表示されるので，実行したいタスク（fpm install）を選択します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/fd3eefd9-0406-e95f-24aa-6395b74256cc.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/934c2e45-a2e1-3e66-b1a4-0b465c366161.png)

実行すると，`build\gfortran_24048832C8A119A4`をビルドディレクトリとしてビルドされた実行ファイル`non_number.exe`が，`.\build\bin`にコピーされていることが確認されます．

`fpm install`をタスクとして登録しておくことで，異なるオプションでビルドする度に，このディレクトリを探して変更する必要がなくなります．

# デバッグの設定
ビルドしたアプリケーションのデバッグも，VSCodeで可能です．VSCodeではいくつかのデバッガと統合されており，VSCode内と統合されたインタフェースを利用してデバッグできます．gdbもVSCodeと統合されているので，gfortranの場合はVSCode内でデバッグが可能です．

なお，ここで述べるデバッグの設定は，[インストールタスク](#fpm-install)を実行して，バイナリを`.\build\bin`にコピーすることを前提としています．

デバッグの設定は，`.vscode\launch.json`に記述します．

[実行]→[デバッグの開始]→[Fortran (GDB)]を選択し，launch.jsonを作成します．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/855a4542-19ac-89c5-0efb-63539d7844b6.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/d2e3862c-f7c3-a8bc-2074-2ec4393d04c6.png)

```json:launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 起動",
            "type": "cppdbg",
            "request": "launch",
            "program": "プログラム名を入力してください (例: ${workspaceFolder}/a.exe)",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/path/to/gdb",
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "逆アセンブリ フレーバーを Intel に設定",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}
```

既定の`launch.json`の内容を書き換え，デバッグできるように設定します．

- `"program": `を，`fpm install`タスクでコピーしたバイナリの名前に変更
    - 今回は`${workspaceFolder}/build/bin/non_number.exe`に変更する．
- `"stopAtEntry": `を`true`に変更
    - この設定がないとただバイナリが実行されて終了するだけになる．
- `"cwd": `を`${workspaceFolder}`に変更
    - fpmプロジェクトのルートで実行する設定．
- `"miDebuggerPath": `を`gdb`に変更
    - gdbにパスを通しているため．パスを通していない場合は，gdbへのフルパスに変更する．

`configurations:`の内容は下記のようになりました．

```json:launch.json
    "configurations": [
        {
            "name": "(gdb) 起動",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/bin/non_number.exe",
            "args": [],
            "stopAtEntry": true,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "逆アセンブリ フレーバーを Intel に設定",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }
    ]
```

この状態で，再び[実行]→[デバッグの開始]を実行すると，デバッグが開始されます．ブレークポイントを設定するには，VSCodeの拡張機能の一つであるFortran Breakpoint Supportをインストールします．

ステップインをクリックしてデバッグを進めていくと，左のデバッグウィンドウに表示された変数の値が変化していくことを確認できます．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/7695aa76-42e0-5aeb-2da7-857fd13b0c15.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/db10938f-c576-4a31-f933-e428044db76d.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/040b125a-4f58-c579-1441-54e5af13b89c.png)

表示だけでなく，メモリを直接編集することや，特定の変数や式を常に監視し続けることも可能です．

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/15ed404e-8077-46b5-4fc0-2401a2b5a174.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224040/6aab949f-e9bb-6b60-0380-56906b905a19.png)


# まとめ
fpmとVSCodeを連携する方法を紹介しました．

VSCodeを利用することで，fpm特有の煩わしさはかなり改善できるようになりました．しかし，タスクごとにコンパイラオプションを設定する必要があり，まとめて変更する事はできません．

将来的にはコンパイラオプションは`fpm.toml`に記述できるようになるので幾分簡略化できますが，このあたりの挙動はもう少し改善してほしいと思います．