(5) やっぱりCMakeが分からない

やっぱりプロジェクトの構造、特にCMake,CMakeListsの使い方が良く分かりません。そこで、ESP-IDF Programming GuideでCMakeListsを検索したらこんな下りを見つけました。

簡単に訳すと

  • トップレベルの
    • CMakeLists.txt
      • プロジェクトをどの様にビルドするか設定するファイル
      • /tools/cmake/project.cmake ファイルをインクルードする。
    • sdkconfig
      • プロジェクト構成ファイル。
      • idf.py menuconfigの実行で作成/更新
      • プロジェクト内のすべてのコンポーネント(ESP-IDF自体を含む)の構成を保持。
    • Optional “components” directory
      • 再利用可能なコードやサードパーティコンポーネントを含めると便利
      • EXTRA_COMPONENT_DIRSをトップレベルのCMakeLists.txtに設定すると他の場所のコンポーネントを探す事が出来る
      • プロジェクトに多数のソースファイルがある場合、コンポーネントでグループ化することをお勧め。
      • このディレクトリーはプロジェクトに必ず有るとは限らない。
    • “main” directory
      • プロジェクトのソースコードを含む特別なコンポーネントディレクトリー。
      • 「main」はデフォルト名。変更可能。
      • CMake変数COMPONENT_DIRSにはこのコンポーネントが含まれている。
    • “build” directory
      • ビルド出力が作成される場所
      • このディレクトリに中間ビルドファイルを生成。
      • 中間オブジェクトファイルとライブラリ、および最終的なバイナリ出力ファイルが含まれる。

コンポーネントディレクトリーの構造に関して、

  • CMakeLists.txt
    • 全てのコンポーネントファイルが保有。
    • コンポーネントのビルドプロセス、およびプロジェクト全体への統合を制御するための変数定義が含まれる。
  • Kconfig file
    • menuconfigでオプションを設定できる様になる
    • プロジェクトの一部をオーバーライドするための特別なファイル、Kconfig.projbuildファイルとproject_include.cmakeファイルが有る。

分かった様な分からない様な。

サンプルプロジェクト(下記)を用いて詳細を説明しています。


- autoProject/
      - CMakeLists.txt
      - components/
                   - car/
                         - CMakeLists.txt
                         - car.c
                         - car.h
                   - engine/ 
                         - CMakeLists.txt
                         - engine.c
                         - include/
                                  - engine.h
                   - spark_plug/  
                         - CMakeLists.txt
                         - plug.c
                         - plug.h

理解を深める為に同じ様なプロジェクトを製作して説明通りに行こうと思います。先ずは下記のプロジェクトを作成します。


~/esp/autoProject/
      - CMakeLists.txt
      - components/
                   - car/
                         - CMakeLists.txt
                         - car.c
                         - car.h
                   - engine/ 
                         - CMakeLists.txt
                         - engine.c
                         - include/
                                  - engine.h
                   - spark_plug/  
                         - CMakeLists.txt
                         - plug.c
                         - plug.h
     - main/       
                   - CMakeLists.txt
                   - autoProject.c
  • プロジェクト直下の、
    • CMakeLists.txt
      •  
        # For more information about build system see   
        # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html   
        # The following five lines of boilerplate have to be in your project's   
        # CMakeLists in this exact order for cmake to work correctly   
        
        cmake_minimum_required(VERSION 3.5)   
        include($ENV{IDF_PATH}/tools/cmake/project.cmake)   
        set(EXTRA_COMPONENT_DIRS $ENV{HOME}/esp/autoProject/components)   
        project(autoProject)
        
        • 8行:set(EXTRA_COMPONENT_DIRS $ENV{HOME}/esp/autoProject/components)
        • メイン関数がcomponentsフォルダーの関数を使用する為ここで参照先を指定
        • $ENV{HOME}/esp/autoProject/componentsはcomponentsフォルダーのパス。
        • これでビルド時このパスが参照される。
    • mainフォルダー
      • autoProject.c
        •     
          #include <stdio.h&st; 
          #include "car.h"    
          #include "engine.h"    
          #include "plug.h"    
          void app_main(void)    
          {
                 printf("autoProject\n");
                 car();
                 engine();
                 plug();    
          } 
        • メイン関数。他で定義されている car(),engine(),plug()関数を実行。
        • それぞれのヘッダーの読み込みが必要と思われるが、プロジェクト直下のCMakeLists.txtでset(EXTRA_COMPONENT_DIRS 〜)を指定しているので不要。
      • CMakeLists.txt
        •   
          idf_component_register(SRCS "autoProject.c"
                                 INCLUDE_DIRS ".") 
          
        • ソースファイルと読み込み先の指定。
    • componentsフォルダー
      • carフォルダー
        • car.c
          •   
            #include <stdio.h&st;   
            #include "car.h"   
            void car()  
            {
                printf("%s needs ",CAR_MODEL);  
            }
             
          • 定義された文字列、”CAR_MODEL”を表示する関数
        • car.h
          •    
            #include "engine.h"  
            
            #ifdef ENGINE_IS_HYBRID #define CAR_MODEL "Hybrid"  
            #else  #define CAR_MODEL "Normal"  
            #endif  
            void car(void);  
            
          • 3から7行で、”CAR_MODEL”に定義する文字列を指定。ここで、engineフォルダーで定義されている ”ENGINE_IS_HYBRID”を使用。
        • CMakeLists.txt
          •   
            idf_component_register(SRCS "car.c"
                                   INCLUDE_DIRS "."
                                   REQUIRES engine) 
            
          • engineフォルダーで定義されている”ENGINE_IS_HYBRID”を使用するのでREQUIRES engine が必要
      • engineフォルダー
        • engine.c
          •   
            #include <stdio.h&st;   
            #include "engine.h"  
            #include "plug.h"  
            void engine()  
            {    
                printf("Engine. \nEngine needs ");  
            }   
            
            void sub_engine()  
            {
                printf("Sub_engine is %s",Sub_Eng);  
            } 
             
          • engine関数とsub_engine関数の定義。
          • sub_engine関数で使用されている”Sub_Eng”は”plug.h”で宣言されている。
          • もしengine()関数のみ外部から参照するなら、#include “engine.h” が、 sub_engine()関数を参照するなら、”#include plug.h”も必要になる。
        • includeフォルダー
          • engine.h
            •    
              #define ENGINE_IS_HYBRID    
              void engine(void);
              void sub_engine(void); 
              
            • ここで、ENGINE_IS_HYBRIDが定義されている。
        • CMakeLists.txt
          •   
            idf_component_register(SRCS "engine.c"
                                   INCLUDE_DIRS "include"
                                   PRIV_REQUIRES spark_plug) 
            
          • INCLUDE_DIRS “include” に注意
      • spark_plugフォルダー
        • plug.c
          •    
            #include <stdio.h&st;    
            #include "plug.h"   
            void plug(void)    
            {
                  printf("Plug\n");    
            }  
        • plug.h
          •   
            #define Sub_Eng "Option"  
            void plug(void); 
             
        • CMakeLists.txt
          •   
            idf_component_register(SRCS "plug.c"
                                    INCLUDE_DIRS ".")  
            

次に~/esp/autoProject/に移動しCPUの指定を行います。今回はESP32を使用するので、idf.py set-target esp32を実行します。実行後プロジェクトにsdkconfigファイルとbuild フォルダーが追加されます。


~/esp/autoProject/
      - CMakeLists.txt
      - components/
                   - car/
                         - CMakeLists.txt
                         - car.c
                         - car.h
                   - engine/ 
                         - CMakeLists.txt
                         - engine.c
                         - include/
                                  - engine.h
                   - spark_plug/  
                         - CMakeLists.txt
                         - plug.c
                         - plug.h
     - main/       
                   - CMakeLists.txt
                   - autoProject.c
     - sdkconfig
     - build/       
                   - ........
                   - ........

今回コンフィグの設定は有りません。idf.py menuconfigは省略。ビルド、モニターコマンドidf.py -p [USB port] flash monitorを実行。実行されるとモニターに以下が表示されます。


.......
........
I (308) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
autoProject
HYBRID_car needs 
Engine. Engine needs 
Plug

最後の4行が今回のプログラムの出力です。各表示とプログラムの関係は以下の様になっています。

  • autoProject ー> printf(“autoProject\n”);
  • HYBRID_car needs ー> car();
  • Engine. Engine needs ー> engine();
  • Plug ー> plug();

今度は、プロジェクト、コンポーネント直下の CMakeLists.txt の役割が何と無く理解で来た様な気がします。

プロジェクト直下のCMakeLists.txtの COMPONENT_DIRS と EXTRA_COMPONENT_DIRS

EXTRA_COMPONENT_DIRSは、補助的な関数(今回の例で言えば、car.c engine.c plug.c など)を個別のフォルダーにまとめプログラムを管理しやすくした状態で、ビルド時にコンパイラーそこを参照するように指定するのに使いました。これと似たコマンドにCOMPONENT_DIRSが有ります。両者の説明は以下の様になっています。

  • COMPONENT_DIRS:
    • コンポーネントを検索するためのディレクトリ。
    • デフォルトはIDF_PATH / components、PROJECT_DIR / components、およびEXTRA_COMPONENT_DIRSです。
    • これらの場所でコンポーネントを検索したくない場合は、この変数をオーバーライドします。
  • EXTRA_COMPONENT_DIRS:
    • コンポーネントを検索するための追加のディレクトリのオプションのリスト。
    • パスは、プロジェクトディレクトリからの相対パス、または絶対パスにすることができます。

これを今回のautoProjectに当てはめると、各検索パスは以下の様になります。

  • COMPONENT_DIRS:
    • デフォルト:IDF_PATH / component
    • PROJECT_DIR / components:autoProject / components
    • EXTRA_COMPONENT_DIRS:autoProject / components
  • EXTRA_COMPONENT_DIRS:
    • autoProject / components

だとすると、今回EXTRA_COMPONENT_DIRSで指定したパス autoProject / componentsは既にCOMPONENT_DIRSに設定されている事になります。

それを確かめる為に下記の作業をして見ました。

  • プロジェクト直下のCMakeLists.txtのset(EXTRA_COMPONENT_DIRS $ENV{HOME}/esp/autoProject/components)を削除してビルド
    • ===>問題無く実行出来ました。
  • その状態でcomponentsフォルダーをcompに変更してビルド
    • ===>エラー。 autoProject.cの”car.h”が見つからない
  • プロジェクト直下のCMakeLists.txtのset(EXTRA_COMPONENT_DIRS $ENV{HOME}/esp/autoProject/comp)に変更してビルド
    • ===>問題無く実行出来ました。

確かにCOMPONENT_DIRSに登録されている事が分かりました。そもそもこの2つどう使い分けるのか。

そこで、COMPONENT_DIRSのデフォルトパス IDF_PATH / component を見ると以下の様になっていました。


@G500:~/esp/esp-idf/components$ ls
README.md           esp_eth                esptool_py       protobuf-c
app_trace           esp_event              fatfs            protocomm
app_update          esp_gdbstub            freemodbus       pthread
asio                esp_hid                freertos         riscv
bootloader          esp_http_client        hal              sdmmc
bootloader_support  esp_http_server        heap             soc
bt                  esp_https_ota          http_parser      spi_flash
cmock               esp_https_server       idf_test         spiffs
coap                esp_hw_support         ieee802154       tcp_transport
console             esp_lcd                json             tcpip_adapter
cxx                 esp_local_ctrl         linux            tinyusb
driver              esp_netif              log              touch_element
efuse               esp_phy                lwip             ulp
esp-tls             esp_pm                 mbedtls          unity
esp32               esp_ringbuf            mdns             usb
esp32c2             esp_rom                mqtt             vfs
esp32c3             esp_serial_slave_link  newlib           wear_levelling
esp32h2             esp_system             nvs_flash        wifi_provisioning
esp32s2             esp_timer              openssl          wpa_supplicant
esp32s3             esp_websocket_client   openthread       xtensa
esp_adc_cal         esp_wifi               partition_table
esp_common          espcoredump            perfmon
@G500:~/esp/esp-idf/components$ 

ブルートゥース、WiFi、タイマー等のフォルダーが有りました。これらはシステムで予め用意されたコンポーネント群です。つまり、COMPONENT_DIRSはシステムが用意したコンポーネント群を指している様です。そう考えると、EXTRA_COMPONENT_DIRSはそれ以外に参照して欲しいパス(例えばユーザーが開発中のコンポーネントのパス)を指定する時に使用するとなります。

コンポーネント直下のCMakeLists.txtの REQUIRES と PRIV_REQUIRES

この2つも使い方が良く分かりません。ESP-IDF Programming Guideでは下記の様に説明しています。

  • REQUIRES should be set to all components whose header files are #included from the public header files of this component.
  • PRIV_REQUIRES should be set to all components whose header files are #included from any source files in this component, unless already listed in REQUIRES. Also any component which is required to be linked in order for this component to function correctly.

使い方は今回のサンプルから想像することにします。

次回は

次回は、Kconfigに付いて調べたいと思います。