How-To's Tips & Tricks

CLion 中的自定义编译器:关于在 CLion 中使用任意编译器的快速指南

Read this post in other languages:

作为一款 IDE,CLion 提供了许多功能来帮助开发者。 它可以显示文档弹出窗口、实时检测错误、建议修复等。 内部代码分析器始终在后台运行,在您输入时分析 C 和 C++ 代码。 对于自动分析器来说,C 和 C++ 是具有挑战性的语言,因为需要特定于编译器的数据才能正确解析代码。 引擎需要知道标题搜索路径、预定义的宏定义和其他一些详细信息。

对于一组预定义的已知编译器,CLion 会使用特殊选项运行项目的编译器,并在解析项目时收集所需信息。 当然,这只适用于有限数量的编译器,如 GCC、Clang、MSVC、IAR 及其衍生工具。 如果某个项目使用特定的自定义编译器、很少使用的编译器或我们无权访问的专有编译器来正确集成,则 CLion 无法通过其标准过程进行使用。 在这种情况下,您可以选择使用自定义编译器功能。

要在 CLion 中使用自定义编译器,您需要一个包含必要编译器数据的 YAML 文件。 此类文件应包含一个或多个小节,每个小节描述一个特定的编译器或编译器变体。 每个小节应有一个描述、一个或多个匹配记录,以及多个信息记录。

当 CLion 解析项目并遇到针对项目文件的编译器时,CLion 会逐节扫描,并检查编译器是否与记录匹配。 匹配会使用一个或多个匹配记录来完成。 可用的匹配记录包括:

  • match-compiler-exe 是正则表达式,用来检查编译器名称。 一个与平台无关的良好示例是 "(.*/)?sdcc(.exe)?"
  • match-source 也是正则表达式,用来与要编译的源文件的名称匹配。 对于 C,通常为 ".*\.c",而对于 C++,通常为 ".*\.cpp"
  • match-args 是单个命令行开关、开关序列或无序开关数组。 例如,"-a" 匹配实参 -a"-b -c" 匹配实参 -b -c,而 ["-b", "-c"] 匹配 -b -c-c -b
  • match-language 可能为 CCPP。 对于 CMake 项目,它会与 CMake 检测到的语言匹配;对于其他类型的项目,该语句会被直接忽略。

在所有匹配项成功解析之后,就会选取小节信息记录,并跳过 YAML 文件的其余部分。 编译器数据获取自信息记录,然后直接传递给 CLion 的代码分析器;也就是说,在编译器试运行期间不收集数据。

如果这些小节都没有为 IDE 提供将记录与特定编译器积极匹配的信息,它会尝试以通常的方式(即通过猜测编译器的类型并运行编译器)来收集编译器数据。

如何编写自定义编译器 YAML 文件

要开始编写自定义编译器 YAML 文件,您需要确定有关您的编译器的以下详细信息:

  • 您希望支持的语言。 每种受支持的语言或语言变体都需要至少一个配置小节。
  • 编译器二进制文件名称。 可以有不止一个。 一些工具链具有不同的 C 和 C++ 编译器二进制文件、不同的内存模型、不同的语言变体等。 这些信息可以在工具链文档中找到,并且必须反映在您的自定义编译器 YAML 文件中。
  • 标准包含文件所在的目录。 要标识这些目录,请阅读编译器的文档或直接浏览已安装的工具链文件夹树。
  • 预定义宏的列表。 一些编译器可以报告预定义宏的列表,而其他编译器则不能。 请参考您的编译器文档,以了解如何为您的编译器获取它们。

实用示例

我们通过为 Small Device C Compiler (SDCC) 编写一个自定义编译器 YAML 文件来实践此过程。 SDCC 支持多种 8 位 CPU 架构,如 STM8、Z80 等。 该编译器是开源的,是多个 MCU 系列中唯一的免费编译器。 在此示例中,我们将为其 Microchip PIC-16 变体(端口)编写一个 YAML 文件。

以下是我们需要了解的有关此编译器的详细信息:

  • 仅支持 C 语言。
  • 编译器二进制文件名称为 sdccsdcc.exe(在 Windows 中)。
  • -mpic16 选项对于 PIC16 架构支持是必需的,还应该使用 --use-non-free
  • 要报告标题搜索路径列表,编译器应使用 --print-search-dirs 选项运行。
  • 要报告预定义的宏,编译器应使用 -E-dM 选项和一个空白源文件运行。

我们将从创建一个测试项目开始。 此最小项目包含一个 C 文件、YAML 文件本身和一个构建系统文件。 我们将使用 Makefile。 如果要使用 C++,那么还应该添加另一个使用 C++ 编写的源文件。

首先,创建一个项目文件夹,并在里面创建一个空白 Makefile

在 CLion 中打开该文件夹,然后在弹出的 Load Project(加载项目)对话框中点击 Cancel(取消)。

在 CLion 编辑器中打开 Makefile,并将最少内容添加到该 Makefile

clean:
    all: main.c
    sdcc -mpic16 --use-non-free -S main.c

该文件现在包含一个用于在不需要更多步骤的情况下将 main.c 编译成程序集的命令。

创建一个 main.c 文件。 我们来创建一个经典的 “Hello, World”,并带有额外特定于 SDCC 的附加内容,即__code 关键字和预定义的宏 __SDCC_pic16

#include <stdio.h>
int main() {
    printf("Hello, World!n");
    return 0;
}
int __code i = 0;
#ifndef __SDCC_pic16
#  error "__SDCC_pic16 macro is expected to be defined"
#endif

接下来,我们编写一个自定义编译器配置存根:

  1. 创建一个名为 clion-custom-compiler-sdcc-pic16.yaml 的文件。
  2. 将 JSON 架构“Custom Compiler Definition”分配给该文件。
    Custom compiler scheme validated这样,CLion 可以通过动态文件结构验证和输入提示为您提供帮助。 架构名称显示在 IDE 状态栏中,可以通过点击名称进行选择。

存根为:

compilers:
 - description: SDCC for PIC-16
   match-compiler-exe: "(.*/)?sdcc(.exe)?"

目前,我们只通过名称进行匹配。 它是一个匹配 Linux 和 Windows 编译器二进制文件名称的正则表达式,无论包含文件夹是什么。

下一步是在 CLion 中使用我们的自定义编译器配置。 打开 Settings/Preferences | Build, Execution, Deployment | Toolchains | Custom Compiler(设置/偏好设置 | 构建、执行、部署 | 工具链 | 自定义编译器)。 选中 Use custom compiler config(使用自定义编译器配置),并选择项目的 YAML 文件作为配置文件。

自定义编译器设置

Tools(工具)菜单中选择 Makefile | Clean and Reload Makefile Project(Makefile | 清理并重新加载 Makefile 项目)。如果一切正常,Build(构建)工具窗口将显示项目已成功加载。

项目已加载

如果项目未正确加载,则应仔细检查 YAML 文件中的 match-compiler-exe 语句。 记录值是一个正则表达式,它是该过程中最脆弱的部分。 如有问题,请参考这些示例

现在,我们来看看 CLion 是否真的适配了 SDCC,我们的项目是否按预期运行。

检查自定义编译器是否正常运行

在编辑器中打开 main.c。 此时看到一些错误没有关系。 转到 Help | Diagnostics Tools(帮助 | 诊断工具),选择 Show Compiler Info(显示编译器信息)。 这将打开一个诊断页面,其中包含正在编辑的文件的编译器信息。 在这里,您可以看到编译器种类(“Custom Defined Compiler”)和检测到的编译器描述(“SDCC for PIC-16”)。 这意味着 CLion 感知到了项目结构,但是代码分析器还没有所需的编译器数据,因此在 main.c 中显示了各种错误。

自定义编译器正常运行

收集缺少的编译器信息

我们的 main.c 文件目前似乎已损坏。 找不到 stdio.hprintf 未定义,__code 修饰符错误,并且未定义文档中的预定义宏。

找不到 stdio

我们来通过提供正确的编译器信息修正所有这些问题。

幸运的是,SDCC 可以打印标题位置和预定义的宏。 将以下各行添加到 Makefile 即可解决这个问题:

gather_info:
#   List directories
    sdcc -mpic16 --use-non-free --print-search-dirs
#   Create a void C file
    echo //void > void.c
#   List predefined macros
    sdcc -mpic16 --use-non-free -E -dM void.c

接下来,我们需要构建 gather_info 目标并查看输出。 首先,有一个标题搜索路径列表:

…
includedir:
C:Program FilesSDCCbin..includepic16
C:Program FilesSDCCbin..include
C:Program FilesSDCCbin..non-freeincludepic16
C:Program FilesSDCCbin..non-freeinclude
…

唯一的问题是这些路径是绝对路径。 出于可移植性原因,我们将它们设为相对于编译器位置,并将它们作为 include-dirs 数组添加到编译器定义中。

接下来是预定义的宏。 它们被打印在输出的最底部。 可以通过 defines-text 将它们添加到 YAML 文件。

最后但同样重要的是,我们需要通过使用 match-args: -mpic16 match-language: C 语句使我们的编译器匹配得更具体一些,然后作为空定义添加 SDCC 语言扩展字。 完成后,最终的 YAML 文件将如下所示:

compilers:
  - description: SDCC for PIC-16
    match-compiler-exe: "(.*/)?sdcc(.exe)?"
    match-args: -mpic16
    match-language: C
    include-dirs:
      - ${compiler-exe-dir}/../include/pic16
      - ${compiler-exe-dir}/../include
      - ${compiler-exe-dir}/../non-free/include/pic16
      - ${compiler-exe-dir}/../non-free/include
    defines-text: "
#define __SDCC_USE_NON_FREE 1
#define __SDCC_PIC18F452 1
#define __18f452 1
#define __STACK_MODEL_SMALL 1
#define __SDCC_pic16 1
#define __SDCC_ALL_CALLEE_SAVES 1
#define __STDC_VERSION__ 201112L
#define __STDC_HOSTED__ 0
#define __SDCCCALL 0
#define __STDC_UTF_16__ 1
#define __SDCC_VERSION_MINOR 2
#define __STDC_ISO_10646__ 201409L
#define __SDCC_VERSION_PATCH 0
#define __SDCC_VERSION_MAJOR 4
#define __STDC_NO_VLA__ 1
#define __SDCC 4_2_0
#define __STDC_UTF_32__ 1
#define __STDC_NO_THREADS__ 1
#define __SDCC_CHAR_UNSIGNED 1
#define __STDC_NO_ATOMICS__ 1
#define __STDC__ 1
#define __SDCC_REVISION 13081
#define __STDC_NO_COMPLEX__ 1

#define __interrupt
#define __code
#define __at
"

现在,我们可以重新加载项目 Tools | Makefile | Reload Makefile Project(工具 | Makefile | 重新加载 Makefile 项目)并再次检查 main.c 文件。

解析正常运行

错误已经消失,并且可以导航到 stdio.hShow Compiler Info(显示编译器信息)窗口显示了正确的信息,包括预定义的宏、语言功能和标题搜索路径。

这个最终的 YAML 文件可以在 GitHub 上找到。

就是这样! 为您的自定义编译器尝试自定义编译器功能,并告诉我们效果如何。 欢迎向我们的编译器支持集合提交拉取请求!

 

本博文英文原作者:

Sue

Anastasia Kazakova

Discover more

使用 CLion 进行 Arduino 开发:从业余爱好到专业项目 – 第一部分

如今,许多开发者都在尝试基于 Arduino 的项目。 Arduino 很容易上手,通过灯光闪烁或机器人移动看到自己的编码成果是一种令人愉快的体验。 但大多数 Arduino 开发者止步于此。 他们像周末勇士一样从事自己的项目,只是把它们当作小爱好和编外项目。 在这一系列博文中,我们会探讨如何将您的私人兴趣项目提升到更专业的水平,以及像 CLion 这样的 IDE 如何帮助您实现这一目标。 我们要构建什么 我们为这个项目设定了一个宏大的目标。 我们将构建一个电子气压计,这是一种测量气压并将其绘制在图表上以预测未来几个小时天气状况的设备。 这比传统的气压计更有用,因为跟踪压强变化比跟踪绝对值更重要。 该设备应当由电池供电,充一次电应当可以工作几周。 我们将从构建经典项目 DIY 气压计开始。我们将使用: Bosch BMP085 气压传感器。 我们将使用 GY-65 模块,不过也可以用 BMP180 (GY-68) 代替。 Arduino Uno 开发板。 我们使用 DFRduino UNO R3,因为它有彩色连接器 :) Waveshare 4.2 英寸电子纸屏幕。 导线、USB 线缆、几个塑料 PCB 支架、M3 螺栓和螺母。 IKEA 塑料平板电脑支架,用于将所有原型部件固定在一起。 听起来很有趣,对吗? 我们开始吧! 从原型开始 电子装置安装 我们首先在平板电脑

想要无线刷写和调试 MCU 吗? 只需使用您的 Raspberry Pi

嵌入式开发者如何设置进行编程所需的一切? 典型的有线设置包括放在开发者桌上的设备,以及将其连接到调试探头的带状线缆,调试探头又通过 USB 线缆连接到开发者的计算机。 计算机运行探头专用软件和 IDE。 在许多情况下,这种设置可以正常工作。 但嵌入式设备需要处理现实世界问题。 有时,我们会使用基于 MCU 的控制板管理高压或危险设备。 有时,这些设备安装在户外。 设备可能会在轮子上移动,也可能漂浮在水面上。 它们甚至可能安装在某个远程位置。 在这些情况下使用线缆进行设置通常不太方便,甚至可能会有危险。 当我开始开发户外设备时,我立即遇到了这个问题。 该如何解决呢? 我盘点了一下手头上的东西,发现了一块 Raspberry Pi 开发板。 我花了几个小时将它设置为无线调试探头,现在它运行得很棒。 最简单的无线设置原则 哪种开发板更合适? Raspberry PI 操作系统和连接 OpenOCD 安装 最终 Raspberry 设置 设置示例项目 硬件接线 启动 附加内容 – JTAG 和 RISC-V 最简单的无线设置原则 每个基于 ARM 的 MCU 都有一个双线调试接口 (SWD) 和一条复位线。 这三条线都应该连接到调试探头。 在我的无线设置中,我使用 Raspberry Pi GPIO 排针直接连接到设备。 在软件方面,我的 Raspberry Pi 运行开源的片上刷写和