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 licence. The activation `code:` form takes precedence;
      # provide it via a GitHub secret. For server-based activation, use
      # `server:` + `product:` with the product code for the entitlement you
      # hold. The action's `product:` input defaults to KEMDK-COM0, a
      # non-commercial edition, so a production workflow must override it.
      # 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}

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, and no armlm deactivate at job end

Licensing happens once, before anything expensive runs, using Arm’s own cmsis-actions/armlm action. There is deliberately no deactivate step at the end, because a per-job deactivate does not do what its name suggests. Arm’s User-based Licensing User Guide states that “Deactivating a license is an activity local to a single device” and that “Deactivation does not release the license so it can be used by another user.” For a licence server, a licence is released for another user only when it has not been renewed for a minimum of 7 days. Arm’s own ARM-software/cmsis-actions/armlm action has no deactivate input and no post-job step, and Arm’s CMSIS_6 CoreValidation workflow activates without ever deactivating.

The mechanics of what actually has to survive between jobs, and why per-job activation stops scaling, are in our Arm UBL in CI/CD guide.

2. The matrix is bounded

Two architectures × two build types = four parallel jobs. Not “every architecture × every compiler × every test profile”. The matrix is sized intentionally; per-PR coverage is not the place to test every combination. A wide matrix costs runner minutes, and if every job activates its own licence it also multiplies licence server requests.

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-actions/vcpkg@v1 is Arm’s own GitHub Action, and it installs the development environment described in vcpkg-configuration.json, handling 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 activation and renewal need outbound HTTPS from the runner. Per Arm’s User-based Licensing User Guide, activation-code licensing requires https://api.arm.com/p-software-licensing and https://arm.compliance.flexnetoperations.eu/instances, over HTTP with SSL on TCP port 443, so an allow-list built only around *.arm.com misses the second hostname. Indian corporate networks often put an outbound proxy in front of the runner host. Symptoms: armlm activate hangs or fails with a network error.

Fix: the documented routes are a Local License Server (LLS) on the reachable side, or proxy activation with a transfer file for runners that cannot meet the network requirements. Arm’s licensing documentation does not describe an HTTPS_PROXY setting for this, so treat that as a local workaround rather than the first thing to try. See the Arm partner page UBL section for CLS-vs-LLS guidance, and the Arm UBL in CI/CD guide for the exact firewall ask and the two curl reachability checks Arm publishes.

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) 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.