mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-05-30 17:20:05 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df8c765c4d | |||
| 9b552800ad |
+10
@@ -141,15 +141,22 @@ Checks: '*,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-hicpp-avoid-goto,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-unhandled-self-assignment,
|
||||
-cert-oop54-cpp,
|
||||
-performance-enum-size,
|
||||
-readability-avoid-nested-conditional-operator,
|
||||
-cppcoreguidelines-prefer-member-initializer,
|
||||
-cppcoreguidelines-explicit-virtual-functions,
|
||||
-cppcoreguidelines-virtual-class-destructor,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-make-member-function-const,
|
||||
-bugprone-assignment-in-if-condition,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-bugprone-incorrect-roundings,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-multi-level-implicit-pointer-conversion,
|
||||
-bugprone-signed-char-misuse,
|
||||
-bugprone-too-small-loop-variable,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-use-default-member-init,
|
||||
-hicpp-multiway-paths-covered,
|
||||
@@ -160,10 +167,13 @@ Checks: '*,
|
||||
-readability-avoid-return-with-void-value,
|
||||
-readability-avoid-unconditional-preprocessor-if,
|
||||
-readability-delete-null-pointer,
|
||||
-readability-duplicate-include,
|
||||
-readability-redundant-casting,
|
||||
-readability-redundant-member-init,
|
||||
-readability-reference-to-constructed-temporary,
|
||||
-readability-simplify-boolean-expr,
|
||||
-bugprone-unsafe-functions,
|
||||
-cert-msc24-c,
|
||||
-cert-msc32-c,
|
||||
-cert-msc33-c,
|
||||
-cert-msc51-cpp,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: commit
|
||||
description: Create a conventional commit for PX4 changes
|
||||
disable-model-invocation: true
|
||||
argument-hint: "[optional: description of changes]"
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
# PX4 Conventional Commit
|
||||
|
||||
Create a git commit: `type(scope): description`
|
||||
|
||||
**NEVER add Co-Authored-By lines. No Claude attribution in commits.**
|
||||
|
||||
Follow [CONTRIBUTING.md](../../CONTRIBUTING.md) for full project conventions.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read [CONTRIBUTING.md](../../CONTRIBUTING.md)** for commit message format, types, scopes, and conventions.
|
||||
2. Check branch (`git branch --show-current`). If on `main`, create a feature branch. Use `<username>/<description>` format where `<username>` comes from `gh api user --jq .login`. If unavailable, just use `<description>`.
|
||||
3. Run `git status` and `git diff --staged`. If nothing staged, ask what to stage.
|
||||
4. Follow the commit message convention from CONTRIBUTING.md: pick the correct **type** and **scope**, write a concise imperative description.
|
||||
5. Body (if needed): explain **why**, not what.
|
||||
6. Run `make format` or `./Tools/astyle/fix_code_style.sh <file>` on changed C/C++ files before committing.
|
||||
7. Check if GPG signing is available: `git config --get user.signingkey`. If set, use `git commit -S -s`. Otherwise, use `git commit -s`.
|
||||
8. Stage and commit. No `Co-Authored-By`.
|
||||
|
||||
If the user provided arguments, use them as context: $ARGUMENTS
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: pr
|
||||
description: Create a pull request with conventional commit title and description
|
||||
disable-model-invocation: true
|
||||
argument-hint: "[optional: target branch or description]"
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
# PX4 Pull Request
|
||||
|
||||
**No Claude attribution anywhere (no Co-Authored-By, no "Generated with Claude").**
|
||||
|
||||
Follow [CONTRIBUTING.md](../../CONTRIBUTING.md) for full project conventions.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Check branch. If on `main`, create a feature branch. Use `<username>/<description>` format where `<username>` comes from `gh api user --jq .login`. If unavailable, just use `<description>`.
|
||||
2. Gather context: `git status`, `git log --oneline main..HEAD`, `git diff main...HEAD --stat`, check if remote tracking branch exists.
|
||||
3. PR **title**: `type(scope): description` — under 72 chars, describes the overall change across all commits. This becomes the squash-merge commit message.
|
||||
4. PR **body**: brief summary + bullet points for key changes. No filler.
|
||||
5. Push with `-u` if needed, then `gh pr create`. Default base is `main` unless user says otherwise.
|
||||
6. Return the PR URL.
|
||||
|
||||
If the user provided arguments, use them as context: $ARGUMENTS
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
name: rebase-onto-main
|
||||
description: Rebase a branch onto main, handling squash-merged parent branches cleanly
|
||||
argument-hint: "[optional: branch name, defaults to current branch]"
|
||||
allowed-tools: Bash, Read, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
# Rebase Branch onto Main
|
||||
|
||||
Rebase the current (or specified) branch onto `main`, correctly handling the case where the branch was built on top of another branch that has since been squash-merged into `main`.
|
||||
|
||||
## Background
|
||||
|
||||
When a parent branch is squash-merged, its individual commits become a single new commit on `main` with a different hash. A normal `git rebase main` will try to replay the parent's original commits, causing messy conflicts. The fix is to **cherry-pick only the commits unique to this branch** onto a fresh branch from `main`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Identify the branch.** Use `$ARGUMENTS` if provided, otherwise use the current branch.
|
||||
|
||||
2. **Fetch and update main:**
|
||||
```
|
||||
git fetch origin main:main
|
||||
```
|
||||
|
||||
3. **Find the merge base** between the branch and `main`:
|
||||
```
|
||||
git merge-base <branch> main
|
||||
```
|
||||
|
||||
4. **List all commits** on the branch since the merge base:
|
||||
```
|
||||
git log --oneline <merge-base>..<branch>
|
||||
```
|
||||
|
||||
5. **Identify which commits are unique to this branch** vs. inherited from a parent branch. Look for:
|
||||
- Squash-merged commits on `main` that correspond to a group of commits at the bottom of the branch's history (check PR titles, commit message keywords).
|
||||
- The boundary commit: the first commit that belongs to *this* branch's work, not the parent's.
|
||||
- If ALL commits are unique (no parent branch), just do a normal `git rebase main` and skip the rest.
|
||||
|
||||
6. **Create a fresh branch from `main`:**
|
||||
```
|
||||
git checkout -b <branch>-rebase main
|
||||
```
|
||||
|
||||
7. **Cherry-pick only the unique commits** (oldest first):
|
||||
```
|
||||
git cherry-pick <first-unique-commit>^..<branch>
|
||||
```
|
||||
The `A^..B` range means "from the parent of A through B inclusive."
|
||||
|
||||
8. **Handle conflicts** if any arise during cherry-pick. Resolve and `git cherry-pick --continue`.
|
||||
|
||||
9. **Replace the old branch:**
|
||||
```
|
||||
git branch -m <branch> <branch>-old
|
||||
git branch -m <branch>-rebase <branch>
|
||||
```
|
||||
|
||||
10. **Verify** the result:
|
||||
```
|
||||
git log --oneline main..<branch>
|
||||
```
|
||||
Confirm only the expected commits are present.
|
||||
|
||||
11. **Ask the user** before force-pushing. When approved:
|
||||
```
|
||||
git push origin <branch> --force-with-lease
|
||||
```
|
||||
|
||||
12. **Clean up** the old branch:
|
||||
```
|
||||
git branch -D <branch>-old
|
||||
```
|
||||
@@ -1,207 +0,0 @@
|
||||
---
|
||||
name: review-pr
|
||||
description: Review a pull request with structured, domain-aware feedback
|
||||
argument-hint: "<PR number or URL>"
|
||||
allowed-tools: Bash, Read, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
# PX4 Pull Request Review
|
||||
|
||||
Review a pull request with domain-aware checks based on which files are changed.
|
||||
|
||||
**No Claude attribution anywhere.**
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Fetch PR context.** Run these in parallel:
|
||||
- `gh pr view <PR> --json number,title,body,baseRefName,headRefName,files,commits,reviewRequests,reviews,author`
|
||||
- `gh pr checks <PR>` (exit code 8 means some checks are pending, this is normal, not an error)
|
||||
- `gh pr diff <PR>` -- if this fails with HTTP 406 (300+ files), do NOT retry. Instead use `gh api repos/OWNER/REPO/pulls/NUMBER/files --paginate` to get the full file list in one call, then fetch patches for key infrastructure files individually and sample representative changes from each domain touched.
|
||||
- `gh api repos/OWNER/REPO/pulls/NUMBER/comments --paginate --jq '.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'` to get inline review comments
|
||||
- `gh api repos/OWNER/REPO/issues/NUMBER/comments --paginate --jq '.[] | {user: .user.login, body: .body, created_at: .created_at}'` to get PR conversation comments
|
||||
|
||||
From the PR metadata, note:
|
||||
- **Assigned reviewers**: who has been requested to review (from `reviewRequests`)
|
||||
- **Existing reviews**: who has already reviewed and their verdict (from `reviews` -- approved, changes_requested, commented, dismissed)
|
||||
- **PR comments and inline comments**: read all existing feedback to avoid duplicating points already raised by other reviewers, and to build on their discussion rather than ignoring it
|
||||
|
||||
2. **Check CI status.** From the `gh pr checks` output in step 1, summarize pass/fail/pending. If there are failures, fetch logs with `gh run view <run-id> --log-failed`. Include CI status in the output.
|
||||
|
||||
3. **Recommend merge strategy.** Analyze the commit history and recommend squash or rebase merge. This decision informs all subsequent commit hygiene feedback.
|
||||
|
||||
**Recommend rebase merge** when:
|
||||
- Commits are atomic, each builds/works independently
|
||||
- Each commit has a proper `type(scope): description` message
|
||||
- The PR intentionally separates logical changes (e.g., refactor + feature, or one commit per module)
|
||||
- The commit history tells a useful story that would be lost by squashing
|
||||
|
||||
**Recommend squash merge** when:
|
||||
- There are WIP, fixup, or review-response commits
|
||||
- Commit messages are messy or inconsistent
|
||||
- The PR is a single logical change spread across multiple commits
|
||||
- There are "oops" or "make format" commits mixed in
|
||||
|
||||
Include the recommendation in the output. If recommending rebase, flag any commits that break atomicity or have bad messages. If recommending squash, don't bother flagging individual commit messages (they'll be discarded) but ensure the PR title is correct since it becomes the squash commit message.
|
||||
|
||||
4. **Check conventional commit title.** Verify the PR title follows `type(scope): description` per CONTRIBUTING.md. The PR title becomes the commit message on squash-merge, so it must be accurate and descriptive. Verify the scope matches the primary area of changed files. If the PR introduces breaking changes, the title must include `!` before the colon. If rebase merge was recommended in step 3, also scan individual commit messages for anti-patterns: vague messages ("fix", "update"), missing type prefix, review-response noise ("apply suggestions from code review", "do make format"), or WIP markers. Flag these for rewording.
|
||||
|
||||
5. **Identify domains touched.** Classify changed files into domains based on paths (a PR may touch multiple):
|
||||
- **Estimation**: `src/modules/ekf2/`, `src/lib/wind_estimator/`, `src/lib/world_magnetic_model/`
|
||||
- **Control**: `src/modules/mc_*control*/`, `src/modules/fw_*control*/`, `src/modules/flight_mode_manager/`, `src/lib/rate_control/`, `src/lib/npfg/`, `src/modules/vtol_att_control/`
|
||||
- **Drivers/CAN**: `src/drivers/`, `src/modules/cyphal/`, `src/drivers/uavcan*/`
|
||||
- **Simulation**: `src/modules/simulation/`, `Tools/simulation/`
|
||||
- **System**: `src/modules/commander/`, `src/modules/logger/`, `src/systemcmds/`, `platforms/`, `src/modules/dataman/`
|
||||
- **Board Addition**: `boards/{manufacturer}/{board}/` (new directories only, not modifications to existing boards)
|
||||
- **CI/Build**: `.github/`, `CMakeLists.txt`, `Makefile`, `cmake/`, `Tools/`, `Kconfig`
|
||||
- **Messages/Protocol**: `msg/`, `src/modules/mavlink/`, `src/modules/uxrce_dds_client/`
|
||||
|
||||
6. **Apply core checks** (always):
|
||||
- **Correctness**: logic errors, off-by-ones, unhandled edge cases
|
||||
- **Type safety**: int16 overflow, float/double promotion, unsigned subtraction, use `uint64_t` for absolute time
|
||||
- **Initialization**: uninitialized variables, missing default construction
|
||||
- **Buffer safety**: unchecked array access, stack allocation of large buffers, snprintf bounds
|
||||
- **Magic numbers**: every numeric literal needs a named constant or justification
|
||||
- **Framework reuse**: use PX4_ERR/WARN/INFO, existing libraries (AlphaFilter, SlewRate, RateControl), MAVLink constants from the library
|
||||
- **Naming**: accurate, no unjustified abbreviations, current terminology (GPS -> GNSS for new code)
|
||||
- **Unnecessary complexity**: can code be removed instead of added? Is there a simpler pattern?
|
||||
- **Test coverage**: new features should include unit or integration tests; bug fixes should include regression tests where practical. When automated testing is infeasible (hardware-specific), require a flight log link from https://logs.px4.io or bench test evidence.
|
||||
- **PR hygiene**: focused scope, no unrelated formatting, no stale submodule changes. Commits should be atomic and independently revertable. Multiple WIP or review-response commits should be squashed. Clean, logical commits will be preserved individually on main via rebase merge. **Do NOT assume PRs are squash-merged. Both squash and rebase merge are enabled; merge commits are disabled.** Verify the PR targets `main` unless it is a backport or release-specific fix.
|
||||
- **Formatting**: `make format` / `make check_format` (astyle) for C/C++ files; `clang-tidy` clean. Python files checked with `mypy` and `flake8`. PRs failing CI format or lint checks will not be merged.
|
||||
- **Coding style**: C/C++ must follow the [PX4 coding style](https://docs.px4.io/main/en/contribute/code.html)
|
||||
- **Necessity**: challenge every addition with "Why?" Is this actually needed or just copied? Can we change a default instead of adding runtime detection?
|
||||
- **Root cause vs symptom**: is this fixing the real problem or masking it?
|
||||
- **Ecosystem impact**: what does this change mean for QGC users, log analysis tools, and third-party integrations?
|
||||
- **Sustainability**: who will maintain this? Does it create long-term burden?
|
||||
- **Architecture fit**: does the code live in the module that naturally owns the data? Are there unnecessary cross-module dependencies?
|
||||
- **End user impact**: will parameters confuse less-technical users? Are error messages actionable in QGC?
|
||||
|
||||
7. **Apply domain checks** based on step 5:
|
||||
|
||||
**Estimation:**
|
||||
- Singularities in aerospace math (euler angles near gimbal lock, sideslip at low airspeed)
|
||||
- Aliasing from downsampling sensor data without filtering
|
||||
- Kalman filter correctness (Joseph form, innovation variance, covariance symmetry)
|
||||
- CPU cost on embedded targets (avoid unnecessary sqrt, limit fusion rate)
|
||||
- Frame/coordinate system correctness (FRD vs NED, body vs earth)
|
||||
|
||||
**Control:**
|
||||
- Phase margin: output filters consume margin for no benefit; prefer adjusting gyro/d-gyro cutoffs
|
||||
- Circular dependencies: sensor data feeding back into its own control loop (e.g., throttle-based airspeed in TECS)
|
||||
- NaN propagation in flight-critical math; check `PX4_ISFINITE` before magnitude checks
|
||||
- Setpoint generation vs output-stage hacks: prefer proper setpoint smoothing over controller output filtering
|
||||
- Yaw control edge cases: heading lock, drift, setpoint propagation
|
||||
- Flight task inheritance chain: correct base class for the desired behavior
|
||||
- Control allocation: actuator function ordering, motor index mapping
|
||||
|
||||
**Drivers/CAN:**
|
||||
- CAN bus devices behave differently from serial/SPI; check driver assumptions
|
||||
- ESC index mapping: telemetry index != channel when motors are disabled
|
||||
- ESC hardware quirks: 4-in-1 ESCs may report current on only one channel
|
||||
- device_id correctness and I2CSPIDriver patterns
|
||||
- Time representation: prefer `hrt_abstime` over iteration counts
|
||||
|
||||
**Simulation:**
|
||||
- Physics fidelity: noise models should match reality (GPS noise is not Gaussian)
|
||||
- Keep gz_bridge generic; vehicle-specific logic belongs in plugins
|
||||
- Prefer gz-transport over ROS2 dependencies when possible
|
||||
- Wrench commands for physics correctness vs kinematic constraints
|
||||
- Library generic/specific boundary: only base classes in common libs
|
||||
|
||||
**System:**
|
||||
- Race conditions and concurrency: no partial fixes, demand complete solutions
|
||||
- Semaphore/scheduling edge cases; understand RTOS guarantees
|
||||
- State machine sequential-logic bugs (consecutive RTL, armed/disarmed alternation)
|
||||
- uORB-driven scheduling (`SubscriptionCallback`), not extra threads
|
||||
- param_set triggers auto-save; no redundant param_save_default
|
||||
- Flash/memory efficiency: avoid `std::string` on embedded, minimize SubscriptionData usage
|
||||
- Constructor initialization order matters
|
||||
|
||||
**CI/Build:**
|
||||
- Pipeline race conditions (tag + branch push double-trigger, git describe correctness)
|
||||
- Container image size (check layer bloat)
|
||||
- Ubuntu LTS support policy (latest + one prior only)
|
||||
- Build time impact
|
||||
- CMake preferred over Makefiles
|
||||
|
||||
**Messages/Protocol:**
|
||||
- Backwards compatibility: will this break QGC, post-flight tools, or uLog parsers?
|
||||
- uORB: `timestamp` for publication metadata, `timestamp_sample` close to physical sample, include `device_id`
|
||||
- Don't version messages unless strictly needed
|
||||
- Parameter UX: will this confuse users in a GCS? Every new param is a configuration burden
|
||||
- MAVLink: use library constants, don't implement custom stream rates
|
||||
|
||||
**Board Addition:**
|
||||
- **Flight logs**: require a link to https://logs.px4.io demonstrating basic operation for the vehicle type (hover for multicopters, stable flight for fixed-wing, driving for rovers, etc.); short bench-only logs are insufficient
|
||||
- **Documentation**: require a docs page in `docs/en/flight_controller/` with pinout, where-to-buy, connector types, version badge, and manufacturer-supported notice block
|
||||
- **USB VID/PID**: must not reuse another manufacturer's Vendor ID; manufacturer must use their own
|
||||
- **Board naming**: directory is `boards/{manufacturer}/{board}/`, both lowercase, hyphens for board name
|
||||
- **Unique board_id**: registered in `boards/boards.json`, no collisions
|
||||
- **Copied code cleanup**: check for leftover files, configs, or comments from the template board; "Is this real or leftover?"
|
||||
- **RC configuration**: prefer `CONFIG_DRIVERS_COMMON_RC` over legacy `CONFIG_DRIVERS_RC_INPUT`
|
||||
- **No board-specific custom modules**: reject copy-pasted drivers (e.g., custom heater) when existing infrastructure works
|
||||
- **Bootloader**: expect a bootloader defconfig (`nuttx-config/bootloader/defconfig`) or explanation of shared bootloader
|
||||
- **CI integration**: board must be added to CI compile workflows so it builds on every PR
|
||||
- **Flash constraints**: verify enabled modules fit in flash; we are running low across all board targets
|
||||
- **Port labels**: serial port labels must match what is physically printed on the board
|
||||
- **Hardware availability**: for unknown manufacturers, verify the product exists and is purchasable (no vaporware)
|
||||
|
||||
8. **Format output** as:
|
||||
- **CI status**: pass/fail summary, link to failed runs if any
|
||||
- **Merge strategy**: recommend squash or rebase merge with reasoning
|
||||
- **Title check**: pass/fail with suggestion
|
||||
- **Review status**: list assigned reviewers and any existing reviews (who approved, who requested changes, key points already raised). Note if your review would duplicate feedback already given.
|
||||
- **Domains detected**: list which domain checks were applied
|
||||
- **Summary**: one paragraph on what the PR does and whether the approach is sound
|
||||
- **Issues**: numbered list, each with file:line, severity (blocker/warning/nit), and explanation. Skip issues already raised by other reviewers unless you have something to add.
|
||||
- **Verdict**: approve, request changes, or needs discussion
|
||||
|
||||
After the structured output, also display a **draft PR comment** formatted using the PR comment formatting rules from step 9. This gives the user a preview of what would be posted.
|
||||
|
||||
9. **Interactive dialog.** After displaying the review, present the user with these options:
|
||||
|
||||
Present options based on the verdict:
|
||||
|
||||
If verdict is **approve**:
|
||||
```
|
||||
What would you like to do?
|
||||
1. Chat about this PR (ask questions, explore code) [default]
|
||||
2. Approve this PR and post the review comment
|
||||
3. Adjust the review or draft (tell me what to change)
|
||||
4. Done for now
|
||||
```
|
||||
|
||||
If verdict is **request changes**:
|
||||
```
|
||||
What would you like to do?
|
||||
1. Chat about this PR (ask questions, explore code) [default]
|
||||
2. Request changes on this PR and post the review comment
|
||||
3. Adjust the review or draft (tell me what to change)
|
||||
4. Done for now
|
||||
```
|
||||
|
||||
If verdict is **needs discussion**:
|
||||
```
|
||||
What would you like to do?
|
||||
1. Chat about this PR (ask questions, explore code) [default]
|
||||
2. Post the review as a comment (no approval or rejection)
|
||||
3. Adjust the review or draft (tell me what to change)
|
||||
4. Done for now
|
||||
```
|
||||
|
||||
Wait for the user to choose before proceeding. If they pick:
|
||||
- **1 (chat)**: enter a free-form conversation about the PR. The user can ask about specific files, code paths, or decisions. When done, loop back to the options. This is the default if the user just presses enter.
|
||||
- **2 (submit)**: use the draft PR comment already shown. Before posting, check if you have review permissions: run `gh api repos/OWNER/REPO/collaborators/$(gh api user --jq .login)/permission --jq .permission` -- if `admin` or `write`, submit as a formal review with `gh pr review <PR> --approve --body "..."` or `gh pr review <PR> --request-changes --body "..."` based on the verdict. If no write access, fall back to `gh pr comment <PR> --body "..."`. Always confirm with the user before posting.
|
||||
- **3 (adjust)**: ask what to change, update the review and draft, then loop back to the options.
|
||||
- **4 (done)**: stop.
|
||||
|
||||
**PR comment formatting rules** (for the draft):
|
||||
When writing the GitHub comment, rewrite the review to sound like a human reviewer, not a structured report. Do NOT include the full skill output. Instead:
|
||||
- Drop most meta-sections (CI status, title check, domains detected, severity labels) but keep the merge strategy recommendation (e.g., "I'd suggest a rebase merge here since the commits are clean and atomic" or "This should be squash-merged, the commit history is messy")
|
||||
- Write conversationally: "Nice work on this. A few things I noticed:" not "Issues: 1. file:line (warning):"
|
||||
- Lead with a brief take on the overall change (1-2 sentences)
|
||||
- List only actionable feedback as natural review comments, not numbered checklists
|
||||
- Skip nits unless they are particularly useful
|
||||
- End with a clear stance: looks good to merge, needs a few changes, or let's discuss X
|
||||
- Post with `gh pr comment <PR> --body "$(cat <<'EOF' ... EOF)"`. Do not post without explicit confirmation.
|
||||
|
||||
If the user provided arguments, use them as context: $ARGUMENTS
|
||||
@@ -1 +0,0 @@
|
||||
build/
|
||||
@@ -1,115 +0,0 @@
|
||||
name: Build PX4 .deb Package
|
||||
description: Build PX4 SITL, run cpack, validate the .deb, and upload artifact
|
||||
|
||||
inputs:
|
||||
target:
|
||||
description: 'Build target: default or sih'
|
||||
required: true
|
||||
artifact-name:
|
||||
description: Name for the uploaded artifact
|
||||
required: true
|
||||
ccache-key-prefix:
|
||||
description: Prefix for ccache cache keys
|
||||
default: deb-ccache
|
||||
ccache-max-size:
|
||||
description: Maximum ccache size
|
||||
default: 400M
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Restore ccache
|
||||
id: ccache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}-
|
||||
${{ inputs.ccache-key-prefix }}-${{ github.base_ref || 'main' }}-
|
||||
${{ inputs.ccache-key-prefix }}-
|
||||
|
||||
- name: Configure ccache
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ~/.ccache
|
||||
echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
|
||||
echo "compression = true" >> ~/.ccache/ccache.conf
|
||||
echo "compression_level = 6" >> ~/.ccache/ccache.conf
|
||||
echo "max_size = ${{ inputs.ccache-max-size }}" >> ~/.ccache/ccache.conf
|
||||
echo "hash_dir = false" >> ~/.ccache/ccache.conf
|
||||
echo "compiler_check = content" >> ~/.ccache/ccache.conf
|
||||
ccache -s
|
||||
ccache -z
|
||||
|
||||
- name: Build PX4 SITL
|
||||
shell: bash
|
||||
run: make px4_sitl_${{ inputs.target }}
|
||||
|
||||
- name: ccache stats
|
||||
if: always()
|
||||
shell: bash
|
||||
run: ccache -s
|
||||
|
||||
- name: Save ccache
|
||||
uses: actions/cache/save@v4
|
||||
if: always()
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ${{ inputs.ccache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
- name: Build .deb package
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/px4_sitl_${{ inputs.target }}
|
||||
cpack -G DEB
|
||||
|
||||
- name: Print package info and contents
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/px4_sitl_${{ inputs.target }}
|
||||
echo "--- Package info ---"
|
||||
dpkg-deb -I *.deb
|
||||
echo "--- Package contents ---"
|
||||
dpkg-deb -c *.deb
|
||||
|
||||
- name: Validate sih package
|
||||
if: inputs.target == 'sih'
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/px4_sitl_sih
|
||||
echo "--- Verify NO Gazebo resources ---"
|
||||
! dpkg-deb -c px4_*.deb | grep share/gz > /dev/null && echo "PASS: no Gazebo" || { echo "FAIL: Gazebo found"; exit 1; }
|
||||
echo "--- Install test ---"
|
||||
dpkg -i px4_*.deb
|
||||
test -x /opt/px4/bin/px4 || { echo "FAIL: px4 binary not found"; exit 1; }
|
||||
test -L /usr/bin/px4 || { echo "FAIL: symlink not created"; exit 1; }
|
||||
test ! -d /opt/px4/share/gz || { echo "FAIL: Gazebo dir should not exist"; exit 1; }
|
||||
echo "--- Smoke test ---"
|
||||
/opt/px4/bin/px4 -h
|
||||
echo "PASS: sih package validation successful"
|
||||
|
||||
- name: Validate gazebo package
|
||||
if: inputs.target == 'default'
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/px4_sitl_default
|
||||
echo "--- Verify Gazebo resources in package ---"
|
||||
dpkg-deb -c px4-gazebo_*.deb | grep share/gz/models > /dev/null || { echo "FAIL: models missing"; exit 1; }
|
||||
dpkg-deb -c px4-gazebo_*.deb | grep share/gz/worlds > /dev/null || { echo "FAIL: worlds missing"; exit 1; }
|
||||
echo "--- Install test ---"
|
||||
dpkg -i px4-gazebo_*.deb
|
||||
test -x /opt/px4-gazebo/bin/px4 || { echo "FAIL: px4 binary not found"; exit 1; }
|
||||
test -x /opt/px4-gazebo/bin/px4-gazebo || { echo "FAIL: wrapper not found"; exit 1; }
|
||||
test -L /usr/bin/px4-gazebo || { echo "FAIL: symlink not created"; exit 1; }
|
||||
test -d /opt/px4-gazebo/share/gz/models || { echo "FAIL: Gazebo models not installed"; exit 1; }
|
||||
echo "--- Smoke test ---"
|
||||
/opt/px4-gazebo/bin/px4 -h
|
||||
echo "PASS: gazebo package validation successful"
|
||||
|
||||
- name: Upload .deb artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.artifact-name }}
|
||||
path: build/px4_sitl_${{ inputs.target }}/*.deb
|
||||
if-no-files-found: error
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Build Gazebo Classic SITL
|
||||
description: Build PX4 firmware and Gazebo Classic plugins with ccache stats
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build - PX4 Firmware (SITL)
|
||||
shell: bash
|
||||
run: make px4_sitl_default
|
||||
|
||||
- name: Cache - Stats after PX4 Firmware
|
||||
shell: bash
|
||||
run: ccache -s
|
||||
|
||||
- name: Build - Gazebo Classic Plugins
|
||||
shell: bash
|
||||
run: make px4_sitl_default sitl_gazebo-classic
|
||||
|
||||
- name: Cache - Stats after Gazebo Plugins
|
||||
shell: bash
|
||||
run: ccache -s
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Save ccache
|
||||
description: Print ccache stats and save to cache
|
||||
|
||||
inputs:
|
||||
cache-primary-key:
|
||||
description: Primary cache key from setup-ccache output
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cache - Stats
|
||||
if: always()
|
||||
shell: bash
|
||||
run: ccache -s
|
||||
|
||||
- name: Cache - Save ccache
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ${{ inputs.cache-primary-key }}
|
||||
@@ -1,56 +0,0 @@
|
||||
name: Setup ccache
|
||||
description: Restore ccache from cache and configure ccache.conf
|
||||
|
||||
inputs:
|
||||
cache-key-prefix:
|
||||
description: Cache key prefix (e.g. ccache-sitl)
|
||||
required: true
|
||||
max-size:
|
||||
description: Max ccache size (e.g. 300M)
|
||||
required: false
|
||||
default: '300M'
|
||||
base-dir:
|
||||
description: ccache base_dir value
|
||||
required: false
|
||||
default: '${GITHUB_WORKSPACE}'
|
||||
install-ccache:
|
||||
description: Install ccache via apt before configuring
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
cache-primary-key:
|
||||
description: Primary cache key (pass to save-ccache)
|
||||
value: ${{ steps.restore.outputs.cache-primary-key }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cache - Install ccache
|
||||
if: inputs.install-ccache == 'true'
|
||||
shell: bash
|
||||
run: apt-get update && apt-get install -y ccache
|
||||
|
||||
- name: Cache - Restore ccache
|
||||
id: restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ inputs.cache-key-prefix }}-${{ github.ref_name }}-
|
||||
${{ inputs.cache-key-prefix }}-${{ github.base_ref || 'main' }}-
|
||||
${{ inputs.cache-key-prefix }}-
|
||||
|
||||
- name: Cache - Configure ccache
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ~/.ccache
|
||||
echo "base_dir = ${{ inputs.base-dir }}" > ~/.ccache/ccache.conf
|
||||
echo "compression = true" >> ~/.ccache/ccache.conf
|
||||
echo "compression_level = 6" >> ~/.ccache/ccache.conf
|
||||
echo "max_size = ${{ inputs.max-size }}" >> ~/.ccache/ccache.conf
|
||||
echo "hash_dir = false" >> ~/.ccache/ccache.conf
|
||||
echo "compiler_check = content" >> ~/.ccache/ccache.conf
|
||||
ccache -s
|
||||
ccache -z
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
applyTo: "boards/**"
|
||||
---
|
||||
|
||||
# Board Addition Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines, when reviewing new board additions:
|
||||
|
||||
- **Flight logs**: require a link to https://logs.px4.io demonstrating basic operation for the vehicle type (hover for multicopters, stable flight for fixed-wing, driving for rovers, etc.); short bench-only logs are insufficient
|
||||
- **Documentation**: require a docs page in `docs/en/flight_controller/` with pinout, where-to-buy, connector types, version badge, and manufacturer-supported notice block
|
||||
- **USB VID/PID**: must not reuse another manufacturer's Vendor ID; manufacturer must use their own
|
||||
- **Board naming**: directory is `boards/{manufacturer}/{board}/`, both lowercase, hyphens for board name
|
||||
- **Unique board_id**: registered in `boards/boards.json`, no collisions
|
||||
- **Copied code cleanup**: check for leftover files, configs, or comments from the template board. Ask "Is this real or leftover?"
|
||||
- **RC configuration**: prefer `CONFIG_DRIVERS_COMMON_RC` over legacy `CONFIG_DRIVERS_RC_INPUT`
|
||||
- **No board-specific custom modules**: reject copy-pasted drivers (e.g., custom heater) when existing infrastructure works
|
||||
- **Bootloader**: expect a bootloader defconfig (`nuttx-config/bootloader/defconfig`) or explanation of shared bootloader
|
||||
- **CI integration**: board must be added to CI compile workflows so it builds on every PR
|
||||
- **Flash constraints**: verify enabled modules fit in flash; we are running low across all board targets
|
||||
- **Port labels**: serial port labels must match what is physically printed on the board
|
||||
- **Hardware availability**: for unknown manufacturers, verify the product exists and is purchasable (no vaporware)
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
applyTo: ".github/**,cmake/**,Makefile,CMakeLists.txt,Tools/**,**/Kconfig"
|
||||
---
|
||||
|
||||
# CI/Build Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Check for pipeline race conditions (tag + branch push double-trigger, git describe correctness)
|
||||
- Container image size: check for layer bloat
|
||||
- Ubuntu LTS support policy: only latest + one prior LTS version
|
||||
- Consider build time impact of changes
|
||||
- Prefer CMake over Makefiles
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
applyTo: "src/**,boards/**,platforms/**,msg/**,cmake/**,Makefile,CMakeLists.txt,Tools/**,.github/**"
|
||||
---
|
||||
|
||||
# PX4 Code Review Guidelines
|
||||
|
||||
## Conventions
|
||||
|
||||
- PR titles must follow conventional commits: `type(scope): description` (see CONTRIBUTING.md)
|
||||
- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
|
||||
- Scope should match the primary area of changed files
|
||||
- Append `!` before the colon for breaking changes
|
||||
- Both squash merge and rebase merge are enabled; merge commits are disabled
|
||||
- Commits should be atomic and independently revertable
|
||||
- WIP or review-response commits should be squashed before merge
|
||||
|
||||
## Core Checks (always apply)
|
||||
|
||||
- **Correctness**: logic errors, off-by-ones, unhandled edge cases
|
||||
- **Type safety**: int16 overflow, float/double promotion, unsigned subtraction, use `uint64_t` for absolute time
|
||||
- **Initialization**: uninitialized variables, missing default construction
|
||||
- **Buffer safety**: unchecked array access, stack allocation of large buffers, snprintf bounds
|
||||
- **Magic numbers**: every numeric literal needs a named constant or justification
|
||||
- **Framework reuse**: use PX4_ERR/WARN/INFO, existing libraries (AlphaFilter, SlewRate, RateControl), MAVLink constants from the library
|
||||
- **Naming**: accurate, no unjustified abbreviations, current terminology (GPS -> GNSS for new code)
|
||||
- **Unnecessary complexity**: can code be removed instead of added? Is there a simpler pattern?
|
||||
- **Test coverage**: new features should include unit or integration tests; bug fixes should include regression tests where practical
|
||||
- **Formatting**: `make format` / `make check_format` (astyle) for C/C++ files; `clang-tidy` clean
|
||||
- **Coding style**: C/C++ must follow the PX4 coding style (https://docs.px4.io/main/en/contribute/code.html)
|
||||
- **Necessity**: challenge every addition. Is this actually needed or just copied?
|
||||
- **Architecture fit**: does the code live in the module that naturally owns the data? No unnecessary cross-module dependencies
|
||||
- **Ecosystem impact**: consider QGC users, log analysis tools, and third-party integrations
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
applyTo: "src/modules/mc_*control*/**,src/modules/fw_*control*/**,src/modules/flight_mode_manager/**,src/lib/rate_control/**,src/lib/npfg/**,src/modules/vtol_att_control/**"
|
||||
---
|
||||
|
||||
# Control Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Phase margin: output filters consume margin for no benefit; prefer adjusting gyro/d-gyro cutoffs
|
||||
- Check for circular dependencies: sensor data feeding back into its own control loop (e.g., throttle-based airspeed in TECS)
|
||||
- NaN propagation in flight-critical math; check `PX4_ISFINITE` before magnitude checks
|
||||
- Prefer proper setpoint smoothing over controller output filtering (setpoint generation vs output-stage hacks)
|
||||
- Check yaw control edge cases: heading lock, drift, setpoint propagation
|
||||
- Verify flight task inheritance chain uses the correct base class for desired behavior
|
||||
- Control allocation: verify actuator function ordering and motor index mapping
|
||||
@@ -21,10 +21,8 @@ applyTo: "docs/en/**"
|
||||
- Do not apply bold or italic styling inside a heading.
|
||||
- **Formatting:**
|
||||
- **Bold:** Only for UI elements (buttons, menu items).
|
||||
- **Italics (Emphasis):** For tool names (e.g., *QGroundControl*).
|
||||
- **Inline Code:** Use backticks for file paths, parameters, and CLI commands (e.g., `prettier`).
|
||||
Backticks are optional for hyperlinked CLI commands and tool names.
|
||||
- **Italics (Emphasis):** Use for application names (e.g., *QGroundControl*).
|
||||
Emphasis is optional for hyperlinked applications.
|
||||
- **Structure:** End every line at the end of a sentence (Semantic Line Breaks).
|
||||
|
||||
## Linking & Navigation
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
applyTo: "src/drivers/**,src/modules/cyphal/**"
|
||||
---
|
||||
|
||||
# Drivers/CAN Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- CAN bus devices behave differently from serial/SPI; check driver assumptions
|
||||
- ESC index mapping: telemetry index != channel when motors are disabled
|
||||
- ESC hardware quirks: 4-in-1 ESCs may report current on only one channel
|
||||
- Verify device_id correctness and I2CSPIDriver patterns
|
||||
- Time representation: prefer `hrt_abstime` over iteration counts
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
applyTo: "src/modules/ekf2/**,src/lib/wind_estimator/**,src/lib/world_magnetic_model/**"
|
||||
---
|
||||
|
||||
# Estimation Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Check for singularities in aerospace math (euler angles near gimbal lock, sideslip at low airspeed)
|
||||
- Flag aliasing from downsampling sensor data without proper filtering
|
||||
- Verify Kalman filter correctness (Joseph form, innovation variance, covariance symmetry)
|
||||
- Consider CPU cost on embedded targets (avoid unnecessary sqrt, limit fusion rate)
|
||||
- Verify frame/coordinate system correctness (FRD vs NED, body vs earth frame)
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
applyTo: "msg/**,src/modules/mavlink/**,src/modules/uxrce_dds_client/**"
|
||||
---
|
||||
|
||||
# Messages/Protocol Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Backwards compatibility: will this break QGC, post-flight tools, or uLog parsers?
|
||||
- uORB: `timestamp` for publication metadata, `timestamp_sample` close to physical sample, include `device_id`
|
||||
- Don't version messages unless strictly needed
|
||||
- Parameter UX: will this confuse users in a GCS? Every new param is a configuration burden
|
||||
- MAVLink: use library constants, don't implement custom stream rates
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
applyTo: "src/modules/simulation/**,Tools/simulation/**"
|
||||
---
|
||||
|
||||
# Simulation Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Physics fidelity: noise models should match reality (GPS noise is not Gaussian)
|
||||
- Keep gz_bridge generic; vehicle-specific logic belongs in plugins
|
||||
- Prefer gz-transport over ROS2 dependencies when possible
|
||||
- Use wrench commands for physics correctness vs kinematic constraints
|
||||
- Library generic/specific boundary: only base classes in common libs
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
applyTo: "src/modules/commander/**,src/modules/logger/**,src/systemcmds/**,platforms/**,src/modules/dataman/**"
|
||||
---
|
||||
|
||||
# System Review Guidelines
|
||||
|
||||
In addition to the core code review guidelines:
|
||||
|
||||
- Race conditions and concurrency: no partial fixes, demand complete solutions
|
||||
- Semaphore/scheduling edge cases; understand RTOS guarantees
|
||||
- State machine sequential-logic bugs (consecutive RTL, armed/disarmed alternation)
|
||||
- Use uORB-driven scheduling (`SubscriptionCallback`), not extra threads
|
||||
- `param_set` triggers auto-save; no redundant `param_save_default`
|
||||
- Flash/memory efficiency: avoid `std::string` on embedded, minimize SubscriptionData usage
|
||||
- Constructor initialization order matters
|
||||
@@ -265,8 +265,5 @@ jobs:
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ steps.upload-location.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
artifacts/*.px4
|
||||
artifacts/*.deb
|
||||
artifacts/**/*.sbom.spdx.json
|
||||
files: artifacts/*.px4
|
||||
name: ${{ steps.upload-location.outputs.uploadlocation }}
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
name: SITL Packages and Containers
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cmake/package.cmake'
|
||||
- 'platforms/posix/CMakeLists.txt'
|
||||
- 'Tools/packaging/**'
|
||||
- 'boards/px4/sitl/sih.px4board'
|
||||
- '.github/workflows/build_deb_package.yml'
|
||||
- '.github/actions/build-deb/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deploy_containers:
|
||||
description: 'Push container images to registry'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Setup: extract version and determine whether to push containers
|
||||
# ---------------------------------------------------------------------------
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: [runs-on,"runner=1cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
outputs:
|
||||
px4_version: ${{ steps.version.outputs.px4_version }}
|
||||
should_push: ${{ steps.push.outputs.should_push }}
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-tags: true
|
||||
submodules: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set PX4 version
|
||||
id: version
|
||||
run: echo "px4_version=$(git describe --tags --match 'v[0-9]*')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check if we should push containers
|
||||
id: push
|
||||
run: |
|
||||
if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]] || \
|
||||
[[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.deploy_containers }}" == "true" ]]; then
|
||||
echo "should_push=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_push=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build .deb packages (all distros, arches, targets)
|
||||
# ---------------------------------------------------------------------------
|
||||
build-deb:
|
||||
name: "Build .deb (${{ matrix.target }}/${{ matrix.codename }}/${{ matrix.arch }})"
|
||||
needs: setup
|
||||
runs-on: [runs-on,"runner=4cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
container:
|
||||
image: ${{ matrix.container }}
|
||||
volumes:
|
||||
- /github/workspace:/github/workspace
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { codename: noble, arch: amd64, runner: x64, container: "ubuntu:24.04", target: default, setup_flags: "" }
|
||||
- { codename: noble, arch: arm64, runner: arm64, container: "ubuntu:24.04", target: default, setup_flags: "" }
|
||||
- { codename: jammy, arch: amd64, runner: x64, container: "ubuntu:22.04", target: default, setup_flags: "" }
|
||||
- { codename: jammy, arch: arm64, runner: arm64, container: "ubuntu:22.04", target: default, setup_flags: "" }
|
||||
- { codename: noble, arch: amd64, runner: x64, container: "ubuntu:24.04", target: sih, setup_flags: "--no-sim-tools" }
|
||||
- { codename: noble, arch: arm64, runner: arm64, container: "ubuntu:24.04", target: sih, setup_flags: "--no-sim-tools" }
|
||||
- { codename: jammy, arch: amd64, runner: x64, container: "ubuntu:22.04", target: sih, setup_flags: "--no-sim-tools" }
|
||||
- { codename: jammy, arch: arm64, runner: arm64, container: "ubuntu:22.04", target: sih, setup_flags: "--no-sim-tools" }
|
||||
env:
|
||||
RUNS_IN_DOCKER: true
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
|
||||
- name: Fix git in container
|
||||
run: |
|
||||
apt-get update && apt-get install -y git
|
||||
git config --global --add safe.directory $(realpath .)
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Use AWS regional apt mirror
|
||||
if: startsWith(runner.name, 'runs-on--')
|
||||
run: ./Tools/ci/use_aws_apt_mirror.sh
|
||||
|
||||
- name: Cache apt packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}-${{ hashFiles('Tools/setup/ubuntu.sh') }}
|
||||
restore-keys: apt-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./Tools/setup/ubuntu.sh --no-nuttx ${{ matrix.setup_flags }}
|
||||
|
||||
- name: Build and package .deb
|
||||
uses: ./.github/actions/build-deb
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
artifact-name: px4-sitl-debs-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}
|
||||
ccache-key-prefix: deb-ccache-${{ matrix.target }}-${{ matrix.codename }}-${{ matrix.arch }}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build Docker images from Noble .debs
|
||||
# ---------------------------------------------------------------------------
|
||||
build-docker:
|
||||
name: "Build Image (${{ matrix.image }}/${{ matrix.arch }})"
|
||||
needs: [setup, build-deb]
|
||||
runs-on: [runs-on,"runner=4cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { image: sih, repo: px4-sitl, target: sih, arch: amd64, runner: x64, platform: "linux/amd64", dockerfile: Dockerfile.sih }
|
||||
- { image: sih, repo: px4-sitl, target: sih, arch: arm64, runner: arm64, platform: "linux/arm64", dockerfile: Dockerfile.sih }
|
||||
- { image: gazebo, repo: px4-sitl-gazebo, target: default, arch: amd64, runner: x64, platform: "linux/amd64", dockerfile: Dockerfile.gazebo }
|
||||
- { image: gazebo, repo: px4-sitl-gazebo, target: default, arch: arm64, runner: arm64, platform: "linux/arm64", dockerfile: Dockerfile.gazebo }
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Download Noble .deb artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: px4-sitl-debs-${{ matrix.target }}-noble-${{ matrix.arch }}
|
||||
path: docker-context
|
||||
|
||||
- name: Prepare build context
|
||||
run: cp Tools/packaging/px4-entrypoint.sh docker-context/
|
||||
|
||||
- name: Login to registries
|
||||
if: needs.setup.outputs.should_push == 'true'
|
||||
run: |
|
||||
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
platforms: ${{ matrix.platform }}
|
||||
|
||||
- name: Build and push container image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: docker-context
|
||||
file: Tools/packaging/${{ matrix.dockerfile }}
|
||||
tags: |
|
||||
px4io/${{ matrix.repo }}:${{ needs.setup.outputs.px4_version }}-${{ matrix.arch }}
|
||||
px4io/${{ matrix.repo }}:latest-${{ matrix.arch }}
|
||||
ghcr.io/px4/${{ matrix.repo }}:${{ needs.setup.outputs.px4_version }}-${{ matrix.arch }}
|
||||
ghcr.io/px4/${{ matrix.repo }}:latest-${{ matrix.arch }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
load: false
|
||||
push: ${{ needs.setup.outputs.should_push == 'true' }}
|
||||
provenance: false
|
||||
cache-from: type=gha,scope=sitl-${{ matrix.image }}-${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=sitl-${{ matrix.image }}-${{ matrix.arch }}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deploy: create multi-arch manifests and push to registries
|
||||
# ---------------------------------------------------------------------------
|
||||
deploy:
|
||||
name: "Deploy (${{ matrix.image }})"
|
||||
needs: [setup, build-docker]
|
||||
if: needs.setup.outputs.should_push == 'true'
|
||||
runs-on: [runs-on,"runner=1cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { image: sih, repo: px4-sitl }
|
||||
- { image: gazebo, repo: px4-sitl-gazebo }
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
|
||||
- name: Login to registries
|
||||
run: |
|
||||
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
|
||||
- name: Create and push multi-arch manifests
|
||||
run: |
|
||||
VERSION="${{ needs.setup.outputs.px4_version }}"
|
||||
|
||||
for REGISTRY in px4io ghcr.io/px4; do
|
||||
IMAGE="${REGISTRY}/${{ matrix.repo }}"
|
||||
|
||||
for TAG in ${VERSION} latest; do
|
||||
docker manifest create ${IMAGE}:${TAG} \
|
||||
--amend ${IMAGE}:${TAG}-arm64 \
|
||||
--amend ${IMAGE}:${TAG}-amd64
|
||||
|
||||
docker manifest annotate ${IMAGE}:${TAG} ${IMAGE}:${TAG}-arm64 --arch arm64
|
||||
docker manifest annotate ${IMAGE}:${TAG} ${IMAGE}:${TAG}-amd64 --arch amd64
|
||||
|
||||
docker manifest push ${IMAGE}:${TAG}
|
||||
done
|
||||
done
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -46,11 +46,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Building [${{ matrix.check }}]
|
||||
env:
|
||||
PX4_SBOM_DISABLE: 1
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
git config --system --add safe.directory '*'
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
make ${{ matrix.check }}
|
||||
|
||||
- name: Uploading Coverage to Codecov.io
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
name: Clang-Tidy
|
||||
runs-on: [runs-on, runner=16cpu-linux-x64, "run-id=${{ github.run_id }}", "extras=s3-cache"]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.17.0-beta1
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
name: Commit Quality
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
name: PR Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: Tools/ci
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check PR title
|
||||
id: check
|
||||
run: |
|
||||
python3 Tools/ci/check_pr_title.py "${{ github.event.pull_request.title }}" --markdown-file comment.md && rc=0 || rc=$?
|
||||
echo "exit_code=$rc" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post or clear comment
|
||||
if: env.IS_FORK == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.exit_code }}" != "0" ]; then
|
||||
python3 Tools/ci/pr_comment.py --marker pr-title --pr "$PR_NUMBER" --result fail < comment.md
|
||||
else
|
||||
python3 Tools/ci/pr_comment.py --marker pr-title --pr "$PR_NUMBER" --result pass
|
||||
fi
|
||||
|
||||
- name: Result
|
||||
if: steps.check.outputs.exit_code != '0'
|
||||
run: |
|
||||
echo "::error::PR title does not follow conventional commits format. See the PR comment for details."
|
||||
exit 1
|
||||
|
||||
commit-messages:
|
||||
name: Commit Messages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: Tools/ci
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check commit messages
|
||||
id: check
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh api \
|
||||
"repos/${{ github.repository }}/pulls/${PR_NUMBER}/commits?per_page=100" \
|
||||
| python3 Tools/ci/check_commit_messages.py --markdown-file comment.md && rc=0 || rc=$?
|
||||
echo "exit_code=$rc" >> "$GITHUB_OUTPUT"
|
||||
# Check for warnings (non-empty markdown on exit 0)
|
||||
if [ "$rc" -eq 0 ] && [ -s comment.md ]; then
|
||||
echo "has_warnings=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_warnings=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Post or clear comment
|
||||
if: env.IS_FORK == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.exit_code }}" != "0" ]; then
|
||||
python3 Tools/ci/pr_comment.py --marker commit-msgs --pr "$PR_NUMBER" --result fail < comment.md
|
||||
elif [ "${{ steps.check.outputs.has_warnings }}" == "true" ]; then
|
||||
python3 Tools/ci/pr_comment.py --marker commit-msgs --pr "$PR_NUMBER" --result warn < comment.md
|
||||
else
|
||||
python3 Tools/ci/pr_comment.py --marker commit-msgs --pr "$PR_NUMBER" --result pass
|
||||
fi
|
||||
|
||||
- name: Result
|
||||
if: steps.check.outputs.exit_code != '0'
|
||||
run: |
|
||||
echo "::error::Commit message errors found. See the PR comment for details."
|
||||
exit 1
|
||||
|
||||
pr-body:
|
||||
name: PR Description
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: env.IS_FORK == 'false'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: Tools/ci
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check PR body
|
||||
id: check
|
||||
env:
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
message=""
|
||||
if [ -z "$PR_BODY" ]; then
|
||||
message="PR description is empty. Please add a summary of the changes."
|
||||
echo "::warning::PR description is empty."
|
||||
else
|
||||
cleaned=$(echo "$PR_BODY" | sed 's/<!--.*-->//g' | tr -d '[:space:]')
|
||||
if [ -z "$cleaned" ]; then
|
||||
message="PR description contains only template comments. Please fill in the details."
|
||||
echo "::warning::PR description contains only template comments."
|
||||
fi
|
||||
fi
|
||||
echo "message=$message" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post or clear comment
|
||||
if: env.IS_FORK == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [ -n "${{ steps.check.outputs.message }}" ]; then
|
||||
printf '%s\n' \
|
||||
"## PR Description (advisory)" \
|
||||
"" \
|
||||
"This is **not blocking**, but your PR description appears to be empty or incomplete." \
|
||||
"" \
|
||||
"${{ steps.check.outputs.message }}" \
|
||||
"" \
|
||||
"A good PR description helps reviewers understand what changed and why." \
|
||||
"" \
|
||||
"---" \
|
||||
"*This comment will be automatically removed once the issue is resolved.*" \
|
||||
| python3 Tools/ci/pr_comment.py --marker pr-body --pr "$PR_NUMBER" --result warn
|
||||
else
|
||||
python3 Tools/ci/pr_comment.py --marker pr-body --pr "$PR_NUMBER" --result pass
|
||||
fi
|
||||
@@ -49,10 +49,6 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use AWS regional apt mirror
|
||||
if: startsWith(runner.name, 'runs-on--')
|
||||
run: ./Tools/ci/use_aws_apt_mirror.sh
|
||||
|
||||
- name: Install Deps, Build, and Make Quick Check
|
||||
run: |
|
||||
# we need to install dependencies and build on the same step
|
||||
|
||||
@@ -24,11 +24,6 @@ on:
|
||||
description: 'Container tag (e.g. v1.16.0)'
|
||||
required: true
|
||||
type: string
|
||||
build_ref:
|
||||
description: 'Git ref to build from (branch, tag, or SHA). Leave empty to build from the dispatch ref.'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
deploy_to_registry:
|
||||
description: 'Whether to push built images to the registry'
|
||||
required: false
|
||||
@@ -50,10 +45,9 @@ jobs:
|
||||
meta_tags: ${{ steps.meta.outputs.tags }}
|
||||
meta_labels: ${{ steps.meta.outputs.labels }}
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v5
|
||||
- uses: runs-on/action@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.build_ref || github.ref }}
|
||||
fetch-tags: true
|
||||
submodules: false
|
||||
fetch-depth: 0
|
||||
@@ -70,7 +64,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/PX4/px4-dev
|
||||
@@ -95,23 +89,22 @@ jobs:
|
||||
runner: x64
|
||||
runs-on: [runs-on,"runner=4cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v5
|
||||
- uses: runs-on/action@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.build_ref || github.ref }}
|
||||
fetch-tags: true
|
||||
submodules: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_registry) }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_registry) }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -119,13 +112,13 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
platforms: ${{ matrix.platform }}
|
||||
|
||||
- name: Build and Load Container Image
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
id: docker
|
||||
with:
|
||||
context: Tools/setup
|
||||
@@ -137,8 +130,8 @@ jobs:
|
||||
load: false
|
||||
push: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_registry) }}
|
||||
provenance: false
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }},ignore-error=true
|
||||
cache-from: type=gha,version=1,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,version=1,mode=max,scope=${{ matrix.arch }}
|
||||
|
||||
deploy:
|
||||
name: Deploy To Registry
|
||||
@@ -147,27 +140,23 @@ jobs:
|
||||
packages: write
|
||||
runs-on: [runs-on,"runner=4cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}",extras=s3-cache,spot=false]
|
||||
needs: [build, setup]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.setup.result == 'success' &&
|
||||
(startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_registry == 'true'))
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_registry) }}
|
||||
steps:
|
||||
- uses: runs-on/action@v2
|
||||
- uses: actions/checkout@v5
|
||||
- uses: runs-on/action@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.build_ref || github.ref }}
|
||||
fetch-tags: true
|
||||
submodules: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -175,10 +164,10 @@ jobs:
|
||||
|
||||
- name: Verify Images Exist Before Creating Manifest
|
||||
run: |
|
||||
docker manifest inspect px4io/px4-dev:${{ needs.setup.outputs.px4_version }}-arm64
|
||||
docker manifest inspect px4io/px4-dev:${{ needs.setup.outputs.px4_version }}-amd64
|
||||
docker manifest inspect ghcr.io/px4/px4-dev:${{ needs.setup.outputs.px4_version }}-arm64
|
||||
docker manifest inspect ghcr.io/px4/px4-dev:${{ needs.setup.outputs.px4_version }}-amd64
|
||||
docker manifest inspect px4io/px4-dev:${{ needs.setup.outputs.px4_version }}-arm64 || echo "⚠️ Warning: No ARM64 image found!"
|
||||
docker manifest inspect px4io/px4-dev:${{ needs.setup.outputs.px4_version }}-amd64 || echo "⚠️ Warning: No AMD64 image found!"
|
||||
docker manifest inspect ghcr.io/px4/px4-dev:${{ needs.setup.outputs.px4_version }}-arm64 || echo "⚠️ Warning: No ARM64 image found!"
|
||||
docker manifest inspect ghcr.io/px4/px4-dev:${{ needs.setup.outputs.px4_version }}-amd64 || echo "⚠️ Warning: No AMD64 image found!"
|
||||
|
||||
- name: Create and Push Multi-Arch Manifest for Docker Hub
|
||||
run: |
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
contents: read
|
||||
runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false,extras=s3-cache]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.17.0-beta1
|
||||
steps:
|
||||
- uses: runs-on/action@v1
|
||||
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
contents: write
|
||||
runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false,extras=s3-cache]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.17.0-beta1
|
||||
steps:
|
||||
- uses: runs-on/action@v1
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
commit_message: 'docs(i18n): PX4 guide translations (Crowdin) - ${{ matrix.lc }}'
|
||||
commit_message: New Crowdin translations - ${{ matrix.lc }}
|
||||
localization_branch_name: l10n_crowdin_docs_translations_${{ matrix.lc }}
|
||||
crowdin_branch_name: main
|
||||
create_pull_request: true
|
||||
pull_request_base_branch_name: 'main'
|
||||
pull_request_title: 'docs(i18n): PX4 guide translations (Crowdin) - ${{ matrix.lc }}'
|
||||
pull_request_body: 'docs(i18n): PX4 guide Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action) for ${{ matrix.lc }}'
|
||||
pull_request_title: New PX4 guide translations (Crowdin) - ${{ matrix.lc }}
|
||||
pull_request_body: 'New PX4 guide Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action) for ${{ matrix.lc }}'
|
||||
pull_request_labels: 'Documentation 📑'
|
||||
pull_request_reviewers: hamishwillee
|
||||
download_language: ${{ matrix.lc }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: main test
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
git config --system --add safe.directory '*'
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
make tests TESTFILTER=EKF
|
||||
|
||||
- name: Check if there is a functional change
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
|
||||
env:
|
||||
GIT_COMMITTER_EMAIL: bot@px4.io
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: main test
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
git config --system --add safe.directory '*'
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
make tests TESTFILTER=EKF
|
||||
|
||||
- name: Check if there exists diff and save result in variable
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
"failsafe_web",
|
||||
]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
|
||||
steps:
|
||||
- name: Install Node v20.18.0
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
name: Analyzing ${{ matrix.target }}
|
||||
runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
strategy:
|
||||
matrix:
|
||||
target: [px4_fmu-v5x, px4_fmu-v6x]
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Checking ${{ matrix.target }}
|
||||
runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
px4io/px4-dev-ros-melodic:2021-09-08 \
|
||||
bash -c '
|
||||
git config --global --add safe.directory /workspace
|
||||
PX4_SBOM_DISABLE=1 make px4_sitl_default
|
||||
PX4_SBOM_DISABLE=1 make px4_sitl_default sitl_gazebo-classic
|
||||
make px4_sitl_default
|
||||
make px4_sitl_default sitl_gazebo-classic
|
||||
./test/rostest_px4_run.sh \
|
||||
mavros_posix_test_mission.test \
|
||||
mission:=MC_mission_box \
|
||||
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
px4io/px4-dev-ros-melodic:2021-09-08 \
|
||||
bash -c '
|
||||
git config --global --add safe.directory /workspace
|
||||
PX4_SBOM_DISABLE=1 make px4_sitl_default
|
||||
PX4_SBOM_DISABLE=1 make px4_sitl_default sitl_gazebo-classic
|
||||
make px4_sitl_default
|
||||
make px4_sitl_default sitl_gazebo-classic
|
||||
./test/rostest_px4_run.sh \
|
||||
mavros_posix_tests_offboard_posctl.test \
|
||||
vehicle:=iris
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/px4/px4-dev:v1.17.0-rc2
|
||||
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Build PX4 and Run Test [${{ matrix.config }}]
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
git config --system --add safe.directory '*'
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
export PX4_EXTRA_NUTTX_CONFIG='CONFIG_NSH_LOGIN_PASSWORD="test";CONFIG_NSH_CONSOLE_LOGIN=y'
|
||||
echo "PX4_EXTRA_NUTTX_CONFIG: $PX4_EXTRA_NUTTX_CONFIG"
|
||||
|
||||
|
||||
@@ -89,17 +89,7 @@ jobs:
|
||||
. /opt/ros/galactic/setup.bash
|
||||
mkdir -p /opt/px4_ws/src
|
||||
cd /opt/px4_ws/src
|
||||
# On a PR, target the branch we're merging into (main or release/X.Y).
|
||||
# On a direct push, fall back to the branch we're running on.
|
||||
BRANCH="${GITHUB_BASE_REF:-$GITHUB_REF_NAME}"
|
||||
REPO_URL="https://github.com/Auterion/px4-ros2-interface-lib.git"
|
||||
if git ls-remote --heads "$REPO_URL" "$BRANCH" | grep -q "$BRANCH"; then
|
||||
echo "Cloning px4-ros2-interface-lib with matching branch: $BRANCH"
|
||||
git clone --recursive --branch "$BRANCH" "$REPO_URL"
|
||||
else
|
||||
echo "Branch '$BRANCH' not found in px4-ros2-interface-lib, using default (main)"
|
||||
git clone --recursive "$REPO_URL"
|
||||
fi
|
||||
git clone --recursive https://github.com/Auterion/px4-ros2-interface-lib.git
|
||||
# Ignore python packages due to compilation issue (can be enabled when updating ROS)
|
||||
touch px4-ros2-interface-lib/px4_ros2_py/COLCON_IGNORE || true
|
||||
touch px4-ros2-interface-lib/examples/python/COLCON_IGNORE || true
|
||||
@@ -112,8 +102,6 @@ jobs:
|
||||
run: ccache -s
|
||||
|
||||
- name: Build PX4
|
||||
env:
|
||||
PX4_SBOM_DISABLE: 1
|
||||
run: make px4_sitl_default
|
||||
- name: ccache post-run px4/firmware
|
||||
run: ccache -s
|
||||
@@ -132,7 +120,7 @@ jobs:
|
||||
run: |
|
||||
. /opt/px4_ws/install/setup.bash
|
||||
/opt/Micro-XRCE-DDS-Agent/build/MicroXRCEAgent udp4 localhost -p 8888 -v 0 &
|
||||
test/ros_test_runner.py --verbose --model iris --force-color
|
||||
test/ros_test_runner.py --verbose --model iris --upload --force-color
|
||||
timeout-minutes: 45
|
||||
|
||||
- name: Upload failed logs
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: SBOM License Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'release/**'
|
||||
- 'stable'
|
||||
paths:
|
||||
- '.gitmodules'
|
||||
- 'Tools/ci/license-overrides.yaml'
|
||||
- 'Tools/ci/generate_sbom.py'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
paths:
|
||||
- '.gitmodules'
|
||||
- 'Tools/ci/license-overrides.yaml'
|
||||
- 'Tools/ci/generate_sbom.py'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
verify-licenses:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
submodules: false
|
||||
|
||||
- name: Install PyYAML
|
||||
run: pip install pyyaml --break-system-packages
|
||||
|
||||
- name: Verify submodule licenses
|
||||
run: python3 Tools/ci/generate_sbom.py --verify-licenses --source-dir .
|
||||
@@ -1,132 +0,0 @@
|
||||
name: SBOM Monthly Audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# First Monday of each month at 09:00 UTC
|
||||
- cron: '0 9 1-7 * 1'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to audit (leave empty for current)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
submodules: recursive
|
||||
|
||||
- name: Install PyYAML
|
||||
run: pip install pyyaml --break-system-packages
|
||||
|
||||
- name: Run license verification
|
||||
id: verify
|
||||
continue-on-error: true
|
||||
run: |
|
||||
python3 Tools/ci/generate_sbom.py --verify-licenses --source-dir . 2>&1 | tee /tmp/sbom-verify.txt
|
||||
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check for issues
|
||||
id: check
|
||||
run: |
|
||||
if grep -q "<-- UNRESOLVED" /tmp/sbom-verify.txt; then
|
||||
echo "has_issues=true" >> "$GITHUB_OUTPUT"
|
||||
# Extract only genuinely unresolved license lines
|
||||
grep "<-- UNRESOLVED" /tmp/sbom-verify.txt > /tmp/sbom-issues.txt || true
|
||||
# Extract copyleft lines
|
||||
sed -n '/Copyleft licenses detected/,/^$/p' /tmp/sbom-verify.txt > /tmp/sbom-copyleft.txt || true
|
||||
else
|
||||
echo "has_issues=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create issue if problems found
|
||||
if: steps.check.outputs.has_issues == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
const fullOutput = fs.readFileSync('/tmp/sbom-verify.txt', 'utf8');
|
||||
let issueLines = '';
|
||||
try {
|
||||
issueLines = fs.readFileSync('/tmp/sbom-issues.txt', 'utf8');
|
||||
} catch (e) {
|
||||
issueLines = 'No specific NOASSERTION lines captured.';
|
||||
}
|
||||
let copyleftLines = '';
|
||||
try {
|
||||
copyleftLines = fs.readFileSync('/tmp/sbom-copyleft.txt', 'utf8');
|
||||
} catch (e) {
|
||||
copyleftLines = '';
|
||||
}
|
||||
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
const branch = '${{ inputs.branch || github.ref_name }}';
|
||||
|
||||
// Check for existing open issue to avoid duplicates
|
||||
const existing = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'sbom-audit',
|
||||
state: 'open',
|
||||
});
|
||||
|
||||
if (existing.data.length > 0) {
|
||||
// Update existing issue with new findings
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: existing.data[0].number,
|
||||
body: `## Monthly audit update (${date})\n\nIssues still present:\n\n\`\`\`\n${issueLines}\n\`\`\`\n${copyleftLines ? `\n### Copyleft warnings\n\`\`\`\n${copyleftLines}\n\`\`\`` : ''}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `chore(sbom): license audit found NOASSERTION entries on ${branch} (${date})`,
|
||||
labels: ['sbom-audit'],
|
||||
assignees: ['mrpollo'],
|
||||
body: [
|
||||
`## SBOM Monthly Audit -- ${branch} -- ${date}`,
|
||||
'',
|
||||
'The automated SBOM license audit found submodules with unresolved licenses.',
|
||||
'',
|
||||
'### NOASSERTION entries',
|
||||
'',
|
||||
'```',
|
||||
issueLines,
|
||||
'```',
|
||||
'',
|
||||
copyleftLines ? `### Copyleft warnings\n\n\`\`\`\n${copyleftLines}\n\`\`\`\n` : '',
|
||||
'### How to fix',
|
||||
'',
|
||||
'1. Check the submodule repo for a LICENSE file',
|
||||
'2. Add an override to `Tools/ci/license-overrides.yaml`',
|
||||
'3. Run `python3 Tools/ci/generate_sbom.py --verify-licenses --source-dir .` to confirm',
|
||||
'',
|
||||
'### Full output',
|
||||
'',
|
||||
'<details>',
|
||||
'<summary>Click to expand</summary>',
|
||||
'',
|
||||
'```',
|
||||
fullOutput,
|
||||
'```',
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
'cc @mrpollo',
|
||||
].join('\n'),
|
||||
});
|
||||
@@ -71,7 +71,6 @@ jobs:
|
||||
- name: Build PX4
|
||||
env:
|
||||
PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
|
||||
PX4_SBOM_DISABLE: 1
|
||||
run: make px4_sitl_default
|
||||
|
||||
- name: Cache Post-Run [px4_sitl_default]
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Sync release branch to px4-ros2-interface-lib
|
||||
|
||||
on:
|
||||
create:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Release branch name (e.g. release/1.18)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
notify-interface-lib:
|
||||
if: >-
|
||||
github.repository == 'PX4/PX4-Autopilot' &&
|
||||
(
|
||||
(github.event_name == 'create' && github.ref_type == 'branch' && startsWith(github.ref_name, 'release/')) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine branch name
|
||||
id: params
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
BRANCH="${{ inputs.branch }}"
|
||||
else
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
fi
|
||||
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
echo "Dispatching for branch: $BRANCH"
|
||||
|
||||
- name: Dispatch release branch creation
|
||||
run: |
|
||||
BRANCH="${{ steps.params.outputs.branch }}"
|
||||
curl -s -f -X POST \
|
||||
-H "Authorization: token ${{ secrets.PX4BUILTBOT_PERSONAL_ACCESS_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/Auterion/px4-ros2-interface-lib/dispatches \
|
||||
-d "{\"event_type\":\"px4_release_branch\",\"client_payload\":{\"branch\":\"$BRANCH\"}}"
|
||||
echo "Dispatched px4_release_branch event for $BRANCH"
|
||||
@@ -1,135 +0,0 @@
|
||||
name: Tag px4_msgs from PX4 release tags
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'PX4 tag to propagate (example: v1.17.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tag_px4_msgs:
|
||||
if: github.repository == 'PX4/PX4-Autopilot'
|
||||
runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu22-full-x64,"run-id=${{ github.run_id }}",spot=false]
|
||||
env:
|
||||
TAG_NAME: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout PX4 repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup git credentials
|
||||
run: |
|
||||
git config --global user.name "${{ secrets.PX4BUILDBOT_USER }}"
|
||||
git config --global user.email "${{ secrets.PX4BUILDBOT_EMAIL }}"
|
||||
|
||||
- name: Resolve release branch from tag
|
||||
id: tag_info
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "${TAG_NAME}" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "Tag format is not stable vX.Y.Z, skipping: ${TAG_NAME}"
|
||||
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
major="${BASH_REMATCH[1]}"
|
||||
minor="${BASH_REMATCH[2]}"
|
||||
release_branch="release/${major}.${minor}"
|
||||
|
||||
git show-ref --verify --quiet "refs/heads/${release_branch}" || {
|
||||
echo "PX4 branch ${release_branch} not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
tag_date="$(git for-each-ref --format='%(creatordate:iso8601)' "refs/tags/${TAG_NAME}")"
|
||||
if [[ -z "${tag_date}" ]]; then
|
||||
echo "Unable to resolve tag date for ${TAG_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "release_branch=${release_branch}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_date=${tag_date}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone px4_msgs repo
|
||||
if: steps.tag_info.outputs.should_run == 'true'
|
||||
run: |
|
||||
git clone https://${{ secrets.PX4BUILTBOT_PERSONAL_ACCESS_TOKEN }}@github.com/PX4/px4_msgs.git
|
||||
|
||||
- name: Checkout matching px4_msgs release branch
|
||||
if: steps.tag_info.outputs.should_run == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd px4_msgs
|
||||
|
||||
release_branch="${{ steps.tag_info.outputs.release_branch }}"
|
||||
if git show-ref --verify --quiet "refs/remotes/origin/${release_branch}"; then
|
||||
git checkout -B "${release_branch}" "origin/${release_branch}"
|
||||
else
|
||||
echo "px4_msgs branch ${release_branch} does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify msg and srv trees are identical
|
||||
if: steps.tag_info.outputs.should_run == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
release_branch="${{ steps.tag_info.outputs.release_branch }}"
|
||||
git checkout "${release_branch}"
|
||||
|
||||
# Use the same synchronization logic as sync_to_px4_msgs.yml,
|
||||
# then verify there are no changes in px4_msgs.
|
||||
rm -f px4_msgs/msg/*.msg
|
||||
rm -f px4_msgs/msg/versioned/*.msg
|
||||
rm -f px4_msgs/srv/*.srv
|
||||
rm -f px4_msgs/srv/versioned/*.srv
|
||||
cp msg/*.msg px4_msgs/msg/
|
||||
cp msg/versioned/*.msg px4_msgs/msg/ || true
|
||||
cp srv/*.srv px4_msgs/srv/
|
||||
cp srv/versioned/*.srv px4_msgs/srv/ || true
|
||||
|
||||
if ! git -C px4_msgs diff --exit-code -- msg srv; then
|
||||
echo "Message/service definitions differ between PX4 ${release_branch} and px4_msgs ${release_branch}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create and push tag in px4_msgs
|
||||
if: steps.tag_info.outputs.should_run == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd px4_msgs
|
||||
|
||||
target="$(git rev-parse HEAD)"
|
||||
existing_target="$(git rev-parse "refs/tags/${TAG_NAME}^{}" 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "${existing_target}" ]]; then
|
||||
if [[ "${existing_target}" == "${target}" ]]; then
|
||||
echo "Tag ${TAG_NAME} already exists on ${target}; nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
echo "Tag ${TAG_NAME} already exists on ${existing_target}, expected ${target}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GIT_COMMITTER_DATE="${{ steps.tag_info.outputs.tag_date }}" \
|
||||
git tag -a "${TAG_NAME}" "${target}" \
|
||||
-m "PX4 msgs and srvs definitions matching PX4 stable release ${TAG_NAME#v}"
|
||||
|
||||
git push origin "refs/tags/${TAG_NAME}"
|
||||
+34
-2
@@ -113,5 +113,37 @@ keys/
|
||||
# metadata
|
||||
_emscripten_sdk/
|
||||
|
||||
# virtual Python environment
|
||||
.venv
|
||||
# AI coding agents -- only AGENTS.md is committed
|
||||
# Tool-specific configs, settings, and local state
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
CLAUDE.local.md
|
||||
.cursor/
|
||||
.cursorrules
|
||||
.cursorignore
|
||||
.cursorindexingignore
|
||||
.windsurf/
|
||||
.windsurfrules
|
||||
.codeiumignore
|
||||
.aider*
|
||||
.clinerules
|
||||
.clinerules-*
|
||||
.clineignore
|
||||
.continue/
|
||||
.continuerc.json
|
||||
.continueignore
|
||||
.codex/
|
||||
.amazonq/
|
||||
.augment/
|
||||
.augmentignore
|
||||
.augment-guidelines
|
||||
.cody/
|
||||
.junie/
|
||||
.tabnine_root
|
||||
.tabnineignore
|
||||
GEMINI.md
|
||||
.gemini/
|
||||
.geminiignore
|
||||
.aiexclude
|
||||
.mcp.json
|
||||
.claudeignore
|
||||
|
||||
@@ -109,9 +109,3 @@
|
||||
[submodule "src/lib/rl_tools/rl_tools"]
|
||||
path = src/lib/rl_tools/rl_tools
|
||||
url = https://github.com/rl-tools/rl-tools.git
|
||||
[submodule "libmodal-json"]
|
||||
path = boards/modalai/voxl2/src/lib/mpa/libmodal-json
|
||||
url = https://gitlab.com/voxl-public/voxl-sdk/core-libs/libmodal-json.git
|
||||
[submodule "libmodal-pipe"]
|
||||
path = boards/modalai/voxl2/src/lib/mpa/libmodal-pipe
|
||||
url = https://gitlab.com/voxl-public/voxl-sdk/core-libs/libmodal-pipe.git
|
||||
|
||||
Vendored
-5
@@ -336,11 +336,6 @@ CONFIG:
|
||||
buildType: MinSizeRel
|
||||
settings:
|
||||
CONFIG: cuav_x25-evo_default
|
||||
cuav_x25-super_default:
|
||||
short: cuav_x25-super
|
||||
buildType: MinSizeRel
|
||||
settings:
|
||||
CONFIG: cuav_x25-super_default
|
||||
cubepilot_cubeorange_test:
|
||||
short: cubepilot_cubeorange
|
||||
buildType: MinSizeRel
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# AGENTS.md -- PX4-Autopilot
|
||||
|
||||
Open-source flight controller (C/C++, NuttX/POSIX/QURT). Docs live in-tree at `docs/en/` (rendered at https://docs.px4.io).
|
||||
|
||||
## Docs (read these first)
|
||||
|
||||
- **Building & targets:** `docs/en/dev_setup/building_px4.md`
|
||||
- **Code style, conventions & commits:** `docs/en/contribute/code.md`
|
||||
- **Architecture & modules:** `docs/en/concept/architecture.md`
|
||||
- **uORB messaging:** `docs/en/middleware/uorb.md`
|
||||
- **Simulation (incl. choosing SIH vs Gazebo):** `docs/en/simulation/index.md`
|
||||
- **Testing:** `docs/en/test_and_ci/unit_tests.md`
|
||||
- **Test flights:** `docs/en/test_and_ci/test_flights.md`
|
||||
|
||||
## Build Verification
|
||||
|
||||
Always test across architectures before submitting:
|
||||
|
||||
```bash
|
||||
make px4_sitl_default # POSIX / SITL
|
||||
make px4_fmu-v6x_default # STM32H7 (Pixhawk 6X)
|
||||
make px4_fmu-v5x_default # STM32F7 (Pixhawk 5X)
|
||||
make nxp_fmuk66-v3_default # NXP K66
|
||||
```
|
||||
|
||||
## Areas That Benefit From Extra Review
|
||||
|
||||
| Directory | Controls | Before modifying |
|
||||
|-----------|----------|------------------|
|
||||
| `src/modules/commander/` | Arming, failsafe, mode transitions | Verify failsafe in SITL for all vehicle types |
|
||||
| `src/modules/ekf2/` | State estimation | Run EKF replay tests |
|
||||
| `src/modules/mc_*_control/` | Multicopter controllers | Test in SIH; verify all MC airframes |
|
||||
| `src/modules/fw_*/` | Fixed-wing controllers | Test FW airframes in SITL |
|
||||
| `src/modules/vtol_att_control/` | VTOL transitions | Test MC + FW modes and transitions |
|
||||
| `msg/*.msg` | uORB schemas | Check all publishers/subscribers |
|
||||
| `ROMFS/px4fmu_common/init.d/` | Startup & default params | Can change behavior on all boards |
|
||||
|
||||
## Rules
|
||||
|
||||
- Never bypass safety checks (arming, geofence, failsafe) without justification
|
||||
- Document parameter changes, they affect flight behavior
|
||||
- Specify units in comments for physical quantities
|
||||
- No magic numbers, use named `constexpr` constants
|
||||
|
||||
## Agent Decision Framework
|
||||
|
||||
**Do without asking:** write or update the docs, ensure successful build, ensure CI checks are successful, run `make format`, add tests for new features or fixes.
|
||||
|
||||
**Ask first:** parameter defaults, control algorithms, failsafe logic, uORB schema changes, board configs.
|
||||
|
||||
**Stop, do not proceed:** if you can't verify flight safety, if modifying EKF2/controller math without SITL/SIH, if removing safety guards.
|
||||
@@ -1,27 +0,0 @@
|
||||
cff-version: 1.2.0
|
||||
title: "PX4 Autopilot"
|
||||
message: "If you use PX4 in your research, please cite it using this metadata."
|
||||
type: software
|
||||
authors:
|
||||
- family-names: Meier
|
||||
given-names: Lorenz
|
||||
- name: "The PX4 Contributors"
|
||||
repository-code: "https://github.com/PX4/PX4-Autopilot"
|
||||
url: "https://px4.io"
|
||||
abstract: >-
|
||||
PX4 is an open-source autopilot stack for drones and
|
||||
unmanned vehicles. It supports multirotors, fixed-wing,
|
||||
VTOL, rovers, and many more platforms. PX4 runs on both
|
||||
RTOS and POSIX-compatible operating systems.
|
||||
keywords:
|
||||
- autopilot
|
||||
- drone
|
||||
- uav
|
||||
- flight-controller
|
||||
- robotics
|
||||
- ros2
|
||||
license: BSD-3-Clause
|
||||
identifiers:
|
||||
- type: doi
|
||||
value: "10.5281/zenodo.595432"
|
||||
description: "Zenodo concept DOI (resolves to latest version)"
|
||||
+1
-9
@@ -240,15 +240,8 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE ${PX4_BUILD_TYPE} CACHE STRING "Build type" FORCE)
|
||||
endif()
|
||||
|
||||
if(CONFIG_BOARD_SUPPORT_FORTIFIED_TOOLCHAIN)
|
||||
set(PX4_DEBUG_OPT_LEVEL -Og)
|
||||
message(STATUS "fortified toolchain support enabled: PX4_DEBUG_OPT_LEVEL=${PX4_DEBUG_OPT_LEVEL}")
|
||||
else()
|
||||
set(PX4_DEBUG_OPT_LEVEL -O0)
|
||||
endif()
|
||||
|
||||
if((CMAKE_BUILD_TYPE STREQUAL "Debug") OR (CMAKE_BUILD_TYPE STREQUAL "Coverage"))
|
||||
set(MAX_CUSTOM_OPT_LEVEL ${PX4_DEBUG_OPT_LEVEL})
|
||||
set(MAX_CUSTOM_OPT_LEVEL -O0)
|
||||
elseif(CMAKE_BUILD_TYPE MATCHES "Sanitizer")
|
||||
set(MAX_CUSTOM_OPT_LEVEL -O1)
|
||||
elseif(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
@@ -491,7 +484,6 @@ include(bloaty)
|
||||
|
||||
include(metadata)
|
||||
include(package)
|
||||
include(sbom)
|
||||
|
||||
# install python requirements using configured python
|
||||
add_custom_target(install_python_requirements
|
||||
|
||||
+20
-57
@@ -2,82 +2,45 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
## Our Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at coc@dronecode.org. All complaints will be reviewed and investigated promptly and fairly.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at lorenz@px4.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
+21
-147
@@ -1,170 +1,44 @@
|
||||
# Contributing to PX4-Autopilot
|
||||
# Contributing to PX4 Firmware
|
||||
|
||||
We follow the [GitHub flow](https://guides.github.com/introduction/flow/) development model.
|
||||
We follow the [Github flow](https://guides.github.com/introduction/flow/) development model.
|
||||
|
||||
## Fork the project, then clone your repo
|
||||
### Fork the project, then clone your repo
|
||||
|
||||
First [fork and clone](https://help.github.com/articles/fork-a-repo) the project.
|
||||
First [fork and clone](https://help.github.com/articles/fork-a-repo) the project project.
|
||||
|
||||
## Create a feature branch
|
||||
### Create a feature branch
|
||||
|
||||
Always branch off `main` for new features.
|
||||
*Always* branch off main for new features.
|
||||
|
||||
```
|
||||
git checkout -b mydescriptivebranchname
|
||||
```
|
||||
|
||||
## Edit and build the code
|
||||
### Edit and build the code
|
||||
|
||||
The [developer guide](https://docs.px4.io/main/en/development/development.html) explains how to set up the development environment on Mac OS, Linux or Windows.
|
||||
The [developer guide](https://docs.px4.io/main/en/development/development.html) explains how to set up the development environment on Mac OS, Linux or Windows. Please take note of our [coding style](https://docs.px4.io/main/en/contribute/code.html) when editing files.
|
||||
|
||||
### Coding standards
|
||||
### Commit your changes
|
||||
|
||||
All C/C++ code must follow the [PX4 coding style](https://docs.px4.io/main/en/contribute/code.html). Formatting is enforced by [astyle](http://astyle.sourceforge.net/) in CI (`make check_format`). Code quality checks run via [clang-tidy](https://clang.llvm.org/extra/clang-tidy/). Pull requests that fail either check will not be merged.
|
||||
Always write descriptive commit messages and add a fixes or relates note to them with an [issue number](https://github.com/px4/Firmware/issues) (Github will link these then conveniently)
|
||||
|
||||
Python code is checked with [mypy](https://mypy-lang.org/) and [flake8](https://flake8.pycqa.org/).
|
||||
|
||||
## Commit message convention
|
||||
|
||||
PX4 uses [conventional commits](https://www.conventionalcommits.org/) for all commit messages and PR titles.
|
||||
|
||||
### Format
|
||||
**Example:**
|
||||
|
||||
```
|
||||
type(scope): short description of the change
|
||||
Change how the attitude controller works
|
||||
|
||||
- Fixes rate feed forward
|
||||
- Allows a local body rate override
|
||||
|
||||
Fixes issue #123
|
||||
```
|
||||
|
||||
| Part | Rule |
|
||||
|------|------|
|
||||
| **type** | Category of change (see types table below) |
|
||||
| **scope** | The module, driver, board, or area of PX4 affected |
|
||||
| **`!`** (optional) | Append before `:` to mark a breaking change |
|
||||
| **description** | What the change does, at least 5 characters, written in imperative form |
|
||||
### Test your changes
|
||||
|
||||
### Types
|
||||
Since we care about safety, we will regularly ask you for test results. Best is to do a test flight (or bench test where it applies) and upload the logfile from it (on the microSD card in the logs directory) to Google Drive or Dropbox and share the link.
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `feat` | A new feature |
|
||||
| `fix` | A bug fix |
|
||||
| `docs` | Documentation only changes |
|
||||
| `style` | Formatting, whitespace, no code change |
|
||||
| `refactor` | Code change that neither fixes a bug nor adds a feature |
|
||||
| `perf` | Performance improvement |
|
||||
| `test` | Adding or correcting tests |
|
||||
| `build` | Build system or external dependencies |
|
||||
| `ci` | CI configuration files and scripts |
|
||||
| `chore` | Other changes that don't modify src or test files |
|
||||
| `revert` | Reverts a previous commit |
|
||||
### Push your changes
|
||||
|
||||
### Scopes
|
||||
|
||||
The scope identifies which part of PX4 is affected. Common scopes:
|
||||
|
||||
| Scope | Area |
|
||||
|-------|------|
|
||||
| `ekf2` | Extended Kalman Filter (state estimation) |
|
||||
| `mavlink` | MAVLink messaging protocol |
|
||||
| `commander` | Commander and mode management |
|
||||
| `navigator` | Mission, RTL, Land, and other navigation modes |
|
||||
| `sensors` | Sensor drivers and processing |
|
||||
| `drivers` | Hardware drivers |
|
||||
| `boards/px4_fmu-v6x` | Board-specific changes (use the board name) |
|
||||
| `mc_att_control` | Multicopter attitude control |
|
||||
| `mc_pos_control` | Multicopter position control |
|
||||
| `fw_att_control` | Fixed-wing attitude control |
|
||||
| `vtol` | VTOL-specific logic |
|
||||
| `actuators` | Mixer and actuator output |
|
||||
| `battery` | Battery monitoring and estimation |
|
||||
| `logger` | On-board logging |
|
||||
| `param` | Parameter system |
|
||||
| `simulation` | SITL, Gazebo, SIH |
|
||||
| `ci` | Continuous integration and workflows |
|
||||
| `docs` | Documentation |
|
||||
| `build` | CMake, toolchain, build system |
|
||||
| `uorb` | Inter-module messaging |
|
||||
|
||||
For changes spanning multiple subsystems, use the primary one affected. Look at the directory path of the files you changed to find the right scope: `src/modules/ekf2/` uses `ekf2`, `src/drivers/imu/` uses `drivers/imu`, `.github/workflows/` uses `ci`.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
Append `!` before the colon to indicate a breaking change:
|
||||
|
||||
```
|
||||
feat(ekf2)!: remove deprecated height fusion API
|
||||
```
|
||||
|
||||
### Good commit messages
|
||||
|
||||
```
|
||||
feat(ekf2): add height fusion timeout
|
||||
fix(mavlink): correct BATTERY_STATUS_V2 parsing
|
||||
refactor(navigator): simplify RTL altitude logic
|
||||
ci(workflows): migrate to reusable workflows
|
||||
docs(ekf2): update tuning guide
|
||||
feat(boards/px4_fmu-v6x)!: remove deprecated driver API
|
||||
perf(mc_rate_control): reduce loop latency
|
||||
```
|
||||
|
||||
### Commits to avoid
|
||||
|
||||
These will be flagged by CI and should be squashed or reworded before merging:
|
||||
|
||||
```
|
||||
fix # too vague, no type or scope
|
||||
update # too vague, no type or scope
|
||||
ekf2: fix something # missing type prefix
|
||||
apply suggestions from code review # squash into parent commit
|
||||
do make format # squash into parent commit
|
||||
WIP: trying something # not ready for main
|
||||
oops # not descriptive
|
||||
```
|
||||
|
||||
### PR titles
|
||||
|
||||
The PR title follows the same `type(scope): description` format. This is enforced by CI and is especially important because the PR title becomes the commit message when a PR is squash-merged.
|
||||
|
||||
### Merge policy
|
||||
|
||||
Commits should be atomic and independently revertable. Squash at reviewer discretion for obvious cases (multiple WIP commits, messy review-response history). When your commits are clean and logical, they will be preserved as individual commits on `main`.
|
||||
|
||||
### Cleaning up commits
|
||||
|
||||
If CI flags your commit messages, you can fix them with an interactive rebase:
|
||||
|
||||
```bash
|
||||
# Squash all commits into one:
|
||||
git rebase -i HEAD~N # replace N with the number of commits
|
||||
# mark all commits except the first as 'squash' or 'fixup'
|
||||
# reword the remaining commit to follow the format
|
||||
git push --force-with-lease
|
||||
|
||||
# Or reword specific commits:
|
||||
git rebase -i HEAD~N
|
||||
# mark the bad commits as 'reword'
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
## Test your changes
|
||||
|
||||
PX4 is safety-critical software. All contributions must include adequate testing where practical:
|
||||
|
||||
- **New features** must include unit tests and/or integration tests that exercise the new functionality, where practical. Hardware-dependent changes that cannot be tested in SITL should include bench test or flight test evidence.
|
||||
- **Bug fixes** must include a regression test where practical. When automated testing is not feasible (hardware-specific issues, race conditions, etc.), provide a link to a flight log demonstrating the fix and the reproduction steps for the original bug.
|
||||
- **Reviewers** will verify that tests or test evidence exist before approving a pull request.
|
||||
|
||||
### Types of tests
|
||||
|
||||
| Test type | When to use | How to run |
|
||||
|-----------|-------------|------------|
|
||||
| **Unit tests** (gtest) | Module-level logic, math, parsing | `make tests` |
|
||||
| **SITL integration tests** (MAVSDK) | Flight behavior, failsafes, missions | `test/mavsdk_tests/` |
|
||||
| **Bench tests / flight logs** | Hardware-dependent changes | Upload logs to [Flight Review](https://logs.px4.io) |
|
||||
|
||||
Since we care about safety, we will regularly ask you for test results. Best is to do a test flight (or bench test where it applies) and upload the log file from it (on the microSD card in the logs directory) to Google Drive or Dropbox and share the link.
|
||||
|
||||
## Push your changes
|
||||
|
||||
Push changes to your repo and send a [pull request](https://github.com/PX4/PX4-Autopilot/compare/).
|
||||
Push changes to your repo and send a [pull request](https://github.com/PX4/Firmware/compare/).
|
||||
|
||||
Make sure to provide some testing feedback and if possible the link to a flight log file. Upload flight log files to [Flight Review](http://logs.px4.io) and link the resulting report.
|
||||
|
||||
@@ -67,16 +67,6 @@ menu "Toolchain"
|
||||
help
|
||||
Enables Cmake Release for -O3 optimization
|
||||
|
||||
config BOARD_SUPPORT_FORTIFIED_TOOLCHAIN
|
||||
bool "Fortified toolchain support"
|
||||
default n
|
||||
help
|
||||
Enable compatibility with toolchains that define
|
||||
_FORTIFY_SOURCE.
|
||||
|
||||
This switches PX4_DEBUG_OPT_LEVEL from -O0 to -Og. Keep this
|
||||
disabled unless the fortified toolchain requires optimization.
|
||||
|
||||
config BOARD_ROMFSROOT
|
||||
string "ROMFSROOT"
|
||||
default "px4fmu_common"
|
||||
@@ -220,10 +210,6 @@ menu "examples"
|
||||
source "src/examples/Kconfig"
|
||||
endmenu
|
||||
|
||||
menu "templates"
|
||||
source "src/templates/Kconfig"
|
||||
endmenu
|
||||
|
||||
menu "platforms"
|
||||
depends on PLATFORM_QURT || PLATFORM_POSIX || PLATFORM_NUTTX
|
||||
source "platforms/Kconfig"
|
||||
|
||||
+2
-13
@@ -1,11 +1,9 @@
|
||||
Maintainers
|
||||
===========
|
||||
|
||||
PX4 is maintained by a group of contributors trusted to steward the project. All maintainers listed below are members of the @PX4/dev-team, have write access, and participate in maintainer decisions. We recognize two types: **Code Owners**, responsible for specific components, and **Reviewers**, who help across the project without a fixed component.
|
||||
See [the documentation on Maintainers](https://docs.px4.io/main/en/contribute/maintainers.html) to learn about the role of the maintainers and the process to become one.
|
||||
|
||||
See [the documentation on Maintainers](https://docs.px4.io/main/en/contribute/maintainers) to learn about the role of the maintainers and the process to become one.
|
||||
|
||||
**Code Owners**
|
||||
**Active Maintainers**
|
||||
|
||||
| Name | Sector | GitHub | Chat | email
|
||||
|-------------------------|--------|--------|------|----------------
|
||||
@@ -25,15 +23,6 @@ See [the documentation on Maintainers](https://docs.px4.io/main/en/contribute/ma
|
||||
| Jacob Dahl | Simulation | [@dakejahl](https://github.com/dakejahl) | dakejahl | <dahl.jakejacob@gmail.com>
|
||||
|
||||
|
||||
**Reviewers**
|
||||
|
||||
Reviewers help maintain PX4 across the project without ownership of a specific component.
|
||||
|
||||
| Name | GitHub | Chat | email
|
||||
|------|--------|------|----------------------
|
||||
| _No reviewers yet._ | | |
|
||||
|
||||
|
||||
**Documentation Maintainers**
|
||||
|
||||
| Name | GitHub | Chat | email
|
||||
|
||||
@@ -162,12 +162,6 @@ else
|
||||
|
||||
endif
|
||||
|
||||
# Prefer the interpreter from an active Python virtual environment.
|
||||
# Otherwise leave PYTHON_EXECUTABLE unset and let CMake resolve Python.
|
||||
ifneq ($(strip $(VIRTUAL_ENV)),)
|
||||
PYTHON_EXECUTABLE ?= $(VIRTUAL_ENV)/bin/python
|
||||
endif
|
||||
|
||||
# Pick up specific Python path if set
|
||||
ifdef PYTHON_EXECUTABLE
|
||||
override CMAKE_ARGS += -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
|
||||
@@ -232,22 +226,9 @@ CONFIG_TARGETS_DEFAULT := $(patsubst %_default,%,$(filter %_default,$(ALL_CONFIG
|
||||
$(CONFIG_TARGETS_DEFAULT):
|
||||
@$(call cmake-build,$@_default$(BUILD_DIR_SUFFIX))
|
||||
|
||||
# Multi-processor boards: build all processor targets together
|
||||
# VOXL2 apps processor (default) depends on SLPI DSP being built first
|
||||
modalai_voxl2_default: modalai_voxl2_slpi
|
||||
modalai_voxl2: modalai_voxl2_slpi
|
||||
modalai_voxl2_deb: modalai_voxl2_slpi
|
||||
|
||||
all_config_targets: $(ALL_CONFIG_TARGETS)
|
||||
all_default_targets: $(CONFIG_TARGETS_DEFAULT)
|
||||
|
||||
# DEB package targets: builds _default config, then runs cpack.
|
||||
# Multi-processor boards (e.g. VOXL2) chain companion builds automatically
|
||||
# via existing cmake prerequisites.
|
||||
%_deb:
|
||||
@$(call cmake-build,$(subst _deb,_default,$@)$(BUILD_DIR_SUFFIX))
|
||||
@cd "$(SRC_DIR)/build/$(subst _deb,_default,$@)" && cpack -G DEB
|
||||
|
||||
updateconfig:
|
||||
@./Tools/kconfig/updateconfig.py
|
||||
|
||||
@@ -351,7 +332,6 @@ bootloaders_update: \
|
||||
cuav_7-nano_bootloader \
|
||||
cuav_fmu-v6x_bootloader \
|
||||
cuav_x25-evo_bootloader \
|
||||
cuav_x25-super_bootloader \
|
||||
cubepilot_cubeorange_bootloader \
|
||||
cubepilot_cubeorangeplus_bootloader \
|
||||
hkust_nxt-dual_bootloader \
|
||||
@@ -554,8 +534,7 @@ validate_module_configs:
|
||||
-not -path "$(SRC_DIR)/src/modules/zenoh/zenoh-pico/*" \
|
||||
-not -path "$(SRC_DIR)/src/lib/events/libevents/*" \
|
||||
-not -path "$(SRC_DIR)/src/lib/cdrstream/*" \
|
||||
-not -path "$(SRC_DIR)/src/lib/crypto/libtommath/*" \
|
||||
-not -path "$(SRC_DIR)/src/lib/tensorflow_lite_micro/*" -print0 | \
|
||||
-not -path "$(SRC_DIR)/src/lib/crypto/libtommath/*" -print0 | \
|
||||
xargs -0 "$(SRC_DIR)"/Tools/validate_yaml.py --schema-file "$(SRC_DIR)"/validation/module_schema.yaml
|
||||
|
||||
# Cleanup
|
||||
|
||||
@@ -9,16 +9,10 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/PX4/PX4-Autopilot/releases"><img src="https://img.shields.io/github/release/PX4/PX4-Autopilot.svg" alt="Release"></a>
|
||||
<a href="https://github.com/PX4/PX4-Autopilot/releases"><img src="https://img.shields.io/github/release/PX4/PX4-Autopilot.svg" alt="Releases"></a>
|
||||
<a href="https://zenodo.org/badge/latestdoi/22634/PX4/PX4-Autopilot"><img src="https://zenodo.org/badge/22634/PX4/PX4-Autopilot.svg" alt="DOI"></a>
|
||||
<a href="https://discord.gg/dronecode"><img src="https://img.shields.io/discord/1022170275984457759?label=discord&logo=discord&logoColor=white&color=5865F2" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.bestpractices.dev/projects/6520"><img src="https://www.bestpractices.dev/projects/6520/badge" alt="OpenSSF Best Practices"></a>
|
||||
<a href="https://insights.linuxfoundation.org/project/px4"><img src="https://insights.linuxfoundation.org/api/badge/health-score?project=px4" alt="LFX Health Score"></a>
|
||||
<a href="https://insights.linuxfoundation.org/project/px4"><img src="https://insights.linuxfoundation.org/api/badge/contributors?project=px4" alt="LFX Contributors"></a>
|
||||
<a href="https://insights.linuxfoundation.org/project/px4"><img src="https://insights.linuxfoundation.org/api/badge/active-contributors?project=px4" alt="LFX Active Contributors"></a>
|
||||
<a href="https://github.com/PX4/PX4-Autopilot/actions/workflows/build_all_targets.yml"><img src="https://github.com/PX4/PX4-Autopilot/actions/workflows/build_all_targets.yml/badge.svg?branch=main" alt="Build Targets"></a>
|
||||
<a href="https://discord.gg/dronecode"><img src="https://discordapp.com/api/guilds/1022170275984457759/widget.png?style=shield" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -104,22 +98,6 @@ make px4_sitl
|
||||
|
||||
We welcome contributions of all kinds — bug reports, documentation, new features, and code reviews. Please read the [Contribution Guide](https://docs.px4.io/main/en/contribute/) to get started.
|
||||
|
||||
## Citation
|
||||
|
||||
If you use PX4 in academic work, please cite it. BibTeX:
|
||||
|
||||
```bibtex
|
||||
@software{px4_autopilot,
|
||||
author = {Meier, Lorenz and {The PX4 Contributors}},
|
||||
title = {{PX4 Autopilot}},
|
||||
publisher = {Zenodo},
|
||||
doi = {10.5281/zenodo.595432},
|
||||
url = {https://px4.io}
|
||||
}
|
||||
```
|
||||
|
||||
The DOI above is a Zenodo concept DOI that always resolves to the latest release. For a version-pinned citation, see the [Zenodo record](https://doi.org/10.5281/zenodo.595432) or our [`CITATION.cff`](CITATION.cff).
|
||||
|
||||
## Governance
|
||||
|
||||
The PX4 Autopilot project is hosted by the [Dronecode Foundation](https://www.dronecode.org/), a [Linux Foundation](https://www.linuxfoundation.org/) Collaborative Project. Dronecode holds all PX4 trademarks and serves as the project's legal guardian, ensuring vendor-neutral stewardship — no single company owns the name or controls the roadmap. The source code is licensed under the [BSD 3-Clause](LICENSE) license, so you are free to use, modify, and distribute it in your own projects.
|
||||
|
||||
@@ -34,7 +34,6 @@ param set-default PWM_MAIN_FUNC2 102
|
||||
param set-default PWM_MAIN_FUNC3 103
|
||||
param set-default PWM_MAIN_FUNC4 104
|
||||
|
||||
param set-default SENS_GPS0_DELAY 0
|
||||
param set-default SENS_GPS1_DELAY 0
|
||||
param set-default EKF2_GPS_DELAY 0
|
||||
|
||||
param set SIH_VEHICLE_TYPE 0
|
||||
|
||||
@@ -44,8 +44,7 @@ param set-default PWM_MAIN_FUNC2 202
|
||||
param set-default PWM_MAIN_FUNC3 203
|
||||
param set-default PWM_MAIN_FUNC4 101
|
||||
|
||||
param set-default SENS_GPS0_DELAY 0
|
||||
param set-default SENS_GPS1_DELAY 0
|
||||
param set-default EKF2_GPS_DELAY 0
|
||||
|
||||
# Rate controllers
|
||||
param set-default FW_RR_P 0.0500
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
PX4_SIMULATOR=${PX4_SIMULATOR:=sihsim}
|
||||
PX4_SIM_MODEL=${PX4_SIM_MODEL:=xvert}
|
||||
|
||||
param set-default SENS_GPS0_DELAY 0
|
||||
param set-default SENS_GPS1_DELAY 0
|
||||
param set-default EKF2_GPS_DELAY 0
|
||||
param set-default EKF2_FUSE_BETA 0 # side slip fusion is currently not supported for tailsitters
|
||||
|
||||
param set-default SENS_EN_GPSSIM 1
|
||||
|
||||
@@ -27,8 +27,7 @@ param set-default SENS_EN_BAROSIM 1
|
||||
param set-default SENS_EN_MAGSIM 1
|
||||
param set-default SENS_EN_ARSPDSIM 1
|
||||
|
||||
param set-default SENS_GPS0_DELAY 0
|
||||
param set-default SENS_GPS1_DELAY 0
|
||||
param set-default EKF2_GPS_DELAY 0
|
||||
|
||||
param set-default VT_TYPE 2
|
||||
param set-default MPC_MAN_Y_MAX 60
|
||||
|
||||
@@ -18,7 +18,6 @@ param set-default SENS_EN_BAROSIM 1
|
||||
param set-default SENS_EN_MAGSIM 1
|
||||
|
||||
param set SIH_VEHICLE_TYPE 4
|
||||
param set-default MAV_TYPE 13
|
||||
|
||||
# Symmetric hexacopter X clockwise motor numbering
|
||||
param set-default CA_ROTOR_COUNT 6
|
||||
@@ -45,5 +44,4 @@ param set-default PWM_MAIN_FUNC4 104
|
||||
param set-default PWM_MAIN_FUNC5 105
|
||||
param set-default PWM_MAIN_FUNC6 106
|
||||
|
||||
param set-default SENS_GPS0_DELAY 0
|
||||
param set-default SENS_GPS1_DELAY 0
|
||||
param set-default EKF2_GPS_DELAY 0
|
||||
|
||||
@@ -44,6 +44,8 @@ param set-default FW_T_SINK_MIN 3
|
||||
|
||||
param set-default FW_W_EN 1
|
||||
|
||||
param set-default FD_ESCS_EN 0
|
||||
|
||||
param set-default MIS_TAKEOFF_ALT 30
|
||||
|
||||
param set-default NAV_ACC_RAD 15
|
||||
|
||||
@@ -104,3 +104,4 @@ param set-default VT_FWD_THRUST_EN 4
|
||||
param set-default VT_PITCH_MIN -5
|
||||
param set-default VT_F_TRANS_THR 1
|
||||
param set-default VT_TYPE 2
|
||||
param set-default FD_ESCS_EN 0
|
||||
|
||||
@@ -20,8 +20,8 @@ param set-default COM_DISARM_LAND 0.5
|
||||
# EKF2 parameters
|
||||
param set-default EKF2_DRAG_CTRL 1
|
||||
param set-default EKF2_IMU_POS_X 0.02
|
||||
param set-default SENS_GPS0_OFFX 0.055
|
||||
param set-default SENS_GPS0_OFFZ -0.15
|
||||
param set-default EKF2_GPS_POS_X 0.055
|
||||
param set-default EKF2_GPS_POS_Z -0.15
|
||||
param set-default EKF2_MIN_RNG 0.03
|
||||
param set-default EKF2_OF_CTRL 1
|
||||
param set-default EKF2_OF_POS_X 0.055
|
||||
|
||||
@@ -26,6 +26,7 @@ param set-default SENS_EN_GPSSIM 1
|
||||
param set-default SENS_EN_BAROSIM 1
|
||||
param set-default SENS_EN_MAGSIM 1
|
||||
param set-default COM_ARM_CHK_ESCS 0 # We don't have ESCs
|
||||
param set-default FD_ESCS_EN 0 # We don't have ESCs - but maybe we need this later?
|
||||
|
||||
# Set proper failsafes
|
||||
param set-default COM_ACT_FAIL_ACT 0
|
||||
@@ -41,21 +42,23 @@ param set-default FD_FAIL_R 0
|
||||
param set-default CA_ROTOR_COUNT 8
|
||||
param set-default CA_R_REV 255
|
||||
|
||||
param set-default CA_ROTOR0_AX 1
|
||||
param set-default CA_ROTOR0_AY -1
|
||||
param set-default CA_ROTOR0_AX -1
|
||||
param set-default CA_ROTOR0_AY 1
|
||||
param set-default CA_ROTOR0_AZ 0
|
||||
param set-default CA_ROTOR0_KM 0
|
||||
param set-default CA_ROTOR0_PX 0.14
|
||||
param set-default CA_ROTOR0_PY 0.10
|
||||
param set-default CA_ROTOR0_PZ 0.06
|
||||
#param set-default CA_ROTOR0_PZ 0.0
|
||||
|
||||
param set-default CA_ROTOR1_AX 1
|
||||
param set-default CA_ROTOR1_AY 1
|
||||
param set-default CA_ROTOR1_AX -1
|
||||
param set-default CA_ROTOR1_AY -1
|
||||
param set-default CA_ROTOR1_AZ 0
|
||||
param set-default CA_ROTOR1_KM 0
|
||||
param set-default CA_ROTOR1_PX 0.14
|
||||
param set-default CA_ROTOR1_PY -0.10
|
||||
param set-default CA_ROTOR1_PZ 0.06
|
||||
#param set-default CA_ROTOR1_PZ 0.0
|
||||
|
||||
param set-default CA_ROTOR2_AX 1
|
||||
param set-default CA_ROTOR2_AY 1
|
||||
@@ -64,6 +67,7 @@ param set-default CA_ROTOR2_KM 0
|
||||
param set-default CA_ROTOR2_PX -0.14
|
||||
param set-default CA_ROTOR2_PY 0.10
|
||||
param set-default CA_ROTOR2_PZ 0.06
|
||||
#param set-default CA_ROTOR2_PZ 0.0
|
||||
|
||||
param set-default CA_ROTOR3_AX 1
|
||||
param set-default CA_ROTOR3_AY -1
|
||||
@@ -75,7 +79,7 @@ param set-default CA_ROTOR3_PZ 0.06
|
||||
|
||||
param set-default CA_ROTOR4_AX 0
|
||||
param set-default CA_ROTOR4_AY 0
|
||||
param set-default CA_ROTOR4_AZ -1
|
||||
param set-default CA_ROTOR4_AZ 1
|
||||
param set-default CA_ROTOR4_KM 0
|
||||
param set-default CA_ROTOR4_PX 0.12
|
||||
param set-default CA_ROTOR4_PY 0.22
|
||||
@@ -99,7 +103,7 @@ param set-default CA_ROTOR6_PZ 0
|
||||
|
||||
param set-default CA_ROTOR7_AX 0
|
||||
param set-default CA_ROTOR7_AY 0
|
||||
param set-default CA_ROTOR7_AZ -1
|
||||
param set-default CA_ROTOR7_AZ 1
|
||||
param set-default CA_ROTOR7_KM 0
|
||||
param set-default CA_ROTOR7_PX -0.12
|
||||
param set-default CA_ROTOR7_PY -0.22
|
||||
|
||||
@@ -28,6 +28,7 @@ param set-default SIM_GZ_EN 1
|
||||
|
||||
param set-default SENS_EN_MAGSIM 1
|
||||
param set-default COM_ARM_CHK_ESCS 0 # We don't have ESCs
|
||||
param set-default FD_ESCS_EN 0
|
||||
|
||||
param set-default CA_AIRFRAME 14
|
||||
param set-default MAV_TYPE 45
|
||||
|
||||
@@ -28,6 +28,7 @@ param set-default SIM_GZ_EN 1
|
||||
|
||||
param set-default SENS_EN_MAGSIM 1
|
||||
param set-default COM_ARM_CHK_ESCS 0 # We don't have ESCs
|
||||
param set-default FD_ESCS_EN 0
|
||||
|
||||
param set-default CA_AIRFRAME 14
|
||||
param set-default MAV_TYPE 45
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
# shellcheck disable=SC2154
|
||||
|
||||
# EKF2 specifics
|
||||
param set-default SENS_GPS0_DELAY 10
|
||||
param set-default SENS_GPS1_DELAY 10
|
||||
param set-default EKF2_GPS_DELAY 10
|
||||
param set-default EKF2_MULTI_IMU 3
|
||||
param set-default SENS_IMU_MODE 0
|
||||
|
||||
|
||||
@@ -119,11 +119,10 @@ else
|
||||
param set SYS_AUTOCONFIG 1
|
||||
fi
|
||||
|
||||
# To trigger a parameter reset during boot SYS_AUTOCONFIG was set to 1 before
|
||||
if param greater SYS_AUTOCONFIG 0
|
||||
if param compare SYS_AUTOCONFIG 1
|
||||
then
|
||||
# Reset parameters except airframe, parameter version, sensor calibration, total flight time, flight UUID
|
||||
param reset_all SYS_AUTOSTART SYS_PARAM_VER CAL_* LND_FLIGHT* TC_* COM_FLIGHT*
|
||||
# Reset params except Airframe, RC calibration, sensor calibration, flight modes, total flight time, and next flight UUID.
|
||||
param reset_all SYS_AUTOSTART RC* CAL_* COM_FLTMODE* LND_FLIGHT* TC_* COM_FLIGHT*
|
||||
set AUTOCNF yes
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
param set UAVCAN_ENABLE 0
|
||||
|
||||
param set-default CA_AIRFRAME 1
|
||||
param set-default CA_ROTOR_COUNT 1
|
||||
param set-default CA_ROTOR0_PX 0.3
|
||||
|
||||
@@ -39,6 +38,7 @@ param set-default SYS_HITL 2
|
||||
# - without real battery
|
||||
param set-default CBRK_SUPPLY_CHK 894281
|
||||
|
||||
param set SIH_T_MAX 6
|
||||
param set SIH_MASS 0.3
|
||||
param set SIH_IXX 0.00402
|
||||
param set SIH_IYY 0.0144
|
||||
@@ -48,21 +48,3 @@ param set SIH_KDV 0.2
|
||||
|
||||
param set SIH_VEHICLE_TYPE 1 # sih as fixed wing
|
||||
param set RWTO_TKOFF 1 # enable takeoff from runway (as opposed to launched)
|
||||
|
||||
# pusher propeller model with advance ratio, model from UIUC APC 8x6"
|
||||
param set SIH_F_T_MAX 6
|
||||
param set SIH_F_Q_MAX 0.03
|
||||
# if SIH_F_CT0 > 0, SIH_F_T_MAX and SIH_F_Q_MAX will be overridden
|
||||
param set SIH_F_CT0 0.131
|
||||
param set SIH_F_CT1 0.004
|
||||
param set SIH_F_CT2 -0.146
|
||||
param set SIH_F_CP0 0.0777
|
||||
param set SIH_F_CP1 0.0498
|
||||
param set SIH_F_CP2 -0.11
|
||||
param set SIH_F_DIA_INCH 8
|
||||
param set SIH_F_RPM_MAX 9000
|
||||
|
||||
param set-default FW_AIRSPD_MIN 7
|
||||
param set-default FW_AIRSPD_TRIM 10
|
||||
param set-default FW_AIRSPD_MAX 12
|
||||
param set-default FW_PSP_OFF 0.5
|
||||
|
||||
@@ -28,7 +28,6 @@ param set-default VT_FW_DIFTHR_EN 1
|
||||
param set-default VT_FW_DIFTHR_S_Y 0.3
|
||||
param set-default MPC_MAN_Y_MAX 60
|
||||
param set-default MC_PITCH_P 5
|
||||
param set-default FW_PSP_OFF 5
|
||||
|
||||
param set-default CA_AIRFRAME 4
|
||||
param set-default CA_ROTOR_COUNT 2
|
||||
@@ -57,6 +56,7 @@ param set-default HIL_ACT_REV 32
|
||||
param set-default MAV_TYPE 19
|
||||
|
||||
|
||||
|
||||
# set SYS_HITL to 2 to start the SIH and avoid sensors startup
|
||||
param set-default SYS_HITL 2
|
||||
|
||||
@@ -66,9 +66,8 @@ param set-default CBRK_SUPPLY_CHK 894281
|
||||
|
||||
param set-default SENS_DPRES_OFF 0.001
|
||||
|
||||
# tailsitter is equipped with two forward propellers
|
||||
param set SIH_F_T_MAX 2
|
||||
param set SIH_F_Q_MAX 0.0165
|
||||
param set SIH_T_MAX 2.0
|
||||
param set SIH_Q_MAX 0.0165
|
||||
param set SIH_MASS 0.2
|
||||
# IXX and IZZ are inverted from the thesis as the body frame is pitched by 90 deg
|
||||
param set SIH_IXX 0.00354
|
||||
@@ -78,19 +77,6 @@ param set SIH_IXZ 0
|
||||
param set SIH_KDV 0.2
|
||||
param set SIH_L_ROLL 0.145
|
||||
|
||||
# propeller diameter, rpm, and coeffs coming from the thesis
|
||||
# Modeling and control of a flying wing tailsitter unmanned aerial vehicle."
|
||||
# Chiappinelli, Romain, supervised by Nahon, Meyer, McGill University, Masters thesis, 2018.
|
||||
# if SIH_F_CT0 > 0, SIH_F_T_MAX and SIH_F_Q_MAX will be overridden
|
||||
param set SIH_F_CT0 0.1342
|
||||
param set SIH_F_CT1 -0.1196
|
||||
param set SIH_F_CT2 -0.1281
|
||||
param set SIH_F_CP0 0.0522
|
||||
param set SIH_F_CP1 -0.0146
|
||||
param set SIH_F_CP2 -0.0602
|
||||
param set SIH_F_DIA_INCH 5
|
||||
param set SIH_F_RPM_MAX 14000
|
||||
|
||||
# sih as tailsitter
|
||||
param set SIH_VEHICLE_TYPE 2
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ param set-default CA_SV_CS2_TYPE 4 # rudder
|
||||
param set-default FW_AIRSPD_MIN 7
|
||||
param set-default FW_AIRSPD_TRIM 10
|
||||
param set-default FW_AIRSPD_MAX 12
|
||||
param set-default VT_FWD_THRUST_EN 1
|
||||
|
||||
param set-default HIL_ACT_FUNC1 101
|
||||
param set-default HIL_ACT_FUNC2 102
|
||||
@@ -78,7 +77,6 @@ param set-default CBRK_SUPPLY_CHK 894281
|
||||
|
||||
param set-default SENS_DPRES_OFF 0.001
|
||||
|
||||
# quadrotor propellers
|
||||
param set SIH_T_MAX 2.0
|
||||
param set SIH_Q_MAX 0.0165
|
||||
param set SIH_MASS 0.2
|
||||
@@ -90,18 +88,5 @@ param set SIH_IXZ 0
|
||||
param set SIH_KDV 0.2
|
||||
param set SIH_L_ROLL 0.2
|
||||
|
||||
# pusher propeller model with advance ratio, model from UIUC APC 8x6"
|
||||
param set SIH_F_T_MAX 6
|
||||
param set SIH_F_Q_MAX 0.03
|
||||
# if SIH_F_CT0 > 0, SIH_F_T_MAX and SIH_F_Q_MAX will be overridden
|
||||
param set SIH_F_CT0 0.131
|
||||
param set SIH_F_CT1 0.004
|
||||
param set SIH_F_CT2 -0.146
|
||||
param set SIH_F_CP0 0.0777
|
||||
param set SIH_F_CP1 0.0498
|
||||
param set SIH_F_CP2 -0.11
|
||||
param set SIH_F_DIA_INCH 8
|
||||
param set SIH_F_RPM_MAX 9000
|
||||
|
||||
# sih as standard vtol
|
||||
param set SIH_VEHICLE_TYPE 3
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# @name SIH Hexacopter X
|
||||
#
|
||||
# @type Simulation
|
||||
# @class Copter
|
||||
#
|
||||
# @maintainer Romain Chiappinelli <romain.chiap@gmail.com>
|
||||
#
|
||||
# @board px4_fmu-v2 exclude
|
||||
#
|
||||
|
||||
. ${R}etc/init.d/rc.mc_defaults
|
||||
|
||||
param set UAVCAN_ENABLE 0
|
||||
|
||||
# set SYS_HITL to 2 to start the SIH and avoid sensors startup
|
||||
param set SYS_HITL 2
|
||||
|
||||
# disable some checks to allow to fly:
|
||||
# - without real battery
|
||||
param set-default CBRK_SUPPLY_CHK 894281
|
||||
|
||||
param set SIH_VEHICLE_TYPE 4
|
||||
|
||||
# Symmetric hexacopter X clockwise motor numbering
|
||||
param set-default CA_ROTOR_COUNT 6
|
||||
param set-default CA_ROTOR0_PX 0.866
|
||||
param set-default CA_ROTOR0_PY 0.5
|
||||
param set-default CA_ROTOR1_PX 0
|
||||
param set-default CA_ROTOR1_PY 1
|
||||
param set-default CA_ROTOR1_KM -0.05
|
||||
param set-default CA_ROTOR2_PX -0.866
|
||||
param set-default CA_ROTOR2_PY 0.5
|
||||
param set-default CA_ROTOR3_PX -0.866
|
||||
param set-default CA_ROTOR3_PY -0.5
|
||||
param set-default CA_ROTOR3_KM -0.05
|
||||
param set-default CA_ROTOR4_PX 0
|
||||
param set-default CA_ROTOR4_PY -1
|
||||
param set-default CA_ROTOR5_PX 0.866
|
||||
param set-default CA_ROTOR5_PY -0.5
|
||||
param set-default CA_ROTOR5_KM -0.05
|
||||
|
||||
param set-default HIL_ACT_FUNC1 101
|
||||
param set-default HIL_ACT_FUNC2 102
|
||||
param set-default HIL_ACT_FUNC3 103
|
||||
param set-default HIL_ACT_FUNC4 104
|
||||
param set-default HIL_ACT_FUNC5 105
|
||||
param set-default HIL_ACT_FUNC6 106
|
||||
@@ -19,8 +19,8 @@ param set-default COM_DISARM_LAND 0.5
|
||||
# EKF2 parameters
|
||||
param set-default EKF2_DRAG_CTRL 1
|
||||
param set-default EKF2_IMU_POS_X 0.02
|
||||
param set-default SENS_GPS0_OFFX 0.055
|
||||
param set-default SENS_GPS0_OFFZ -0.15
|
||||
param set-default EKF2_GPS_POS_X 0.055
|
||||
param set-default EKF2_GPS_POS_Z -0.15
|
||||
param set-default EKF2_MIN_RNG 0.03
|
||||
param set-default EKF2_OF_CTRL 1
|
||||
param set-default EKF2_OF_POS_X 0.055
|
||||
|
||||
@@ -19,8 +19,8 @@ param set-default COM_DISARM_LAND 0.5
|
||||
# EKF2 parameters
|
||||
param set-default EKF2_DRAG_CTRL 1
|
||||
param set-default EKF2_IMU_POS_X 0.02
|
||||
param set-default SENS_GPS0_OFFX 0.055
|
||||
param set-default SENS_GPS0_OFFZ -0.15
|
||||
param set-default EKF2_GPS_POS_X 0.055
|
||||
param set-default EKF2_GPS_POS_Z -0.15
|
||||
param set-default EKF2_MIN_RNG 0.03
|
||||
param set-default EKF2_OF_CTRL 1
|
||||
param set-default EKF2_OF_POS_X 0.055
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# @name HolyBro QAV250
|
||||
#
|
||||
# @url https://docs.px4.io/main/en/frames_multicopter/holybro_qav250_pixhawk4_mini
|
||||
# @url https://docs.px4.io/main/en/frames_multicopter/holybro_qav250_pixhawk4_mini.html
|
||||
#
|
||||
# @type Quadrotor x
|
||||
# @class Copter
|
||||
|
||||
@@ -47,9 +47,8 @@ param set-default EKF2_BCOEF_Y 25.5
|
||||
|
||||
param set-default EKF2_DRAG_CTRL 1
|
||||
|
||||
param set-default SENS_GPS0_DELAY 100
|
||||
param set-default SENS_GPS1_DELAY 100
|
||||
param set-default SENS_GPS0_OFFX 0.06
|
||||
param set-default EKF2_GPS_DELAY 100
|
||||
param set-default EKF2_GPS_POS_X 0.06
|
||||
param set-default EKF2_GPS_V_NOISE 0.5
|
||||
|
||||
param set-default EKF2_IMU_POS_X 0.06
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# @name Aion Robotics R1 UGV
|
||||
#
|
||||
# @url https://docs.px4.io/main/en/complete_vehicles_rover/aion_r1
|
||||
# @url https://docs.px4.io/main/en/complete_vehicles_rover/aion_r1.html
|
||||
#
|
||||
# @type Rover
|
||||
# @class Rover
|
||||
|
||||
@@ -49,7 +49,6 @@ if(CONFIG_MODULES_SIMULATION_PWM_OUT_SIM)
|
||||
1101_rc_plane_sih.hil
|
||||
1102_tailsitter_duo_sih.hil
|
||||
1103_standard_vtol_sih.hil
|
||||
1105_rc_hexa_x_sih.hil
|
||||
)
|
||||
if(CONFIG_MODULES_ROVER_ACKERMANN)
|
||||
px4_add_romfs_files(
|
||||
|
||||
@@ -238,7 +238,7 @@ then
|
||||
fi
|
||||
|
||||
# Start TMP102 temperature sensor
|
||||
if param compare -s SENS_EN_TMP102 1
|
||||
if param compare SENS_EN_TMP102 1
|
||||
then
|
||||
tmp102 start -X
|
||||
fi
|
||||
|
||||
@@ -188,11 +188,11 @@ else
|
||||
netman update -i eth0
|
||||
fi
|
||||
|
||||
# To trigger a parameter reset during boot SYS_AUTOCONFIG was set to 1 before
|
||||
# To trigger a parameter reset during boot SYS_AUTCONFIG was set to 1 before
|
||||
if param greater SYS_AUTOCONFIG 0
|
||||
then
|
||||
# Reset parameters except airframe, parameter version, sensor calibration, total flight time, flight UUID
|
||||
param reset_all SYS_AUTOSTART SYS_PARAM_VER CAL_* LND_FLIGHT* TC_* COM_FLIGHT*
|
||||
# Reset parameters except airframe, parameter version, RC calibration, sensor calibration, flight modes, total flight time, flight UUID
|
||||
param reset_all SYS_AUTOSTART SYS_PARAM_VER RC* CAL_* COM_FLTMODE* LND_FLIGHT* TC_* COM_FLIGHT*
|
||||
fi
|
||||
|
||||
#
|
||||
@@ -633,15 +633,12 @@ else
|
||||
#
|
||||
# Start the VTX services.
|
||||
#
|
||||
if ! param compare -s VTX_SER_CFG 0
|
||||
set RC_VTXTABLE ${R}etc/init.d/rc.vtxtable
|
||||
if [ -f ${RC_VTXTABLE} ]
|
||||
then
|
||||
set RC_VTXTABLE ${R}etc/init.d/rc.vtxtable
|
||||
if [ -f ${RC_VTXTABLE} ]
|
||||
then
|
||||
. ${RC_VTXTABLE}
|
||||
fi
|
||||
unset RC_VTXTABLE
|
||||
. ${RC_VTXTABLE}
|
||||
fi
|
||||
unset RC_VTXTABLE
|
||||
|
||||
#
|
||||
# Set additional parameters and env variables for selected AUTOSTART.
|
||||
|
||||
+12
-28
@@ -2,40 +2,24 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions receive security updates:
|
||||
The following is a list of versions the development team is currently supporting.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.16.x | :white_check_mark: |
|
||||
| < 1.16 | :x: |
|
||||
| 1.4.x | :white_check_mark: |
|
||||
| 1.3.3 | :white_check_mark: |
|
||||
| < 1.3 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We receive security vulnerability reports through GitHub Security Advisories.
|
||||
We currently only receive security vulnerability reports through GitHub.
|
||||
|
||||
To begin a report, go to the [PX4/PX4-Autopilot](https://github.com/PX4/PX4-Autopilot) repository
|
||||
and click on the **Security** tab. If you are on mobile, click the **...** dropdown menu, then click **Security**.
|
||||
To begin a report, please go to the top-level repository, for example, PX4/PX4-Autopilot,
|
||||
and click on the Security tab. If you are on mobile, click the ... dropdown menu, and then click Security.
|
||||
|
||||
Click **Report a Vulnerability** to open the advisory form. Fill in the advisory details form.
|
||||
Make sure your title is descriptive and the description contains all relevant details needed
|
||||
to verify the issue. We welcome logs, screenshots, photos, and videos.
|
||||
Click Report a Vulnerability to open the advisory form. Fill in the advisory details form.
|
||||
Make sure your title is descriptive, and the development team can find all of the relevant details needed
|
||||
to verify on the description box. We recommend you add as much data as possible. We welcome logs,
|
||||
screenshots, photos, and videos, anything that can help us verify and identify the issues being reported.
|
||||
|
||||
At the bottom of the form, click **Submit report**.
|
||||
|
||||
## Response Process
|
||||
|
||||
1. **Acknowledgment**: The maintainer team will acknowledge your report within **7 days**.
|
||||
2. **Triage**: We will assess severity and impact and communicate next steps.
|
||||
3. **Disclosure**: We coordinate disclosure with the reporter. We follow responsible disclosure practices and will credit reporters in the advisory unless they request anonymity.
|
||||
|
||||
If you do not receive acknowledgment within 7 days, please follow up by emailing the [release managers](MAINTAINERS.md).
|
||||
|
||||
## Secure Development Practices
|
||||
|
||||
The PX4 development team applies the following practices to reduce security risk:
|
||||
|
||||
- **Code review**: All changes require peer review before merging.
|
||||
- **Static analysis**: [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) runs on every pull request with warnings treated as errors.
|
||||
- **Fuzzing**: A daily fuzzing pipeline using [Google fuzztest](https://github.com/google/fuzztest) tests MAVLink message handling and GNSS driver protocol parsing.
|
||||
- **Input validation**: All external inputs (MAVLink messages, RC signals, sensor data) are validated against expected ranges before use.
|
||||
- **Compiler hardening**: Builds use `-Wall -Werror`, stack protectors, and other hardening flags where supported by the target platform.
|
||||
At the bottom of the form, click Submit report. The maintainer team will be notified and will get back to you ASAP.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#! /bin/bash
|
||||
|
||||
# exit when any command fails
|
||||
set -e
|
||||
|
||||
@@ -39,8 +39,6 @@ exec find boards msg src platforms test \
|
||||
-path src/lib/cdrstream/rosidl -prune -o \
|
||||
-path src/modules/zenoh/zenoh-pico -prune -o \
|
||||
-path boards/modalai/voxl2/libfc-sensor-api -prune -o \
|
||||
-path boards/modalai/voxl2/src/lib/mpa/libmodal-json -prune -o \
|
||||
-path boards/modalai/voxl2/src/lib/mpa/libmodal-pipe -prune -o \
|
||||
-path src/drivers/actuators/vertiq_io/iq-module-communication-cpp -prune -o \
|
||||
-path src/lib/tensorflow_lite_micro/tflite_micro -prune -o \
|
||||
-path src/drivers/ins/sbgecom/sbgECom -prune -o \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
exit 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
# Flash PX4 to a device running AuterionOS in the local network
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 -f <firmware.px4|.elf> [-c <configuration_dir>] -d <IP/Device> [-u <user>] [-p <ssh_port>] [--revert]"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
# This script is meant to be used by the build_all.yml workflow in a github runner
|
||||
# Please only modify if you know what you are doing
|
||||
set -e
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate commit messages in a PR against conventional commits format.
|
||||
|
||||
Reads a JSON array of GitHub commit objects from stdin (as returned by the
|
||||
GitHub API's /pulls/{n}/commits endpoint) and checks each message for
|
||||
blocking errors and advisory warnings.
|
||||
|
||||
With --markdown, outputs a formatted PR comment body instead of plain text.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from conventional_commits import (
|
||||
EXEMPT_PREFIXES,
|
||||
parse_header,
|
||||
)
|
||||
|
||||
# Blocking: prefixes that indicate unsquashed fixup commits
|
||||
FIXUP_PREFIXES = ('fixup!', 'squash!', 'amend!')
|
||||
|
||||
# Blocking: single-word throwaway messages (case-insensitive exact match)
|
||||
THROWAWAY_WORDS = frozenset({
|
||||
'fix', 'fixed', 'fixes',
|
||||
'update', 'updated', 'updates',
|
||||
'test', 'tests', 'testing',
|
||||
'tmp', 'temp',
|
||||
'oops', 'wip',
|
||||
'debug', 'cleanup',
|
||||
})
|
||||
|
||||
# Blocking: debug session leftovers
|
||||
DEBUG_KEYWORDS = ('tmate',)
|
||||
|
||||
# Warning: review-response messages (case-insensitive substring match)
|
||||
REVIEW_RESPONSE_PATTERNS = (
|
||||
'address review',
|
||||
'apply suggestions from code review',
|
||||
'code review',
|
||||
)
|
||||
|
||||
# Warning: formatter-only commits
|
||||
FORMATTER_PATTERNS = (
|
||||
'do make format',
|
||||
'make format',
|
||||
'run formatter',
|
||||
'apply format',
|
||||
)
|
||||
|
||||
MIN_MESSAGE_LENGTH = 5
|
||||
|
||||
|
||||
def check_commit(message: str) -> tuple[list[str], list[str]]:
|
||||
"""Return (errors, warnings) for a single commit message."""
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
|
||||
first_line = message.split('\n', 1)[0].strip()
|
||||
lower = first_line.lower()
|
||||
|
||||
# --- Blocking checks ---
|
||||
|
||||
for prefix in FIXUP_PREFIXES:
|
||||
if lower.startswith(prefix):
|
||||
errors.append(f'Unsquashed commit: starts with "{prefix}"')
|
||||
|
||||
if lower == 'wip' or lower.startswith('wip ') or lower.startswith('wip:'):
|
||||
errors.append('WIP commit should not be merged')
|
||||
|
||||
if len(first_line) < MIN_MESSAGE_LENGTH:
|
||||
errors.append(f'Message too short ({len(first_line)} chars, minimum {MIN_MESSAGE_LENGTH})')
|
||||
|
||||
if first_line.strip() and first_line.strip().lower() in THROWAWAY_WORDS:
|
||||
errors.append(f'Single-word throwaway message: "{first_line.strip()}"')
|
||||
|
||||
for kw in DEBUG_KEYWORDS:
|
||||
if kw in lower:
|
||||
errors.append(f'Debug session leftover: contains "{kw}"')
|
||||
|
||||
# --- Warning checks ---
|
||||
|
||||
for pattern in REVIEW_RESPONSE_PATTERNS:
|
||||
if pattern in lower:
|
||||
warnings.append('Review-response commit')
|
||||
break
|
||||
|
||||
for pattern in FORMATTER_PATTERNS:
|
||||
if pattern in lower:
|
||||
warnings.append('Formatter-only commit')
|
||||
break
|
||||
|
||||
if not parse_header(first_line):
|
||||
# Exempt merge commits
|
||||
for prefix in EXEMPT_PREFIXES:
|
||||
if first_line.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
warnings.append(
|
||||
'Missing conventional commit format '
|
||||
'(e.g. "feat(ekf2): add something")'
|
||||
)
|
||||
|
||||
return errors, warnings
|
||||
|
||||
|
||||
def suggest_commit(message: str) -> str | None:
|
||||
"""Suggest how to fix a bad commit message."""
|
||||
first_line = message.split('\n', 1)[0].strip()
|
||||
lower = first_line.lower()
|
||||
|
||||
for prefix in FIXUP_PREFIXES:
|
||||
if lower.startswith(prefix):
|
||||
return 'Squash this into the commit it fixes'
|
||||
|
||||
if lower == 'wip' or lower.startswith('wip ') or lower.startswith('wip:'):
|
||||
return 'Reword with a descriptive message (e.g. "feat(scope): what changed")'
|
||||
|
||||
if len(first_line) < MIN_MESSAGE_LENGTH:
|
||||
return 'Reword with a descriptive message (e.g. "feat(ekf2): what changed")'
|
||||
|
||||
if first_line.strip().lower() in THROWAWAY_WORDS:
|
||||
return 'Reword with a descriptive message (e.g. "fix(scope): what changed")'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def format_plain(data: list) -> tuple[bool, bool]:
|
||||
"""Print plain text output. Returns (has_blocking, has_warnings)."""
|
||||
has_blocking = False
|
||||
has_warnings = False
|
||||
|
||||
for commit in data:
|
||||
sha = commit.get('sha', '?')[:10]
|
||||
message = commit.get('commit', {}).get('message', '')
|
||||
first_line = message.split('\n', 1)[0].strip()
|
||||
|
||||
errors, warnings = check_commit(message)
|
||||
|
||||
if errors or warnings:
|
||||
print(f"\n {sha} {first_line}")
|
||||
|
||||
for err in errors:
|
||||
print(f" ERROR: {err}")
|
||||
has_blocking = True
|
||||
|
||||
for warn in warnings:
|
||||
print(f" WARNING: {warn}")
|
||||
has_warnings = True
|
||||
|
||||
if has_blocking:
|
||||
print(
|
||||
"\n"
|
||||
"ERROR = must fix before merging (CI will block the PR)\n"
|
||||
"WARNING = advisory, not blocking, but recommended to fix\n"
|
||||
"\n"
|
||||
"See the contributing guide for details:\n"
|
||||
" https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention\n",
|
||||
)
|
||||
|
||||
elif has_warnings:
|
||||
print(
|
||||
"\n"
|
||||
"WARNING = advisory, not blocking, but recommended to fix\n"
|
||||
"\n"
|
||||
"See the contributing guide for details:\n"
|
||||
" https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention\n",
|
||||
)
|
||||
|
||||
return has_blocking, has_warnings
|
||||
|
||||
|
||||
def format_markdown_blocking(data: list) -> str:
|
||||
"""Format a blocking error markdown comment."""
|
||||
error_groups: dict[str, list[str]] = {}
|
||||
unique_commits: list[tuple[str, str, list[str], str]] = []
|
||||
|
||||
for commit in data:
|
||||
sha = commit.get('sha', '?')[:10]
|
||||
message = commit.get('commit', {}).get('message', '')
|
||||
first_line = message.split('\n', 1)[0].strip()
|
||||
|
||||
errors, _ = check_commit(message)
|
||||
if not errors:
|
||||
continue
|
||||
|
||||
suggestion = suggest_commit(message) or ''
|
||||
unique_commits.append((sha, first_line, errors, suggestion))
|
||||
|
||||
for err in errors:
|
||||
error_groups.setdefault(err, []).append(sha)
|
||||
|
||||
lines = [
|
||||
"## \u274c Commit messages need attention before merging",
|
||||
"",
|
||||
]
|
||||
|
||||
has_large_group = any(len(shas) > 3 for shas in error_groups.values())
|
||||
|
||||
if has_large_group:
|
||||
lines.extend([
|
||||
"**Issues found:**",
|
||||
"",
|
||||
])
|
||||
for err_msg, shas in error_groups.items():
|
||||
if len(shas) > 3:
|
||||
lines.append(f"- **{len(shas)} commits**: {err_msg} "
|
||||
f"(`{shas[0]}`, `{shas[1]}`, ... `{shas[-1]}`)")
|
||||
else:
|
||||
sha_list = ', '.join(f'`{s}`' for s in shas)
|
||||
lines.append(f"- {err_msg}: {sha_list}")
|
||||
|
||||
distinct_messages = {msg for _, msg, _, _ in unique_commits}
|
||||
if len(distinct_messages) <= 5:
|
||||
lines.extend(["", "**Affected commits:**", ""])
|
||||
for sha, msg, errors, suggestion in unique_commits:
|
||||
safe_msg = msg.replace('|', '\\|')
|
||||
lines.append(f"- `{sha}` {safe_msg}")
|
||||
else:
|
||||
lines.extend([
|
||||
"| Commit | Message | Issue | Suggested fix |",
|
||||
"|--------|---------|-------|---------------|",
|
||||
])
|
||||
for sha, msg, errors, suggestion in unique_commits:
|
||||
issues = '; '.join(errors)
|
||||
safe_msg = msg.replace('|', '\\|')
|
||||
lines.append(f"| `{sha}` | {safe_msg} | {issues} | {suggestion} |")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"See [CONTRIBUTING.md](https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention) "
|
||||
"for how to clean up commits.",
|
||||
"",
|
||||
"---",
|
||||
"*This comment will be automatically removed once the issues are resolved.*",
|
||||
])
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def format_markdown_advisory(data: list) -> str:
|
||||
"""Format an advisory warning markdown comment."""
|
||||
lines = [
|
||||
"## \U0001f4a1 Commit messages could be improved",
|
||||
"",
|
||||
"Not blocking, but these commit messages could use some cleanup.",
|
||||
"",
|
||||
"| Commit | Message | Suggestion |",
|
||||
"|--------|---------|------------|",
|
||||
]
|
||||
|
||||
for commit in data:
|
||||
sha = commit.get('sha', '?')[:10]
|
||||
message = commit.get('commit', {}).get('message', '')
|
||||
first_line = message.split('\n', 1)[0].strip()
|
||||
|
||||
_, warnings = check_commit(message)
|
||||
if not warnings:
|
||||
continue
|
||||
|
||||
suggestion = '; '.join(warnings)
|
||||
safe_msg = first_line.replace('|', '\\|')
|
||||
lines.append(f"| `{sha}` | {safe_msg} | {suggestion} |")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"See the [commit message convention](https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention) "
|
||||
"for details.",
|
||||
"",
|
||||
"---",
|
||||
"*This comment will be automatically removed once the issues are resolved.*",
|
||||
])
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
markdown_stdout = '--markdown' in sys.argv
|
||||
markdown_file = None
|
||||
for i, a in enumerate(sys.argv):
|
||||
if a == '--markdown-file' and i + 1 < len(sys.argv):
|
||||
markdown_file = sys.argv[i + 1]
|
||||
elif a.startswith('--markdown-file='):
|
||||
markdown_file = a.split('=', 1)[1]
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError as exc:
|
||||
print(f"Failed to parse JSON input: {exc}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if not isinstance(data, list):
|
||||
print("Expected a JSON array of commit objects.", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
# Always compute blocking/warning state
|
||||
has_blocking = False
|
||||
has_warnings = False
|
||||
for commit in data:
|
||||
message = commit.get('commit', {}).get('message', '')
|
||||
errors, warnings = check_commit(message)
|
||||
if errors:
|
||||
has_blocking = True
|
||||
if warnings:
|
||||
has_warnings = True
|
||||
|
||||
# Generate markdown if needed
|
||||
md = None
|
||||
if has_blocking:
|
||||
md = format_markdown_blocking(data)
|
||||
elif has_warnings:
|
||||
md = format_markdown_advisory(data)
|
||||
|
||||
if md:
|
||||
if markdown_stdout:
|
||||
print(md)
|
||||
if markdown_file:
|
||||
with open(markdown_file, 'w') as f:
|
||||
f.write(md + '\n')
|
||||
elif markdown_file:
|
||||
with open(markdown_file, 'w') as f:
|
||||
pass
|
||||
|
||||
# Plain text output to stderr for CI logs (always, unless --markdown only)
|
||||
if not markdown_stdout:
|
||||
has_blocking, _ = format_plain(data)
|
||||
|
||||
sys.exit(1 if has_blocking else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#! /bin/bash
|
||||
# Copy a git diff between two commits if msg versioning is added
|
||||
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate that a PR title follows conventional commits format.
|
||||
|
||||
Format: type(scope): description
|
||||
|
||||
Can output plain text for CI logs or markdown for PR comments.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from conventional_commits import (
|
||||
CONVENTIONAL_TYPES,
|
||||
EXEMPT_PREFIXES,
|
||||
parse_header,
|
||||
suggest_scope,
|
||||
suggest_type,
|
||||
)
|
||||
|
||||
|
||||
def suggest_title(title: str) -> str | None:
|
||||
"""Try to suggest a corrected title in conventional commits format."""
|
||||
stripped = title.strip()
|
||||
|
||||
# Remove common bracket prefixes like [docs], [CI], etc.
|
||||
bracket_match = re.match(r'^\[([^\]]+)\]\s*(.+)', stripped)
|
||||
if bracket_match:
|
||||
prefix = bracket_match.group(1).strip().lower()
|
||||
rest = bracket_match.group(2).strip()
|
||||
rest = re.sub(r'^[\-:]\s*', '', rest).strip()
|
||||
if len(rest) >= 5:
|
||||
# Try to map bracket content to a type
|
||||
commit_type = prefix if prefix in CONVENTIONAL_TYPES else suggest_type(rest)
|
||||
scope = suggest_scope(rest)
|
||||
if scope:
|
||||
return f"{commit_type}({scope}): {rest}"
|
||||
|
||||
# Already has old-style "subsystem: description" format - convert it
|
||||
colon_match = re.match(r'^([a-zA-Z][a-zA-Z0-9_/\-\. ]*): (.+)$', stripped)
|
||||
if colon_match:
|
||||
old_subsystem = colon_match.group(1).strip()
|
||||
desc = colon_match.group(2).strip()
|
||||
if len(desc) >= 5:
|
||||
commit_type = suggest_type(desc)
|
||||
# Use the old subsystem as scope (clean it up)
|
||||
scope = old_subsystem.lower().replace(' ', '_')
|
||||
return f"{commit_type}({scope}): {desc}"
|
||||
|
||||
# No format at all - try to guess both type and scope
|
||||
commit_type = suggest_type(stripped)
|
||||
scope = suggest_scope(stripped)
|
||||
if scope:
|
||||
desc = stripped[0].lower() + stripped[1:] if stripped else stripped
|
||||
return f"{commit_type}({scope}): {desc}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def check_title(title: str) -> bool:
|
||||
title = title.strip()
|
||||
|
||||
if not title:
|
||||
print("PR title is empty.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
for prefix in EXEMPT_PREFIXES:
|
||||
if title.startswith(prefix):
|
||||
return True
|
||||
|
||||
if parse_header(title):
|
||||
return True
|
||||
|
||||
types_str = ', '.join(f'`{t}`' for t in CONVENTIONAL_TYPES.keys())
|
||||
print(
|
||||
f"PR title does not match conventional commits format.\n"
|
||||
f"\n"
|
||||
f" Title: {title}\n"
|
||||
f"\n"
|
||||
f"Expected format: type(scope): description\n"
|
||||
f"\n"
|
||||
f"Valid types: {types_str}\n"
|
||||
f"\n"
|
||||
f"Good examples:\n"
|
||||
f" feat(ekf2): add height fusion timeout\n"
|
||||
f" fix(mavlink): correct BATTERY_STATUS_V2 parsing\n"
|
||||
f" ci(workflows): migrate to reusable workflows\n"
|
||||
f" feat(boards/px4_fmu-v6x)!: remove deprecated driver API\n"
|
||||
f"\n"
|
||||
f"Bad examples:\n"
|
||||
f" fix stuff\n"
|
||||
f" Update file\n"
|
||||
f" ekf2: fix something (missing type prefix)\n"
|
||||
f"\n"
|
||||
f"See the contributing guide for details:\n"
|
||||
f" https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention\n",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def format_markdown(title: str) -> str:
|
||||
"""Format a markdown PR comment body for a bad title."""
|
||||
lines = [
|
||||
"## \u274c PR title needs conventional commit format",
|
||||
"",
|
||||
"Expected format: `type(scope): description` "
|
||||
"([conventional commits](https://www.conventionalcommits.org/)).",
|
||||
"",
|
||||
"**Your title:**",
|
||||
f"> {title}",
|
||||
"",
|
||||
]
|
||||
|
||||
suggestion = suggest_title(title)
|
||||
if suggestion:
|
||||
lines.extend([
|
||||
"**Suggested fix:**",
|
||||
f"> {suggestion}",
|
||||
"",
|
||||
])
|
||||
|
||||
lines.extend([
|
||||
"**To fix this:** click the ✏️ next to the PR title at the top "
|
||||
"of this page and update it.",
|
||||
"",
|
||||
"See [CONTRIBUTING.md](https://github.com/PX4/PX4-Autopilot/blob/main/CONTRIBUTING.md#commit-message-convention) "
|
||||
"for details.",
|
||||
"",
|
||||
"---",
|
||||
"*This comment will be automatically removed once the issue is resolved.*",
|
||||
])
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='Check PR title format')
|
||||
parser.add_argument('title', help='The PR title to validate')
|
||||
parser.add_argument('--markdown', action='store_true',
|
||||
help='Output markdown to stdout on failure')
|
||||
parser.add_argument('--markdown-file', metavar='FILE',
|
||||
help='Write markdown to FILE on failure')
|
||||
args = parser.parse_args()
|
||||
|
||||
passed = check_title(args.title)
|
||||
|
||||
if not passed:
|
||||
md = format_markdown(args.title)
|
||||
if args.markdown:
|
||||
print(md)
|
||||
if args.markdown_file:
|
||||
with open(args.markdown_file, 'w') as f:
|
||||
f.write(md + '\n')
|
||||
elif args.markdown_file:
|
||||
with open(args.markdown_file, 'w') as f:
|
||||
pass
|
||||
|
||||
sys.exit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,146 +0,0 @@
|
||||
"""Shared constants and helpers for conventional commit validation.
|
||||
|
||||
Format: type(scope): description
|
||||
Optional breaking change marker: type(scope)!: description
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
CONVENTIONAL_TYPES = {
|
||||
'feat': 'A new feature',
|
||||
'fix': 'A bug fix',
|
||||
'docs': 'Documentation only changes',
|
||||
'style': 'Formatting, whitespace, no code change',
|
||||
'refactor': 'Code change that neither fixes a bug nor adds a feature',
|
||||
'perf': 'Performance improvement',
|
||||
'test': 'Adding or correcting tests',
|
||||
'build': 'Build system or external dependencies',
|
||||
'ci': 'CI configuration files and scripts',
|
||||
'chore': 'Other changes that don\'t modify src or test files',
|
||||
'revert': 'Reverts a previous commit',
|
||||
}
|
||||
|
||||
# type(scope)[!]: description
|
||||
# - type: one of CONVENTIONAL_TYPES keys
|
||||
# - scope: required, alphanumeric with _/-/.
|
||||
# - !: optional breaking change marker
|
||||
# - description: at least 5 chars
|
||||
HEADER_PATTERN = re.compile(
|
||||
r'^(' + '|'.join(CONVENTIONAL_TYPES.keys()) + r')'
|
||||
r'\(([a-zA-Z0-9_/\-\.]+)\)'
|
||||
r'(!)?'
|
||||
r': (.{5,})$'
|
||||
)
|
||||
|
||||
EXEMPT_PREFIXES = ('Merge ',)
|
||||
|
||||
# Common PX4 subsystem scopes for suggestions
|
||||
KNOWN_SCOPES = [
|
||||
'ekf2', 'mavlink', 'commander', 'navigator', 'sensors',
|
||||
'mc_att_control', 'mc_pos_control', 'mc_rate_control',
|
||||
'fw_att_control', 'fw_pos_control', 'fw_rate_control',
|
||||
'vtol', 'actuators', 'battery', 'param', 'logger',
|
||||
'uorb', 'drivers', 'boards', 'simulation', 'sitl',
|
||||
'gps', 'rc', 'safety', 'can', 'serial',
|
||||
'ci', 'docs', 'build', 'cmake', 'tools',
|
||||
'mixer', 'land_detector', 'airspeed', 'gyroscope',
|
||||
'accelerometer', 'magnetometer', 'barometer',
|
||||
]
|
||||
|
||||
# Keyword patterns to suggest scopes from description text
|
||||
KEYWORD_SCOPES = [
|
||||
(r'\b(ekf|estimator|height|fusion|imu|baro)\b', 'ekf2'),
|
||||
(r'\b(mavlink|MAVLink|MAVLINK|command_int|heartbeat)\b', 'mavlink'),
|
||||
(r'\b(uorb|orb|pub|sub|topic)\b', 'uorb'),
|
||||
(r'\b(board|fmu|nuttx|stm32)\b', 'boards'),
|
||||
(r'\b(mixer|actuator|motor|servo|pwm|dshot)\b', 'actuators'),
|
||||
(r'\b(battery|power)\b', 'battery'),
|
||||
(r'\b(param|parameter)\b', 'param'),
|
||||
(r'\b(log|logger|sdlog)\b', 'logger'),
|
||||
(r'\b(sensor|accel|gyro)\b', 'sensors'),
|
||||
(r'\b(land|takeoff|rtl|mission|navigator|geofence)\b', 'navigator'),
|
||||
(r'\b(position|velocity|attitude|rate)\s*(control|ctrl)\b', 'mc_att_control'),
|
||||
(r'\b(mc|multicopter|quad)\b', 'mc_att_control'),
|
||||
(r'\b(fw|fixedwing|fixed.wing|plane)\b', 'fw_att_control'),
|
||||
(r'\b(vtol|transition)\b', 'vtol'),
|
||||
(r'\b(ci|workflow|github.action|pipeline)\b', 'ci'),
|
||||
(r'\b(doc|docs|documentation|readme)\b', 'docs'),
|
||||
(r'\b(cmake|make|toolchain|compiler)\b', 'build'),
|
||||
(r'\b(sitl|simulation|gazebo|jmavsim|sih)\b', 'simulation'),
|
||||
(r'\b(can|uavcan|cyphal|dronecan)\b', 'can'),
|
||||
(r'\b(serial|uart|spi|i2c)\b', 'serial'),
|
||||
(r'\b(safety|failsafe|arm|disarm|kill)\b', 'safety'),
|
||||
(r'\b(rc|radio|sbus|crsf|elrs|dsm)\b', 'rc'),
|
||||
(r'\b(gps|gnss|rtk|ubx)\b', 'gps'),
|
||||
(r'\b(optical.flow|flow|rangefinder|lidar|distance)\b', 'sensors'),
|
||||
(r'\b(orbit|follow|offboard)\b', 'commander'),
|
||||
(r'\b(driver)\b', 'drivers'),
|
||||
]
|
||||
|
||||
# Verb patterns to suggest conventional commit type
|
||||
VERB_TYPE_MAP = [
|
||||
(r'^fix(e[ds])?[\s:]', 'fix'),
|
||||
(r'^bug[\s:]', 'fix'),
|
||||
(r'^add(s|ed|ing)?[\s:]', 'feat'),
|
||||
(r'^implement', 'feat'),
|
||||
(r'^introduce', 'feat'),
|
||||
(r'^support', 'feat'),
|
||||
(r'^enable', 'feat'),
|
||||
(r'^update[ds]?[\s:]', 'feat'),
|
||||
(r'^improv(e[ds]?|ing)', 'perf'),
|
||||
(r'^optimi[zs](e[ds]?|ing)', 'perf'),
|
||||
(r'^refactor', 'refactor'),
|
||||
(r'^clean\s*up', 'refactor'),
|
||||
(r'^restructure', 'refactor'),
|
||||
(r'^simplif(y|ied)', 'refactor'),
|
||||
(r'^remov(e[ds]?|ing)', 'refactor'),
|
||||
(r'^delet(e[ds]?|ing)', 'refactor'),
|
||||
(r'^deprecat', 'refactor'),
|
||||
(r'^replac(e[ds]?|ing)', 'refactor'),
|
||||
(r'^renam(e[ds]?|ing)', 'refactor'),
|
||||
(r'^migrat', 'refactor'),
|
||||
(r'^revert', 'revert'),
|
||||
(r'^doc(s|ument)', 'docs'),
|
||||
(r'^test', 'test'),
|
||||
(r'^format', 'style'),
|
||||
(r'^lint', 'style'),
|
||||
(r'^whitespace', 'style'),
|
||||
(r'^build', 'build'),
|
||||
(r'^ci[\s:]', 'ci'),
|
||||
]
|
||||
|
||||
|
||||
def parse_header(text: str) -> dict | None:
|
||||
"""Parse a conventional commit header into components.
|
||||
|
||||
Returns dict with keys {type, scope, breaking, subject} or None if
|
||||
the text doesn't match conventional commits format.
|
||||
"""
|
||||
text = text.strip()
|
||||
m = HEADER_PATTERN.match(text)
|
||||
if not m:
|
||||
return None
|
||||
return {
|
||||
'type': m.group(1),
|
||||
'scope': m.group(2),
|
||||
'breaking': m.group(3) == '!',
|
||||
'subject': m.group(4),
|
||||
}
|
||||
|
||||
|
||||
def suggest_type(text: str) -> str:
|
||||
"""Infer a conventional commit type from description text."""
|
||||
lower = text.strip().lower()
|
||||
for pattern, commit_type in VERB_TYPE_MAP:
|
||||
if re.search(pattern, lower):
|
||||
return commit_type
|
||||
return 'feat'
|
||||
|
||||
|
||||
def suggest_scope(text: str) -> str | None:
|
||||
"""Infer a scope from keywords in the text."""
|
||||
lower = text.strip().lower()
|
||||
for pattern, scope in KEYWORD_SCOPES:
|
||||
if re.search(pattern, lower, re.IGNORECASE):
|
||||
return scope
|
||||
return None
|
||||
@@ -148,18 +148,18 @@ grouped_targets['base']['manufacturers'] = {}
|
||||
grouped_targets['base']['manufacturers']['px4'] = []
|
||||
grouped_targets['base']['manufacturers']['px4'] += metadata_targets
|
||||
|
||||
for manufacturer in sorted(os.scandir(os.path.join(source_dir, '../boards')), key=lambda e: e.name):
|
||||
for manufacturer in os.scandir(os.path.join(source_dir, '../boards')):
|
||||
if not manufacturer.is_dir():
|
||||
continue
|
||||
if manufacturer.name in excluded_manufacturers:
|
||||
if verbose: print(f'excluding manufacturer {manufacturer.name}')
|
||||
continue
|
||||
|
||||
for board in sorted(os.scandir(manufacturer.path), key=lambda e: e.name):
|
||||
for board in os.scandir(manufacturer.path):
|
||||
if not board.is_dir():
|
||||
continue
|
||||
|
||||
for files in sorted(os.scandir(board.path), key=lambda e: e.name):
|
||||
for files in os.scandir(board.path):
|
||||
if files.is_file() and files.name.endswith('.px4board'):
|
||||
|
||||
board_name = manufacturer.name + '_' + board.name
|
||||
@@ -189,65 +189,6 @@ for manufacturer in sorted(os.scandir(os.path.join(source_dir, '../boards')), ke
|
||||
if target is not None:
|
||||
build_configs.append(target)
|
||||
|
||||
# Remove companion targets from CI groups (parent target builds them via Make prerequisite)
|
||||
for manufacturer in sorted(os.scandir(os.path.join(source_dir, '../boards')), key=lambda e: e.name):
|
||||
if not manufacturer.is_dir():
|
||||
continue
|
||||
for board in sorted(os.scandir(manufacturer.path), key=lambda e: e.name):
|
||||
if not board.is_dir():
|
||||
continue
|
||||
companion_file = os.path.join(board.path, 'companion_targets')
|
||||
if os.path.exists(companion_file):
|
||||
with open(companion_file) as f:
|
||||
companions = {l.strip() for l in f if l.strip() and not l.startswith('#')}
|
||||
for arch in grouped_targets:
|
||||
for man in grouped_targets[arch]['manufacturers']:
|
||||
grouped_targets[arch]['manufacturers'][man] = [
|
||||
t for t in grouped_targets[arch]['manufacturers'][man]
|
||||
if t not in companions
|
||||
]
|
||||
|
||||
# Append _deb targets for boards that have cmake/package.cmake
|
||||
for manufacturer in sorted(os.scandir(os.path.join(source_dir, '../boards')), key=lambda e: e.name):
|
||||
if not manufacturer.is_dir():
|
||||
continue
|
||||
if manufacturer.name in excluded_manufacturers:
|
||||
continue
|
||||
for board in sorted(os.scandir(manufacturer.path), key=lambda e: e.name):
|
||||
if not board.is_dir():
|
||||
continue
|
||||
board_name = manufacturer.name + '_' + board.name
|
||||
if board_name in excluded_boards:
|
||||
continue
|
||||
package_cmake = os.path.join(board.path, 'cmake', 'package.cmake')
|
||||
if os.path.exists(package_cmake):
|
||||
deb_target = board_name + '_deb'
|
||||
if target_filter and not any(deb_target.startswith(f) for f in target_filter):
|
||||
continue
|
||||
# Determine the container and group for this board
|
||||
container = default_container
|
||||
if board_name in board_container_overrides:
|
||||
container = board_container_overrides[board_name]
|
||||
target_entry = {'target': deb_target, 'container': container}
|
||||
if args.group:
|
||||
# Find the group where this board's _default target already lives
|
||||
default_target = board_name + '_default'
|
||||
group = None
|
||||
for g in grouped_targets:
|
||||
targets_in_group = grouped_targets[g].get('manufacturers', {}).get(manufacturer.name, [])
|
||||
if default_target in targets_in_group:
|
||||
group = g
|
||||
break
|
||||
if group is None:
|
||||
group = 'base'
|
||||
target_entry['arch'] = group
|
||||
if group not in grouped_targets:
|
||||
grouped_targets[group] = {'container': container, 'manufacturers': {}}
|
||||
if manufacturer.name not in grouped_targets[group]['manufacturers']:
|
||||
grouped_targets[group]['manufacturers'][manufacturer.name] = []
|
||||
grouped_targets[group]['manufacturers'][manufacturer.name].append(deb_target)
|
||||
build_configs.append(target_entry)
|
||||
|
||||
if(verbose):
|
||||
import pprint
|
||||
print("============================")
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate SPDX 2.3 JSON SBOM for a PX4 firmware build.
|
||||
|
||||
Produces one SBOM per board target containing:
|
||||
- PX4 firmware as the primary package
|
||||
- Git submodules as CONTAINS dependencies
|
||||
- Python build requirements as BUILD_DEPENDENCY_OF packages
|
||||
- Board-specific modules as CONTAINS packages
|
||||
|
||||
Requires PyYAML (pyyaml) for loading license overrides.
|
||||
"""
|
||||
import argparse
|
||||
import configparser
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
# Ordered most-specific first: all keywords must appear for a match.
|
||||
LICENSE_PATTERNS = [
|
||||
# Copyleft licenses first (more specific keywords prevent false matches)
|
||||
("GPL-3.0-only", ["GNU GENERAL PUBLIC LICENSE", "Version 3"]),
|
||||
("GPL-2.0-only", ["GNU GENERAL PUBLIC LICENSE", "Version 2"]),
|
||||
("LGPL-3.0-only", ["GNU LESSER GENERAL PUBLIC LICENSE", "Version 3"]),
|
||||
("LGPL-2.1-only", ["GNU Lesser General Public License", "Version 2.1"]),
|
||||
("AGPL-3.0-only", ["GNU AFFERO GENERAL PUBLIC LICENSE", "Version 3"]),
|
||||
# Permissive licenses
|
||||
("Apache-2.0", ["Apache License", "Version 2.0"]),
|
||||
("MIT", ["Permission is hereby granted"]),
|
||||
("BSD-3-Clause", ["Redistribution and use", "Neither the name"]),
|
||||
("BSD-2-Clause", ["Redistribution and use", "THIS SOFTWARE IS PROVIDED"]),
|
||||
("ISC", ["Permission to use, copy, modify, and/or distribute"]),
|
||||
("EPL-2.0", ["Eclipse Public License", "2.0"]),
|
||||
("Unlicense", ["The Unlicense", "unlicense.org"]),
|
||||
]
|
||||
|
||||
COPYLEFT_LICENSES = {
|
||||
"GPL-2.0-only", "GPL-3.0-only",
|
||||
"LGPL-2.1-only", "LGPL-3.0-only",
|
||||
"AGPL-3.0-only",
|
||||
}
|
||||
|
||||
def load_license_overrides(source_dir):
|
||||
"""Load license overrides and comments from YAML config file.
|
||||
|
||||
Returns (overrides, comments) dicts mapping submodule path to values.
|
||||
Falls back to empty dicts if the file is missing.
|
||||
"""
|
||||
yaml_path = source_dir / "Tools" / "ci" / "license-overrides.yaml"
|
||||
if not yaml_path.exists():
|
||||
return {}, {}
|
||||
|
||||
with open(yaml_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
overrides = {}
|
||||
comments = {}
|
||||
for path, entry in (data.get("overrides") or {}).items():
|
||||
overrides[path] = entry["license"]
|
||||
if "comment" in entry:
|
||||
comments[path] = entry["comment"]
|
||||
|
||||
return overrides, comments
|
||||
|
||||
LICENSE_FILENAMES = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "LICENCE.md", "COPYING", "COPYING.md"]
|
||||
|
||||
|
||||
def detect_license(submodule_dir):
|
||||
"""Auto-detect SPDX license ID from LICENSE/COPYING file in a directory.
|
||||
|
||||
Reads the first 100 lines of the first license file found and matches
|
||||
keywords against LICENSE_PATTERNS. Returns 'NOASSERTION' if no file
|
||||
is found or no pattern matches.
|
||||
"""
|
||||
for fname in LICENSE_FILENAMES:
|
||||
license_file = submodule_dir / fname
|
||||
if license_file.is_file():
|
||||
try:
|
||||
lines = license_file.read_text(errors="replace").splitlines()[:100]
|
||||
text = "\n".join(lines)
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
text_upper = text.upper()
|
||||
for spdx_id_val, keywords in LICENSE_PATTERNS:
|
||||
if all(kw.upper() in text_upper for kw in keywords):
|
||||
return spdx_id_val
|
||||
|
||||
return "NOASSERTION"
|
||||
|
||||
return "NOASSERTION"
|
||||
|
||||
|
||||
def get_submodule_license(source_dir, sub_path, license_overrides):
|
||||
"""Return the SPDX license for a submodule: override > auto-detect."""
|
||||
if sub_path in license_overrides:
|
||||
return license_overrides[sub_path]
|
||||
return detect_license(source_dir / sub_path)
|
||||
|
||||
|
||||
def spdx_id(name: str) -> str:
|
||||
"""Convert a name to a valid SPDX identifier (letters, digits, dots, hyphens)."""
|
||||
return re.sub(r"[^a-zA-Z0-9.\-]", "-", name)
|
||||
|
||||
|
||||
def parse_gitmodules(source_dir):
|
||||
"""Parse .gitmodules and return list of {name, path, url}."""
|
||||
gitmodules_path = source_dir / ".gitmodules"
|
||||
if not gitmodules_path.exists():
|
||||
return []
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(str(gitmodules_path))
|
||||
|
||||
submodules = []
|
||||
for section in config.sections():
|
||||
if section.startswith("submodule "):
|
||||
name = section.split('"')[1] if '"' in section else section.split(" ", 1)[1]
|
||||
path = config.get(section, "path", fallback="")
|
||||
url = config.get(section, "url", fallback="")
|
||||
submodules.append({"name": name, "path": path, "url": url})
|
||||
|
||||
return submodules
|
||||
|
||||
|
||||
def get_submodule_commits(source_dir):
|
||||
"""Get commit hashes for all submodules via git ls-tree -r (works without init)."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "ls-tree", "-r", "HEAD"],
|
||||
cwd=str(source_dir),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
commits = {}
|
||||
for line in result.stdout.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 4 and parts[1] == "commit":
|
||||
commits[parts[3]] = parts[2]
|
||||
|
||||
return commits
|
||||
|
||||
|
||||
def get_git_info(source_dir: Path) -> dict:
|
||||
"""Get PX4 git version and hash."""
|
||||
info = {"version": "unknown", "hash": "unknown"}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "describe", "--always", "--tags", "--dirty"],
|
||||
cwd=str(source_dir),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
info["version"] = result.stdout.strip()
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
cwd=str(source_dir),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
info["hash"] = result.stdout.strip()
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
return info
|
||||
|
||||
|
||||
def parse_requirements(requirements_path):
|
||||
"""Parse pip requirements.txt into list of {name, version_spec}."""
|
||||
if not requirements_path.exists():
|
||||
return []
|
||||
|
||||
deps = []
|
||||
for line in requirements_path.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or line.startswith("-"):
|
||||
continue
|
||||
# Split on version specifiers
|
||||
match = re.match(r"^([a-zA-Z0-9_\-]+)(.*)?$", line)
|
||||
if match:
|
||||
deps.append({
|
||||
"name": match.group(1),
|
||||
"version_spec": match.group(2).strip() if match.group(2) else "",
|
||||
})
|
||||
return deps
|
||||
|
||||
|
||||
def read_module_list(modules_file, source_dir):
|
||||
"""Read board-specific module list from file.
|
||||
|
||||
Paths may be absolute; they are converted to relative paths under src/.
|
||||
Duplicates are removed while preserving order.
|
||||
"""
|
||||
if not modules_file or not modules_file.exists():
|
||||
return []
|
||||
|
||||
seen = set()
|
||||
modules = []
|
||||
source_str = str(source_dir.resolve()) + "/"
|
||||
|
||||
for line in modules_file.read_text().splitlines():
|
||||
path = line.strip()
|
||||
if not path or path.startswith("#"):
|
||||
continue
|
||||
# Convert absolute path to relative
|
||||
if path.startswith(source_str):
|
||||
path = path[len(source_str):]
|
||||
if path not in seen:
|
||||
seen.add(path)
|
||||
modules.append(path)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def make_purl(pkg_type: str, namespace: str, name: str, version: str = "") -> str:
|
||||
"""Construct a Package URL (purl)."""
|
||||
purl = f"pkg:{pkg_type}/{namespace}/{name}"
|
||||
if version:
|
||||
purl += f"@{version}"
|
||||
return purl
|
||||
|
||||
|
||||
def extract_git_host_org_repo(url):
|
||||
"""Extract host type, org, and repo from a git URL.
|
||||
|
||||
Returns (host, org, repo) where host is 'github', 'gitlab', or ''.
|
||||
"""
|
||||
match = re.search(r"github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$", url)
|
||||
if match:
|
||||
return "github", match.group(1), match.group(2)
|
||||
match = re.search(r"gitlab\.com[:/](.+?)/([^/]+?)(?:\.git)?$", url)
|
||||
if match:
|
||||
return "gitlab", match.group(1), match.group(2)
|
||||
return "", "", ""
|
||||
|
||||
|
||||
def generate_sbom(source_dir, board, modules_file, compiler, platform=""):
|
||||
"""Generate a complete SPDX 2.3 JSON document."""
|
||||
license_overrides, license_comments = load_license_overrides(source_dir)
|
||||
git_info = get_git_info(source_dir)
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Deterministic namespace using UUID5 from git hash + board
|
||||
ns_seed = f"{git_info['hash']}:{board}"
|
||||
doc_namespace = f"https://spdx.org/spdxdocs/{board}-{uuid.uuid5(uuid.NAMESPACE_URL, ns_seed)}"
|
||||
|
||||
doc = {
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": f"PX4 Firmware SBOM for {board}",
|
||||
"documentNamespace": doc_namespace,
|
||||
"creationInfo": {
|
||||
"created": timestamp,
|
||||
"creators": [
|
||||
"Tool: px4-generate-sbom",
|
||||
"Organization: Dronecode Foundation",
|
||||
],
|
||||
"licenseListVersion": "3.22",
|
||||
},
|
||||
"packages": [],
|
||||
"relationships": [],
|
||||
}
|
||||
|
||||
# Primary package: PX4 firmware
|
||||
primary_spdx_id = f"SPDXRef-PX4-{spdx_id(board)}"
|
||||
doc["packages"].append({
|
||||
"SPDXID": primary_spdx_id,
|
||||
"name": board,
|
||||
"versionInfo": git_info["version"],
|
||||
"packageFileName": f"{board}.px4",
|
||||
"supplier": "Organization: Dronecode Foundation",
|
||||
"downloadLocation": "https://github.com/PX4/PX4-Autopilot",
|
||||
"filesAnalyzed": False,
|
||||
"primaryPackagePurpose": "FIRMWARE",
|
||||
"licenseConcluded": "BSD-3-Clause",
|
||||
"licenseDeclared": "BSD-3-Clause",
|
||||
"copyrightText": "Copyright (c) PX4 Development Team",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": make_purl(
|
||||
"github", "PX4", "PX4-Autopilot", git_info["version"]
|
||||
),
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
doc["relationships"].append({
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relationshipType": "DESCRIBES",
|
||||
"relatedSpdxElement": primary_spdx_id,
|
||||
})
|
||||
|
||||
# Git submodules (filtered to those relevant to this board's modules)
|
||||
submodules = parse_gitmodules(source_dir)
|
||||
submodule_commits = get_submodule_commits(source_dir)
|
||||
modules = read_module_list(modules_file, source_dir)
|
||||
|
||||
def submodule_is_relevant(sub_path):
|
||||
"""A submodule is relevant if any board module path overlaps with it."""
|
||||
# NuttX platform submodules are only relevant for NuttX builds
|
||||
if sub_path.startswith("platforms/nuttx/"):
|
||||
return platform in ("nuttx", "")
|
||||
if not modules:
|
||||
return True # no module list means include all
|
||||
# Other platform submodules are always relevant
|
||||
if sub_path.startswith("platforms/"):
|
||||
return True
|
||||
for mod in modules:
|
||||
# Module is under this submodule, or submodule is under a module
|
||||
if mod.startswith(sub_path + "/") or sub_path.startswith(mod + "/"):
|
||||
return True
|
||||
return False
|
||||
|
||||
for sub in submodules:
|
||||
if not submodule_is_relevant(sub["path"]):
|
||||
continue
|
||||
sub_path = sub["path"]
|
||||
sub_path_id = sub_path.replace("/", "-")
|
||||
sub_spdx_id = f"SPDXRef-Submodule-{spdx_id(sub_path_id)}"
|
||||
commit = submodule_commits.get(sub_path, "unknown")
|
||||
license_id = get_submodule_license(source_dir, sub_path, license_overrides)
|
||||
|
||||
host, org, repo = extract_git_host_org_repo(sub["url"])
|
||||
download = sub["url"] if sub["url"] else "NOASSERTION"
|
||||
|
||||
# Use repo name from URL for human-readable name, fall back to last path component
|
||||
display_name = repo if repo else sub_path.rsplit("/", 1)[-1]
|
||||
|
||||
pkg = {
|
||||
"SPDXID": sub_spdx_id,
|
||||
"name": display_name,
|
||||
"versionInfo": commit,
|
||||
"supplier": f"Organization: {org}" if org else "NOASSERTION",
|
||||
"downloadLocation": download,
|
||||
"filesAnalyzed": False,
|
||||
"licenseConcluded": license_id,
|
||||
"licenseDeclared": license_id,
|
||||
"copyrightText": "NOASSERTION",
|
||||
}
|
||||
|
||||
comment = license_comments.get(sub_path)
|
||||
if comment:
|
||||
pkg["licenseComments"] = comment
|
||||
|
||||
if host and org and repo:
|
||||
pkg["externalRefs"] = [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": make_purl(host, org, repo, commit),
|
||||
}
|
||||
]
|
||||
|
||||
doc["packages"].append(pkg)
|
||||
doc["relationships"].append({
|
||||
"spdxElementId": primary_spdx_id,
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": sub_spdx_id,
|
||||
})
|
||||
|
||||
# Python build dependencies
|
||||
requirements_path = source_dir / "Tools" / "setup" / "requirements.txt"
|
||||
py_deps = parse_requirements(requirements_path)
|
||||
|
||||
for dep in py_deps:
|
||||
dep_name = dep["name"]
|
||||
dep_spdx_id = f"SPDXRef-PyDep-{spdx_id(dep_name)}"
|
||||
version_str = dep["version_spec"] if dep["version_spec"] else "NOASSERTION"
|
||||
|
||||
doc["packages"].append({
|
||||
"SPDXID": dep_spdx_id,
|
||||
"name": dep_name,
|
||||
"versionInfo": version_str,
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": f"https://pypi.org/project/{dep_name}/",
|
||||
"filesAnalyzed": False,
|
||||
"primaryPackagePurpose": "APPLICATION",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": f"pkg:pypi/{dep_name}",
|
||||
}
|
||||
],
|
||||
})
|
||||
doc["relationships"].append({
|
||||
"spdxElementId": dep_spdx_id,
|
||||
"relationshipType": "BUILD_DEPENDENCY_OF",
|
||||
"relatedSpdxElement": primary_spdx_id,
|
||||
})
|
||||
|
||||
# Board-specific modules (already read above for submodule filtering)
|
||||
for mod in modules:
|
||||
mod_path_id = mod.replace("/", "-")
|
||||
mod_spdx_id = f"SPDXRef-Module-{spdx_id(mod_path_id)}"
|
||||
|
||||
# Derive short name: strip leading src/ for readability
|
||||
display_name = mod
|
||||
if display_name.startswith("src/"):
|
||||
display_name = display_name[4:]
|
||||
|
||||
doc["packages"].append({
|
||||
"SPDXID": mod_spdx_id,
|
||||
"name": display_name,
|
||||
"versionInfo": git_info["version"],
|
||||
"supplier": "Organization: Dronecode Foundation",
|
||||
"downloadLocation": "https://github.com/PX4/PX4-Autopilot",
|
||||
"filesAnalyzed": False,
|
||||
"licenseConcluded": "BSD-3-Clause",
|
||||
"licenseDeclared": "BSD-3-Clause",
|
||||
"copyrightText": "NOASSERTION",
|
||||
})
|
||||
doc["relationships"].append({
|
||||
"spdxElementId": primary_spdx_id,
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": mod_spdx_id,
|
||||
})
|
||||
|
||||
# Compiler as a build tool
|
||||
if compiler:
|
||||
compiler_spdx_id = f"SPDXRef-Compiler-{spdx_id(compiler)}"
|
||||
doc["packages"].append({
|
||||
"SPDXID": compiler_spdx_id,
|
||||
"name": compiler,
|
||||
"versionInfo": "NOASSERTION",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": False,
|
||||
"primaryPackagePurpose": "APPLICATION",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
})
|
||||
doc["relationships"].append({
|
||||
"spdxElementId": compiler_spdx_id,
|
||||
"relationshipType": "BUILD_TOOL_OF",
|
||||
"relatedSpdxElement": primary_spdx_id,
|
||||
})
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def verify_licenses(source_dir):
|
||||
"""Verify license detection for all submodules. Returns exit code."""
|
||||
license_overrides, _ = load_license_overrides(source_dir)
|
||||
submodules = parse_gitmodules(source_dir)
|
||||
if not submodules:
|
||||
print("No submodules found in .gitmodules")
|
||||
return 1
|
||||
|
||||
has_noassertion = False
|
||||
print(f"{'Submodule Path':<65} {'Detected':<16} {'Override':<16} {'Final'}")
|
||||
print("-" * 115)
|
||||
|
||||
for sub in submodules:
|
||||
sub_path = sub["path"]
|
||||
sub_dir = source_dir / sub_path
|
||||
|
||||
checked_out = sub_dir.is_dir() and any(sub_dir.iterdir())
|
||||
has_explicit_override = sub_path in license_overrides
|
||||
if not checked_out:
|
||||
detected = "(not checked out)"
|
||||
override = license_overrides.get(sub_path, "")
|
||||
final = override if override else "NOASSERTION"
|
||||
else:
|
||||
detected = detect_license(sub_dir)
|
||||
override = license_overrides.get(sub_path, "")
|
||||
final = override if override else detected
|
||||
|
||||
if final == "NOASSERTION" and has_explicit_override:
|
||||
# Explicitly acknowledged in overrides file — not a failure
|
||||
marker = " (acknowledged)"
|
||||
elif final == "NOASSERTION" and checked_out:
|
||||
has_noassertion = True
|
||||
marker = " <-- UNRESOLVED"
|
||||
elif final == "NOASSERTION" and not checked_out:
|
||||
marker = " (skipped)"
|
||||
else:
|
||||
marker = ""
|
||||
|
||||
print(f"{sub_path:<65} {str(detected):<16} {str(override) if override else '':<16} {final}{marker}")
|
||||
|
||||
# Copyleft warning (informational, not a failure)
|
||||
copyleft_found = []
|
||||
for sub in submodules:
|
||||
sub_path = sub["path"]
|
||||
sub_dir = source_dir / sub_path
|
||||
checked_out = sub_dir.is_dir() and any(sub_dir.iterdir())
|
||||
override = license_overrides.get(sub_path, "")
|
||||
if checked_out:
|
||||
final_lic = override if override else detect_license(sub_dir)
|
||||
else:
|
||||
final_lic = override if override else "NOASSERTION"
|
||||
for cl in COPYLEFT_LICENSES:
|
||||
if cl in final_lic:
|
||||
copyleft_found.append((sub_path, final_lic))
|
||||
break
|
||||
|
||||
print()
|
||||
if copyleft_found:
|
||||
print("Copyleft licenses detected (informational):")
|
||||
for path, lic in copyleft_found:
|
||||
print(f" {path}: {lic}")
|
||||
print()
|
||||
|
||||
if has_noassertion:
|
||||
print("FAIL: Some submodules have unresolved licenses. "
|
||||
"Add an entry to Tools/ci/license-overrides.yaml or check the LICENSE file.")
|
||||
return 1
|
||||
|
||||
print("OK: All submodules have a resolved license.")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate SPDX 2.3 JSON SBOM for PX4 firmware"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source-dir",
|
||||
type=Path,
|
||||
default=Path.cwd(),
|
||||
help="PX4 source directory (default: cwd)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verify-licenses",
|
||||
action="store_true",
|
||||
help="Verify license detection for all submodules and exit",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--board",
|
||||
default=None,
|
||||
help="Board target name (e.g. px4_fmu-v5x_default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modules-file",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Path to config_module_list.txt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--compiler",
|
||||
default="",
|
||||
help="Compiler identifier (e.g. arm-none-eabi-gcc)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
default="",
|
||||
help="PX4 platform (nuttx, posix, qurt). Filters platform-specific submodules.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Output SBOM file path",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verify_licenses:
|
||||
raise SystemExit(verify_licenses(args.source_dir))
|
||||
|
||||
if not args.board:
|
||||
parser.error("--board is required when not using --verify-licenses")
|
||||
if not args.output:
|
||||
parser.error("--output is required when not using --verify-licenses")
|
||||
|
||||
sbom = generate_sbom(
|
||||
source_dir=args.source_dir,
|
||||
board=args.board,
|
||||
modules_file=args.modules_file,
|
||||
compiler=args.compiler,
|
||||
platform=args.platform,
|
||||
)
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(sbom, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
pkg_count = len(sbom["packages"])
|
||||
print(f"SBOM generated: {args.output} ({pkg_count} packages)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect a PX4 SPDX SBOM file.
|
||||
|
||||
Usage:
|
||||
inspect_sbom.py <sbom.spdx.json> # full summary
|
||||
inspect_sbom.py <sbom.spdx.json> search <term> # search packages by name
|
||||
inspect_sbom.py <sbom.spdx.json> ntia # NTIA minimum elements check
|
||||
inspect_sbom.py <sbom.spdx.json> licenses # license summary
|
||||
inspect_sbom.py <sbom.spdx.json> list <type> # list packages (Submodule|PyDep|Module|all)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load(path):
|
||||
return json.loads(Path(path).read_text())
|
||||
|
||||
|
||||
def pkg_type(pkg):
|
||||
spdx_id = pkg["SPDXID"]
|
||||
for prefix in ("Submodule", "PyDep", "Module", "Compiler", "PX4"):
|
||||
if f"-{prefix}-" in spdx_id or spdx_id.startswith(f"SPDXRef-{prefix}"):
|
||||
return prefix
|
||||
return "Other"
|
||||
|
||||
|
||||
def summary(doc):
|
||||
print(f"spdxVersion: {doc['spdxVersion']}")
|
||||
print(f"name: {doc['name']}")
|
||||
print(f"namespace: {doc['documentNamespace']}")
|
||||
print(f"created: {doc['creationInfo']['created']}")
|
||||
print(f"creators: {', '.join(doc['creationInfo']['creators'])}")
|
||||
print()
|
||||
|
||||
types = Counter(pkg_type(p) for p in doc["packages"])
|
||||
print(f"Packages: {len(doc['packages'])}")
|
||||
for t, c in types.most_common():
|
||||
print(f" {t}: {c}")
|
||||
print()
|
||||
|
||||
rc = Counter(r["relationshipType"] for r in doc["relationships"])
|
||||
print(f"Relationships: {len(doc['relationships'])}")
|
||||
for t, n in rc.most_common():
|
||||
print(f" {t}: {n}")
|
||||
print()
|
||||
|
||||
primary = doc["packages"][0]
|
||||
print(f"Primary package:")
|
||||
print(f" name: {primary['name']}")
|
||||
print(f" version: {primary['versionInfo']}")
|
||||
print(f" purpose: {primary.get('primaryPackagePurpose', 'N/A')}")
|
||||
print(f" license: {primary['licenseDeclared']}")
|
||||
print()
|
||||
|
||||
noassert = [
|
||||
p["name"]
|
||||
for p in doc["packages"]
|
||||
if pkg_type(p) == "Submodule" and p["licenseDeclared"] == "NOASSERTION"
|
||||
]
|
||||
if noassert:
|
||||
print(f"WARNING: {len(noassert)} submodules with NOASSERTION license:")
|
||||
for n in noassert:
|
||||
print(f" - {n}")
|
||||
else:
|
||||
print("All submodule licenses mapped")
|
||||
|
||||
print(f"\nFile size: {Path(sys.argv[1]).stat().st_size // 1024}KB")
|
||||
|
||||
|
||||
def search(doc, term):
|
||||
term = term.lower()
|
||||
found = [p for p in doc["packages"] if term in p["name"].lower()]
|
||||
if not found:
|
||||
print(f"No packages matching '{term}'")
|
||||
return
|
||||
print(f"Found {len(found)} packages matching '{term}':\n")
|
||||
for p in found:
|
||||
print(json.dumps(p, indent=2))
|
||||
print()
|
||||
|
||||
|
||||
def ntia_check(doc):
|
||||
required = ["SPDXID", "name", "versionInfo", "supplier", "downloadLocation"]
|
||||
missing = []
|
||||
for p in doc["packages"]:
|
||||
for f in required:
|
||||
if f not in p or p[f] in ("", None):
|
||||
missing.append((p["name"], f))
|
||||
|
||||
if missing:
|
||||
print(f"FAIL: {len(missing)} missing fields:")
|
||||
for name, field in missing:
|
||||
print(f" {name}: missing {field}")
|
||||
else:
|
||||
print(f"PASS: All {len(doc['packages'])} packages have required fields")
|
||||
|
||||
print(f"\nCreators: {doc['creationInfo']['creators']}")
|
||||
print(f"Timestamp: {doc['creationInfo']['created']}")
|
||||
|
||||
rels = [r for r in doc["relationships"] if r["relationshipType"] == "DESCRIBES"]
|
||||
print(f"DESCRIBES relationships: {len(rels)}")
|
||||
|
||||
return len(missing) == 0
|
||||
|
||||
|
||||
def licenses(doc):
|
||||
by_license = {}
|
||||
for p in doc["packages"]:
|
||||
lic = p.get("licenseDeclared", "NOASSERTION")
|
||||
by_license.setdefault(lic, []).append(p["name"])
|
||||
|
||||
for lic in sorted(by_license.keys()):
|
||||
names = by_license[lic]
|
||||
print(f"\n{lic} ({len(names)}):")
|
||||
for n in sorted(names):
|
||||
print(f" {n}")
|
||||
|
||||
|
||||
def list_packages(doc, filter_type):
|
||||
filter_type = filter_type.lower()
|
||||
for p in sorted(doc["packages"], key=lambda x: x["name"]):
|
||||
t = pkg_type(p)
|
||||
if filter_type != "all" and t.lower() != filter_type:
|
||||
continue
|
||||
lic = p.get("licenseDeclared", "?")
|
||||
ver = p["versionInfo"][:20] if len(p["versionInfo"]) > 20 else p["versionInfo"]
|
||||
print(f" {t:10s} {p['name']:50s} {ver:20s} {lic}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
doc = load(sys.argv[1])
|
||||
cmd = sys.argv[2] if len(sys.argv) > 2 else "summary"
|
||||
|
||||
if cmd == "summary":
|
||||
summary(doc)
|
||||
elif cmd == "search":
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: inspect_sbom.py <file> search <term>")
|
||||
sys.exit(1)
|
||||
search(doc, sys.argv[3])
|
||||
elif cmd == "ntia":
|
||||
if not ntia_check(doc):
|
||||
sys.exit(1)
|
||||
elif cmd == "licenses":
|
||||
licenses(doc)
|
||||
elif cmd == "list":
|
||||
filter_type = sys.argv[3] if len(sys.argv) > 3 else "all"
|
||||
list_packages(doc, filter_type)
|
||||
else:
|
||||
print(f"Unknown command: {cmd}")
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,62 +0,0 @@
|
||||
# SPDX license overrides for submodules where auto-detection fails or is wrong.
|
||||
# Each entry maps a submodule path to its SPDX license identifier and an
|
||||
# optional comment explaining why the override exists.
|
||||
#
|
||||
# Run `python3 Tools/ci/generate_sbom.py --verify-licenses` to validate.
|
||||
|
||||
overrides:
|
||||
src/modules/mavlink/mavlink:
|
||||
license: "LGPL-3.0-only AND MIT"
|
||||
comment: "Generator is LGPL-3.0; PX4 ships only MIT-licensed generated headers."
|
||||
|
||||
Tools/simulation/gazebo-classic/sitl_gazebo-classic:
|
||||
license: "BSD-3-Clause"
|
||||
comment: >-
|
||||
PX4 org project. No LICENSE file in repo; source files carry
|
||||
BSD-3-Clause headers consistent with the PX4 project license.
|
||||
|
||||
src/lib/cdrstream/cyclonedds:
|
||||
license: "EPL-2.0 OR BSD-3-Clause"
|
||||
comment: >-
|
||||
Dual-licensed. PX4 elects BSD-3-Clause.
|
||||
No board currently enables CONFIG_LIB_CDRSTREAM.
|
||||
|
||||
src/lib/cdrstream/rosidl:
|
||||
license: "Apache-2.0"
|
||||
|
||||
src/lib/crypto/monocypher:
|
||||
license: "BSD-2-Clause OR CC0-1.0"
|
||||
comment: >-
|
||||
Dual-licensed. LICENCE.md offers BSD-2-Clause with CC0-1.0 as
|
||||
public domain fallback.
|
||||
|
||||
src/lib/crypto/libtomcrypt:
|
||||
license: "Unlicense"
|
||||
comment: "Public domain dedication. Functionally equivalent to Unlicense."
|
||||
|
||||
src/lib/crypto/libtommath:
|
||||
license: "Unlicense"
|
||||
comment: "Public domain dedication. Functionally equivalent to Unlicense."
|
||||
|
||||
platforms/nuttx/NuttX/nuttx:
|
||||
license: "Apache-2.0"
|
||||
comment: >-
|
||||
Composite LICENSE (6652 lines) includes BSD/MIT/ISC sub-components.
|
||||
Primary license is Apache-2.0. NOTICE file contains FAT LFN patent warnings.
|
||||
|
||||
platforms/nuttx/NuttX/apps:
|
||||
license: "Apache-2.0"
|
||||
|
||||
boards/modalai/voxl2/libfc-sensor-api:
|
||||
license: "NOASSERTION"
|
||||
comment: >-
|
||||
No LICENSE file in repo. README describes it as public interface
|
||||
for proprietary sensor library.
|
||||
|
||||
boards/modalai/voxl2/src/lib/mpa/libmodal-json:
|
||||
license: "LGPL-3.0-only"
|
||||
comment: "LGPL-3.0 weak copyleft. Used via header includes in VOXL2 mpa library."
|
||||
|
||||
boards/modalai/voxl2/src/lib/mpa/libmodal-pipe:
|
||||
license: "LGPL-3.0-only"
|
||||
comment: "LGPL-3.0 weak copyleft. Used via header includes in VOXL2 mpa library."
|
||||
@@ -123,7 +123,7 @@ generate_parameters() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make parameters_metadata
|
||||
else
|
||||
make parameters_metadata >/dev/null
|
||||
make parameters_metadata >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ generate_airframes() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make airframe_metadata
|
||||
else
|
||||
make airframe_metadata >/dev/null
|
||||
make airframe_metadata >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ generate_modules() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make module_documentation
|
||||
else
|
||||
make module_documentation >/dev/null
|
||||
make module_documentation >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ generate_msg_docs() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make msg_docs
|
||||
else
|
||||
make msg_docs >/dev/null
|
||||
make msg_docs >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ generate_uorb_graphs() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make uorb_graphs
|
||||
else
|
||||
make uorb_graphs >/dev/null
|
||||
make uorb_graphs >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ generate_failsafe_web() {
|
||||
if [[ "$VERBOSE" == "true" ]]; then
|
||||
make failsafe_web
|
||||
else
|
||||
make failsafe_web >/dev/null
|
||||
make failsafe_web >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
mkdir artifacts
|
||||
cp **/**/*.px4 artifacts/ 2>/dev/null || true
|
||||
cp **/**/*.elf artifacts/ 2>/dev/null || true
|
||||
cp **/**/*.deb artifacts/ 2>/dev/null || true
|
||||
for build_dir_path in build/*/ ; do
|
||||
build_dir_path=${build_dir_path::${#build_dir_path}-1}
|
||||
build_dir=${build_dir_path#*/}
|
||||
mkdir -p artifacts/$build_dir
|
||||
mkdir artifacts/$build_dir
|
||||
find artifacts/ -maxdepth 1 -type f -name "*$build_dir*"
|
||||
# Airframe (NuttX: build root, SITL: docs/ subdirectory)
|
||||
airframes_src=""
|
||||
if [ -f "$build_dir_path/airframes.xml" ]; then
|
||||
airframes_src="$build_dir_path/airframes.xml"
|
||||
elif [ -f "$build_dir_path/docs/airframes.xml" ]; then
|
||||
airframes_src="$build_dir_path/docs/airframes.xml"
|
||||
fi
|
||||
if [ -n "$airframes_src" ]; then
|
||||
cp "$airframes_src" "artifacts/$build_dir/"
|
||||
fi
|
||||
# Airframe
|
||||
cp $build_dir_path/airframes.xml artifacts/$build_dir/
|
||||
# Parameters
|
||||
cp $build_dir_path/parameters.xml artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/parameters.json artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/parameters.json.xz artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/parameters.xml artifacts/$build_dir/
|
||||
cp $build_dir_path/parameters.json artifacts/$build_dir/
|
||||
cp $build_dir_path/parameters.json.xz artifacts/$build_dir/
|
||||
# Actuators
|
||||
cp $build_dir_path/actuators.json artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/actuators.json.xz artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/actuators.json artifacts/$build_dir/
|
||||
cp $build_dir_path/actuators.json.xz artifacts/$build_dir/
|
||||
# Events
|
||||
mkdir -p artifacts/$build_dir/events/
|
||||
cp $build_dir_path/events/all_events.json.xz artifacts/$build_dir/events/ 2>/dev/null || true
|
||||
# Also copy to top level: firmware advertises the metadata URI without the events/ subdirectory
|
||||
# (see src/lib/component_information/CMakeLists.txt comp_metadata_events_uri_board)
|
||||
cp $build_dir_path/events/all_events.json.xz artifacts/$build_dir/ 2>/dev/null || true
|
||||
# SBOM
|
||||
cp $build_dir_path/*.sbom.spdx.json artifacts/$build_dir/ 2>/dev/null || true
|
||||
cp $build_dir_path/events/all_events.json.xz artifacts/$build_dir/
|
||||
# ROS 2 msgs
|
||||
cp $build_dir_path/events/all_events.json.xz artifacts/$build_dir/
|
||||
# Module Docs
|
||||
ls -la artifacts/$build_dir
|
||||
echo "----------"
|
||||
done
|
||||
|
||||
if [ -d artifacts/px4_sitl_default ]; then
|
||||
# general metadata (used by Flight Review and other downstream consumers)
|
||||
mkdir -p artifacts/_general/
|
||||
# general metadata
|
||||
mkdir artifacts/_general/
|
||||
cp artifacts/px4_sitl_default/airframes.xml artifacts/_general/
|
||||
# Airframe
|
||||
if [ -f artifacts/px4_sitl_default/airframes.xml ]; then
|
||||
cp artifacts/px4_sitl_default/airframes.xml artifacts/_general/
|
||||
else
|
||||
echo "Error: expected 'artifacts/px4_sitl_default/airframes.xml' not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
cp artifacts/px4_sitl_default/airframes.xml artifacts/_general/
|
||||
# Parameters
|
||||
cp artifacts/px4_sitl_default/parameters.xml artifacts/_general/
|
||||
cp artifacts/px4_sitl_default/parameters.json artifacts/_general/
|
||||
@@ -56,11 +40,9 @@ if [ -d artifacts/px4_sitl_default ]; then
|
||||
cp artifacts/px4_sitl_default/actuators.json artifacts/_general/
|
||||
cp artifacts/px4_sitl_default/actuators.json.xz artifacts/_general/
|
||||
# Events
|
||||
if [ -f artifacts/px4_sitl_default/events/all_events.json.xz ]; then
|
||||
cp artifacts/px4_sitl_default/events/all_events.json.xz artifacts/_general/
|
||||
else
|
||||
echo "Error: expected 'artifacts/px4_sitl_default/events/all_events.json.xz' not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
cp artifacts/px4_sitl_default/events/all_events.json.xz artifacts/_general/
|
||||
# ROS 2 msgs
|
||||
cp artifacts/px4_sitl_default/events/all_events.json.xz artifacts/_general/
|
||||
# Module Docs
|
||||
ls -la artifacts/_general/
|
||||
fi
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Post, update, or delete a PR comment with deduplication.
|
||||
|
||||
Uses hidden HTML markers to find existing comments and avoid duplicates.
|
||||
Reads comment body from stdin when posting or updating.
|
||||
|
||||
Usage:
|
||||
echo "comment body" | python3 pr_comment.py --marker pr-title --pr 123 --result fail
|
||||
python3 pr_comment.py --marker pr-title --pr 123 --result pass
|
||||
|
||||
Results:
|
||||
fail - post/update comment with body from stdin
|
||||
warn - post/update comment with body from stdin
|
||||
pass - delete existing comment if any
|
||||
|
||||
Requires GH_TOKEN and GITHUB_REPOSITORY environment variables.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def gh_api(endpoint: str, method: str = 'GET', body: dict | None = None) -> str:
|
||||
"""Call the GitHub API via gh cli."""
|
||||
cmd = ['gh', 'api', endpoint, '-X', method]
|
||||
if body:
|
||||
for key, value in body.items():
|
||||
cmd.extend(['-f', f'{key}={value}'])
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0 and method != 'DELETE':
|
||||
print(f"gh api error: {result.stderr}", file=sys.stderr)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def find_comment(repo: str, pr: int, marker: str) -> str | None:
|
||||
"""Find an existing comment by its hidden marker. Returns comment ID or None."""
|
||||
response = gh_api(f"repos/{repo}/issues/{pr}/comments?per_page=100")
|
||||
try:
|
||||
comments = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
if not isinstance(comments, list):
|
||||
return None
|
||||
|
||||
for comment in comments:
|
||||
if isinstance(comment, dict) and comment.get('body', '').startswith(marker):
|
||||
return str(comment['id'])
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description='Manage PR quality comments')
|
||||
parser.add_argument('--marker', required=True,
|
||||
help='Marker name (e.g. pr-title, commit-msgs, pr-body)')
|
||||
parser.add_argument('--pr', required=True, type=int,
|
||||
help='Pull request number')
|
||||
parser.add_argument('--result', required=True, choices=['pass', 'fail', 'warn'],
|
||||
help='Check result: pass deletes comment, fail/warn posts it')
|
||||
args = parser.parse_args()
|
||||
|
||||
repo = os.environ.get('GITHUB_REPOSITORY', '')
|
||||
if not repo:
|
||||
print("GITHUB_REPOSITORY not set", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
marker = f"<!-- commit-quality-{args.marker} -->"
|
||||
existing_id = find_comment(repo, args.pr, marker)
|
||||
|
||||
if args.result == 'pass':
|
||||
if existing_id:
|
||||
gh_api(f"repos/{repo}/issues/comments/{existing_id}", method='DELETE')
|
||||
return
|
||||
|
||||
# Read comment body from stdin
|
||||
body_content = sys.stdin.read().strip()
|
||||
if not body_content:
|
||||
print("No comment body provided on stdin", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
full_body = f"{marker}\n{body_content}"
|
||||
|
||||
if existing_id:
|
||||
gh_api(f"repos/{repo}/issues/comments/{existing_id}", method='PATCH',
|
||||
body={'body': full_body})
|
||||
else:
|
||||
gh_api(f"repos/{repo}/issues/{args.pr}/comments", method='POST',
|
||||
body={'body': full_body})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,147 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run clang-tidy incrementally on files changed in a PR.
|
||||
|
||||
Usage: run-clang-tidy-pr.py <base-ref>
|
||||
base-ref: e.g. origin/main
|
||||
|
||||
Computes the set of translation units (TUs) affected by the PR diff,
|
||||
then invokes Tools/run-clang-tidy.py on that subset only.
|
||||
Exits 0 silently when no C++ files were changed.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
EXTENSIONS_CPP = {'.cpp', '.c'}
|
||||
EXTENSIONS_HDR = {'.hpp', '.h'}
|
||||
# Manual exclusions from Makefile:508
|
||||
EXCLUDE_EXTRA = '|'.join([
|
||||
'src/systemcmds/tests',
|
||||
'src/examples',
|
||||
'src/modules/gyro_fft/CMSIS_5',
|
||||
'src/lib/drivers/smbus',
|
||||
'src/drivers/gpio',
|
||||
r'src/modules/commander/failsafe/emscripten',
|
||||
r'failsafe_test\.dir',
|
||||
])
|
||||
|
||||
|
||||
def repo_root():
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
['git', 'rev-parse', '--show-toplevel'], text=True).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
print('error: not inside a git repository', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def changed_files(base_ref, root):
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
['git', 'diff', '--name-only', f'{base_ref}...HEAD',
|
||||
'--', '*.cpp', '*.hpp', '*.h', '*.c'],
|
||||
text=True, cwd=root).strip()
|
||||
return out.splitlines() if out else []
|
||||
except subprocess.CalledProcessError:
|
||||
print(f'error: could not diff against "{base_ref}" — '
|
||||
'is the ref valid and fetched?', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def submodule_paths(root):
|
||||
# Returns [] if .gitmodules is absent or has no paths — both valid
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
['git', 'config', '--file', '.gitmodules',
|
||||
'--get-regexp', 'path'],
|
||||
text=True, cwd=root).strip()
|
||||
return [line.split()[1] for line in out.splitlines()]
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
|
||||
def build_exclude(root):
|
||||
submodules = '|'.join(submodule_paths(root))
|
||||
return f'{submodules}|{EXCLUDE_EXTRA}' if submodules else EXCLUDE_EXTRA
|
||||
|
||||
|
||||
def load_db(build_dir):
|
||||
db_path = os.path.join(build_dir, 'compile_commands.json')
|
||||
if not os.path.isfile(db_path):
|
||||
print(f'error: {db_path} not found', file=sys.stderr)
|
||||
print('Run "make px4_sitl_default-clang" first to generate '
|
||||
'the compilation database', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
with open(db_path) as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f'error: compile_commands.json is malformed: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def find_tus(changed, db, root):
|
||||
db_files = {e['file'] for e in db}
|
||||
result = set()
|
||||
for f in changed:
|
||||
abs_path = os.path.join(root, f)
|
||||
ext = os.path.splitext(f)[1]
|
||||
if ext in EXTENSIONS_CPP:
|
||||
if abs_path in db_files:
|
||||
result.add(abs_path)
|
||||
elif ext in EXTENSIONS_HDR:
|
||||
hdr = os.path.basename(f)
|
||||
for e in db:
|
||||
try:
|
||||
if hdr in open(e['file']).read():
|
||||
result.add(e['file'])
|
||||
except OSError:
|
||||
pass # file deleted in PR — skip
|
||||
return sorted(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument('base_ref',
|
||||
help='Git ref to diff against, e.g. origin/main')
|
||||
args = parser.parse_args()
|
||||
|
||||
root = repo_root()
|
||||
build_dir = os.path.join(root, 'build', 'px4_sitl_default-clang')
|
||||
|
||||
run_tidy = os.path.join(root, 'Tools', 'run-clang-tidy.py')
|
||||
if not os.path.isfile(run_tidy):
|
||||
print(f'error: {run_tidy} not found', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
changed = changed_files(args.base_ref, root)
|
||||
if not changed:
|
||||
print('No C++ files changed — skipping clang-tidy')
|
||||
sys.exit(0)
|
||||
|
||||
db = load_db(build_dir)
|
||||
tus = find_tus(changed, db, root)
|
||||
|
||||
if not tus:
|
||||
print('No matching TUs in compile_commands.json — skipping clang-tidy')
|
||||
sys.exit(0)
|
||||
|
||||
print(f'Running clang-tidy on {len(tus)} translation unit(s)')
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, run_tidy,
|
||||
'-header-filter=.*\\.hpp',
|
||||
'-j0',
|
||||
f'-exclude={build_exclude(root)}',
|
||||
'-p', build_dir] + tus
|
||||
)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
# This script runs the fuzz tests from a given binary for a certain amount of time
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Rewrite the container's apt sources to point at the AWS regional Ubuntu
|
||||
# mirror that is local to the runs-on instance.
|
||||
#
|
||||
# The default archive.ubuntu.com round-robin sometimes serves out-of-sync
|
||||
# index files mid-sync, breaking apt-get update with errors like:
|
||||
# File has unexpected size (25378 != 25381). Mirror sync in progress?
|
||||
# The Canonical-operated EC2 mirrors are region-local and sync aggressively,
|
||||
# eliminating that failure mode.
|
||||
#
|
||||
# This script is a no-op outside runs-on, so it is safe to call from any CI
|
||||
# job (forks, self-hosted runners, local docker runs, etc.) without changing
|
||||
# behavior there.
|
||||
#
|
||||
# Usage (from a workflow step running inside the container):
|
||||
# ./Tools/ci/use_aws_apt_mirror.sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$RUNS_ON_AWS_REGION" ]; then
|
||||
echo "use_aws_apt_mirror: not running on runs-on (RUNS_ON_AWS_REGION unset), skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MIRROR="http://${RUNS_ON_AWS_REGION}.ec2.archive.ubuntu.com/ubuntu"
|
||||
echo "use_aws_apt_mirror: rewriting apt sources to ${MIRROR}"
|
||||
|
||||
# Noble (24.04+) uses the deb822 format at /etc/apt/sources.list.d/ubuntu.sources
|
||||
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
|
||||
sed -i \
|
||||
-e "s|http://archive.ubuntu.com/ubuntu|${MIRROR}|g" \
|
||||
-e "s|http://security.ubuntu.com/ubuntu|${MIRROR}|g" \
|
||||
/etc/apt/sources.list.d/ubuntu.sources
|
||||
fi
|
||||
|
||||
# Jammy (22.04) and earlier use the legacy /etc/apt/sources.list
|
||||
if [ -f /etc/apt/sources.list ]; then
|
||||
sed -i \
|
||||
-e "s|http://archive.ubuntu.com/ubuntu|${MIRROR}|g" \
|
||||
-e "s|http://security.ubuntu.com/ubuntu|${MIRROR}|g" \
|
||||
/etc/apt/sources.list
|
||||
fi
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user