Kconfig与CMake初步模块化工程

Arm交叉编译的环境,这个是编译的必需品,选择10.3的经典版本,实际上选择最新版(13.3和14.2)也没问题

Version 10.3-2021.10

https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads

14.2.Rel1 版本

https://developer.arm.com/-/media/Files/downloads/gnu/14.2.rel1/binrel/arm-gnu-toolchain-14.2.rel1-mingw-w64-x86_64-arm-none-eabi.exe

安装时勾选加入环境变量,省的手动添加

MinGW64

https://github.com/niXman/mingw-builds-binaries/releases

还需要mingw64的make工具链,需要手动加入环境变量,最好顺手把mingw32-make.exe改成make.exe,不然用起来很麻烦

CMake

https://cmake.org/download/

安装时勾选加入环境变量,省的手动添加

Kconfig-Python

还需要python环境和对应的包,某些情况可能会用到离线包,这里也一起说明

image-20250222185916218

注意python安装时的tcl/tk 这个需要安装,否则会缺少tkinter,打不开kconfig gui

核心就是这两个包得安装

pip install kconfiglib
pip install windows-curses

如果使用whl的离线安装包,就可以这样安装

pip install windows_curses-2.4.1-cp313-cp313-win_amd64.whl
pip install kconfiglib-2.2.2-py2.py3-none-any.whl

架构

https://github.com/elmagnificogi/CMake_Kconfig_Example

Demo示例写在这里,会随着我不断完善这个例子

结构

核心就几个文件夹,把各个模块解耦,组合到一起就行了

  • application,系统的入口,启动文件,调用各个module完成业务
  • driver,驱动层,驱动各种具体的设备
  • module,任务层,实现各种业务细节
  • platform,硬件层,给driver层提供硬件接口,实际使用的MCU的HAL或者标准驱动层
  • rtos,使用的操作系统
  • tools,完成Kconfig转换的代码
  • cmake,编译器或者跨平台的相关CMake代码

这里重点是如何利用CMake和Kconfig进行组合,不是架构内的细节代码要怎么写

使用方法

配置工程

python -m guiconfig

生成头文件

python tools/kconfig.py Kconfig .config autoconf.h kconfigLog.txt .config

预设

cmake -S . -B build/Debug --preset Debug

make

cmake --build build/Debug --target project-debug-make

编译

cmake --build build/Debug --target project-debug-build

清空

cmake --build build/Debug --target project-debug-clean

解析

实现细节

如果是Makefile+Kconfig,平常也是把Kconfig的宏引入到Makefile中,同理CMake+Kconfig也需要,否则只能通过宏去括起来整个文件,那种方式就非常麻烦

ifdef CONFIG_XXX
...
endif
  • 有很多代码都需要挨个加这个宏,那可太费劲了

最关键的点就是这里,将Kconfig生成的宏利用CMake读取进来,并且转换成CMake的宏

# Add sources and includes
file(READ "autoconf.h" config_content)
#message(STATUS "Found config macro: ${config_content}")
string(REGEX MATCHALL "CONFIG_[A-Z0-9_]+" config_macros ${config_content})

foreach(macro ${config_macros})
    #message(STATUS "Found config macro: ${macro}")
    set(${macro} ${macro})
    #target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE ${macro})
endforeach()

还有一种方式就是通过python独去Kconfig,然后转成一个CMake文件,再被主CMakeLists引入,不过这样就需要依赖python,我不太喜欢,实现代码如下

import os


def parse_config_file_cmake(config_file):
    config_vars = {}
    with open(config_file, 'r') as f:
        for line in f:
            # Skip comments and empty lines
            if line.startswith('#') or line.strip() == '':
                continue
            # Parse configuration line
            key, value = line.strip().split('=', 1)
            # Remove 'CONFIG_' prefix from the key name
            key = key[7:] if key.startswith('CONFIG_') else key
            # Handle quotes in the value
            if value.startswith('"') and value.endswith('"'):
                value = value[1:-1]
            # Add to dictionary
            config_vars[key] = value
    return config_vars


def write_cmake_file(config_vars, cmake_file):
    with open(cmake_file, 'w') as f:
        f.write('# Automatically generated by config_to_cmake.py\n')
        for key, value in config_vars.items():
            f.write('set({} "{}")\n'.format(key, value))


def convert_config_to_cmake(config_file, cmake_file):
    config_vars = parse_config_file_cmake(config_file)
    write_cmake_file(config_vars, cmake_file)


def parse_config_file_header(config_file):
    config_vars = []
    with open(config_file, 'r') as f:
        for line in f:
            # Skip comments and empty lines
            if line.startswith('#') or line.strip() == '':
                continue
            # Parse configuration line
            key, value = line.strip().split('=', 1)
            # If the value is 'y', replace it with a space
            value = value.strip()
            if value == 'y':
                value = '1'
            # Remove 'CONFIG_' prefix from the key name
            key = key[7:] if key.startswith('CONFIG_') else key
            # Add to list
            config_vars.append((key, value))
    return config_vars


def write_config_header(config_vars, header_file):
    with open(header_file, 'w') as f:
        f.write('// Automatically generated by config_to_header.py\n')
        f.write('#ifndef __KCONFIG_H__\n')
        f.write('#define __KCONFIG_H__\n\n')

        for key, value in config_vars:
            f.write('#define {} {}\n'.format(key, value))

        f.write('\n#endif // __KCONFIG_H__')


def convert_config_to_header(config_file, header_file):
    config_vars = parse_config_file_header(config_file)
    write_config_header(config_vars, header_file)


config_file = '.config'       # Path to your .config file
cmake_file = 'kconfig.cmake'  # Path to the generated kconfig.cmake file
header_file = 'kconfig.h'     # Path to the generated kconfig.h file

# Convert .config file to kconfig.cmake file
convert_config_to_cmake(config_file, cmake_file)

# Convert .config file to kconfig.h file
convert_config_to_header(config_file, header_file)

每个需要做选择的地方都放置一个kconfig,用来生成宏,在对应上级的位置引入这个kconfig

menu "Driver Configuration"
  config LED
      bool "LED"

  config Motor
      bool "Motor"
endmenu

有了宏以后就可以通过宏来控制文件编译

# sources

if(CONFIG_LED)
    target_sources(${CMAKE_PROJECT_NAME} PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/driver/led.c
    )
    # include
    target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 
        ${CMAKE_CURRENT_SOURCE_DIR}/driver
    )
endif()

if(CONFIG_MOTOR)
    file(GLOB_RECURSE DRIVER_SOURCES
        # 加入文件夹
        ${CMAKE_CURRENT_SOURCE_DIR}/driver/motor/*.*
    )
    target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${DRIVER_SOURCES})

    # include
    target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 
        ${CMAKE_CURRENT_SOURCE_DIR}/driver/motor
    )
endif()
小技巧

利用 dir /b指令可以快速获取当前文件夹内各文件的名称,从而给AI去帮我们写模板化的代码(AI无法获取文件结构)

image-20250222175500661

不仅仅kconfig可以这么处理,实际上cmake也可以这么处理,快速将已有的文件加入到某个编译选型内

  • 注意检查,copilot偶尔会写错,比如之前把某两个字母顺序写倒了,编译半天总是报错,才发现路径不对

其他

记录一些CMake的操作

CMake排除文件,使用正则的方式进行排除

file(GLOB_RECURSE USER_SOURCES
    "libs/*.*"
)

将含有math/vector路径、desperate的文件排除
list(FILTER USER_SOURCES EXCLUDE REGEX "desperated")
list(FILTER USER_SOURCES EXCLUDE REGEX "math/vector")

还有一种使用删除进行删除

file(GLOB_RECURSE FILE *.cpp *.c *.h)
 
file(GLOB_RECURSE WEBRTC_FILE webrtc_test.*  ${CMAKE_CURRENT_SOURCE_DIR}/src/webrtc_*)
 
file(GLOB_RECURSE WEBRTC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/webrtc/*)
list(REMOVE_ITEM FILE ${WEBRTC_FILE})
list(REMOVE_ITEM FILE ${WEBRTC_DIR})

CMake有两种方式添加库,或者说叫添加.a文件

# Add linked libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
    # Add user defined libraries
    -l:xxxx.a
    libyyyy.a
)

一种是需要你把原本的库命名为lib开头的,一种是通过-l指定非lab开头的库

.cmake和CMakeLists的区别

  • .cmake,是通过include()方式进行引入的,本质上是嵌入主cmake文件中的,所以是按照顺序执行的
  • CMakeLists,是通过add_subdirectory()方式引入,不嵌入主cmake中,执行顺序是比主cmake优先的

.cmake由于是嵌入,所以也就不存在什么局部变量全局变量一说,都是在主cmake中的,所以通用,但是CMakeLists不是,他作为子部分,默认情况下他的变量只能他自己使用,只有cmake自己的全局变量是可以被使用的,用户自定义的是无法使用的。

但是也有办法使用主cmake的变量

set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/xxx/link.ld CACHE STRING "Linker Script Path" FORCE)

首先是将变量变成CACHE,这样这个变量就会被全局缓存,但是这种方式只是会保存这个变量,也就是子cmake可以用主cmake的这个变量的值,但是子cmake不能反过来设置这个值

如果要设置这个值,必须使用后面的FORCE关键字来修改

总而言之子CMakeLists的作用域类似于编程语言中的函数,写在其中的内容的作用域也类似于一个函数内的各种变量的作用域

Summary

还没有完全优化完成,比如同时编译四五个配置,怎么把这四五个配置暂存下来,方便后续编译,而不是每次都要切换配置去编译,还有CMake和Kconfig能不能只写其中一个,另外一个自动生成?

目前对于模块的引入能不能自动识别加入,而不用手动写引入的部分。

CMake和Kconfig这种模式还有一个小问题,生成的宏是直接文件级别跳过编译的,但是如果要同时兼容IDE或者VSCode这类的时候,能不能将这些关联导入到这些IDE里,否则IDE里总是显示不正常的,快速跳转也会跳到错误的位置上

Quote

https://github.com/LuckkMaker/apm32-kconfig-example

<think>好的,我现在需要帮助用户解决关于kconfgen模块缺失导致的CMake错误的问题。用户提到了在ESP-IDF环境中遇到SDK配置生成失败的情况,错误信息是kconfgen模块找不到。首先,我得回忆一下ESP-IDF的构建流程和相关依赖。 首先,用户的问题可能出现在Python环境上。因为kconfgen是ESP-IDF的一个Python工具,用于生成配置文件。如果Python环境有问题,比如依赖未正确安装或者Python版本不兼容,可能会导致这个模块无法被找到。所以,我需要检查用户的Python版本是否符合ESP-IDF的要求。通常,ESP-IDF需要Python 3.6或更高版本,但具体版本可能因IDF版本而异,用户需要确认他们的Python版本是否匹配。 接下来,考虑依赖安装的问题。ESP-IDF在安装时通常会通过install.sh或者export脚本来安装所有必需的Python包。如果用户在安装过程中跳过了某些步骤,或者网络问题导致依赖安装不完整,就可能会出现缺少kconfgen的情况。这时候,重新运行安装脚本或者手动安装依赖可能会有帮助。例如,使用requirements.txt文件来安装所有必需的包。 另外,环境变量的问题也可能导致模块找不到。用户是否正确地设置了IDF_PATH环境变量?这个变量需要指向ESP-IDF的根目录,这样构建系统才能找到所有必要的工具和脚本。如果这个变量没有设置或者设置错误,CMake可能无法定位kconfgen模块,从而报错。用户可以通过echo $IDF_PATH来检查当前设置,并确保其正确性。 还有可能的问题是子模块未初始化。如果用户是通过Git克隆的ESP-IDF仓库,可能需要初始化子模块。kconfgen可能作为子模块存在于项目中,如果未正确初始化或更新,就会导致缺失。这时候,运行git submodule update --init --recursive可以拉取所有必要的子模块,包括kconfgen所在的目录。 此外,构建目录可能存在残留的缓存文件,导致CMake使用了旧的配置。这种情况下,清理构建目录并重新生成配置可能会解决问题。用户可以删除build目录,然后重新运行cmake和make命令。 最后,如果以上方法都无效,可能需要考虑重新克隆或下载ESP-IDF,并严格按照官方文档的步骤进行安装,确保所有步骤都正确执行,避免遗漏。 现在,我需要将这些思考整理成一个结构化的解决方案,分步骤指导用户逐一排查问题。同时,生成的相关问题需要围绕常见的ESP-IDF安装和配置问题,比如Python依赖、环境变量设置、子模块初始化等,帮助用户进一步理解可能遇到的类似问题。</think>### 解决kconfgen模块缺失导致的CMake错误 以下是分步解决方案: 1. **验证Python环境** - 确认使用Python 3.6及以上版本: ```bash python3 --version ``` - 如果版本低于3.6,需升级Python或使用虚拟环境: ```bash sudo apt install python3.8 # 以Ubuntu为例 ``` 2. **安装ESP-IDF依赖** - 进入ESP-IDF目录,重新运行安装脚本: ```bash cd ~/esp/esp-idf ./install.sh # 或使用install.bat(Windows) ``` - 手动安装Python依赖包: ```bash python3 -m pip install -r requirements.txt ``` 3. **检查环境变量** - 确保`IDF_PATH`已正确设置: ```bash echo $IDF_PATH # 应输出类似/home/user/esp/esp-idf的路径 ``` - 若未设置,手动添加至`.bashrc`: ```bash echo "export IDF_PATH=~/esp/esp-idf" >> ~/.bashrc source ~/.bashrc ``` 4. **初始化Git子模块** - 进入ESP-IDF目录,更新子模块: ```bash git submodule update --init --recursive ``` 5. **清理并重建项目** - 删除现有构建目录: ```bash rm -rf build ``` - 重新生成构建配置: ```bash idf.py set-target esp32 # 根据实际芯片型号调整 idf.py build ``` 6. **检查ESP-IDF版本兼容性** - 确认使用的ESP-IDF版本项目兼容,可尝试切换至稳定分支: ```bash git checkout release/v4.4 ``` ### 典型错误原因分析 该错误通常由以下原因导致: 1. Python依赖未完整安装(常见于跳过`install.sh`步骤) 2. 项目目录未正确初始化子模块 3. 环境变量污染或未正确设置 4. 构建缓存残留导致配置错误[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值