banner
yono

yono

哈喽~欢迎光临
follow
github

CMAKE的合理化結構

關於#

🎉終於終於,在 keil 的綠屍寒壓力下,公司準備轉型到 gcc 編譯。而我覺得 gcc 編譯鏈的整合應該沒有比 CMAKE 更好的方案 (當然 EIDE 插件也不錯),長久獨立探索,終於徹底把 keil 踢掉了。eclipse 還無法完全踢掉,畢竟 TI 的 MCU 還得 CCS 來,什麼時候能實現 vscode 全面普及呢?

最近把原本的項目工程轉成完全的 CMAKE 工程,發現一些可以優化的工程結構的點。

工具#

在博客文件服務頁應該有便攜 vscode 的下載,各種需要的工具都集成進去了,綠色免安裝。

有一個 ==1_首次打開請運行.bat== 的文件,雙擊運行一下,vscode 就可以直接用了。這個腳本的主要作用是,提權到管理員模式,把各個工具鏈的 path 自動添加到環境變量裡。

文件服務 - 土星環的基地

vscode 工作區的合理化#

在 .code-workspace 文件的 settings 段可以增加如下的部分

    "settings": {
        "C_Cpp.formatting": "clangFormat",
        "cmake.configureEnvironment": {
            "PROGRAM_NAME": "H7_GCC_PIONEER"
        }
    },

其中指定了格式化程序,是 clangFormat ,這個格式化程序的可執行文件會在 ==1_首次打開請運行.bat== 時增加到環境變量裡。

指定了一個 CMAKE 的環境變量 ,這裡是為了方便配置生成的燒錄文件的名字。

在主 cmakelist 也就是根目錄下,此前有這樣的內容

set(CMAKE_PROJECT_NAME DebugBuild)

修改為如下的部分

if(DEFINED ENV{PROGRAM_NAME})
    set(CMAKE_PROJECT_NAME $ENV{PROGRAM_NAME})
else()
    message(WARNING "PROGRAM_NAME environment variable is not set. Using default project name.")
    set(CMAKE_PROJECT_NAME "DefaultProjectName")
endif()

這樣就會以工作區 .code-workspace 文件中的 PROGRAM_NAME 為準了。
這樣操作主要是為了開發這個項目的人儘可能不要修改 CMAKE 相關的任何文件,後續還有以這個為目的的其他操作。

主構建的分支#

對於 CMAKE_BUILD_TYPE ,會從 CMAKE 插件傳入實際構建。但除了這個構建類型,還希望有一些自定義的構建類型,可以進行如下的操作。

在可執行主構建較為靠前的地方使用自定義的屬性變量。

set(CMAKE_BUILD_BL "YES")
set(CMAKE_BUILD_BL "NO")

而在工具鏈指定文件中我們可以這樣操作 (當然其他文件中也都類似,主構建的這些屬性變量會傳播到子目錄和子構建中)

if(CMAKE_BUILD_BL MATCHES YES)
    set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -T \"${CMAKE_SOURCE_DIR}/CPU/GNU/stm32H743XI_INCBL.ld\"")   # 添加鏈接腳本
endif()
if(CMAKE_BUILD_BL MATCHES NO)
    set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -T \"${CMAKE_SOURCE_DIR}/CPU/GNU/stm32H743XI.ld\"")   # 添加鏈接腳本
endif()

這樣操作控制了兩個不同的鏈接器腳本,YES 時的鏈接器腳本具備 bootload 部分,主要用於出廠燒錄;NO 時則不包括 bootload 部分,用於升級。這樣又減少了一部分操作,EIDE 插件卻做不到這麼方便,每次需要手動小改一下腳本。當然為了儘可能不讓開發者修改 CMAKE 相關的任何文件,也可以如上個操作一樣新建一個環境變量。

主構建中定義後任務#

我的主構建最末尾有如下兩句

# 為單獨的文件添加編譯標籤
include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/extra-compile-flags.cmake")

# 運行一下構建後任務
include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/post-build-tasks.cmake")

extra-compile-flags.cmake#

這個文件的內容如下

include(${CMAKE_CURRENT_LIST_DIR}/cmake_func/functions.cmake)

set_compile_flags_for_matching_files(${CMAKE_PROJECT_NAME} "6_Rtos|7_Exlib" "-w")
set_compile_flags_for_matching_files(user_src "6_Rtos|7_Exlib" "-w")

作用是將主構建工程 ${CMAKE_PROJECT_NAME} 以及源碼引入工程 user_src 中關於 6_Rtos 或 7_Exlib 文件夾下的源碼都添加 -w 的編譯標誌,意為忽略所有警告。因為我們開啟的超嚴格全警告,所以操作系統或者別人寫的一些庫中或許會有一些警告,非常煩人又不能改,故有這樣的操作。注意的是,由於源碼引入工程 user_src 原本是所有部分都以 INTERFACE 屬性定義,所以在 user_src 中的源碼無法正常添加編譯標誌,所以 target_sources 部分現在應該以 PUBLIC 屬性定義,暫未發現無法接受的副作用。

第一個傳參是項目名,第二個傳參是指路徑包含這些字段的源碼文件,第三個字段是期望添加的編譯標籤,這又引入新文件了 functions.cmake 如下。

# FUNC

# 遞歸包含頭文件的函數
function(include_sub_directories_recursively root_dir)
    if (IS_DIRECTORY ${root_dir})               # 當前路徑是一個目錄嗎,是的話就加入到包含目錄
        #        if (${root_dir} MATCHES "include")
        message("include dir: " ${root_dir})
        target_include_directories(${PROJECT_NAME} INTERFACE
        ${root_dir}
        )
        #        endif()
    endif()

    file(GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*) # 獲得當前目錄下的所有文件,讓如ALL_SUB列表中
    foreach(sub ${ALL_SUB})
        if (IS_DIRECTORY ${root_dir}/${sub})
            include_sub_directories_recursively(${root_dir}/${sub}) # 對子目錄遞歸調用,包含
        endif()
    endforeach()
endfunction()

# 給某個目標,路徑帶有關鍵詞的源文件,添加期望添加的標籤(例如"-w")
function(set_compile_flags_for_matching_files target_name keywords compile_flags)
    # 獲取目標的原始源文件列表
    get_target_property(src_list ${target_name} SOURCES)

    # 檢查目標是否存在並有源文件
    if(NOT src_list)
        message(WARNING "Target '${target_name}' does not exist or has no sources.")
        return()
    endif()

    # 創建一個新的列表用於存放需要設置編譯標誌的文件
    set(filtered_src_list)

    # 遍歷原始的源文件列表,篩選出需要的文件
    foreach(src_file IN LISTS src_list)
        if("${src_file}" MATCHES "${keywords}")
            list(APPEND filtered_src_list ${src_file})
        endif()
    endforeach()

    # 為篩選出的源文件設置編譯標誌
    foreach(src_file IN LISTS filtered_src_list)
        set_source_files_properties(${src_file} PROPERTIES COMPILE_FLAGS "${compile_flags}")
    endforeach()
endfunction()


# FUNC END

這是一個我專用來編寫 cmake 函數的文件,不過多介紹,反正需要用到函數的時候就會 include 。

post-build-tasks.cmake#

這個文件是為了方便管理編譯後任務,內容如下

# 單獨管理構建後的任務

# 定義工具鏈工具
set(OBJCOPY arm-none-eabi-objcopy)
set(OBJDUMP arm-none-eabi-objdump)

# 添加自定義命令生成 bin 和 hex 文件
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
    COMMAND ${OBJCOPY} -O binary $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.bin
    COMMAND ${OBJCOPY} -O ihex $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.hex
    COMMAND  ${CMAKE_CURRENT_LIST_DIR}/gccMapView.exe ${CMAKE_BINARY_DIR}
    COMMENT "Generating bin and hex files from elf"
)

這個是為了生成 bin 文件和 hex 文件。
隨後運行了一下 gccMapView.exe ,這是我為了整理 map 做的小軟件,因為 GCC 的原版 map 簡直醜陋所以有這個小軟件,開源地址在這裡,效果還是非常不錯的。

stbanana/gcc-elf-link-sort: A tool for sorting the symbols of .elf by address

原 map.png

整理輸出.png

工程源碼引入使用遍歷搜索自動化#

工程源碼遍歷頭文件路徑並且包含,有這樣的部分

# cmake FUNC
include(${CMAKE_CURRENT_LIST_DIR}/cmake_func/functions.cmake)

# 遞歸包含頭文件
include_sub_directories_recursively(${CMAKE_CURRENT_LIST_DIR}/../../../1_App)
include_sub_directories_recursively(${CMAKE_CURRENT_LIST_DIR}/../../../2_Contorl)
include_sub_directories_recursively(${CMAKE_CURRENT_LIST_DIR}/../../../3_Module)
include_sub_directories_recursively(${CMAKE_CURRENT_LIST_DIR}/../../../4_Driver)

這會調用到 cmake 函數,所以也在統一函數定義文件中,作用就是將這四個文件夾下的所有路徑包括子目錄統統添加 include path 。

又有這樣的部分

# 遞歸查找所有源碼文件
file(GLOB_RECURSE 1_APP_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../1_App/*.c)
file(GLOB_RECURSE 2_CONTORL_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../2_Contorl/*.c)
file(GLOB_RECURSE 3_MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../3_Module/*.c)
file(GLOB_RECURSE 4_DRIVER_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../4_Driver/*.c)
file(GLOB_RECURSE 5_DRIVER_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../5_PhysicalChip/Stm32H7/*.c)

target_sources(${CMAKE_PROJECT_NAME} PUBLIC
    ../../../main.c
    ${1_APP_SRC}
    ${2_CONTORL_SRC}
    ${3_MODULE_SRC}
    ${4_DRIVER_SRC}
    ${5_DRIVER_SRC}
)

作用就是將這四個文件夾下包括子目錄中的所有源碼添加到源碼文件中。

而更底層以及 RTOS 或引入庫的部分。引入源碼和 include path ,仍然是手動指定。

這樣做了以後,所有的垃圾業務源碼都可以隨便編寫隨便修改,此前改個命名改一下結構要到處改好多工程配置,現在可以完全流暢地開發業務代碼了。業務代碼和驅動支持進一步解耦。

在這裡有一個前文說到的修改,target_sources 部分現在應該以 PUBLIC 屬性定義,否則沒辦法指定單個文件的編譯標誌,INTERFACE 屬性雖然在實際編譯中會進行,也會掛在主構建工程中,但是在 CMAKE 系統中卻沒有這個文件實例,本質上他是虛掛在源碼引入工程 user_src 上,但 user_src 也是一個INTERFACE 接口虛庫不會實際編譯,所以所有的源碼是作為工程 user_src 整體被鏈接進主構建工程,無法找到任何一個準確的文件了,只是繼承主構建工程的編譯標誌在參與編譯。

此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.yono233.cn/posts/novel/24_11_25_CMAKE%E7%9A%84%E5%90%88%E7%90%86%E5%8C%96%E7%BB%93%E6%9E%84

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。