0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C言語×GitHub Actions CI#4】組込みC言語プロジェクトにCIを導入する:ハマりどころ編

0
Last updated at Posted at 2026-05-26

はじめに

本記事は全5部構成のシリーズです。

本記事では、CI環境の構築中に実際に遭遇したエラーとその対処法をまとめます。同じところでつまずいた方の参考になれば幸いです。また最後に、今回の構築でGitHub Copilotをどう活用したかについても触れます。

サンプルコードはこちらです。
https://github.com/YusukeHarada/c-sample


ハマりどころと対処

make test が成功するのにテストが1件も実行されない

状況: make test がexit 0で終了してCIも成功しているが、GoogleTestの [==========] 出力が一切出ない。

原因: main.cmain() が定義されており、-lgtest_main が提供する main() より優先されていた。リンカは .o ファイルをライブラリより先に処理するため、main_obj.omain() が採用され、GoogleTestのランナーが起動しなかった。

この問題の厄介な点はCIが成功しているように見えること。 テストが0件でもexit 0で終了するため気づきにくい。

対処: main.cmain() をテストビルド時にマクロで除外する。

#ifndef TESTING
int main()
{
    run_program();
    return 0;
}
#endif // TESTING
$(OUT_DIR)/main_obj.o: $(SRC)
    $(CC) $(CFLAGS) -DTESTING -c $(SRC) -o $(OUT_DIR)/main_obj.o

リンクエラー: undefined reference to 'add' / 'multiply' / 'run_program'

状況: make test を実行するとリンク時にエラーが発生する。

undefined reference to 'add'
undefined reference to 'multiply'
undefined reference to 'run_program'

原因: テストバイナリのリンク時に main.c のオブジェクトファイルが含まれていなかった。また、test_main.cc 側に main() が定義されており、GoogleTestの main() と競合していた。

対処:

  1. test_main.cc から独自の main() を削除する(GoogleTestが提供する main() を使う)
  2. main.hextern "C" ガードを追加する
#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);
int multiply(int a, int b);
void run_program();

#ifdef __cplusplus
}
#endif
  1. makefileでテストビルド時に main.c のオブジェクトを明示的にリンクする
$(OUT_DIR)/$(TEST_TARGET): $(OUT_DIR)/main_obj.o $(OUT_DIR)/main_test.o
    $(CXX) $(CXXFLAGS) -o $(OUT_DIR)/$(TEST_TARGET) \
        $(OUT_DIR)/main_obj.o $(OUT_DIR)/main_test.o $(GTEST_FLAGS)

$(OUT_DIR)/main_obj.o: $(SRC)
    $(CC) $(CFLAGS) -DTESTING -c $(SRC) -o $(OUT_DIR)/main_obj.o

main.c は必ず $(CC)(gcc)でコンパイルする点がポイントです。$(CXX)(g++)でコンパイルすると後述のカバレッジ問題にもつながります。


gcov がカバレッジデータを出力しない

状況: make coverage を実行してもカバレッジが表示されない。No executable lines というメッセージが出る。

原因: main.c がC++コンパイラ(g++)でコンパイルされていた。gcovはコンパイル時と収集時のコンパイラが一致している必要があり、C言語のソースはgccでコンパイルしないと .gcda / .gcno ファイルが正しく生成されない。

対処: makefileの coverage ターゲットで main.c を必ず $(CC)(gcc)でコンパイルするよう修正。また CFLAGSCXXFLAGS の両方にカバレッジフラグを付加する。

coverage: CXXFLAGS += $(COVERAGE_FLAGS)
coverage: CFLAGS += $(COVERAGE_FLAGS)
coverage: clean
    $(CC) $(CFLAGS) -DTESTING -c $(SRC) -o $(OUT_DIR)/main_obj.o   # gcc でコンパイル
    $(CXX) $(CXXFLAGS) -c $(TEST_SRC) -o $(OUT_DIR)/main_test.o
    ...

確認方法として、.gcda / .gcno ファイルの有無を確認してみてください。

find . -name '*.gcda' -o -name '*.gcno' -print

これらのファイルが生成されていない場合、カバレッジフラグが正しく付与されていません。make clean && make coverage で再ビルドしてみてください。


lcov がエラーで終了する

状況: make coverage-html を実行すると lcov がエラーで終了する。

lcov: ERROR: mismatch while reading ...

または

lcov: ERROR: unused input files ...

原因: 新しいバージョンの lcov では、コンパイル時とカバレッジ収集時のソースパスの不一致(mismatch)や、フィルタ後にカバレッジデータが空になった場合(unused)にデフォルトでエラーを返すようになっている。

対処: 各lcovコマンドに --ignore-errors オプションを追加する。

# キャプチャ時
lcov --directory $(OUT_DIR) --capture \
    --output-file $(OUT_DIR)/coverage/coverage.info \
    --ignore-errors mismatch

# フィルタ時
lcov --remove $(OUT_DIR)/coverage/coverage.info \
    '/usr/include/*' '/usr/local/*' '*/test_main.cc' \
    --output-file $(OUT_DIR)/coverage/coverage_filtered.info \
    --ignore-errors mismatch,unused

# HTML生成時
genhtml $(OUT_DIR)/coverage/coverage_filtered.info \
    --output-directory $(OUT_DIR)/coverage/html \
    --ignore-errors mismatch

actions/upload-artifact@v3 非推奨によるCI失敗

状況: GitHub ActionsのワークフローがNode.jsの非推奨警告とともに失敗する。

原因: actions/upload-artifact@v3 および actions/checkout@v3 がNode.js 16を使用しており、非推奨になっていた。

対処:

  1. actions/checkout@v4 に更新する
  2. actions/upload-artifact@v4 に更新する

CI環境でのGoogleTestビルド失敗

状況: ローカルでは通るのにCI(ubuntu-latest)でGoogleTestのリンクに失敗する。

原因: libgtest-dev のライブラリパスがディストリビューションのバージョンによって異なるため、ハードコードしたパスが通らないケースがある。

対処: Makefileの GTEST_FLAGS にライブラリパスを明示的に指定する。

GTEST_FLAGS = -L/usr/lib/x86_64-linux-gnu -lgtest -lgtest_main -pthread

cppcheck によるCI失敗

状況: ローカルでは make check が通るのにCIで失敗する。

原因: ローカルの cppcheck とCI環境の cppcheck のバージョンが異なり、検出される警告が異なっていた。また --error-exitcode=1 が付いていなかったため、ローカルでは警告が出ていても気づかなかった。

対処: makefileの check ターゲットに --error-exitcode=1 を追加し、ローカルとCIで同じ挙動にする。

check:
    cppcheck --enable=all --suppress=missingIncludeSystem --error-exitcode=1 $(SRC) main.h

おまけ:GitHub Copilotの活用

今回の構築ではGitHub Copilotを以下の場面で活用しました。

  • makefileの初期テンプレートの生成
  • ci.ymlのステップ構成の叩き台作成
  • GoogleTestのテストケース追加

特にci.ymlは「ゼロから書き起こす手間」が大幅に減りました。各ステップの基本的な構成はCopilotに任せて、細かい調整だけ自分で行うという流れが効率的でした。

一方で、C/C++混在のリンクエラーやlcovのバージョン差異によるエラーはCopilotだけでは解消できず、エラーメッセージを読んで自分で原因を追う必要がありました。「生成してもらったものが動かない場合の調査」は依然として自分のスキルが問われる部分です。

Copilotは「調べる時間・書き起こす時間を減らす」ツールとして非常に有効ですが、そのまま動くとは限らないという点は念頭に置いておく必要があります。


まとめ

本記事まで(#1〜#4)で、C言語プロジェクトへのGitHub Actions CI環境構築を一通り解説しました。

項目 使用ツール
ビルド gcc / make
単体テスト GoogleTest
静的解析 cppcheck
カバレッジ計測 gcovr / lcov
CI GitHub Actions

C言語とC++(GoogleTest)の混在環境やlcovのバージョン差異など、いくつかハマりどころはありましたが、一度整備してしまえばpushのたびに自動で品質チェックが走る環境が手に入ります。

次回は、GitLab CI経験者向けにGitHub Actionsのワークフロー構文を対比形式で解説します。


0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?