JavaScriptを有効にしてください

ArduinoのFormatter・Linter・Build CIをGitHub Actionsでやる方法

 ·   ·  ☕ 4 分で読めます

Arduinoでフワッと開発してもいいけどちゃんとCIやろうぜという記事です.


概要

CI

CIはContinuous Integrationの略で日本語だと継続的インテグレーションと言います.自動テスト・自動ビルドを用いて品質を保証した上でコードの変更を頻繁にメインブランチに統合して開発を進めることを言います.

GitHub Actions

GitHub Actionsは,GitHubのリポジトリ内で自動化されたワークフローを作成・実行するためのツールです.今回は,GitHub Actionsを用いてArduinoのプログラムに対してテストを行うCIのフローを作成します.

Formatter

フォーマッターは,コードの書式を整えるツールです.Arduino IDEではclang-formatがフォーマッターとして使用されています.今回はArduino IDEのフォーマッタにあったプログラムになっているかどうか自動で検知できるようにします.結果はreviewdogを用いてPull Request上に表示します.

Linter

リンターは,プログラムの潜在的な問題を検出するツールです.コンパイルエラーにはならないが怪しい要素がある部分も検出してくれます.今回はcpplintを使い,結果はreviewdogを用いてPull Request上に表示します.

Ardino CLI

Arduino CLIは,Arduinoプロジェクトのビルドや書き込み等をコマンドラインで行うことができるツールです.これを使ってGitHub Actions上でプログラムをビルドし,コンパイルエラーがないか確認します.

設定方法

  1. リポジトリの.github/workflows(なかったら作る)にarduino_ci.ymlを配置する.
    このワークフローで使うマイコンはRaspberry Pi PicoとSeeeduino XIAO RP2040ですが,他のマイコンでも同じように設定できます.
    https://github.com/earlephilhower/arduino-pico/tree/master
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
name: Arduino CI

on:
  pull_request:

jobs:
  check-formatting:
    runs-on: ubuntu-latest

    env:
      # See: https://github.com/arduino/arduino-ide/blob/main/arduino-ide-extension/package.json
      CLANG_FORMAT_VERSION: 14.0.0

    steps:
      - name: Set environment variables
        run: |
          # See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
          echo "CLANG_FORMAT_INSTALL_PATH=${{ runner.temp }}/clang-format" >> "$GITHUB_ENV"
          echo "REVIEWDOG_INSTALL_PATH=${{ runner.temp }}/reviewdog" >> "$GITHUB_ENV"          

      - name: Checkout
        uses: actions/checkout@v4

      - name: Download ClangFormat
        id: download
        uses: MrOctopus/download-asset-action@1.0
        with:
          repository: arduino/clang-static-binaries
          tag: ${{ env.CLANG_FORMAT_VERSION }}
          asset: clang-format_${{ env.CLANG_FORMAT_VERSION }}_Linux_64bit.tar.bz2
          target: ${{ env.CLANG_FORMAT_INSTALL_PATH }}

      - name: Install ClangFormat
        run: |
          cd "${{ env.CLANG_FORMAT_INSTALL_PATH }}"
          tar --extract --file="${{ steps.download.outputs.name }}"
          # Add installation to PATH:
          # See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
          echo "${{ env.CLANG_FORMAT_INSTALL_PATH }}/clang_Linux_64bit" >> "$GITHUB_PATH"          

      - name: Format examples
        run: |
          find \
            \( \
              -name '*.c' -or \
              -name '*.cpp' -or \
              -name '*.h' -or \
              -name '*.ino' -or \
              -name '*.ipp' -or \
              -name '*.tpp' \
            \) \
            -type f \
            -exec \
              clang-format \
                --assume-filename=foo.cpp \
                -i \
                --style=file \
                {} \;          

      - name: Install Reviewdog
        run: |
          wget -O - -q https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b ${{ env.REVIEWDOG_INSTALL_PATH }}
          echo "${{ env.REVIEWDOG_INSTALL_PATH }}" >> "$GITHUB_PATH"          

      - name: Check formatting
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if ! git diff --color --exit-code; then
            git diff | reviewdog -f=diff -name="format-check" -reporter=github-pr-review
            echo "Please do an Auto Format on the sketches:"
            echo "Arduino IDE: Tools > Auto Format"
            echo "Arduino Web Editor: Ctrl + B"
            exit 1
          fi          

  cpplint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: reviewdog/action-cpplint@master
      with:
        github_token: ${{ secrets.github_token }}
        reporter: github-pr-review
        flags: --extensions=h,hpp,c,cpp,cc,cu,hh,ipp,ino
        filter: "-readability/braces\
          ,-whitespace/braces\
          ,-whitespace/comments\
          ,-whitespace/indent\
          ,-whitespace/newline\
          ,-whitespace/operators\
          ,-whitespace/parens\
          ,-whitespace/tab\
          ,-whitespace/line_length\
          ,-build/header_guard\
          " # Optional

  build:
    name: ${{ matrix.code.sketch-paths }}
    runs-on: ubuntu-latest

    env:
      UNIVERSAL_LIBRARIES: |
        - name: Servo
          version: latest
        - name: Adafruit BusIO
          version: latest
        - name: Adafruit Unified Sensor
          version: latest
        - name: Adafruit BME280 Library
          version: latest
        - name: Adafruit BNO055
          version: latest        

    strategy:
      fail-fast: false

      matrix:
        code:
          - sketch-paths: Main
            libraries: |
                            - 
            board:
              vendor: rp2040
              arch: rp2040
              name: rpipico
            
          - sketch-paths: Valve
            libraries: |
                            - 
            board:
              vendor: rp2040
              arch: rp2040
              name: seeed_xiao_rp2040

        include:
          - code:
              board:
                vendor: rp2040
                arch: rp2040
            version: latest
            index: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Compile examples
        uses: arduino/compile-sketches@v1
        with:
          cli-version: latest
          fqbn: ${{matrix.code.board.vendor}}:${{matrix.code.board.arch}}:${{matrix.code.board.name}}
          platforms: |
            - name: ${{matrix.code.board.vendor}}:${{matrix.code.board.arch}}
              version: ${{matrix.version}}
              source-url: ${{matrix.index}}            
          libraries: |
            ${{ env.UNIVERSAL_LIBRARIES }}
            ${{ matrix.code.libraries }}            
          sketch-paths: |
                        ${{ matrix.code.sketch-paths }}
  1. リポジトリのルートに.clang-formatを配置する.(本家からSpacesBeforeTrailingCommentsSpacesInLineCommentPrefixを削除するとなぜかArduino IDEと同じ挙動になります)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Source: https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration
---
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: No
AttributeMacros:
  - __capability
BasedOnStyle: LLVM
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
  AfterCaseLabel: false
  AfterClass: false
  AfterControlStatement: Never
  AfterEnum: false
  AfterFunction: false
  AfterNamespace: false
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  AfterExternBlock: false
  BeforeCatch: false
  BeforeElse: false
  BeforeLambdaBody: false
  BeforeWhile: false
  IndentBraces: false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakAfterJavaFieldAnnotations: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: false
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakConstructorInitializersBeforeComma: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
ColumnLimit: 0
CommentPragmas: ''
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Leave
EmptyLineBeforeAccessModifier: Leave
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
  - foreach
  - Q_FOREACH
  - BOOST_FOREACH
IfMacros:
  - KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
  - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
    Priority: 2
    SortPriority: 0
    CaseSensitive: false
  - Regex: '^(<|"(gtest|gmock|isl|json)/)'
    Priority: 3
    SortPriority: 0
    CaseSensitive: false
  - Regex: '.*'
    Priority: 1
    SortPriority: 0
    CaseSensitive: false
IncludeIsMainRegex: ''
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: true
IndentCaseLabels: true
IndentExternBlock: Indent
IndentGotoLabels: false
IndentPPDirectives: None
IndentRequires: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
Language: Cpp
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 100000
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PPIndentWidth: -1
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 1
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 1
PenaltyBreakFirstLessLess: 1
PenaltyBreakOpenParenthesis: 1
PenaltyBreakString: 1
PenaltyBreakTemplateDeclaration: 1
PenaltyExcessCharacter: 1
PenaltyIndentedWhitespace: 1
PenaltyReturnTypeOnItsOwnLine: 1
PointerAlignment: Right
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 0
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
  AfterControlStatements: true
  AfterForeachMacros: true
  AfterFunctionDefinitionName: false
  AfterFunctionDeclarationName: false
  AfterIfMacros: true
  AfterOverloadedOperator: false
  BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesInAngles: Leave
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
  - Q_EMIT
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth: 2
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
  - STRINGIZE
  - PP_STRINGIZE
  - BOOST_PP_STRINGIZE
  - NS_SWIFT_NAME
  - CF_SWIFT_NAME
  1. 終わり.プルリクでコミットするごとに勝手にCIが走る.

ワークフローの中身の解説

書き終わらなかった.そのうち書く.

Formatter

Linter

Build

reviewdog

参考資料

Arduino

GitHub Actions

reviewdog


8bitマイコン
著者
8bitマイコン
組み込み周りで遊ぶ宇宙好き