mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-11-07 07:41:36 +08:00
ci(actions): add composite actions and clang-tidy PR helper
Add four reusable building blocks that upcoming CI optimization PRs will consume. No existing workflow is modified; these files are dormant until referenced. - .github/actions/setup-ccache: restore ~/.ccache with content-hash keys, write ccache.conf with compression and content-based compiler check - .github/actions/save-ccache: print stats and save the cache under the primary key produced by setup-ccache - .github/actions/build-gazebo-sitl: build px4_sitl_default plus the Gazebo Classic plugins with ccache stats between stages - Tools/ci/run-clang-tidy-pr.py: compute the translation units affected by a PR diff and invoke Tools/run-clang-tidy.py on that subset only, exiting silently when no C++ files changed Signed-off-by: Ramon Roche <mrpollo@gmail.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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 }}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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
|
||||||
Executable
+147
@@ -0,0 +1,147 @@
|
|||||||
|
#!/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()
|
||||||
Reference in New Issue
Block a user