Skip to main content
Indian DevOps engineer at a Pune embedded-systems CI/CD lab configuring GitHub Actions pipelines for Cortex-M firmware with csolution and cbuild on multiple monitors

Cortex-M CI/CD with csolution.yml + cbuild + GitHub Actions: A Working Pipeline for Indian Firmware Teams

GSAS Editorial · · 14 min read

The Indian product teams that ship reliably on schedule have one thing in common: every PR triggers a full firmware build and the result is in the developer’s review queue within ten minutes. Teams that ship-and-firefight typically don’t. Modern Cortex-M projects can have the first kind of engineering culture without the heavy infrastructure historical EDA toolchains required, the CMSIS-Toolbox CLI (csolution / cbuild / cpackget) and modern UBL licensing turn Cortex-M CI/CD into a normal-looking GitHub Actions / GitLab CI / Jenkins pipeline.

This is the working pattern GSAS Micro Systems gives Indian Cortex-M firmware teams in Bengaluru, Hyderabad, Chennai, Pune, Mumbai, and Delhi NCR when they ask “how do we get our firmware build into GitHub Actions cleanly?”

GSAS is Arm’s authorized partner in India for Arm Development Tools. The pipeline pattern below works on any of those tiers, and on ATfE Professional or the bundled Arm Hardware Success Kit (both sold direct by Arm).

What CMSIS-Toolbox actually gives you

The legacy Cortex-M build experience was µVision-on-Windows, a .uvprojx project file, and a click on the green “rebuild all” button. Everything about that flow assumed an interactive Windows host. That is not what a CI runner looks like.

The CMSIS-Toolbox CLI is the headless replacement:

ToolWhat it does
csolutionReads a declarative csolution.yml project file and resolves it into a build plan, which target(s), which compiler, which packs, which build types.
cbuildExecutes the build plan that csolution generates. Produces final binaries and link maps.
cpackgetManages CMSIS-Pack installation, the dependency manager equivalent for Arm middleware, drivers, and example code.

All three are open-source, cross-platform (Windows / macOS / Linux), and work with any of Arm Compiler 6, Arm Toolchain for Embedded, or arm-none-eabi-gcc. The same csolution.yml builds locally in Keil Studio (VS Code), in µVision on Windows, and headlessly in CI, there is no project-file divergence.

The minimal csolution.yml

For a simple Cortex-M4 product project, the project file looks like this:

solution:
  cdefault:
  compiler: AC6
  packs:
    - pack: ARM::CMSIS@^6.0.0
    - pack: ARM::CMSIS-Compiler@^2.0.0
    - pack: Keil::STM32F4xx_DFP@^3.0.0

  target-types:
    - type: STM32F411
      device: STMicroelectronics::STM32F411RETx

  build-types:
    - type: Debug
      optimize: debug
      debug: on
    - type: Release
      optimize: speed
      debug: off

  projects:
    - project: ./firmware/firmware.cproject.yml

The companion firmware.cproject.yml lists the source groups, the components, and the build options. Both files are version-controlled YAML, diff-friendly, merge-friendly, code-review-friendly. Compare with the binary-blob .uvprojx files of older Keil projects.

Indian CI/CD engineer at a Pune embedded firmware office configuring a GitHub Actions pipeline for Cortex-M firmware with the CMSIS Toolbox csolution / cbuild / cpackget commands and a green build matrix dashboard

A working GitHub Actions pipeline

The CI pattern most Indian Cortex-M teams converge on, condensed:

name: Firmware CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [STM32F411, STM32H743]
        build_type: [Debug, Release]
      fail-fast: false

    steps:
      - uses: actions/checkout@v4

      # Set up CMSIS-Toolbox + supporting tools via Arm's vcpkg action.
      # See https://github.com/ARM-software/cmsis-actions
      - name: Set up CMSIS-Toolbox via vcpkg
        uses: ARM-software/cmsis-actions/vcpkg@v1
        with:
          config: ./vcpkg-configuration.json

      # Activate the UBL seat. The activation `code:` form takes precedence;
      # provide it via a GitHub secret. For server-based activation, use
      # `server:` + `product:` (with a real Arm product code such as
      # KEMDK-COM0 is the documented default, see Arm's cmsis-actions README.
      # See ARM-software/cmsis-actions README for the documented inputs.
      - name: Activate Arm license
        uses: ARM-software/cmsis-actions/armlm@v1
        with:
          code: ${{ secrets.ARM_UBL_ACTIVATION_CODE }}

      - name: Install CMSIS packs
        run: |
          cpackget update-index
          cpackget add -f packs.txt

      - name: Build
        run: |
          csolution convert -s firmware.csolution.yml \
            -c "${{ matrix.target }}+${{ matrix.build_type }}" \
            -o build
          cbuild build/firmware.${{ matrix.target }}+${{ matrix.build_type }}.cprj

      - name: Run unit tests on FVP
        if: matrix.build_type == 'Debug'
        run: |
          FVP_MPS2_Cortex-M4 -a build/firmware.axf \
            --stat -C cpu.semihosting-enable=1

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: firmware-${{ matrix.target }}-${{ matrix.build_type }}
          path: build/firmware.{axf,bin,map}

      - name: Release UBL seat
        if: always()
        run: armlm deactivate

This pipeline runs four parallel jobs (2 targets × 2 build types) on every PR, optionally smoke-tests the Debug builds on a Fixed Virtual Platform, and uploads the build artefacts.

Four things this pipeline gets right

1. armlm activate at job start, armlm deactivate at job end

The most important habit. UBL seats are short-lived in CI, the bot claims a seat at the top of the job, releases it at the bottom (the if: always() ensures release happens even on build failure). Without explicit deactivate, you can leak seats during failed jobs and over time exhaust your concurrent-seat budget. With explicit deactivate, your seat budget tracks actual concurrent build activity, not historical job failures.

2. The matrix is bounded

Two architectures × two build types = four parallel jobs. Not “every architecture × every compiler × every test profile”, that’s how teams discover their UBL seat budget vanished mid-week. The matrix is sized intentionally; per-PR coverage is not the place to test every combination.

3. Pack installation is idempotent

cpackget update-index followed by cpackget add -f packs.txt is a no-op when packs are already installed. The CI runner caches ~/.cache/cmsis-toolbox/ between runs (configure via actions/cache); pack installation runs in seconds on warm caches. For new runners or post-cache-flush runs, expect 60-90 seconds for pack download.

4. The CMSIS-Toolbox is installed via the official action

ARM-software/cmsis-toolbox-action@v2 is Arm’s official GitHub Action, it handles version pinning, platform-specific binary download, and PATH setup. No bash-glue maintenance.

The four firewall + cache gotchas Indian teams hit

Gotcha 1: *.arm.com not reachable from the CI runner

UBL’s CLS (Cloud License Server) requires outbound HTTPS to *.arm.com. Indian corporate networks often have an outbound proxy that strips this on the runner host. Symptoms: armlm activate hangs or fails with a network error.

Fix: configure the proxy in the runner image (HTTPS_PROXY environment variable) OR move to Local License Server (LLS) if outbound *.arm.com is forbidden by network policy. See the Arm partner page UBL section for CLS-vs-LLS guidance.

Gotcha 2: UBL credentials in repo (NOT acceptable)

UBL activation codes are sensitive. They go in GitHub Actions secrets (secrets.ARM_UBL_ACTIVATION_CODE), never in the repo’s source files, never in commit history, never in logs. The pipeline above demonstrates the right pattern: secret → environment variable → tool. Audit your existing CI YAML; if your activation code is hardcoded in a workflow file, rotate the credential immediately and move it to secrets.

Gotcha 3: Pack cache invalidation across runner-image refreshes

GitHub-hosted runners get fresh images frequently; the ~/.cache/cmsis-toolbox/ cache disappears with the image. Use actions/cache@v4 keyed on packs.txt content hash to persist the pack cache across runner instances. Indian teams that skip this step pay 60-90 seconds of pack-download time on every CI run, which adds up.

Gotcha 4: Bashisms in csolution/cbuild invocation

The csolution and cbuild CLI binaries don’t expand shell globs themselves. If you write cbuild build/firmware*.cprj the shell expands the glob; if no file matches, you get a runtime error rather than a build failure. Use explicit filenames or wrap with find if you need glob behaviour.

Optional patterns Indian teams add later

Parallel matrix with selective skipping

Skip the full matrix on PRs that touch only documentation or non-firmware files. Use GitHub Actions’ paths filter:

on:
  pull_request:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '*.md'

FVP-based regression testing

For larger Indian product teams, the natural next step is regression testing on FVPs, see Fixed Virtual Platforms / Fast Models for the licensing model and the three-layer “FVP → physical → HIL” CI pattern Indian teams settle into.

Static analysis gates

Combine the build pipeline with Perforce Helix QAC or Perforce Klocwork for MISRA-conformance gates as a pre-merge check. The QAC analysis runs as a parallel CI job on the same matrix; failed analysis blocks the merge. Both Helix QAC and Klocwork have first-class GitHub Actions integration today.

Per-PR firmware preview deploys

For consumer-facing Indian products, deploy a built firmware to a hardware-in-the-loop test bench tied to a specific PR, the developer sees the firmware running on actual hardware as part of code review. This is a meaningful step up from FVP-based smoke tests; not every team needs it.

What about GitLab CI / Jenkins / Azure DevOps?

The pattern translates directly. The pipeline shape (CMSIS-Toolbox install → UBL activate → packs → build → test → deactivate) is the same; only the YAML / Groovy / pipeline-DSL syntax changes. GSAS has working examples for all four CI platforms; talk to us if you’re on something other than GitHub Actions.

When to talk to GSAS

  • Your Indian team is moving from a click-driven µVision build to a CI-driven build for the first time.
  • Your CI is timing out on armlm activate and you suspect a firewall or proxy issue.
  • You’re sizing a UBL seat count for a new CI matrix, see the seat-calculation guide.
  • You need air-gapped CI (LLS deployment) for a defence / aerospace programme.
  • You want to layer Helix QAC or Klocwork static analysis into the pipeline.

Talk to GSAS about your Cortex-M CI/CD setup →

GSAS Micro Systems is Arm’s authorized engineering partner in India for the full Arm tools family, across Bengaluru, Hyderabad, Chennai, Pune, Mumbai, Delhi NCR, and Visakhapatnam.

Interested in Arm tools?

Talk to our application engineers for personalized tool recommendations.

Stay in the Loop

Get monthly compliance updates, product insights, and engineering best practices delivered to your inbox.