Automated build pipelines are a crucial part of professional software development. Now, you may not think of your typical hobby Arduino project as professional software development but let’s assume you’re creating an Arduino library for others to use – or even just a small open source project that may become relevant to the community at some point.
自动化的构建管道是专业软件开发的关键部分。 现在,您可能不会将典型的业余Arduino项目视为专业软件开发,而是假设您正在创建Arduino库供他人使用–甚至只是一个可能与社区相关的小型开源项目。
With an automated build pipeline aka continuous integration you can guarantee a certain level of quality for your project. I’m going to discuss two major things continuous integration can do for you:
通过自动构建管道(也称为持续集成),您可以保证项目的质量。 我将讨论持续集成可以为您做的两件事:
- Make sure your code compiles for all intended target platforms 确保您的代码针对所有目标平台进行编译
- Enforce a certain coding style 实施某种编码风格
The first one is particularly interesting for Arduino projects since they tend to target a bunch of different microcontrollers.
对于Arduino项目,第一个特别有趣,因为它们倾向于针对许多不同的微控制器。
The second one will become important as soon as you’re no longer the only person working on your code. Having a consistent coding style throughout the whole project makes life easier for everyone involved.
一旦您不再是唯一处理代码的人,第二个便会变得很重要。 在整个项目中具有一致的编码风格使所有参与人员的工作变得更加轻松。
Besides that, continuous integration can do a lot more, like:
除此之外,持续集成还可以做很多事情,例如:
- Run automated unit tests on your code 在代码上运行自动化的单元测试
- Automatically update the documentation of your code 自动更新代码文档
The latter addresses the often neglected part of proper documentation. You can create the best library – if you’re not going to document it people are most likely not going to use it.
后者解决了适当文档中经常被忽略的部分。 您可以创建最好的库–如果您不打算记录它,人们很可能不会使用它。
项目设置 (Project Setup)
Arduino项目 (Arduino Project)
Your typical Arduino project is just a folder with an Arduino Sketch in it:
您的典型Arduino项目只是其中包含Arduino Sketch的文件夹:
MyArduinoProject
└── MyArduinoProject.ino
If this is your setup then continuous integration may admittedly be overkill. It still can’t hurt to play around with it, though.
如果这是您的设置,那么连续集成可能会被认为是过大的。 不过,玩它仍然不会受到伤害。
If you want to go to the next level consider setting up your project with PlatformIO.
如果您想进入下一个级别,请考虑使用PlatformIO设置项目。
PlatformIO项目 (PlatformIO Project)
PlatformIO is very well integrated with Visual Studio Code and it’s a great alternative to the Arduino IDE. It comes with built-in support for a lot of microcontrollers and even allows you to go beyond the Arduino ecosystem – but that’s definitely a whole other blog post.
PlatformIO与Visual Studio Code很好地集成在一起,它是Arduino IDE的绝佳替代品。 它具有对许多微控制器的内置支持,甚至允许您超越Arduino生态系统-但这绝对是其他博客文章。
Your typical PlatformIO project consists of two files:
典型的PlatformIO项目包含两个文件:
MyPlatformIOProject
├── platformio.ini
└── src
└── main.cpp
Although, you’re not limited to these files as they’re just the starting point. As you progress, your project may grow way beyond just two files. This is definitely going to be a territory where continuous integration makes sense.
虽然,您并不局限于这些文件,因为它们只是起点。 随着您的进度,您的项目可能会超出两个文件。 毫无疑问,这将是持续集成有意义的领域。
Arduino库 (Arduino Library)
Maybe you’re not only working on a personal project but on a new Arduino library to share with the community. If that’s the case then continuous integration is absolutely for you.
也许您不仅在从事个人项目,而且还在与社区共享一个新的Arduino库。 如果是这样,那么持续集成绝对适合您。
Your typical Arduino library will probably look something like this:
您的典型Arduino库可能看起来像这样:
MyArduinoLibrary
├── examples
│ └── HelloWorld
│ └── HelloWorld.ino
├── library.properties
└── src
├── MyArduinoLibrary.cpp
└── MyArduinoLibrary.h
You’ll have at least one header file, one implementation file and one or more example Arduino Sketches.
您将至少有一个头文件,一个实现文件和一个或多个Arduino Sketches示例。
On top of that, you want to make sure that your library is compatible with as many Arduino boards as possible. That’s where an automated build pipeline will really come in handy.
最重要的是,您要确保您的库与尽可能多的Arduino板兼容。 在那里,自动构建管道真的很方便。
GitHub和版本控制 (GitHub and Version Control)
For the rest of this post I’m going to assume that your project is under version control using git and published on GitHub.
在本文的其余部分,我将假设您的项目使用git进行版本控制,并在GitHub上发布 。
If you’re not doing this already I strongly suggest using git for all of your coding projects anyway.
如果您还没有这样做,我强烈建议您将git用于所有编码项目。
GitHub is relevant here because we’re going to make use of its workflows that let you create build pipelines without the use of external services. Similar setups are possible with other services like GitLab or using continuous integration tools like Travis or CircleCI. I encourage you to check out all of these options to find the one most suited to your needs. For this blog post, however, GitHub workflows will be the tool of choice.
GitHub在这里很重要,因为我们将利用其工作流程,使您无需使用外部服务即可创建构建管道。 其他服务(如GitLab)或使用连续集成工具(如Travis或CircleCI)也可以进行类似的设置。 我鼓励您检查所有这些选项,以找到最适合您需要的选项。 但是,对于此博客文章,GitHub工作流将是首选工具。
基本的GitHub工作流程 (A Basic GitHub Workflow)
Using GitHub workflows we’re basically going to use a virtual machine running the latest Ubuntu and executing a bunch of pre-defined tasks whenever a change to the code is made. This may sound overwhelming and while there certainly is a lot going on, we’re going to build this all up step by step.
使用GitHub工作流,我们基本上将使用一台虚拟机,该虚拟机运行最新的Ubuntu,并在更改代码时执行一系列预定义的任务。 这听起来可能让人不知所措,尽管确实有很多事情要做,但我们将逐步构建所有这些。
The virtual machine runs on GitHub’s servers and all we’ll have to do is provide a configuration file inside the folder .github/workflows
in our project. Let’s call it build.yml
:
该虚拟机在GitHub的服务器上运行,我们要做的就是在我们项目的.github/workflows
文件夹中提供一个配置文件。 我们称它为build.yml
:
MyArduinoLibrary
├── .github
│ └── workflows
│ └── build.yml
…
The file will have the following content:
该文件将具有以下内容:
name: buildon: [pull_request, push]jobs:
build:
runs-on: ubuntu-latest steps:
- name: Checkout
uses: actions/checkout@v2
It’s going to have a name
— this can be anything from My awesome automated build
to simply just build
, which is what we’ll be using.
它将会有一个name
-它可以是任何东西,从My awesome automated build
到简单的build
,这就是我们将要使用的。
on: [pull_request, push]
tells GitHub to execute this workflow whenever there’s a push to this repository or a new pull request is created. There are other events and more sophisticated triggers available that I won’t be covering here.
on: [pull_request, push]
告诉GitHub每当对该存储库进行推送或创建新的Pull请求时都执行此工作流。 还有其他事件和更复杂的触发器可用,我将不在这里介绍。
After that, all this workflow does so far is run on the latest Ubuntu and checkout the code. The last step is important because we want the workflow to work with our code.
之后,到目前为止,所有这些工作流程都在最新的Ubuntu上运行并签出代码。 最后一步很重要,因为我们希望工作流能够使用我们的代码。
自动化Arduino构建 (Automated Arduino Builds)
We’re now going to add another step after the Checkout step in our build.yml
:
现在,我们将在build.yml
的Checkout步骤之后添加另一步骤:
…
steps:
- name: Checkout
uses: actions/checkout@v2 - name: Build on Arduino CLI
run: bash ci/build-arduino.sh
This step is called Build on Arduino CLI
and it’s going to run a shell script located at ci/build-arduino.sh
. We’re now going to create this file in our project folder:
此步骤称为Build on Arduino CLI
,它将运行位于ci/build-arduino.sh
的shell脚本。 现在,我们将在项目文件夹中创建此文件:
MyArduinoLibrary
├── .github
│ └── workflows
│ └── build.yml
├── ci
│ └── build-arduino.sh
…
This script is going to install the Arduino CLI on our virtual machine and compile our code for several Arduino boards. If the compilation fails our automated build pipeline will fail, and if it succeeds we’ll know that our code is valid for all the tested Arduino platforms.
该脚本将在我们的虚拟机上安装Arduino CLI ,并为几个Arduino板编译我们的代码。 如果编译失败,我们的自动构建管道将失败,如果成功,我们将知道我们的代码对所有经过测试的Arduino平台均有效。
To install the Arduino CLI we more or less follow its installation guide:
要安装Arduino CLI,我们或多或少遵循其安装指南 :
#!/bin/bash# Exit immediately if a command exits with a non-zero status.
set -e# Enable the globstar shell option
shopt -s globstar# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE# Create directories
mkdir $HOME/Arduino
mkdir $HOME/Arduino/libraries# Install Arduino IDE
export PATH=$PATH:$GITHUB_WORKSPACE/bin
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
arduino-cli config init
arduino-cli core update-index
Now, let’s add the Arduino AVR core:
现在,让我们添加Arduino AVR内核:
# Install Arduino AVR core
arduino-cli core install arduino:avr
We’re almost ready to compile the code. If your project is an Arduino library you need to link it to the Arduino libraries directory first. Otherwise the compiler won’t be able to locate your code.
我们几乎可以编译代码了。 如果您的项目是Arduino库,则需要首先将其链接到Arduino库目录。 否则,编译器将无法找到您的代码。
# Link Arduino library
ln -s $GITHUB_WORKSPACE $HOME/Arduino/libraries/CI_Test_Library
Now, we are ready to compile all the Arduino Sketches for the AVR core. Let’s compile them for the Uno:
现在,我们准备为AVR内核编译所有Arduino草图。 让我们为Uno编译它们:
# Compile all *.ino files for the Arduino Uno
for f in **/*.ino ; do
arduino-cli compile -b arduino:avr:uno $f
done
That’s it. With this setup you have a build pipeline that automatically compiles all of your Arduino Sketches for the Arduino Uno on each push or pull request to your GitHub repository. It’s easy to add additional platforms as you just need to copy and modify the code that installs new cores and compiles your code.
而已。 使用此设置,您将拥有一个构建管道,该管道会在每次向GitHub存储库的推送或拉取请求时自动为Arduino Uno编译所有Arduino草图。 添加其他平台很容易,因为您只需要复制和修改安装新内核并编译代码的代码即可。
For example, let’s add the Arduino Zero. Let modify the code that installs the AVR core and add another core:
例如,让我们添加Arduino Zero。 让我们修改安装AVR核心的代码并添加另一个核心:
# Install Arduino cores
arduino-cli core install arduino:avr
arduino-cli core install arduino:samd
Let’s also modify the code that compiles our sketches accoridngly:
我们还要修改可正确编译草图的代码:
# Compile all *.ino files for the Arduino Uno
for f in **/*.ino ; do
arduino-cli compile -b arduino:avr:uno $f
arduino-cli compile -b arduino:samd:arduino_zero_native $f
done
Now you automatically test for compilation errors of your code on two different platforms.
现在,您可以在两个不同的平台上自动测试代码的编译错误。
If you commit and push all of this to your GitHub repository and go to the Actions tab of your repository you should see your newly created workflow and its output:
如果您提交所有这些内容并将其推送到GitHub存储库,然后转到存储库的“操作”选项卡,则应该看到新创建的工作流及其输出:
![Image for post](https://miro.medium.com/max/9999/1*GWJip7EjzJ2CLVKUkgup1g.png)
If you click on the build entry you should see something like this:
如果单击构建条目,您应该会看到类似以下内容的信息:
![Image for post](https://miro.medium.com/max/9999/1*MtqlSjiJXZIKNn2hDeKC9g.png)
If one of the steps failed you can click on it to see the detailed log output. It also helps a lot to add additional output to your shell scripts in order to better understand what’s being executed.
如果其中一个步骤失败,则可以单击它以查看详细的日志输出。 它还可以帮助您在shell脚本中添加其他输出,从而更好地了解正在执行的内容。
自动化PlatformIO构建 (Automated PlatformIO Builds)
The above script will work for single Arduino Sketches and Arduino libraries with example sketches included. It won’t work for PlatformIO projects, however. For these kinds of project it’s advisable to do automated builds using the PlatformIO CLI instead of the Arduino CLI. A big advantage of this approach is that you can have continuous integration for any kind of PlatformIO project, not only the Arduino ones.
上面的脚本适用于单个Arduino草图和带有示例草图的Arduino库。 但是,它不适用于PlatformIO项目。 对于此类项目,建议使用PlatformIO CLI而非Arduino CLI进行自动构建。 这种方法的一大优势是您可以与任何类型的PlatformIO项目进行持续集成,而不仅仅是Arduino项目。
The steps involved are more or less the same as before. You add a step to your build.yml
:
涉及的步骤与之前大致相同。 您将一个步骤添加到build.yml
:
…
steps:
- name: Checkout
uses: actions/checkout@v2 - name: Build on PlatformIO
run: bash ci/build-platformio.sh
You add a build-platformio.sh
shell script:
您添加一个build-platformio.sh
Shell脚本:
MyPlatformIOProject
├── .github
│ └── workflows
│ └── build.yml
├── ci
│ └── build-platformio.sh
…
We follow the PlatformIO installation guide:
我们遵循PlatformIO安装指南:
#!/bin/bash# Exit immediately if a command exits with a non-zero status.
set -e# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE# Install PlatformIO CLI
export PATH=$PATH:~/.platformio/penv/bin
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -o get-platformio.py
python3 get-platformio.py
We then install the platforms we want to test on:
然后,我们要安装要测试的平台 :
# Install Atmel AVR platform
pio platform install "atmelavr"
If your platformio.ini
is set up correctly all that’s left to do is:
如果正确设置了platformio.ini
则要做的只是:
# Compile project
pio run
Alternatively you can directly target the environments defined in your platformio.ini
:
或者,您可以直接针对platformio.ini
定义的环境:
# Compile project for the Uno
pio run -e uno
That’s it. It’s even easier than using the Arduino CLI.
而已。 它甚至比使用Arduino CLI更容易。
强制使用某种代码格式样式 (Enforce a Certain Code Formatting Style)
When writing C or C++ code, a tool called clang-format is often used to ensure that your code is following certain formatting style guidelines.
在编写C或C ++代码时,通常使用一种称为clang-format的工具来确保您的代码遵循某些格式样式准则。
There are several code styles you can follow — like: LLVM, GNU, Google, Mozilla or Microsoft — and you can configure clang-format to use one of these as a base style for your code. If you’re unsure just pick one. It’s not so much about having the perfect formatting style and more about having one in the first place . You can always change and refine it later and easily reformat your whole code base.
您可以遵循几种代码样式,例如: LLVM , GNU , Google , Mozilla或Microsoft ,并且您可以配置clang格式以将其中一种用作代码的基本样式。 如果不确定,请选择一个。 拥有完美的格式化样式并不仅仅是拥有一个格式化样式。 您以后总是可以更改和完善它,并轻松地重新格式化整个代码库。
If you’re using Visual Studio Code you can add an extension to reformat your code each time you save a file. That way you’ll never have to worry again about the formatting of your code. Similar plugins are available for other editors, too.
如果您使用的是Visual Studio Code,则可以在每次保存文件时添加扩展名以重新格式化代码。 这样,您将不必再担心代码的格式。 类似的插件也可用于其他编辑器。
I suggest adding a .clang-format
file to your project:
我建议在您的项目中添加.clang-format
文件:
MyArduinoLibrary
├── .clang-format
…
Inside of it you configure your chosen base style as well as custom configuration options. My .clang-format
currently looks something like this:
在其内部,您可以配置所选的基本样式以及自定义配置选项。 我的.clang-format
当前看起来像这样:
BasedOnStyle: GoogleLanguage: CppIndentWidth: 4
AlignConsecutiveMacros: true
Feel free to explore all the other options clang-format has to offer.
随意探索clang格式必须提供的所有其他选项 。
Once you added clang-format to your project you can integrate it into your build pipeline to make sure only properly formatted code gets into your code base.
将clang格式添加到项目后,可以将其集成到构建管道中,以确保只有正确格式化的代码才能进入代码库。
To achieve that, we create a new step named Check clang-format conformity
in our build.yml
— I’d suggest adding this one right after the Checkout step:
为此,我们在build.yml
创建一个名为“ Check clang-format conformity
的新步骤-我建议在Checkout步骤之后立即添加此步骤:
…
steps:
- name: Checkout
uses: actions/checkout@v2 - name: Check clang-format conformity
run: bash ci/clang-lint.sh - name: Build on Arduino CLI
run: bash ci/build-arduino.sh
This step runs a shell script called clang-lint.sh
. In this file we add a few lines to install clang-format:
此步骤运行一个名为clang-lint.sh
的shell脚本。 在此文件中,我们添加了几行以安装clang格式:
#!/bin/bash# Exit immediately if a command exits with a non-zero status.
set -e# Enable the globstar shell option
shopt -s globstar# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE# Install clang-format
sudo apt-get -y install clang-format-10
We then loop over all the source code files and check whether their content is properly formatted:
然后,我们遍历所有源代码文件,并检查其内容格式是否正确:
# Check clang-format output
for f in **/*.{h,c,hpp,cpp,ino} ; do
if [ -f "$f" ]; then
diff $f <(clang-format -assume-filename=main.cpp $f) 1>&2
fi
done
The main line in here is this one:
这里的主要内容是:
diff $f <(clang-format -assume-filename=main.cpp $f) 1>&2
It runs clang-format on a file and compares its output with the actual content of the file. If the two are the same the file is already properly formatted. If they differ the file isn’t properly formatted yet.
它对文件运行clang格式,并将其输出与文件的实际内容进行比较。 如果两者相同,则文件已经正确格式化。 如果它们不同,则文件尚未正确格式化。
The output of the diff
command is redirected to stderr
which means that any output counts as an error. If the file is properly formatted the diff
will have no output and there will be no error. As soon as there’s output the build will fail and even show the wrongly formatted lines of code.
diff
命令的输出将重定向到stderr
,这意味着任何输出都将计为错误。 如果文件格式正确,则diff
将没有输出,也不会出现错误。 一旦输出,构建将失败,甚至显示格式错误的代码行。
It’s highly advisable to add some output right before the diff command in order to better understand what files are being checked. Something like:
强烈建议在diff命令之前添加一些输出,以更好地了解正在检查的文件。 就像是:
echo "Checking file ${f}"
diff $f <(clang-format -assume-filename=main.cpp $f) 1>&2
If you know your way around shell scripting you can get quite pleasing output from your build pipeline:
如果您知道如何使用Shell脚本,则可以从构建管道中获得令人愉悦的输出:
![Image for post](https://miro.medium.com/max/9999/1*cMZQELH-BG9fwsAXckaCrw.png)
最后的想法 (Final Thoughts)
As we’ve seen, it’s not that hard to get started with continuous integration for your own projects. Once you’ve got a basic setup in place you can always refine it and reuse it on other projects.
如我们所见,开始为您自己的项目进行持续集成并不难。 完成基本设置后,您就可以随时对其进行优化,并在其他项目中重复使用。
An automated build pipeline gets even more powerful when combined with unit tests. Integrating these into your pipeline could be as easy as adding a pio test
command to one of your scripts.
与单元测试结合使用时,自动化的构建管道将变得更加强大。 将它们集成到您的管道中就像将pio test
命令添加到您的脚本之一一样容易。
It’s up to you to decide whether continuous integration makes sense for your specific use case. As soon as you intend to share your code with others and maybe even let other people contribute to your project, I’d definitely consider it.
由您决定持续集成是否适合您的特定用例。 一旦您打算与他人共享您的代码,甚至让其他人为您的项目做贡献,我肯定会考虑的。