2020年9月27日日曜日

Arduinoのライブラリのsrcにあるcppファイルは、対応するヘッダファイルが参照されてなくても全てビルドされている


背景

Arduino向けの便利関数をutils_asukiaaaというライブラリにまとめていたところ、stringに関する機能だけを使いたかったのに、ヘッダファイルを参照していないwireに関するエラーが表示されました。

対応を探るため、どういうことか調べてみました。

使ったもの

  • Arduino IDE 1.8.13
  • PlatformIO 5.0.1

挙動

下記のsrringに関するサンプルプログラムをPlatformIOでビルドするとエラーが発生しました。
#include <Arduino.h>
#include <utils_asukiaaa.h>
#include <utils_asukiaaa/string.h>

void setup() {
Serial.begin(9600);
}

void loop() {
unsigned long nowMs = millis();
Serial.println(nowMs);
Serial.println(utils_asukiaaa::string::padStart(String(nowMs), 10, ' ') + "ms");
Serial.println(utils_asukiaaa::string::padEnd(String(nowMs), 10, ' ') + "ms");
}

utils_asukiaaa/wire.hはincludeしてないのに、utils_asukiaaa/wire.cppをビルドして、その中で必要なWireが見つからなくてエラーになっているようでした。
ちなみに、utils_asukiaaa.hをincludeする行を消しても挙動は同じでした。
In file included from /home/asuki/pio/shared/utils_asukiaaa/src/utils_asukiaaa/wire.cpp:1:0:
/home/asuki/pio/shared/utils_asukiaaa/src/utils_asukiaaa/wire.h:5:18: fatal error: Wire.h: No such file or directory

**************************************************************
* Looking for Wire.h dependency? Check our library registry!
*
* CLI > platformio lib search "header:Wire.h"
* Web > https://platformio.org/lib/search?query=header:Wire.h
*
**************************************************************

compilation terminated.
*** [.pio/build/leonardo/libd72/utils_asukiaaa/utils_asukiaaa/wire.cpp.o] Error 1

Arduino IDEではエラーは発生しませんでした。


ビルドの内容を確認

PlatformIOではWireが足りないことでエラーになる

下記のようなコマンドでcleanしてビルドすることで、どのファイルをビルドしているのか確認できます。
pio run -t clean && pio run

mainとutils_asukiaaa配下のcppを全てビルドしたあと、それらを結合する際にエラーが発生しているようでした。
Compiling .pio/build/leonardo/src/main.cpp.o
Compiling .pio/build/leonardo/libd72/utils_asukiaaa/utils_asukiaaa/button.cpp.o
Compiling .pio/build/leonardo/libd72/utils_asukiaaa/utils_asukiaaa/string.cpp.o
Compiling .pio/build/leonardo/libd72/utils_asukiaaa/utils_asukiaaa/wire.cpp.o
Archiving .pio/build/leonardo/libFrameworkArduinoVariant.a
Compiling .pio/build/leonardo/FrameworkArduino/CDC.cpp.o
Indexing .pio/build/leonardo/libFrameworkArduinoVariant.a
In file included from /home/asuki/pio/shared/utils_asukiaaa/src/utils_asukiaaa/wire.cpp:1:0:
/home/asuki/pio/shared/utils_asukiaaa/src/utils_asukiaaa/wire.h:5:18: fatal error: Wire.h: No such file or directory

Arduino IDEではWireが使われていることを認識して、それもビルドしている

Arduino IDEは詳細設定で「より詳細な情報を表示する」の「コンパイル」を有効にすると、ビルド内容が表示されるようになります。


Arduino IDEではログが大量に表示されるため、必要そうなところだけを抜粋して共有します。


PlatformIOと同様に、utils_asukiaaa配下のcppファイルは全てビルドされているようでした。
Using cached library dependencies for file: /home/asuki/Arduino/libraries/utils_asukiaaa/src/utils_asukiaaa/button.cpp
Using cached library dependencies for file: /home/asuki/Arduino/libraries/utils_asukiaaa/src/utils_asukiaaa/string.cpp
Using cached library dependencies for file: /home/asuki/Arduino/libraries/utils_asukiaaa/src/utils_asukiaaa/wire.cpp

PlatformIOと異なる点として、上記のログのあとWireが使われていることを認識し、Wireのビルドが行われていました。
Alternatives for Wire.h: [Wire@1.0]
ResolveLibrary(Wire.h)
-> candidates: [Wire@1.0]
Using cached library dependencies for file: /home/asuki/arduino-1.8.13/hardware/arduino/avr/libraries/Wire/src/Wire.cpp
Using cached library dependencies for file: /home/asuki/arduino-1.8.13/hardware/arduino/avr/libraries/Wire/src/utility/twi.c

Arduino IDEではWireが使われていることを認識してWireも含んだ実行ファイルを生成してくれるため、PlatformIOのようにWireが足りないエラーが発生しなかったようです。

ビルドが通れば良いなら、Wireを追加すればPlatformIOでもビルドはできる

Arduino IDEではWireが自動的にビルド対象についかされていたので、プログラムにWire.hのincludeを追加すれば、PlatformIOでもビルドできるようにはなります。
#include <Arduino.h>
#include <Wire.h>
#include <utils_asukiaaa.h>
#include <utils_asukiaaa/string.h>

しかしながら、setupでもloopでもWireを使わないのにWireが必要なのは、実行ファイルの容量が無駄に増えて嬉しくありません。

ファイルの容量を小さくしたいなら、ライブラリを分割する必要がありそう

includeしてないヘッダファイルで必要なcppファイルがビルド対象になってプログラムに取り込まれるため、現状のファイル構成とビルド環境では手の打ちようがなさそうです。
utils_asukiaaa
├ button.cpp
├ button.h
├ string.cpp
├ string.h
├ wire.cpp
└ wire.h

自分が思いつくのは、分割できる単位でライブラリとして分割する案です。
button_asukiaaa.cpp
button_asukiaaa.h
string_asukiaaa.cpp
string_asukiaaa.h
wire_asukiaaa.cpp
wire_asukiaaa.h
こうすれば必要なライブラリのcppだけがビルドされるようになり、stringに関する機能を使うためにWireを含めるようなことはしなくて良くなりそうです。

ヘッダファイルに処理を書けば?-> .hppにすれば良さそう

ヘッダファイルにcppの内容を記述すればヘッダファイルを参照した時だけビルドされるので、今回のような不要な処理のビルドは避けられます。

hファイルに実装を書いていると、依存関係が複雑になった際にエラーが発生することがあります。
hppファイルなら実装込みの記述が想定されているので、期待通りにビルドできました。

cでヘッダファイルに実装を書くと、ライブラリとして使い回す場合にエラーになることがある

まとめ

PlatformIOでもArduino IDEでもライブラリのsrcに含まれるcppファイルを全てビルドして実行ファイルに含めることが分かりました。

汎用的な便利機能をまとめるライブラリを作っていましたが、まとめず分割することを決めました。
分割しなくても必要なcppファイルだけをビルドする方法をご存知でしたら、コメントなどで教えていただけるととても嬉しいです。

変更履歴

2021.10.06
ヘッダファイルに実装を書くのは良くないとしていましたが、hppファイルなら期待通りにビルドできる場合があったので、hppファイルを推奨しました。

0 件のコメント :