Skip to content

feat(manifest): embed per-build metadata and publish unified firmware manifest#25414

Open
mrpollo wants to merge 7 commits into
mainfrom
mrpollo/manifest
Open

feat(manifest): embed per-build metadata and publish unified firmware manifest#25414
mrpollo wants to merge 7 commits into
mainfrom
mrpollo/manifest

Conversation

@mrpollo
Copy link
Copy Markdown
Contributor

@mrpollo mrpollo commented Aug 12, 2025

This PR adds comprehensive firmware manifest metadata to PX4 builds, enabling better firmware discovery and management for tools like QGroundControl. Per-build metadata is generated from the defconfig and CMake board variables, embedded into each .px4 file, and aggregated into a single unified manifest on S3 that QGC and other tools can consume.

Changes

1. Board-level manifest generation

Files: Tools/manifest/gen_board_manifest_from_defconfig.py, platforms/nuttx/CMakeLists.txt

  • Generates per-build manifest JSON with hardware and variant metadata
  • Extracts architecture, USB vendor/product IDs, chip type, manufacturer from defconfig
  • variant field sourced from CMake's ${LABEL} (the .px4board filename stem, e.g. multicopter.px4board"multicopter")
  • Integrated into CMake build system via add_custom_command

2. Manifest bundled into *.px4 files

Files: Tools/px_mkfw.py

  • Each .px4 file now contains a manifest object with board metadata
  • Adds manifest_version and sha256sum fields
  • _merge_manifest helper merges the per-build JSON fragment into the firmware descriptor
  • Backward compatible: tested with QGC 4.4.5 and daily builds

3. Label and category fields

Files: Kconfig, Tools/manifest/gen_board_manifest_from_defconfig.py, boards/px4/fmu-v6x/*.px4board

  • CONFIG_BOARD_LABEL_PRETTY: human-readable variant name shown in QGC (e.g. "Multicopter")
  • CONFIG_BOARD_FIRMWARE_CATEGORY: override for auto-detected category (vehicle, peripheral, dev, bootloader)
  • Auto-detection from variant label and CONFIG_BOARD_ROMFSROOT (cannode → peripheral)
  • Phase 1 rollout on all px4/fmu-v6x variants; follow-up PR will cover the remaining ~258 .px4board files
  • artifact_type discriminator (flat field, value "px4") to allow future non-.px4 producers (VOXL2 .deb, Linux tarballs) to coexist in the same manifest without a format_version bump

4. Unified firmware manifest

Files: Tools/manifest/update_firmware_manifest.py

  • Scans all *.px4 files in an artifacts directory and extracts their embedded metadata
  • Fetches the existing unified manifest from S3 and upserts the current release inline
  • Single file at s3://px4-travis/Firmware/manifest.json contains all releases and all builds for O(1) lookup
  • Schema uses format_version: 2 with releases as a dict keyed by version
  • Auto-detects release channels (stable, beta, dev) from version string and tracks latest_* per channel

5. CI integration

Files: .github/workflows/build_all_targets.yml

On version tag releases:

  1. Backs up the existing manifest.json from S3 as a workflow artifact (90-day retention)
  2. Runs update_firmware_manifest.py to upsert this release into the unified manifest
  3. Uploads the updated manifest.json to S3 and attaches it to the GitHub release

Architecture

s3://px4-travis/Firmware/
├── manifest.json         # Unified: all releases × all builds, O(1) lookup
├── v1.15.0/
│   ├── px4_fmu-v6x_default.px4
│   ├── px4_fmu-v6x_multicopter.px4
│   └── ...
├── v1.16.1/
│   └── ...
└── stable/               # Symlink to latest stable

Example Manifest

{
  "format_version": 2,
  "updated_at": 1738890000,
  "latest_stable": "v1.16.1",
  "latest_beta":   "v1.17.0-beta1",
  "latest_dev":    "v1.17.0-alpha1",
  "releases": {
    "v1.16.1": {
      "channel": "stable",
      "release_date": "2026-01-15",
      "build_count": 170,
      "builds": [
        {
          "filename": "px4_fmu-v6x_multicopter.px4",
          "url": "https://github.com/PX4/PX4-Autopilot/releases/download/v1.16.1/px4_fmu-v6x_multicopter.px4",
          "sha256sum": "abc123...",
          "image_size": 1234567,
          "board_id": 53,
          "artifact_type": "px4",
          "manifest": {
            "name": "px4_fmu-v6x",
            "target": "px4_fmu-v6x_multicopter",
            "variant": "multicopter",
            "label_pretty": "Multicopter",
            "firmware_category": "vehicle",
            "manufacturer": "Holybro",
            "hardware": {
              "architecture": "arm",
              "chip": "stm32h753ii",
              "vendor_id": "0x3185",
              "product_id": "0x0038",
              "productstr": "Pixhawk 6X"
            }
          }
        }
      ]
    }
  }
}

A live test manifest with 5 real releases and 891 firmware builds is published at px4-travis.s3.amazonaws.com/Firmware/manifest.json.

Scope

Manifest generation currently runs only for NuttX targets (via platforms/nuttx/CMakeLists.txt). VOXL2 (qurt/posix), Linux SBCs, and SITL targets are not yet indexed; extending coverage to non-NuttX producers is a follow-up. The artifact_type discriminator is in place so those can land additively without a schema break.

Related

  • QGC companion PR: mavlink/qgroundcontrol#13966
  • Follow-up: populate CONFIG_BOARD_LABEL_PRETTY across remaining ~258 .px4board files
  • Follow-up: extend manifest producers to VOXL2 / Linux / SITL targets

@dagar dagar self-requested a review August 12, 2025 17:49
Comment thread Tools/ci/generate_manifest_json.py Outdated
@dagar
Copy link
Copy Markdown
Member

dagar commented Aug 12, 2025

The easiest thing would be to generate all of this at build time where everything is easily available, but I think it would depend how and where we want to update the manifest.

If the manifest is updated by each build individually that would simplify a lot of things in PX4 itself, but then we'd have more to worry about in atomically updating the manifest.json in S3. Alternatively if we want to do bulk manifest.json updates in a single job maybe we should push everything we possibly need into a .px4 file.

@mrpollo mrpollo force-pushed the mrpollo/manifest branch 3 times, most recently from e34de42 to 4e2ff49 Compare August 15, 2025 18:01
@mrpollo mrpollo marked this pull request as ready for review August 15, 2025 18:24
@mrpollo mrpollo requested a review from bkueng August 15, 2025 18:27
@github-actions github-actions Bot added the status:stale Inactive and may be closed. label Sep 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been closed after being marked as stale with no further activity. Thank you for the time and effort you put into this contribution. If you’d like to continue the discussion or update the work, please feel free to reopen it or submit a new PR.

@dirksavage88
Copy link
Copy Markdown
Contributor

@mrpollo I pulled the feature branch, built for the 6xrt target and flashed the .px4 using QGC linux daily and then went back to QGC 4.4.5 and flashed the .px4 file using that and didn't see any issues there flashing either.
image

@mrpollo mrpollo reopened this Jan 7, 2026
@github-actions github-actions Bot removed the status:stale Inactive and may be closed. label Jan 8, 2026
@mrpollo mrpollo force-pushed the mrpollo/manifest branch 2 times, most recently from 71cdd74 to 3b7a55b Compare January 12, 2026 19:47
@DronecodeBot
Copy link
Copy Markdown

This pull request has been mentioned on Discussion Forum for PX4, Pixhawk, QGroundControl, MAVSDK, MAVLink. There might be relevant details there:

https://discuss.px4.io/t/px4-dev-call-jan-14-2026-team-sync-and-community-q-a/48289/2

@DronecodeBot
Copy link
Copy Markdown

This pull request has been mentioned on Discussion Forum for PX4, Pixhawk, QGroundControl, MAVSDK, MAVLink. There might be relevant details there:

https://discuss.px4.io/t/px4-dev-call-jan-21-2026-team-sync-and-community-q-a/48330/3

@mrpollo
Copy link
Copy Markdown
Contributor Author

mrpollo commented Jan 26, 2026

I spoke with @bkueng, and we agreed that the better approach would be to unify the JSON for the manifest into a single one that includes all releases and all variants instead of an index manifest and having to fetch the releases indepednently, the main argument for this would be the experience for users when they have a board that might be available for specific releases, for those in the originally proposed approach QGC would have to continuously fetch releases until it finds one that matches the hardware which will add more complexity to QGC.

I will be working on an update to this PR that takes this feedback into account, and update ASAP

@mrpollo
Copy link
Copy Markdown
Contributor Author

mrpollo commented Feb 10, 2026

Update: Unified single-file manifest

Per @bkueng's feedback, this has been reworked from the two-tier approach (per-release release_manifest.json + top-level index.json) into a single unified manifest.json containing all releases with all firmware builds inline.

What changed

  • Replaced gen_release_manifest.py + update_firmware_index.py with a single update_firmware_manifest.py tool
  • Schema is now format_version: 2 with releases as a dict keyed by version (O(1) upserts, no linear scans)
  • Manifest is updated only on tag pushes (not branch builds)
  • QGC fetches one file to find firmware for any board across all releases

Test manifest

A test manifest with 5 real releases (891 firmware builds) is live at:
https://px4-travis.s3.amazonaws.com/Firmware/manifest.json

Summary view:

{
  "format_version": 2,
  "latest_stable": "v1.16.1",
  "latest_beta": "v1.17.0-beta1",
  "latest_dev": "v1.17.0-alpha1",
  "releases": {
    "v1.15.0":        { "channel": "stable", "builds": 93 },
    "v1.16.0":        { "channel": "stable", "builds": 208 },
    "v1.16.1":        { "channel": "stable", "builds": 170 },
    "v1.17.0-alpha1": { "channel": "dev",    "builds": 209 },
    "v1.17.0-beta1":  { "channel": "beta",   "builds": 211 }
  }
}

Each release entry contains all builds inline with full metadata (board_id, git_hash, image_size, download URL, board manifest when available). Total uncompressed size: ~330KB for 5 releases / 891 builds.

Quick jq examples

# Get latest pointers
curl -s https://px4-travis.s3.amazonaws.com/Firmware/manifest.json | jq '{latest_stable, latest_beta, latest_dev}'

# Find all firmware for board_id 53 (FMU v6x)
curl -s https://px4-travis.s3.amazonaws.com/Firmware/manifest.json | jq '.releases[].builds[] | select(.board_id == 53) | {version, filename}'

# List all releases
curl -s https://px4-travis.s3.amazonaws.com/Firmware/manifest.json | jq '.releases | to_entries[] | {version: .key, channel: .value.channel, builds: .value.build_count}'

@dakejahl
Copy link
Copy Markdown
Contributor

Hell yeah!. Thanks Ramon, looking good. Is this complete on the PX4 side now? Just needing a QGC implementation?

@mrpollo
Copy link
Copy Markdown
Contributor Author

mrpollo commented Feb 10, 2026

I think this PR still needs to clarify under what circumstances the manifest is built and updated, and ensure those are documented somewhere, otherwise, we might be good for now.

Regarding the QGC implementation, here's the early draft mavlink/qgroundcontrol#13966

@mrpollo
Copy link
Copy Markdown
Contributor Author

mrpollo commented Feb 10, 2026

New fields in manifest label_pretty and firmware_category

Today, when QGC presents firmware options to a user, it shows raw build target names like px4_fmu-v6x_default, px4_fmu-v6x_multicopter, px4_fmu-v6x_bootloader. For experienced developers that's fine, but for regular users picking firmware for their drone it's cryptic — they shouldn't need to parse internal naming conventions to flash the right thing.

Screenshot 2026-02-10 at 11 17 55 AM

As we move toward per-vehicle-type firmware variants (multicopter, fixedwing, rover, etc.), the list of targets per board is growing. Without metadata, QGC has no way to know which builds are meant for end users, which are developer tools, which are sensor peripherals, and which are bootloaders — it has to show everything or resort to fragile filename string-matching.

These two new manifest fields solve both problems.

label_pretty

A human-readable name for the build variant (e.g. "Multicopter", "Rover", "Default"). This is what QGC displays instead of raw target strings.

Set via CONFIG_BOARD_LABEL_PRETTY in each .px4board file. Due to Kconfig inheritance, every variant must set its own value — otherwise it inherits from default.px4board, which would be wrong.

firmware_category

Classifies each build into one of four buckets:

Value What it means QGC behavior
vehicle Production firmware for a vehicle type (multicopter, fixedwing, rover, etc.) Shown to users by default
peripheral Firmware for CAN sensor nodes (GPS, optical flow, magnetometer, distance sensor, etc.) Shown in a dedicated peripheral/sensor section
dev Developer/engineering builds (default, zenoh, mavlink-dev, performance-test, etc.) Hidden by default, advanced mode only
bootloader Bootloader binaries Never shown to end users

This is auto-detected — no manual config needed:

  • Vehicle types are detected from the build label (multicopter, fixedwing, vtol, rover, uuv, spacecraft)
  • Peripherals are detected from CONFIG_BOARD_ROMFSROOT="cannode" — all ~18 CAN sensor boards across ARK, Holybro, CUAV, Freefly, Matek, and NXP use this
  • Bootloaders are detected from bootloader/canbootloader labels
  • Everything else falls to dev

A build-time warning fires if an unrecognized label (not default) falls through to dev, so new vehicle types won't be silently hidden. Boards can always override via CONFIG_BOARD_FIRMWARE_CATEGORY in the .px4board file.

What this means for hardware manufacturers

If you maintain a board in the PX4 tree:

  • Add CONFIG_BOARD_LABEL_PRETTY to your .px4board files so QGC shows a clean name
  • firmware_category auto-detects correctly — you don't need to touch it unless you have a non-standard variant name
  • CAN peripheral boards (anything with ROMFSROOT=cannode) are automatically classified as peripheral

Backward compatibility

Old .px4 files without these fields still work — QGC falls back to raw label / filename matching (existing behavior). These are purely additive fields, no format_version bump needed.

Scope

Right now only px4/fmu-v6x (10 variants) has CONFIG_BOARD_LABEL_PRETTY set. There are ~258 .px4board files across 111 boards, with 17 boards that have vehicle-type variants and ~18 CAN peripheral boards.

Question for reviewers: Should we add CONFIG_BOARD_LABEL_PRETTY to all ~258 board files in this PR, or land the infrastructure now and do a bulk update in a follow-up? It's scriptable — the label is just a title-cased version of the filename in most cases — but it's a large diff that touches every board and might benefit from a separate review. Leaning toward a follow-up PR so this one stays focused on the manifest plumbing.

@github-actions
Copy link
Copy Markdown
Contributor

No flaws found

@hamishwillee
Copy link
Copy Markdown
Contributor

Should we add CONFIG_BOARD_LABEL_PRETTY to all ~258 board files in this PR ...

Probably not, given there is a fallback. However I would like some screenshots of real examples of what a user sees when this is set - like your image in #25414 (comment)

As the image there is now, most of the string is the board type, which is already known by QGC. So you could default those to just the variant string.

What would the vehicle type be for your variant allyes in that image?

@dakejahl
Copy link
Copy Markdown
Contributor

Question for reviewers: Should we add CONFIG_BOARD_LABEL_PRETTY to all ~258 board files in this PR, or land the infrastructure now and do a bulk update in a follow-up?

Let's do it in a follow up PR to keep this clean

@DronecodeBot
Copy link
Copy Markdown

This pull request has been mentioned on Discussion Forum for PX4, Pixhawk, QGroundControl, MAVSDK, MAVLink. There might be relevant details there:

https://discuss.px4.io/t/px4-dev-call-feb-11-2026-team-sync-and-community-q-a/48479/2

@DronecodeBot
Copy link
Copy Markdown

This pull request has been mentioned on Discussion Forum for PX4, Pixhawk, QGroundControl, MAVSDK, MAVLink. There might be relevant details there:

https://discuss.px4.io/t/px4-dev-call-feb-18-2026-team-sync-and-community-q-a/48516/2

@dagar dagar self-requested a review February 18, 2026 16:26
@mrpollo mrpollo moved this from Todo to Push to Next in PX4 v1.17 Release Apr 8, 2026
mrpollo added 7 commits April 8, 2026 17:36
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Replace the two-tier approach (per-release manifest + top-level index)
with a single unified manifest containing all releases and their builds
inline. This allows QGC to fetch one file to find firmware for any board
across all releases, avoiding the need to fetch many per-release manifests.

Schema uses format_version 2 with releases as a dict keyed by version
for O(1) upserts. The manifest is stored at s3://px4-travis/Firmware/manifest.json
and updated on every CI build (tags + main/stable/beta branches).

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Add human-readable labels and firmware classification to the build
manifest so ground stations like QGC can display friendly names and
filter builds by category.

New fields:
- label_pretty: human-readable variant name (e.g. "Multicopter")
- firmware_category: auto-detected classification
  - "vehicle" for multicopter, fixedwing, vtol, rover, uuv, spacecraft
  - "peripheral" for CAN sensor nodes (GPS, flow, mag, etc.)
  - "bootloader" for bootloader/canbootloader
  - "dev" for everything else (default, zenoh, mavlink-dev, etc.)

Peripheral detection uses ROMFSROOT="cannode" which is shared by all
~18 CAN sensor boards across ARK, Holybro, CUAV, Freefly, Matek, NXP.

A build-time warning fires when an unrecognized label falls through to
"dev", so new vehicle types are not silently hidden from end users.

Boards can override via CONFIG_BOARD_FIRMWARE_CATEGORY in .px4board.
CONFIG_BOARD_LABEL_PRETTY set on all px4/fmu-v6x variants as Phase 1.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Move the shebang to line 1 so direct execution works on Linux; the vim
modeline was sitting above it and silently breaking ./Tools/px_mkfw.py.

Fix _merge_manifest to only merge dict values into dst["hardware"] when
the key is "hardware". The previous structure ran the isinstance check
unconditionally, so any dict-valued field in the fragment would have
been shoved into dst["hardware"] regardless of its key, and could
KeyError if a non-"hardware" dict arrived first. It only worked today
because "hardware" happened to be the only dict in the fragment.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Tag every build entry with artifact_type="px4" so the manifest schema
can represent non-.px4 producers in the future (VOXL2 .deb, Linux
tarballs, etc.) without a format_version bump. Purely additive: older
QGC ignores the unknown field, newer consumers branch on it.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Add an explicit "variant" field to the per-build manifest, sourced from
CMake's ${LABEL} variable which is already derived from the .px4board
filename stem (e.g. multicopter.px4board → "multicopter"). This is the
authoritative source of the variant name: CMake uses it everywhere else
in the tree for board selection.

Previously the variant was implicit in "target" (last underscore-
separated segment), forcing consumers to string-split and assume the
target always follows <board>_<variant>. Now it's a stable, machine-
readable field alongside target and label_pretty.

detect_firmware_category() now takes the variant as its first argument
instead of re-deriving it from target, removing a duplicated parse.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
@mrpollo mrpollo force-pushed the mrpollo/manifest branch from 96a3570 to 170b7b4 Compare April 9, 2026 05:55
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

💡 Commit messages could be improved

Not blocking, but these commit messages could use some cleanup.

Commit Message Suggestion
ee056c1536 tools: create firmware manifest Missing conventional commit format (e.g. "feat(ekf2): add something")
80abad2baa build: bundle manifest to *.px4 file Missing conventional commit format (e.g. "feat(ekf2): add something")
68c391328a ci: unified firmware manifest for release discovery Missing conventional commit format (e.g. "feat(ekf2): add something")
63f059fd47 manifest: add label_pretty and firmware_category fields Missing conventional commit format (e.g. "feat(ekf2): add something")

See the commit message convention for details.


This comment will be automatically removed once the issues are resolved.

@mrpollo mrpollo changed the title tools: create firmware manifest feat(manifest): embed per-build metadata and publish unified firmware manifest Apr 9, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

No broken links found in changed files.

@dakejahl
Copy link
Copy Markdown
Contributor

What is this waiting on? Can we get this and the QGC PR across the finish line soon?

@julianoes
Copy link
Copy Markdown
Contributor

I just stumbled on this as well. Yes, this or something like that would be great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

8 participants