阅读视图

发现新文章,点击刷新页面。
🔲 ⭐

Kconfig与CMake初步模块化工程3

Foreword

本篇解决一下CMake和VScode怎么整合到一起

VSCode

适配VSCode

image-20250224184343397

VSCode只需要一个CMake Tools,不需要安装其他CMake插件

image-20250224191713665

Kconfig格式化和代码高亮,还是用nRF的好一些,另外那个Kconfig会识别出错

image-20250225193642891

C/C++提示全家桶也需要安装,会影响到代码提示和跳转体验

缺少 Select a Kit

vscode cmake 缺少选择Select a Kit,主要原因是目录已经有了CMakePresets.json,有预设的情况下不会给你选kit,这个问题找了半天,发现官方文档就写了

image-20250224174751611

但是你搜索的命令又有,只能说官方弄的有点乱,而且本身使用预设和可以选kit我觉得也不矛盾

CMake:Scan for compiles

去掉预设文件以后,这个kit果然就有了,就可以正常选择了

image-20250224174840500

实际上这一步根本不需要,建议不要浪费实际调整这个,预设文件早就把这些设置好了,唯一需要处理的就是搜索本地的编译器,否则VSCode不知道编译的gcc在哪里

CMake:Scan for compiles

修改status bar

默认的 status bar 实在是冗余太多了,从config,build,pack,ctest,cpack,workflow,很多用不上的我就给他隐藏了,而CMake Tools也支持自定义

{
    "cmake.options.statusBarVisibility": "visible",
    "cmake.options.advanced": { 

        "folder": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden",  
            "statusBarLength": 20,
            "projectStatusVisibility": "hidden", 
        }, 
        "configure": { 
            "projectStatusVisibility": "visible", 
        }, 
        "configurePreset": { 
            "statusBarVisibility": "visible",
            "inheritDefault": "visible", 
            "statusBarLength": 20
        }, 
        "kit": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "statusBarLength": 20 
        }, 
        "variant": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
        }, 
        "build": { 
            "statusBarVisibility": "visible", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "visible", 
        }, 
        "buildPreset": { 
            "statusBarVisibility": "hidden",
            "inheritDefault": "hidden", 
            "statusBarLength": 20 
        }, 
        "buildTarget": { 
            "statusBarVisibility": "hidden",
            "inheritDefault": "hidden", 
            "statusBarLength": 20 
        }, 
        "ctest": { 
            "statusBarVisibility": "hidden",
            "inheritDefault": "hidden", 
            "statusBarLength": 20, 
            "color": true,
            "projectStatusVisibility": "hidden", 
        }, 
        "cpack": { 
            "statusBarVisibility": "hidden",
            "inheritDefault": "hidden", 
            "statusBarLength": 20, 
            "color": true,
            "projectStatusVisibility": "hidden", 
        }, 
        "testPreset": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "statusBarLength": 20
        },
        "launchTarget": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "statusBarLength": 20 
        }, 
        "debug": { 
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "hidden", 
        },
        "launch": {
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "hidden", 
        },
        "workflow": {
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "hidden", 
        },
        "workflowPreset": {
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "hidden", 
        },
        "packagePreset": {
            "statusBarVisibility": "hidden", 
            "inheritDefault": "hidden", 
            "projectStatusVisibility": "hidden", 
        }
}
}

将上面代码加入到setting.json就可以隐藏大部分没用的细节了

宏未识别

修改配置文件

常用方法修改c_cpp_properties.json

image-20250224193655583

在其中手动加入对应的宏即可

image-20250224195104640

如果我们有多个配置,那就可以有多个配置不同的宏,然后通过右下角去切换C/C++的配置项,从而切换整体的宏定义

image-20250224201248076

  • 这里如果可以把CMake的defines输出给到c++那样就比较完美了

切换C++的配置和编译的configure不是一个配置,而且他们也不能联动,这就导致你改一下必须得手动切换配置才行

Kconfig的宏

对于一般的宏(区分feature、驱动等等,用来选择文件的宏),这样还是有点傻了,其实我们生成了”autoconf.h”,只需要把他引入项目就可以自动识别宏了

可能有些宏是用户自定义的,类似这样的

    # macro defines
    set(USER_MACRO_DEFINES
        "HSE_VALUE=8000000"
        "USE_HAL_DRIVER"
        "USE_FULL_LL_DRIVER"
        "USE_USB_OTG_FS"
        "STM32F767xx"
    )

这种方式稍微有点傻了,其实只要把他们全部加入到Kconfig里,利用Kconfig去生成这个宏即可,虽然Kconfig的宏是包含了CONFIG_这个头的,但是我们可以在CMake里通过一些手段去掉CONFIG_

还有一种办法是不去掉CONFIG,但是通过引入一个万能的头文件,里面预埋一个宏和其对应即可

#ifdef CONFIG_USE_HAL_DRIVER
	#define USE_HAL_DRIVER
#endif

#ifdef HSE_VALUE8000000
	#define HSE_VALUE 8000000
#endif

EXPORT_COMPILE_COMMANDS

经过一番研究,发现其实CMAKE有一个生成compile_commands.json的操作,这个会生成每个文件的编译命令,如果C++补全或者是高亮读取了这个文件,那么他就知道具体这个文件编译的时候使用了什么宏,那么对应宏是亮起还是灰掉,他就能智能识别

下面的命令就是在cmake编译的时候传递一个参数,让他自己生成这个json

"cmake.configureOnOpen": true,
"cmake.configureArgs": [
    "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
],
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"

实际上这个命令在我们CMakeLists的前几句就写了

image-20250225002207493

但是目前看效果不明显,似乎不会生效,感觉他需要额外再配置一下这个路径才能正确识别(前面的配置文件和自动识别会冲突)

image-20250225002812759

经过测试发现他自动识别了在CMakeLists中增加宏,但是Kconfig的.config里的宏并没有识别,还是需要将头文件引入,才能正常识别

如果把同样的宏放到被排除编译的文件中,会发现所有宏都处于不被激活的状态

image-20250225003002026

反查compile_commands.json也可以知道,这个文件直接就不存在,自然不会有任何反应

继续优化,可以通过C_CPP配置手动将某些文件夹或者文件给排除出去(这里理论上靠compile_commands.json应该也能实现啊)

{
    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
    "C_Cpp.exclusionPolicy": "checkFilesAndFolders",
    "C_Cpp.files.exclude": {
        "**/node_modules": true,
        "**/build": true,
        "**/*.min.js": true
    },
    "cmake.configureOnOpen": true
}

文件排除

项目里一些不需要显示的内容可以通过配置进行排除,让整个目录看起来更清爽

    "files.exclude": {
        "**/Battery": true,
        "**/*.jlink": true,
        "**/*.emProject": true,
        "**/*.emSession": true,
        "**/*.bin": true,
        "**/*.bat": true,
        "**/Boot": true
    },

常见问题

第一次编译可能过不去,这是config没有正确生成

VSCode第一次可能找不到编译器,需要使用Scan搜索一下

第二次编译可能还是过不去,需要clean一下工程,然后重新再编译

Debug

有时候需要调试一下,为什么某些文件被加入了,可以通过下面的代码输出当前加入的源文件,进而确定问题

# 输出当前目标的所有源文件
get_target_property(TARGET_SOURCES ${CMAKE_PROJECT_NAME} SOURCES)
message(STATUS "Current target sources:")
foreach(source ${TARGET_SOURCES})
    message(STATUS "  ${source}")
endforeach()

不整合

如果不整合到VSCode中,也有一些办法可以用来简化工作量

#!/bin/bash

# 提示用户选择预设
echo "请选择一个预设:"
options=("Debug" "Release" "Test")

# 设置自定义提示符
PS3="请输入选项编号: "

select opt in "${options[@]}"
do
    case $opt in
        "Debug"|"Release"|"Test")
            PRESET=$opt
            break
            ;;
        *) echo "无效选项 $REPLY";;
    esac
done

echo "您选择了 $PRESET, 开始构建项目...请先确认config变换"

CONFIG_FILE="./$PRESET.config"
if [ ! -f "$CONFIG_FILE" ]; then
    echo "配置文件 $CONFIG_FILE 不存在,创建一个空白文件..."
    touch "$CONFIG_FILE"
fi

# change project properties
export KCONFIG_CONFIG=./$PRESET.config
python -m guiconfig
# generate .config file
echo "生成config文件..."
python tools/kconfig.py Kconfig $PRESET.config autoconf.h kconfigLog.txt $PRESET.config
# create a build directory and make project
echo "创建预设目录..."
cmake --preset $PRESET
# build project
echo "开始build..."
cmake --build --preset $PRESET

相当于是一键build,只需要选好build的配置和确认Kconfig配置就可以自动进行下一步了

Summary

VSCode作为一个独立编辑器,距离IDE还是有点差距,整体性就是差了那么一点。想要做到完美,那就得自己开发插件,把好几个插件结合到一起,把他们之间的数据传递给弥补上

Quote

https://blog.csdn.net/weixin_39306574/article/details/103763144

https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#id1

https://github.com/microsoft/vscode-cmake-tools/blob/fad04c8b3fef0ba80b61df1856bc747770042dc9/docs/cmake-settings.md

🔲 ⭐

Kconfig与CMake初步模块化工程

Foreword

使用CMkae+Kconfig最小化的创建一个可以模块化的工程,可以适用于大部分MCU类型的工程,并且有一定程度的扩展性。

需求环境

环境需要的东西比较多,要安装4个独立程序才行,对比IDE一键安装,是复杂了一些

Arm GNU Toolchain

https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads

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

image-20250223003214247

不仅仅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

🔲 ☆

Windows上cmake适配多种构建工具和编译器

用CMake的好处一是屏蔽了多种常见编译器的编译选项和命令行参数差异,二是可以选择目标构建工具。我前段时间要写一个SDK,希望SDK能在主流操作系统(Windows,Linux,macOS)上跑,能用常见的编译器套件(MSVC,GCC,Clang,Intel C编译器等等)编译,这正好是CMake的用武之地。

使用不同的构建工具

NMake

以Windows为例,我想用MSVC编译,则用如下命令行:

1
2
3
4
mkdir build
cd build
cmake.exe -DCMAKE_BUILD_TYPE=Release -G"NMake Makefiles" -DCMAKE_PREFIX_PATH=U:\boost_1_77_0  ..
nmake

JOM

如果嫌nmake编译速度慢,Qt提供了一个跟nmake相近但允许像GNU make一样加-j参数并行编译的工具JOM,命令行如下:

1
2
3
4
mkdir build
cd build
cmake.exe -DCMAKE_BUILD_TYPE=Release -G"NMake Makefiles JOM" -DCMAKE_PREFIX_PATH=U:\boost_1_77_0 -DCMAKE_MAKE_PROGRAM=C:\Qt\Tools\QtCreator\bin\jom\jom.exe  ..
jom.exe -j 12

Ninja

也可以用现在很流行的另一个构建工具Ninja,它默认自动进行并行编译,命令行如下:

1
2
3
4
mkdir build
cd build
cmake.exe -DCMAKE_BUILD_TYPE=Release -G"Ninja" -DCMAKE_PREFIX_PATH=U:\boost_1_77_0 -DCMAKE_MAKE_PROGRAM=C:\Tools\ninja.exe  ..
ninja.exe

使用不同的编译器

Clang for Windows

前面是用了MSVC,如果想用Clang,Clang在Windows上有2种,一种是使用MSVC的套件,前端命令工具是clang-cl,另一种是使用MinGW,前端命令工具是clang,在cmake中只要参数设置一下就可以了:

1
2
3
4
mkdir build
cd build
cmake.exe -DCMAKE_BUILD_TYPE=Release -G"NMake Makefiles" -DCMAKE_PREFIX_PATH=U:\boost_1_77_0  -DCMAKE_CXX_COMPILER=clang-cl  ..
nmake

Intel oneAPI Compiler

以前Intel C编译器只在Linux上是免费的,现在Windows和macOS上也有免费安装包下载了,有在线安装和离线安装包2种,根据自己网络情况下载后安装,然后从开始菜单找到Intel oneAPI command prompt for Intel 64 for Visual Studio 2019,它的工具命令行是icx,但后面接了clang,所以也是clang-cl,这点不需要关心,知道是这回事就行,命令行如下:

1
2
3
4
mkdir build
cd build
cmake.exe -DCMAKE_BUILD_TYPE=Release -G"NMake Makefiles" -DCMAKE_PREFIX_PATH=U:\boost_1_77_0  -DCMAKE_CXX_COMPILER=icx  ..
nmake

用Intel oneAPI编译出来的可执行文件相比MSVC和官方Clang编译出来的可执行文件多链接了一个libmmd.dll,可能这是Intel家特有的一个优化点。

GCC/Clang with MinGW

MinGW在Windows上有多套环境,不同环境又有GCC和Clang这2种C++编译器,CMake都支持了这总共4种编译套件,构建工具可以使用MinGW官方的mingw32-make,注意一定要用官方CMake包,不要用msys2中的CMake,会有奇怪的问题。

64位GCC

1
2
3
4
mkdir mingw64-gcc-build
cd mingw64-gcc-build
env PATH=$PATH:/mingw64/bin cmake.exe -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=gcc -DCMAKE_MAKE_PROGRAM=/mingw64/bin/mingw32-make.exe ..
env PATH=$PATH:/mingw64/bin /mingw64/bin/mingw32-make.exe -j `nproc` 

64位Clang

1
2
3
4
mkdir mingw64-clang-build
cd mingw64-clang-build
env PATH=$PATH:/clang64/bin cmake.exe -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang -DCMAKE_MAKE_PROGRAM=/clang64/bin/mingw32-make.exe ..
env PATH=$PATH:/clang64/bin /clang64/bin/mingw32-make.exe -j `nproc` 

32位GCC

1
2
3
4
mkdir mingw32-gcc-build
cd mingw32-gcc-build
env PATH=$PATH:/mingw32/bin cmake.exe -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=gcc -DCMAKE_MAKE_PROGRAM=/mingw32/bin/mingw32-make.exe ..
env PATH=$PATH:/mingw32/bin /mingw32/bin/mingw32-make.exe -j `nproc` 

32位Clang

1
2
3
4
mkdir mingw32-clang-build
cd mingw32-clang-build
env PATH=$PATH:/clang32/bin cmake.exe -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang -DCMAKE_MAKE_PROGRAM=/clang32/bin/mingw32-make.exe ..
env PATH=$PATH:/clang32/bin /clang32/bin/mingw32-make.exe -j `nproc` 

🔲 ☆

使用cmake构建Qt5程序的一些坑

突然想试一下CLion上写Qt5程序是什么体验,反正有JB家的全家桶License。CLion使用cmake作为构建工具,而Qt官方也开始支持cmake,但使用过程中还是遇到一些坑,记录一下。

读Qt的cmake使用帮助

这点很重要,一些最重要的问题在使用帮助中已经提到了,一定要逐字逐句读一遍。

设置Qt路径

这个在网上随便搜一下就能找到很多文章都提到了,就是设置到CMAKE_PREFIX_PATHQt5_DIR中,注意设置到系统环境变量中与作为命令行参数传给cmake效果是有区别的,我发现作为命令行参数传递比较省事。

编译可执行文件或动态库

如果编译的项目是个exe或动态库,需要将Qt模块添加到目标链接库列表中,这样cmake就会自动把Qt的头文件路径和库文件路径添加到编译器的搜索路径列表中:

1
target_link_libraries(MyProgram Qt::Widgets Qt::Network Qt::Xml)

编译静态库

如果要编译的项目是个静态库,没法用target_link_libraries,但光是设置CMAKE_PREFIX_PATH仍然会在编译过程中报错找不到Qt的头文件,需要自己加入头文件搜索路径:

1
2
3
4
5
include_directories(
        ${Qt5Widgets_INCLUDE_DIRS}
        ${Qt5Network_INCLUDE_DIRS}
        ${Qt5Xml_INCLUDE_DIRS}
        )

其中Qt5Widgets_INCLUDE_DIRS一项就包含了WidgetsCoreGui三个模块。

编译Qt plugin项目

如果要编译的项目是以QtPlugin形式写成的库,要自己定义一个宏QT_STATICPLUGIN,这样写就可以:

1
add_compile_definitions(QT_STATICPLUGIN=1)

生成mac bundle

macOS上的bundle要多打包图标等一些资源,稍微麻烦一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (APPLE)
    set(MACOSX_BUNDLE true)
    set(MACOSX_BUNDLE_BUNDLE_NAME MyProgram)
    set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.ismisv.myprogram")
    set(MACOSX_BUNDLE_ICON_FILE "MyProgram.icns")
    set(MACOSX_BUNDLE_INFO_STRING "MyProgram")
    set(MACOSX_BUNDLE_LONG_VERSION_STRING "1.0.0.1")
    set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0")
    set(MACOSX_BUNDLE_BUNDLE_VERSION "1.0")

    set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/MyProgram.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
    set_target_properties(
            MyProgram
            PROPERTIES
            RESOURCE "./MyProgram.icns"
            WIN32_EXECUTABLE TRUE
            MACOSX_BUNDLE TRUE
    )
endif()

指定当前项目使用的编程语言

Cmake可以指定当前项目使用的编程语言,比如:

1
project(MyProgram LANGUAGES CXX)

这样cmake只会编译.cpp.cxx这些C++编译器认的文件,如果项目中有一些.c文件,则会被忽略,所以干脆不写就能编译所有类型的文件:

1
project(MyProgram)

❌