2020年汽车电子软件主流构建方案讲解-cmake

1. 概述

上篇文章提到,在汽车电子软件行业中makefile的标准用法,以及在复杂的构建流程和高度自定义化目标文件的实际情况下,针对工程级的构建方案,大部分情况下无法使用IDE自带的构建系统满足需求。

本篇文章继续讲解,行业内另外一种主流的构建工具链,modern cmake 系统。

2. cmake 简介

这部分资料可以在网上轻松查到,这里需要重点说明的是,关于使用cmake作为构建系统的思想,需要遵循modern camke的思想,可以简单理解为从面向过程到OOP,传统cmake是通过大量可配置的变量,编写复杂判断逻辑进行目标文件的构建。而modern cmake 需要把构建目标封装对象,构建的目标设定可以理解为该对象的构造函数,相关的变量及设定都为成员变量,当构建不同的内容时仅实例化不同的对象即可。

还有一点需要说明,cmake 不同于 makefile 系统,makefile在编写好之后,可以通过make直接调用并进行目标编译,而cmake对应的控制文件为cmakelist,在编写好cmakelist之后还要设定所用的生成器才能开始构建,而在cmake系统当中,所谓的生成器是指代makefile这类能够直接控制编译器进行构建的系统。 常见的生成器有 makefile ,visual studio code,ninja等,可以通过如下图示进行理解。

001

最后,有关cmake的使用和学习建议大家阅读 cmake cookbook这本书。

3. 示例工程构建讲解

本文提供的示例工程为,基于S32K芯片,集成FreeRTOS架构软件,硬件为 NXP S32K144 EVB开发板,具备基础led灯亮灭功能。

示例工程地址为: freertos_s32k144evb_cmake

3.1. 环境准备

3.1.1. linux

这里以deepin系统(linux)为例,使用cmake+ninja作为构建系统,windows 或 mac需要自行安装如下提到软件。

002

  • 首先安装cmake,sudo apt-get install cmake

003

  • 安装ninja, sudo apt-get install ninja-build

004

  • 安装gcc-arm-none-eabi 编译器,编译arm芯片代码,sudo apt-get install gcc-arm-none-eabi

005

3.1.2. windows

同样需要安装 cmakeninja 两个工具 ,ninja 安装方法为解压得到exe文件,然后将exe文件所在路径放入系统路径即可,保证命令行工具可以调用 ninja 命令。

安装编译器有所不同,此项目采用开源编译器,Linaro 组织开发的 arm-none-eabi-gcc V6.3.1 。当前汽车电子行业内多用 iar greenhill 等商业编译器,最早是因为汽车电子芯片大多为异构mcu,例如英飞凌家的TriCore架构以及瑞萨家的G3M内核架构等,与主流的ARM, PowerPC 不符,此类内核的芯片并无开源稳定的编译器可用,但是随着arm架构的普及,车上的arm芯片也逐渐多了起来,但是即便是使用arm,仍旧采用商业编译器。

这部分原因是使用 AutoSAR架构后,工具厂商开始携手垄断,例如各家芯片厂商虽然写基础的MCAL驱动,但是默认把 MCAL 交给 EB 工具链进行配置,然后Vector ,Mentor 等厂商配置生成的代码根本不支持开源编译器,链接脚本命令以及很多汇编代码甚至与语法糖仅支持圈内工具厂商的编译器,真可谓肥水不流外人田。

Windows上安装 arm gcc 编译器,建议直接安装NXP 家的免费IDE - S32DS ,安装此ide会同时安装 arm gcc V6.3.1

3.2. Cmake + Ninja 构建系统详细说明

此套构建系统采用 Cmake进行源码及 编译器设定管理, 共计三个文件:

  • CMakeLists.txt : 主文件,设定参与构建的 源码/静态链接库/链接脚本 路径
  • gcc_arm_eabi_toolchain.cmake : 设定编译器相关信息
  • PreLoad.cmake : 预加载文件,设定Cmake 生成器为Ninja, 当前cmake支持如下几种生成器
The following generators are available on this platform (* marks default):
  Visual Studio 16 2019        = Generates Visual Studio 2019 project files.
                                 Use -A option to specify architecture.
  Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 10 2010 [arch] = Generates Visual Studio 2010 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Borland Makefiles            = Generates Borland makefiles.
* NMake Makefiles              = Generates NMake makefiles.
  NMake Makefiles JOM          = Generates JOM makefiles.
  MSYS Makefiles               = Generates MSYS makefiles.
  MinGW Makefiles              = Generates a make file for use with
                                 mingw32-make.
  Green Hills MULTI            = Generates Green Hills MULTI files
                                 (experimental, work-in-progress).
  Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Ninja Multi-Config           = Generates build-<Config>.ninja files.
  Watcom WMake                 = Generates Watcom WMake makefiles.
  CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
  CodeBlocks - NMake Makefiles JOM
                               = Generates CodeBlocks project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - MinGW Makefiles   = Generates CodeLite project files.
  CodeLite - NMake Makefiles   = Generates CodeLite project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Eclipse CDT4 - NMake Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - MinGW Makefiles
                               = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
  Kate - MinGW Makefiles       = Generates Kate project files.
  Kate - NMake Makefiles       = Generates Kate project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Sublime Text 2 - MinGW Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - NMake Makefiles
                               = Generates Sublime Text 2 project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.

3.2.1. PreLoad.cmake

此文件控制调用cmake前的设定信息,本工程用于生成器设定。

# -----------------------------------------------------------------------------
#  Cmake preload file, for generator setting
#  File Name       :  PreLoad.cmake
#  Generator       :  Ninja
#  Author          :  shibo jiang  
#  Instructions    :  Initial file.             2020/12/19                 V0.1
# 
# -----------------------------------------------------------------------------

# use Ninja as generator
set(CMAKE_GENERATOR "Ninja" CACHE INTERNAL "" FORCE)
# do not check asm compiler
set(CMAKE_ASM_COMPILER_ID_RUN TRUE CACHE INTERNAL "" FORCE)

3.2.2. CMakeLists.txt

此文件用于进行源码及编译器的设定。

# -----------------------------------------------------------------------------
#  Cmake File for cross paltform build
#  File Name      :  CMakeLists.txt
#  CMake Version  :  V3.17.2
#  Author         :  shibo jiang  
#  Instructions   :  Initial file.             2020/12/19                 V0.1
# 
# -----------------------------------------------------------------------------

# user config -----------------------------------------------------------------
# whehter use debug mode, ON/OFF
option(CMAKE_DEBUG "Whether use debug type" ON)
include(${CMAKE_CURRENT_SOURCE_DIR}/gcc_arm_eabi_toolchain.cmake)
set(CMAKE_TOOLCHAIN_FILE "gcc_arm_eabi_toolchain.cmake")
set(target_name "S32K144EVB_LED")

# .h .c .cpp folder ,can auto recursive find source code
set(src_folder  "../Generated_Code"
                "../SDK"
                "../Sources"
                "../Project_Settings"
                "C:/NXP/S32DS_ARM_v2.2/S32DS/build_tools/gcc_v6.3/gcc-6.3-arm32-eabi/include")

# append src (*.c *.h ...) setting.
set(src_files "")

# asm file list
list(APPEND CMAKE_ASM_SOURCE_FILE_EXTENSIONS S asm inc)
set(asm_files "../Project_Settings/Startup_Code/Startup_S32K144.S")
SET_SOURCE_FILES_PROPERTIES(${asm_files} PROPERTIES LANGUAGE ASM)

# .a .o .obj .so .lib files folder , can not recursive find
set(lib_folder "")

# link file ,for embeded platform and cross platform complie
# 此处需注意,链接脚本在链接过程中才开始产生作用,而之前源码目录设定在 cmake 加载并生成
# ninja rules时开始作用,所以在 build目录下 ,使用cmake .. 加载源码生成构建器时,路径
# 使用一层返回 ../ ,含义为从 cmake 文件夹开始向上返回, 而在使用 cmake --build . 进行
# 构建时,需要设定链接脚本相对路径为 两层返回 ../../ ,含义为从 build 文件夹下进行向上
# 查找。
set(link_file "../../Project_Settings/Linker_Files/S32K1xx_flash.ld")

# User defined in pre-compile
add_definitions(-D CPU_S32K144HFT0VLLT)

# system config ,do not change or change it carefully--------------------------
# set minimum cmake version
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
message(STATUS "Target name is set to ${target_name}")
message(STATUS "Generator is set to ${CMAKE_GENERATOR}")

# project name and language
project(${target_name}_prj LANGUAGES C CXX ASM)

# require C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

message(STATUS "src subfolder name:")
# funciton declation for recursively find file
MACRO(SUBDIRLIST result curdir)
  SET(dirlist "")
  SET(spec_dir_index "")
  SET(ignore_dir_index "")
  LIST(APPEND dirlist ${curdir})
  FILE(GLOB_RECURSE children LIST_DIRECTORIES true ${curdir}/*)
  FOREACH(child ${children})
    IF(IS_DIRECTORY ${child})
      # use specific folder name to judge
      # STRING(REGEX REPLACE ".*/" "" sub_folder ${child})
      SET(spec_dir_index -1)
      SET(ignore_dir_index -1)
      # only have file ,then add folder to list 
      FILE(GLOB tp_files ${child}/*.*)
      IF(tp_files)
        LIST(APPEND dirlist ${child})
      ELSE()
        # message("******** 3 This folder has no files, do not add to list!*** ${child}")
      ENDIF()
        # message("child_folder = ${child}")
        # message("sub_folder = ${sub_folder}")
    ENDIF()
  ENDFOREACH()
  SET(${result} ${dirlist})
ENDMACRO()

# debug code ------------------------------------------------------------------
# set(temp_ignore_flg "")
# list(FIND src_ignore "test" temp_ignore_flg)
# message("temp_ignore_flg = ${temp_ignore_flg}")
# set(src_spec_len "")
# list(LENGTH src_spec src_spec_len)
# message("src_spec_len = ${src_spec_len}")
# if(src_spec)
#   message("src_spec ok")
# else()
#   message("src_spec not ok")
# endif()
# if(src_ignore)
#   message("src_ignore ok")
# else()
#   message("src_ignore not ok")
# endif()
# ----------------------------------------------------------------------------- 

# project file and folder
set(SUBDIRS "")
foreach(sub_src_folder ${src_folder})
  SUBDIRLIST(TEMP_SUBDIRS ${sub_src_folder})
  list(APPEND SUBDIRS ${SUBDIRS} ${TEMP_SUBDIRS})
  # include_directories(${sub_dir})
  # aux_source_directory(${sub_dir} temp_src_list)
  # message(${temp_src_list})
endforeach()
# remove dupulicate file and print src file
list(REMOVE_DUPLICATES SUBDIRS)
# add src folder and include folder
set(src_list "")
# set(file_no "")
message("\n#### source file list: ####")
foreach(sub_dir ${SUBDIRS})
  set(temp_src_list "")
  include_directories(${sub_dir})
  aux_source_directory(${sub_dir} temp_src_list)
  # list(APPEND src_list ${src_list} ${temp_src_list})
  foreach(tp_file ${temp_src_list})
    # list(APPEND src_list ${src_list} ${tp_file})
    # message(${tp_file})
    set(src_list ${src_list} ${tp_file})
  endforeach()
endforeach()

# combine asm files
if(asm_files)
  message(STATUS "Project contain asm files: ")
  set(src_list ${src_list} ${asm_files})
  foreach(tp_file ${asm_files})
    # message(${tp_file})
  endforeach()
endif()
# combine append src files
if(src_files)
  message(STATUS "Project contain append src files: ")
  set(src_list ${src_list} ${src_files})
  foreach(tp_file ${src_files})
    # message(${tp_file})
  endforeach()
endif()
# get source file number
list(LENGTH src_list files_no)
message(STATUS "source files no. = ${files_no}")

# set out file
set(EXECUTABLE ${target_name}.elf)
add_executable(${EXECUTABLE} ${src_list})
# target_compile_definitions(${EXECUTABLE} PRIVATE
#                             ${C_OPTIONS})
# target_compile_options(${EXECUTABLE} PRIVATE
#                         ${C_OPTIONS})
target_link_options(${EXECUTABLE} PRIVATE
                    -T ${link_file})
# debug -----------------------------------------------------------------------
# message(STATUS "C_OPTIONS:  ${C_OPTIONS}")
# message(STATUS "LD_OPTIONS:  ${LD_OPTIONS}")
# message(STATUS "${CMAKE_C_LINK_EXECUTABLE}")
# -----------------------------------------------------------------------------

# get lib files
if (lib_folder)
  message("\n#### Extern Lib Files ####")
  foreach(lib_dir ${lib_folder})
    # add lib folder path 
    link_directories(${lib_dir})
    # add lib files to 
    file(GLOB lib_a_files "${lib_dir}/*.a")
    file(GLOB lib_o_files "${lib_dir}/*.o")
    file(GLOB lib_obj_files "${lib_dir}/*.obj")
    file(GLOB lib_so_files "${lib_dir}/*.so")
    file(GLOB lib_lib_files "${lib_dir}/*.lib")
    list(APPEND lib_files ${lib_a_files} 
                          ${lib_obj_files}
                          ${lib_o_files}
                          ${lib_so_files}
                          ${lib_lib_files})
    # message(STATUS "Lib folder: ${lib_dir}")
  endforeach()
  # report files
  # foreach(tp_file ${lib_files})
    # message(${tp_file})
  # endforeach()
  list(LENGTH lib_files lib_files_no)
  message(STATUS "lib files no. = ${lib_files_no}")
  # link lib to excute
  target_link_libraries(${EXECUTABLE}
                        ${lib_files})
else()
  # target_link_libraries(${target_name}_lib ${target_name})
endif()

# Post build ------------------------------------------------------------------
# Print executable size
add_custom_command(TARGET ${EXECUTABLE}
        POST_BUILD
        COMMAND ${CMAKE_GSIZE_TOOL} ${EXECUTABLE}
        COMMAND echo [Work done! Generated ${target_name}.elf])
# Create hex s19 file
add_custom_command(TARGET ${EXECUTABLE}
        POST_BUILD
        COMMAND ${CMAKE_GSREC_TOOL} -O srec ${EXECUTABLE} ${target_name}.s19
        COMMAND ${CMAKE_GSREC_TOOL} -O ihex ${EXECUTABLE} ${target_name}.hex
        COMMAND echo [Work done! Generated ${target_name}.hex ${target_name}.s19])

# end of file -----------------------------------------------------------------

3.2.3. gcc_arm_eabi_toolchain.cmake

此文件用于编译器参数设定

# -----------------------------------------------------------------------------
#  Cmake Compiler File for Linaro gcc arm compiler
#  File Name       :  gcc_arm_eabi_toolchain.cmake
#  Compier Version :  6.3.1
#  Author          :  Tomato  
#  Instructions    :  Initial file.             2020/12/19                 V0.1
# 
# -----------------------------------------------------------------------------

# user config -----------------------------------------------------------------
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)

set(TOOLCHAIN_PREFIX arm-none-eabi-)
# arm-none-eabi-gcc should be added to system path
# if(MINGW OR CYGWIN OR WIN32)
#     set(UTIL_SEARCH_CMD where)
# elseif(UNIX OR APPLE)
#     set(UTIL_SEARCH_CMD which)
# endif()
# Get toolchain folder
# execute_process(
#   COMMAND ${UTIL_SEARCH_CMD} ${TOOLCHAIN_PREFIX}gcc
#   OUTPUT_VARIABLE BINUTILS_PATH
#   OUTPUT_STRIP_TRAILING_WHITESPACE
# )
# get_filename_component(ARM_TOOLCHAIN_DIR ${BINUTILS_PATH} DIRECTORY)

set(ARM_TOOLCHAIN_DIR C:/NXP/S32DS_ARM_v2.2/S32DS/build_tools/gcc_v6.3/gcc-6.3-arm32-eabi/bin)
set(GCC_LIB_PATH C:/NXP/S32DS_ARM_v2.2/S32DS/build_tools/gcc_v6.3/gcc-6.3-arm32-eabi/arm-none-eabi/newlib)

# system config ,do not change or change it carefully--------------------------
# Without that flag CMake is not able to pass test compilation check
set(CMAKE_TRY_COMPILE_TARGET_TYPE   STATIC_LIBRARY)

# which compilers to use
set(CMAKE_AR                        ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}ar.exe)

set(CMAKE_ASM_COMPILER              ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}gcc.exe)
set(CMAKE_C_COMPILER                ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}gcc.exe)
set(CMAKE_CXX_COMPILER              ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}g++.exe)
set(CMAKE_OBJCOPY                   ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy.exe CACHE INTERNAL "objcopy tool")
set(CMAKE_GSIZE_UTIL                ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size.exe CACHE INTERNAL "size tool")

set(CMAKE_GSREC_TOOL                ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy.exe)
set(CMAKE_GSIZE_TOOL                ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size.exe)

# set(CMAKE_C_FLAGS_DEBUG             "-g -O0" CACHE INTERNAL "")
# set(CMAKE_C_FLAGS_RELEASE           "-Os" CACHE INTERNAL "")
# set(CMAKE_CXX_FLAGS_DEBUG           "${CMAKE_C_FLAGS_DEBUG}" CACHE INTERNAL "")
# set(CMAKE_CXX_FLAGS_RELEASE         "${CMAKE_C_FLAGS_RELEASE}" CACHE INTERNAL "")

# setting compiler input parameter
# $<$<CONFIG:normal>:-Ospeed> generator expression only used in high version cmake
# https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html

set(C_OPTIONS "-n -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fdata-sections -ffunction-sections --sysroot=${GCC_LIB_PATH} -Wall")
set(ASM_OPTIONS ${C_OPTIONS})
set(CXX_OPTIONS ${C_OPTIONS})

set(LD_OPTIONS "-n -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nano.specs --sysroot=${GCC_LIB_PATH} -lc -lm -lnosys -Wl,-Map,${target_name}.map -Xlinker --gc-sections")

# judge whether debug
if(CMAKE_DEBUG)
    string(APPEND C_OPTIONS " -Og")
else()
    # string(APPEND C_OPTIONS " -MD")
endif()

set(CMAKE_C_FLAGS ${C_OPTIONS})
set(CMAKE_CXX_FLAGS ${CXX_OPTIONS})
set(CMAKE_ASM_FLAGS ${ASM_OPTIONS})

# use cmakelist file: target_link_options to set ld options
set(CMAKE_EXE_LINKER_FLAGS ${LD_OPTIONS})
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CMAKE_C_FLAGS CMAKE_CXX_FLAGS CMAKE_ASM_FLAGS)

# --------- Userdef linker -------
# set(CMAKE_C_LINK_EXECUTABLE "<CMAKE_C_COMPILER> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")

set(CMAKE_FIND_ROOT_PATH ${BINUTILS_PATH})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

3.3. 构建系统使用教程

如下所有操作均使用命令行,演示系统为windows,命令行工具为Cmder,也可以使用windows自带命令行工具 cmd。

首先检查环境是否正确, 在命令行中使用 cmake --verison ,以及 ninja --version来查看是否能够成功调用 cmake 及 ninja。

3.3.1. Step1. 在cmake文件夹下创建build文件夹 ,并进入文件夹

3.3.2. step2. 在build文件夹下,使用 cmake .. 命令创建Ninja构建系统

3.3.3. step3. 在build文件夹下,使用 cmake --build . 开始构建

3.4. 下载调试

可以使用 S32DS ,配合 S32K144EVB板子上自带的OpenSDA ,或者PE/JLink... 等,进行加载elf文件,设定源码目录,然后开始下载/调试。如下为截图参考,详细示例可以使用S32DS 导入本文中的示例工程,进行点击查看。

基于Cmake构建系统的FreeRTOS s32ds 调试效果如下:

\

[/video]