C 言語サブディレクトリ対応の詳細と実装ガイド

1 概要

このドキュメントは、makefw および testfw フレームワークにおける C 言語ソースコードのサブディレクトリ対応について説明します。

大規模な C 言語プロジェクトでは、ソースコードを機能別にサブディレクトリに分割することが一般的です。
本フレームワークは、以下の観点からサブディレクトリ対応を提供します。

  1. ライブラリ: サブディレクトリに配置したソースファイルを単一のライブラリにリンク
  2. コマンド (実行ファイル): サブディレクトリに配置したソースファイルを単一の実行ファイルにリンク
  3. テスト: サブディレクトリごとのテストコードとテスト対象ソースの管理

2 サブディレクトリ対応の仕様

NO_LINK = 1

動作:

  • NO_LINK = 1 を設定したディレクトリでは、ソースファイルのコンパイルのみが行われます
  • リンク処理 (ライブラリ生成や実行ファイル生成) はスキップされます
  • コンパイル結果のオブジェクトファイル (.o / .obj) は、各サブディレクトリの obj/ に配置されます

配置先:

NO_LINK = 1 は、リンクを行う起点ディレクトリの makechild.mk に定義します。
makechild.mk は自ディレクトリには適用されず子階層以降にのみ有効であるため、起点ディレクトリでは通常のリンクが行われ、すべてのサブディレクトリではコンパイルのみが実行されます。

NO_LINK = 1

これにより、各サブディレクトリに個別の makepart.mkNO_LINK = 1 を定義する必要がなくなります。

2.2 オブジェクトファイルの自動収集

親ディレクトリ (リンクを行うディレクトリ) では、サブディレクトリのオブジェクトファイルが自動的に収集されます。

makelibsrc_c_cpp.mk / makesrc_c_cpp.mk より:

ifeq ($(OS),Windows_NT)
    SUBDIR_OBJS := $(shell find . -type d -name obj -not -path "./obj" -exec find {} -maxdepth 1 -type f -name "*.obj" \; 2>/dev/null)
else
    SUBDIR_OBJS := $(shell find . -type d -name obj -not -path "./obj" -exec find {} -maxdepth 1 -type f -name "*.o" \; 2>/dev/null)
endif
OBJS += $(SUBDIR_OBJS)

これにより、親ディレクトリでリンクを実行すると、サブディレクトリのオブジェクトファイルも含めてリンクされます。

2.3 再帰的 make 処理

makemain.mk は、makefile を含むサブディレクトリを自動検出し、再帰的に make を実行します。

SUBDIRS := $(sort $(dir $(wildcard */GNUmakefile */makefile */Makefile)))

ifneq ($(SUBDIRS),)
    .PHONY: $(SUBDIRS)
    $(SUBDIRS):
    @$(MAKE) -C $@ $(MAKECMDGOALS)

    # 主要なターゲットにサブディレクトリ依存を追加 (サブディレクトリを先に処理)
    default build clean test run restore rebuild: $(SUBDIRS)
endif

3 ライブラリのサブディレクトリ対応

3.1 ディレクトリ構造

prod/subfolder-sample/
+-- lib/                                    # ビルド済みライブラリ出力先
|   +-- liblibsubfolder-sample.so
+-- libsrc/
    +-- makefile                            # 再帰ビルド用
    +-- makepart.mk
    +-- libsubfolder-sample/
        +-- makefile                        # ライブラリ本体 (リンク実行)
        +-- makepart.mk                     # LIB_TYPE = shared 設定
        +-- makechild.mk                    # NO_LINK = 1 (サブフォルダはコンパイルのみ)
        +-- func.c                          # ルートのソースファイル
        +-- obj/
        |   +-- func.o
        +-- subfolder_a/
        |   +-- makefile                    # サブディレクトリ (makechild.mk により NO_LINK 適用)
        |   +-- func_a.c
        |   +-- obj/
        |       +-- func_a.o
        +-- subfolder_b/
            +-- makefile                    # サブディレクトリ (makechild.mk により NO_LINK 適用)
            +-- func_b.c
            +-- obj/
                +-- func_b.o

3.2 設定ファイルの内容

libsubfolder-sample/makepart.mk (起点ディレクトリ):

ifeq ($(OS),Windows_NT)
    # Windows
    CFLAGS   += /DSUBFOLDER_SAMPLE_EXPORTS
    CXXFLAGS += /DSUBFOLDER_SAMPLE_EXPORTS
endif

LIB_TYPE = shared

libsubfolder-sample/makechild.mk (起点ディレクトリ):

NO_LINK = 1

makechild.mk は自ディレクトリには適用されないため、libsubfolder-sample/ ではリンクが実行され、サブディレクトリ (subfolder_a/, subfolder_b/) ではコンパイルのみが行われます。

3.3 ビルドの流れ

  1. makelibsubfolder-sample/ で実行
  2. makemain.mk がサブディレクトリ subfolder_a/subfolder_b/ を検出
  3. 各サブディレクトリで make が再帰的に実行される
  4. サブディレクトリでは NO_LINK = 1 によりコンパイルのみ実行
  5. 親ディレクトリで全オブジェクトファイルを収集してライブラリを生成

生成されるライブラリ:

prod/subfolder-sample/lib/liblibsubfolder-sample.so

このライブラリには、func.ofunc_a.ofunc_b.o が含まれます。

4 コマンド (実行ファイル) のサブディレクトリ対応

4.1 ディレクトリ構造

prod/subfolder-sample/
+-- bin/                                    # ビルド済み実行ファイル出力先
|   +-- sample-app
+-- src/
    +-- makefile                            # 再帰ビルド用
    +-- makepart.mk
    +-- sample-app/
        +-- makefile                        # 実行ファイル本体 (リンク実行)
        +-- makechild.mk                    # NO_LINK = 1 (サブフォルダはコンパイルのみ)
        +-- sample-app.h                    # ヘッダーファイル
        +-- main.c                          # メインソースファイル
        +-- obj/
        |   +-- main.o
        +-- subfolder_a/
        |   +-- makefile                    # サブディレクトリ (makechild.mk により NO_LINK 適用)
        |   +-- helper_a.c
        |   +-- obj/
        |       +-- helper_a.o
        +-- subfolder_b/
            +-- makefile                    # サブディレクトリ (makechild.mk により NO_LINK 適用)
            +-- helper_b.c
            +-- obj/
                +-- helper_b.o

4.2 ソースコードの例

main.c:

#include <stdio.h>
#include "sample-app.h"

int main(void)
{
    int a = 10;
    int b = 20;

    printf("Testing subfolder make for src\n");
    printf("helper_a(%d) = %d\n", a, helper_a(a));
    printf("helper_b(%d) = %d\n", b, helper_b(b));
    printf("helper_a(%d) + helper_b(%d) = %d\n", a, b, helper_a(a) + helper_b(b));

    return 0;
}

subfolder_a/helper_a.c:

#include "../sample-app.h"

int helper_a(int value)
{
    return value * 2;
}

4.3 設定ファイルの内容

sample-app/makechild.mk (起点ディレクトリ):

NO_LINK = 1

ライブラリの場合と同様に、起点ディレクトリの makechild.mkNO_LINK = 1 を定義します。

4.4 ビルドの流れ

ライブラリと同様に、サブディレクトリのオブジェクトファイルが親ディレクトリに収集されてリンクされます。

生成される実行ファイル:

prod/subfolder-sample/bin/sample-app

5 テストのサブディレクトリ対応

5.1 ディレクトリ構造

test/src/subfolder-sample/
+-- makefile                                # 再帰ビルド用
+-- makepart.mk
+-- subfolder-sampleTest/
    +-- makefile                            # テスト本体 (リンク・テスト実行)
    +-- makepart.mk                         # TEST_SRCS 設定 (ルートのテスト対象)
    +-- makechild.mk                        # NO_LINK = 1 (サブフォルダはコンパイルのみ)
    +-- subfolder-sampleTest.cc             # ルートのテストコード
    +-- bin/
    |   +-- subfolder-sampleTest            # テスト実行ファイル
    +-- obj/
    |   +-- func.o                          # テスト対象ソースのオブジェクト
    |   +-- subfolder-sampleTest.o          # テストコードのオブジェクト
    +-- results/                            # テスト結果出力先
    |   +-- all_tests/                      # 全体テスト結果
    |   |   +-- summary.log
    |   |   +-- coverage.xml
    |   |   +-- func.c.gcov.txt
    |   |   +-- func_a.c.gcov.txt
    |   |   +-- func_b.c.gcov.txt
    |   +-- subfolder_sampleTest.test_func/
    |   |   +-- results.log
    |   |   +-- func.c.gcov.txt
    |   +-- subfolder_sampleTest_a.test_func_a/
    |   |   +-- results.log
    |   |   +-- func_a.c.gcov.txt
    |   +-- subfolder_sampleTest_b.test_func_b/
    |       +-- results.log
    |       +-- func_b.c.gcov.txt
    +-- subfolder_a/
    |   +-- makefile                        # サブディレクトリ (makechild.mk により NO_LINK 適用)
    |   +-- makelocal.mk                    # TEST_SRCS 設定 (サブディレクトリのテスト対象)
    |   +-- subfolder-sampleTest_a.cc       # サブディレクトリのテストコード
    |   +-- obj/
    |       +-- func_a.o
    |       +-- subfolder-sampleTest_a.o
    +-- subfolder_b/
        +-- makefile                        # サブディレクトリ (makechild.mk により NO_LINK 適用)
        +-- makelocal.mk                    # TEST_SRCS 設定 (サブディレクトリのテスト対象)
        +-- subfolder-sampleTest_b.cc       # サブディレクトリのテストコード
        +-- obj/
            +-- func_b.o
            +-- subfolder-sampleTest_b.o

5.2 設定ファイルの内容

subfolder-sampleTest/makepart.mk (起点ディレクトリ):

TEST_SRCS := \
    $(WORKSPACE_FOLDER)/prod/subfolder-sample/libsrc/libsubfolder-sample/func.c

subfolder-sampleTest/makechild.mk (起点ディレクトリ):

NO_LINK = 1

subfolder_a/makelocal.mk (サブディレクトリ):

TEST_SRCS := \
    $(WORKSPACE_FOLDER)/prod/subfolder-sample/libsrc/libsubfolder-sample/subfolder_a/func_a.c

テストでは、NO_LINK = 1 は起点ディレクトリの makechild.mk に、TEST_SRCS は各サブディレクトリの makelocal.mk にそれぞれ分離して定義します。
TEST_SRCSmakelocal.mk に配置することで、各テスト対象の指定が自ディレクトリに限定され、親階層に継承されません。

5.3 テストコードの例

subfolder-sampleTest.cc (親ディレクトリ):

#include <testfw.h>
#include <subfolder-sample.h>

class subfolder_sampleTest : public Test
{
};

TEST_F(subfolder_sampleTest, test_func)
{
    // Act
    int rtc = func(); // [手順] - func() を呼び出す。

    // Assert
    EXPECT_EQ(0, rtc); // [確認] - func() から 0 が返されること。
}

subfolder_a/subfolder-sampleTest_a.cc (サブディレクトリ):

#include <testfw.h>
#include <subfolder-sample.h>

class subfolder_sampleTest_a : public Test
{
};

TEST_F(subfolder_sampleTest_a, test_func_a)
{
    // Act
    int rtc = func_a(); // [手順] - func_a() を呼び出す。

    // Assert
    EXPECT_EQ(1, rtc); // [確認] - func_a() から 1 が返されること。
}

5.4 TEST_SRCS の自動収集

テスト実行スクリプト (exec_test_c_cpp.sh) は、サブディレクトリの makepart.mk から TEST_SRCS を自動的に収集します。

if [ -z "$TEST_SRCS" ]; then
    for makepart in $(find . -mindepth 2 -name "makepart.mk" 2>/dev/null); do
        subdir_test_srcs=$(grep -A10 "^TEST_SRCS" "$makepart" 2>/dev/null | \
            grep -v "^TEST_SRCS" | grep -v "^#" | grep -v "^--$" | \
            sed "s|\\\$(WORKSPACE_FOLDER)|$WORKSPACE_FOLDER|g" | \
            xargs 2>/dev/null)
        TEST_SRCS="$TEST_SRCS $subdir_test_srcs"
    done
fi

これにより、各サブディレクトリで個別にテスト対象を指定しつつ、全体のカバレッジレポートを生成できます。

6 テストレポートの構造

make test を実行すると、results/ ディレクトリにテストレポートが生成されます。

6.1 results/ ディレクトリ構造

results/
+-- all_tests/                              # 全体テスト結果
|   +-- summary.log                         # テストサマリー
|   +-- coverage.xml                        # 全体カバレッジ (Cobertura形式)
|   +-- func.c.gcov.txt                     # func.c のカバレッジ詳細
|   +-- func_a.c.gcov.txt                   # func_a.c のカバレッジ詳細
|   +-- func_b.c.gcov.txt                   # func_b.c のカバレッジ詳細
|   +-- lcov/                               # HTML カバレッジレポート (Linux)
+-- <テストクラス>.<テスト名>/              # 個別テスト結果
    +-- results.log                         # テスト実行ログ
    +-- <ソースファイル>.gcov.txt           # 個別テストのカバレッジ

6.2 summary.log の内容例

Test start on Sat Jan 24 07:55:31 JST 2026.
----
MD5 checksums of files in TEST_SRCS:
8c18e38566df7a9630b40ca18881a5d4  prod/subfolder-sample/libsrc/libsubfolder-sample/func.c
----
subfolder_sampleTest_a.test_func_a  PASSED
subfolder_sampleTest_b.test_func_b  PASSED
subfolder_sampleTest.test_func  PASSED
Test results:
----
Total tests 3
Passed      3
Warning(s)  0
Failed      0

------------------------------------------------------------------------------
                             Code Coverage Report
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
func.c                                         2       2   100%
func_a.c                                       2       2   100%
func_b.c                                       2       2   100%
------------------------------------------------------------------------------
TOTAL                                          6       6   100%
------------------------------------------------------------------------------

6.3 個別テスト結果 (results.log) の内容例

Running test: subfolder_sampleTest.test_func on bin/subfolder-sampleTest
----
## テスト項目

### 状態

### 手順

- func() を呼び出す。

### 確認内容 (1)

- func() から 0 が返されること。
----
TEST_F(subfolder_sampleTest, test_func)
{
    // Arrange

    // Pre-Assert

    // Act
    int rtc = func(); // [手順] - func() を呼び出す。

    // Assert
    EXPECT_EQ(0, rtc); // [確認] - func() から 0 が返されること。
}
----
./bin/subfolder-sampleTest --gtest_filter=subfolder_sampleTest.test_func
Running main() from .../gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from subfolder_sampleTest
[ RUN      ] subfolder_sampleTest.test_func
[       OK ] subfolder_sampleTest.test_func (0 ms)
[----------] 1 test from subfolder_sampleTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

6.4 カバレッジファイル (*.gcov.txt) の内容例

        -:    0:Source:func.c
        -:    0:Graph:.../obj/func.gcno
        -:    0:Data:.../obj/func.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <subfolder-sample.h>
        -:    2:
        1:    3:SUBFOLDER_SAMPLE_API int WINAPI func(void)
        -:    4:{
        1:    5:    return 0;
        -:    6:}

各行の先頭の数字は実行回数を示します:
- -: 実行対象外の行 (コメント、空行など)
- 1 以上: 実行された回数

7 サブディレクトリ対応のベストプラクティス

7.1 1. makefile の配置

各サブディレクトリに makefile を配置します。内容は標準テンプレートをそのまま使用します。


find-up = \
    $(if $(wildcard $(1)/$(2)),$(1),\
        $(if $(filter $(1),$(patsubst %/,%,$(dir $(1)))),,\
            $(call find-up,$(patsubst %/,%,$(dir $(1))),$(2))\
        )\
    )
WORKSPACE_FOLDER := $(strip $(call find-up,$(CURDIR),.workspaceRoot))

include $(WORKSPACE_FOLDER)/makefw/makefiles/prepare.mk

include $(WORKSPACE_FOLDER)/makefw/makefiles/makemain.mk

リンクを行う起点ディレクトリに makechild.mk を配置し、NO_LINK = 1 を定義します。
これにより、すべてのサブディレクトリに自動的に適用されます。

NO_LINK = 1

テストの場合は、各サブディレクトリの makelocal.mkTEST_SRCS を設定します。

TEST_SRCS := \
    $(WORKSPACE_FOLDER)/prod/.../subfolder_a/func_a.c

7.3 3. TEST_SRCS の重複回避

テストでは、親ディレクトリとサブディレクトリで TEST_SRCS が重複しないように注意してください。重複すると、同じソースファイルが複数回コンパイルされ、リンクエラーが発生します。

7.4 4. ヘッダーファイルの参照

サブディレクトリから親ディレクトリのヘッダーファイルを参照する場合は、相対パスを使用します。

#include "../sample-app.h"

または、makepart.mkINCDIR を設定します。

INCDIR += $(WORKSPACE_FOLDER)/prod/subfolder-sample/src/sample-app

8 まとめ

観点 設定ファイル 設定 説明
ライブラリ 起点の makechild.mk NO_LINK = 1 サブディレクトリではコンパイルのみ、起点でリンク
コマンド 起点の makechild.mk NO_LINK = 1 サブディレクトリではコンパイルのみ、起点でリンク
テスト 起点の makechild.mk NO_LINK = 1 サブディレクトリではコンパイルのみ、起点でリンク
テスト 各サブの makelocal.mk TEST_SRCS サブディレクトリごとにテスト対象を指定
テストレポート - results/ 個別テスト結果と全体カバレッジを出力