Raspberry Pi Pico 向け TOPPERS/FMP3 を cmake でビルド出来るようにしてみた

# Raspberry Pi Pico 向け TOPPERS/FMP3 を CMake でビルドできるようにしてみた

VSCode で開発を行っていると、組み込み開発でも CMake を使ったプロジェクトをよく目にするようになりました。

VSCode の拡張機能には [Renesas Build Utilities](https://marketplace.visualstudio.com/publishers/RenesasElectronicsCorporation) や [STM32 VS Code Extension](https://marketplace.visualstudio.com/items?itemName=stmicroelectronics.stm32-vscode-extension) があり、今回の [Raspberry Pi Pico](https://marketplace.visualstudio.com/items?itemName=raspberry-pi.raspberry-pi-pico) でも CMake が採用されています。

今回は、 Raspberry Pi Pico 向け TOPPERS/FMP3 を CMake でビルドできるようにしたので、元の Makefile がどのような流れでビルドを行っているかの説明と、CMake でどのように実装したかを紹介したいと思います。

[https://toppers.jp/fmp3-e-download.html#raspi\_pico](https://toppers.jp/fmp3-e-download.html#raspi_pico)

今回作成したものは以下のリポジトリに公開しています。ターゲット非依存部のバージョンを「3.3.3」に更新しました。

[https://github.com/h7ga40/fmp3\_raspberrypi\_pico\_cmake](https://github.com/h7ga40/fmp3_raspberrypi_pico_cmake)

## 付属の Makefile のビルド手順

### 構成

まず、Makefile を用いたビルド手順を確認します。ビルドの前に次の手順を行い、ターゲットに応じた Makefile を生成します。それを用いてプロジェクトをビルドします。以下では、作業フォルダとしてbuildを使用します。

「Raspberry Pi Pico 簡易パッケージ」の「fmp3\_raspberrypi\_pico\_gcc-20211016.tar.gz」をダウンロードして展開してできた`fmp3`フォルダの中で下記のコマンドを実行します。

```bash
mkdir build
cd build
ruby ../configure.rb -T raspberrypi_pico_gcc -w -S "syslog.o banner.o serial.o logtask.o chip_serial.o"
```

これにより、実行したフォルダに、FMP3 の使用例である sample1 をビルドする Makefile が生成されます。続いて`make`コマンドでビルドします。これが付属のビルド方法です。

FMP3 のビルドプロセスには、静的 API を処理するための前処理が含まれています。また、組み込みソフトウェア特有の、ROM に書き込むためのファイル出力が後処理として実行されます。

### ビルド前処理

FMP3 は主に C 言語で記述されていますが、一部の低レベル処理（割り込み処理やディスパッチャ）はアセンブラで実装されています。
このようなケースでは、アセンブラからも C 言語で定義された構造体のメンバオフセットを利用する必要があります。しかし、アセンブラから直接 C 言語の構造体を参照することはできないため、必要な値を得るためのコードを C 言語で出力し、それをビルドして実行ファイルのバイナリダンプから値を取得する手順を取っています。

また、アプリケーションで使用するカーネルオブジェクトなどを`*.cfg`ファイルに静的 API で定義します。この`*.cfg`ファイルから情報を読み取って、C 言語で出力する工程があります。

この処理は、`cfg.rb`という Ruby スクリプトを用いて実行されます。
はじめの段階`pass 1`では、アプリケーションの`*.cfg`ファイルを起点にターゲットやカーネルの定義を読み取り、カーネルのビルドに必要な情報を取得します。その情報を元に`cfg1_out.c`を出力、ビルドし、実行ファイルから、シンボル情報出力とバイナリダンプをします。
続く`pass 2`では、アセンブラで使用する値を`offset.h`に出力し、アプリケーションで使用するカーネルオブジェクトなどの情報を`kernel_cfg.c`および`kernel_cfg.h`に出力します。

### ビルド

FMP3 のカーネルとターゲット依存部のソースコードをビルドして`libkernel.a`を生成します。この`libkernel.a`にはアプリケーション固有の情報は含まれていないので、別のアプリケーションにも使用することができます。

次に、アプリケーションをビルドします。FMP3にはアプリケーションの例として`sample1`が付属していますのでこれを使います。`sample1`はシリアルドライバとログ出力処理を使用していますので、それぞれのフォルダ`sample1`や`syssvc`、`library`の配下のソースファイルをビルドし、`libkernel.a`とリンクすることで、実行バイナリが生成されます。

### ビルド後処理

ビルドで出力される実行ファイルを、ターゲットに書き込むためのバイナリデータにします。

また、FMP3 では`pass 3`として収集した情報を元に、正しくメモリに配置されているかをチェックします。

## CMake で実装してみる

### ファイル構成

次に、CMake を用いたビルド手順について説明します。CMake では、Makefile と同様にターゲット依存部を起点としてビルド情報を収集する仕組みを取り入れています。

ソースコードやコンパイルオプションなどのビルド情報は、ターゲット依存部を参照することで、そこからアーキテクチャが参照され、ツールチェインが参照され、カーネルをビルドする情報をすべて得る仕組みとなっています。

![CMake_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/f47de9b3-902a-3999-a867-db25841c3452.png)

CMake 版では、下記のコマンド手順でビルドすることを目標にしました。メインの`CMakeLists.txt`をルートに置く、ファイル構成になっています。

```bash
mkdir build
cd build
cmake -G Ninja ..
cmake --build .
```

CMake では、まず`cmake -G Ninja ..`で構成を行います。`..`の部分は、ルートの`CMakeLists.txt`が置いてあるパスになるので、フォルダ構成を変えた場合はこのパスを変更します。

ターゲット依存部のフォルダ名は下記のように指定しています。`FMP3_TARGET`変数を使ってターゲットフォルダのパスを編集し、そのフォルダにある`target.cmake`を参照します。

```cmake
set(FMP3_TARGET raspberrypi_pico_gcc)
```

CMake のファイル参照方法には`include`と`add_subdirectory`があります。このうち、ビルド情報を集める目的で`include`を使用しています。
一方、カーネルは別途`CMakeLists.txt`を用意し、アプリケーションから`add_subdirectory`で読み込む構成にしました。
この方法により、カーネルをライブラリとして独立させ、コード変更がなければアプリケーション部分のみをビルドできるようにしています。また、複数のアプリケーションでカーネルライブラリを共有することも可能になります。

### プロジェクトを宣言

FMP3 のフォルダの`CMakeLists.txt`には、下記のように`project`で`fmp3`というプロジェクトであること、`add_library`でスタティックライブラリを出力するとことを宣言し、ビルドするソースコードを指定します。
`FMP3_TARGET`変数で指定された、ターゲット依存部のフォルダにある`target.cmake`を`include`で読み出して、ターゲットの情報を取得します。

```cmake
project(fmp3 C ASM)

include(${PROJECT_SOURCE_DIR}/target/${FMP3_TARGET}/target.cmake)

add_library(fmp3 STATIC
    :
    kernel/task.c
    :
)
```

ルートフォルダの`CMakeLists.txt`には、下記のように`project`で`sample1`というプロジェクトであること、`add_executable`で実行ファイルを出力するとことを宣言し、ビルドするソースコードを指定します。
また、`add_subdirectory`で`fmp3`フォルダの FMP3 プロジェクトをビルドして、`target_link_libraries`で`sample1`プロジェクトに`fmp3`ライブラリをリンクすることを指定します。

```cmake
project(sample1 C)

add_executable(sample1
    ${FMP3_ROOT_DIR}/sample/sample1.c
)

add_subdirectory(fmp3)

target_link_libraries(sample1 fmp3)

include(${FMP3_ROOT_DIR}/library/library.cmake)
include(${FMP3_ROOT_DIR}/syssvc/syssvc.cmake)
```

### CMakeによるビルド前処理の定義

CMake を使用してビルド前処理を実装する方法について説明します。
FMP3 では`pass 1`と`pass 2`のビルド前処理がありますが、これらは Ruby スクリプトを通じて実行されます。
コンパイルやリンク以外のコマンドを書き出すためには`add_custom_command`を使用します。
`pass 1`の記述内容は下記です。

```cmake
add_custom_command(
    OUTPUT cfg1_out.c
    WORKING_DIRECTORY ${CFG1_OUT_DIR}
    COMMAND ${CMAKE_COMMAND} -E echo "Generating cfg1_out.c"
    COMMAND ruby ${PROJECT_SOURCE_DIR}/cfg/cfg.rb --pass 1 --kernel fmp ${CFG_INCLUDE_DIRS} ${CFG_API_TABLES} ${CFG_SYMVAL_TABLES} -M ${CFG1_OUT_DIR}/cfg1_out_c.d ${FMP3_CFG_FILES}
    DEPENDS ${PROJECT_SOURCE_DIR}/cfg/cfg.rb ${FMP3_SYMVAL_TABLES} ${CFG_API_TABLE} ${FMP3_CFG_FILES}
    COMMENT "Running cfg pass 1 to generate cfg1_out.c"
)
```

- `OUTPUT`は、カスタムコマンドで出力されるファイルを指定します
- `WORKING_DIRECTORY`は、コマンドが実行されるディレクトリを指定します
- `COMMAND`は、実行するコマンドを順番に記述します
- `CMAKE_COMMAND`は、`cmake`コマンド自身を指し`-E echo`はメッセージを表示する指定です
- `DEPENDS`は、出力ファイルを生成するために必要な依存ファイルを指定します
- `COMMENT`は、コマンド実行時に表示されるメッセージを指定します

`COMMAND`で`${CFG_INCLUDE_DIRS}`など変数を使って複数のコマンド引数を指定できますが、変数が文字列になっていると一つのコマンド引数として扱われるので注意が必要です。

このようにして`pass 1`の処理を CMake に組み込むことができます。他のビルド処理や`pass 2`についても同様の方法で定義を行います。

次に、`pass 1`で出力した`cfg1_out.c`をビルドして実行ファイル`cfg1_out`を出力する手順を行います。これはアプリケーションの実行ファイルと同じように記述します。

```cmake
add_executable(cfg1_out cfg1_out.c)
```

第一引数がプロジェクト名ではないので、プロジェクトの最終生成物とは扱われないようです。たぶん。

ビルドした`cfg1_out`から情報を取り出すために、`nm`や`objdump`を実行しますが、これは`cfg.rb`の実行と同様に`add_custom_command`を使用します。

### CMakeによるビルド工程の定義

ビルドに必要な情報は、以下の関数を使用して指定します。

- `target_include_directories`：インクルードファイルの検索パスを指定します
- `target_compile_definitions`：プリプロセッサシンボルの定義を指定します
- `target_compile_options`：コンパイルオプションを指定します
- `target_link_options`：リンクオプションを指定します

FMP3プロジェクトでは、以下のように記述されています。

```cmake
target_include_directories(fmp3
    PUBLIC ${FMP3_INCLUDE_DIRS}
    PUBLIC ${FMP3_APP_INCLUDE_DIRS}
    PUBLIC ${KERNEL_CFG_DIR}
    PRIVATE ${PROJECT_SOURCE_DIR}/kernel
)

target_compile_definitions(fmp3
    PRIVATE ALLFUNC
    PUBLIC ${FMP3_COMPILE_DEFS}
)
```

`target_xxx`の第一引数で対象となるプロジェクトを指定します。
`PUBLIC`が付けられた項目は、`add_subdirectory`で取り込まれたプロジェクト（例: `sample1`）にも適用されます。
一方、`PRIVATE`が付けられた項目は、他のプロジェクトには影響を与えません。

### CMakeによるビルド後処理の定義

`pass 3`は、アプリケーションのビルド後に実行する処理ですが、`add_subdirectory`で取り込まれたプロジェクトから、元のアプリケーションのビルドプロセスを定義することはできません。
そこで、FMP3の`CMakeLists.txt`に関数を定義し、アプリケーション側からその関数を呼び出すことで、ビルド後処理を実行するようにしました。
以下は、`fmp3_cfg_check`関数の定義例です。

```cmake
set(FMP3_PASS3_ARGS ${PASS3_ARGS} PARENT_SCOPE)
set(FMP3_KERNEL_CFG_DIR ${KERNEL_CFG_DIR} PARENT_SCOPE)

function(fmp3_cfg_check TARGET)
    add_custom_command(TARGET ${TARGET} POST_BUILD
        WORKING_DIRECTORY ${FMP3_KERNEL_CFG_DIR}
        COMMAND ${CMAKE_COMMAND} -E echo "Running cfg pass 3 to check configuration"
        COMMAND ruby ${FMP3_PASS3_ARGS} --rom-symbol ${TARGET}.syms --rom-image ${TARGET}.srec
        DEPENDS ${TARGET}.syms ${TARGET}.srec
        COMMENT "Running cfg pass 3 to check configuration"
    )
endfunction()
```

- `function`の引数`TARGET`は、呼び出し元のプロジェクト名を受け取ります。例: `fmp3_cfg_check(sample1)`  
- `add_custom_command`の`TARGET ${TARGET} POST_BUILD`は、プロジェクト`${TARGET}`のビルド後に実行する処理を指定します。
  `TARGET`指定は`OUTPUT`指定と排他利用となります。

この設計には工夫が必要でした。
特に、この関数はアプリケーションのスコープで実行されるため、FMP3側で定義された変数が利用できません。
そのため、`set`コマンドに`PARENT_SCOPE`オプションを指定して変数を定義することで、アプリケーション側からもこれらの変数を参照できるようにしています。

## 注意点

### コマンドの記述

`COMMAND`でコマンドを記述する際は、以下の点に注意してください。
コマンド引数はスペースで区切られますが、変数が文字列の場合、一体化して引数として渡されてしまいます。

以下はエラーが発生する例です。

```cmake
set(OPTION "script.rb --version")
add_custom_command(TARGET ${TARGET} POST_BUILD
    COMMAND ruby ${OPTION}
)
```

この場合、`OPTION`変数が1つの引数として扱われるため、Rubyが`No such file or directory`エラーを出力します。
特にリスト形式の変数を加工して文字列化した場合、こうした問題が発生しやすいです。

### 構成とビルド

CMakeは構成時にすべてのビルドコマンドを記述します。
`add_custom_command`はコマンドの定義を行うものであり、構成時に実行されるわけではありません。
構成時にコマンドを実行したい場合は、`execute_process`を使用してください。

## 最後に

CMakeを用いた本格的なビルドプロセス記述は今回が初めてだったため、試行錯誤を重ねつつ実装しました。
何か誤解や認識の誤りがあれば、ご指摘いただけると幸いです。