Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/build_all_targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ jobs:
outputs:
uploadlocation: ${{ steps.upload-location.outputs.uploadlocation }}
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: Tools/manifest

- name: Download Artifacts
uses: actions/download-artifact@v4
with:
Expand Down Expand Up @@ -269,4 +273,57 @@ jobs:
artifacts/*.px4
artifacts/*.deb
artifacts/**/*.sbom.spdx.json
manifest.json
name: ${{ steps.upload-location.outputs.uploadlocation }}

# Update the unified firmware manifest with this release
# The manifest lives at s3://px4-travis/Firmware/manifest.json and provides
# a complete index of all releases and builds for tools like QGroundControl
- name: Backup Existing Firmware Manifest
if: startsWith(github.ref, 'refs/tags/v')
run: |
s3_base="https://px4-travis.s3.us-west-1.amazonaws.com/Firmware"
mkdir -p manifest_backup
curl -sf "${s3_base}/manifest.json" -o manifest_backup/manifest.json.backup || echo "No existing manifest to backup"
if [ -f manifest_backup/manifest.json.backup ]; then
echo "Backed up existing firmware manifest ($(wc -c < manifest_backup/manifest.json.backup) bytes)"
fi

- name: Upload Firmware Manifest Backup
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4
with:
name: firmware_manifest_backup_${{ steps.upload-location.outputs.uploadlocation }}
path: manifest_backup/
if-no-files-found: ignore
retention-days: 90

- name: Update Firmware Manifest
if: startsWith(github.ref, 'refs/tags/v')
run: |
version="${{ steps.upload-location.outputs.uploadlocation }}"
git_tag=""
base_url=""
s3_base="https://px4-travis.s3.us-west-1.amazonaws.com/Firmware"

if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
git_tag="${GITHUB_REF#refs/tags/}"
base_url="https://github.com/${{ github.repository }}/releases/download/${git_tag}"
fi

python3 ./Tools/manifest/update_firmware_manifest.py \
--dir artifacts/ \
--version "$version" \
--git-tag "$git_tag" \
--base-url "$base_url" \
--fetch-url "${s3_base}/manifest.json" \
--out manifest.json

- name: Upload Firmware Manifest to S3
if: startsWith(github.ref, 'refs/tags/v')
run: |
aws s3 cp manifest.json s3://px4-travis/Firmware/manifest.json --acl public-read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-west-1'
24 changes: 24 additions & 0 deletions Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ menu "Toolchain"
string "Architecture"
default ""

config BOARD_LABEL_PRETTY
string "Human-readable label for this build variant"
default ""
help
A short display name for this build variant (e.g. "Multicopter",
"Rover"). Used by ground stations like QGC to show a user-friendly
name instead of raw target strings. Must be set in every .px4board
variant file because non-base variants inherit from default.

config BOARD_FIRMWARE_CATEGORY
string "Firmware category override (vehicle, peripheral, dev, bootloader)"
default ""
help
Override the auto-detected firmware category. Normally inferred
from the build label and board config: vehicle types (multicopter,
fixedwing, etc.) map to "vehicle", bootloader/canbootloader map to
"bootloader", CAN peripheral boards (ROMFSROOT=cannode) map to
"peripheral", and everything else maps to "dev". Set this only
when the auto-detection is wrong for a particular board variant.
IMPORTANT: If you add a new vehicle type with a label not yet in
_VEHICLE_LABELS (in gen_board_manifest_from_defconfig.py), either
add it there or set this to "vehicle" — otherwise the build will
be hidden from end-users in QGC.

config BOARD_LTO
bool "(EXPERIMENTAL) Link Time Optimization (LTO)"
default n
Expand Down
144 changes: 144 additions & 0 deletions Tools/manifest/gen_board_manifest_from_defconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
import argparse, json, os, re, sys
from typing import Dict

_VEHICLE_LABELS = frozenset({
"multicopter", "fixedwing", "vtol", "rover", "uuv", "spacecraft",
})
_BOOTLOADER_LABELS = frozenset({
"bootloader", "canbootloader",
})

def parse_defconfig(path: str) -> Dict[str, str]:
d: Dict[str, str] = {}
if not path or not os.path.exists(path):
return d
with open(path, "r", encoding="utf-8", errors="ignore") as f:
for raw in f:
line = raw.strip()
if not line or not line.startswith("CONFIG_") or "=" not in line or line.startswith("#"):
continue
k, v = line.split("=", 1)
v = v.strip()
if len(v) >= 2 and v[0] == '"' and v[-1] == '"':
v = v[1:-1]
d[k.strip()] = v
return d

def norm_hex(s: str) -> str:
if not s:
return ""
s = s.strip()
if s.lower().startswith("0x"):
return s.lower()
try:
return f"0x{int(s, 0):04x}"
except Exception:
return s

def detect_chip(defcfg: Dict[str,str]) -> str:
for k, v in defcfg.items():
if k.startswith("CONFIG_ARCH_CHIP_") and v == "y":
return k[len("CONFIG_ARCH_CHIP_"):].lower().replace("_", "")
s = defcfg.get("CONFIG_ARCH_CHIP", "")
return s.lower().replace("_", "") if s else ""

def detect_firmware_category(variant: str, defcfg: Dict[str, str], target: str = "") -> str:
"""Infer firmware_category from the variant label and defconfig.

Detection order:
1. bootloader / canbootloader variants → "bootloader"
2. Known vehicle variants → "vehicle"
3. ROMFSROOT == "cannode" (CAN peripheral boards) → "peripheral"
4. Everything else → "dev"

If you are adding a NEW vehicle type (e.g. "balloon"), you must EITHER:
1. Add the label to _VEHICLE_LABELS in this file, OR
2. Set CONFIG_BOARD_FIRMWARE_CATEGORY="vehicle" in the .px4board file
Otherwise the build will be classified as "dev" and hidden from
end-users in ground stations like QGroundControl.
"""
if variant in _BOOTLOADER_LABELS:
return "bootloader"
if variant in _VEHICLE_LABELS:
return "vehicle"
# CAN peripheral boards (sensors, GPS, flow, etc.) use cannode ROMFS
romfsroot = defcfg.get("CONFIG_BOARD_ROMFSROOT", "")
if romfsroot == "cannode":
return "peripheral"
if variant not in ("default", ""):
print(f"WARNING: variant '{variant}' (target '{target}') is not a known "
f"vehicle type — defaulting firmware_category to 'dev'. "
f"If this is a vehicle type, add it to _VEHICLE_LABELS in "
f"{__file__} or set CONFIG_BOARD_FIRMWARE_CATEGORY in the "
f".px4board file.", file=sys.stderr)
return "dev"

def pick(preferred: str, fallback_key: str, defcfg: Dict[str, str]) -> str:
return preferred if preferred else defcfg.get(fallback_key, "")

def main():
ap = argparse.ArgumentParser(description="Generate board manifest (prefer CMake-passed overrides, fallback to defconfig).")
ap.add_argument("--defconfig", required=False, help="Path to defconfig (fallback only)")
# explicit overrides coming from CMake
ap.add_argument("--manufacturer", default="")
ap.add_argument("--productstr", default="")
ap.add_argument("--target", default="")
ap.add_argument("--name", default="")
ap.add_argument("--variant", default="",
help="Build variant label (the .px4board filename stem, e.g. 'multicopter')")
ap.add_argument("--arch", default="")
ap.add_argument("--chip", default="")
ap.add_argument("--vid", default="")
ap.add_argument("--pid", default="")
ap.add_argument("--label-pretty", default="")
ap.add_argument("--firmware-category", default="")
ap.add_argument("--out", help="Write to file instead of stdout")
args = ap.parse_args()

defcfg = parse_defconfig(args.defconfig) if args.defconfig else {}

manufacturer = pick(args.manufacturer, "CONFIG_BOARD_MANUFACTURER", defcfg)
productstr = pick(args.productstr, "CONFIG_BOARD_PRODUCTSTR", defcfg)
target = args.target or ""
name = args.name or ""
variant = (args.variant or "").lower()
arch = (pick(args.arch, "CONFIG_ARCH", defcfg)).lower()
chip = args.chip or detect_chip(defcfg)
vid = norm_hex(pick(args.vid, "CONFIG_CDCACM_VENDORID", defcfg))
pid = norm_hex(pick(args.pid, "CONFIG_CDCACM_PRODUCTID", defcfg))
label_pretty = pick(args.label_pretty, "CONFIG_BOARD_LABEL_PRETTY", defcfg)
firmware_cat = pick(args.firmware_category,"CONFIG_BOARD_FIRMWARE_CATEGORY",defcfg)
if not firmware_cat:
firmware_cat = detect_firmware_category(variant, defcfg, target=target)

manifest = {
"name": name,
"target": target,
"variant": variant,
"label_pretty": label_pretty,
"firmware_category": firmware_cat,
"manufacturer": manufacturer,
"hardware": {
"architecture": arch,
"vendor_id": vid,
"product_id": pid,
"chip": chip,
"productstr": productstr
}
}

if args.out:
out_dir = os.path.dirname(args.out)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
with open(args.out, "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2)
f.write("\n")
else:
json.dump(manifest, sys.stdout, indent=2)
sys.stdout.write("\n")
return 0

if __name__ == "__main__":
sys.exit(main())
Loading
Loading