Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check message definition updates automatically #1204

Merged
merged 5 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions .github/msgs_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/python3

from glob import glob
import re
from dataclasses import dataclass
from typing import Set, Dict
from catkin_pkg.packages import find_packages
from catkin_pkg.package import Package
from subprocess import check_output
from pathlib import Path
from itertools import chain
from json import dumps
import requests
import os


# cspell: ignore srvs
@dataclass
class UsedPackage:
msgs: Set[str]
srvs: Set[str]


# u_int8 -> UInt8
def to_camel_case(snake_str: str):
return "".join(x.capitalize() for x in snake_str.lower().split("_"))


def either_glob(str: str):
return "".join([f"[{c.lower()}{c.upper()}]" if c.isalpha() else c for c in str])


def main():
# Glob all *.cpp / *.hpp files
files = glob("**/*.cpp", recursive=True) + glob("**/*.hpp", recursive=True)

# Message include regex
msg_include_regex = re.compile(r"#include\s*[<\"](.+_msgs)/msg/(.+)\.hpp[>\"]")
srv_include_regex = re.compile(r"#include\s*[<\"](.+_srv)/srv/(.+)\.hpp[>\"]")

# Check for `#include` statements
used_packages: Dict[str, UsedPackage] = {}
for file in files:
with open(file, "r") as f:
lines = f.readlines()
for line in lines:
msg_match = msg_include_regex.match(line)
srv_match = srv_include_regex.match(line)
if msg_match:
package = msg_match.group(1)
msg = to_camel_case(msg_match.group(2))

if package not in used_packages:
used_packages[package] = UsedPackage(set(), set())
used_packages[package].msgs.add(msg)

if srv_match:
package = srv_match.group(1)
srv = to_camel_case(srv_match.group(2))

if package not in used_packages:
used_packages[package] = UsedPackage(set(), set())
used_packages[package].srvs.add(srv)

# Expect external packages to be in the workspace...
pkgs: Dict[str, Package] = find_packages(".")
known_packages = [pkg.name for pkg in pkgs.values()]
unknown_packages = set(used_packages.keys()) - set(known_packages)
if len(unknown_packages) > 0:
print(f"Unknown packages: {unknown_packages}")

updated_patches = []

for path, pkg in pkgs.items():
if pkg.name not in used_packages:
continue

used_msgs = used_packages[pkg.name].msgs
used_srvs = used_packages[pkg.name].srvs
used_names = used_msgs | used_srvs

base_path = Path(path)
check_paths = chain(
*(
[base_path.rglob(f"**/{either_glob(msg)}.msg") for msg in used_names]
+ [base_path.rglob(f"**/{either_glob(msg)}.srv") for msg in used_names]
+ [base_path.rglob(f"**/{either_glob(msg)}.idl") for msg in used_names]
)
)
existing_files = [path for path in check_paths if path.exists()]
existing_files = [str(path.relative_to(base_path)) for path in existing_files]

if len(existing_files) == 0:
print(
f"Package {pkg.name} has no messages or services {used_msgs} {used_srvs}"
)
continue

# Call `git log` for 7 days history
# cspell: ignore oneline
log = (
check_output(
[
"git",
"log",
"--since",
"7.days",
"-p",
"--minimal",
"--pretty=oneline",
"--",
*existing_files,
],
cwd=base_path,
)
.strip()
.decode("utf-8")
)

if len(log) == 0:
print(f"Package {pkg.name} has no recent changes")
continue

updated_patches.append(log)

if len(updated_patches) == 0:
print("No patches to notify")
return

patches = "\n".join(updated_patches)

# Post to GitHub issues comment

body = dumps(
{
"body": f"```diff\n{patches}\n```",
}
)
requests.post(
f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/issues/{os.environ['ISSUE_NUMBER']}/comments",
headers={
"Authorization": f"token {os.environ['GITHUB_TOKEN']}",
"Content-Type": "application/json",
},
data=body,
)


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions .github/workflows/CheckMessageDepsUpdate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Check msgs / srvs update
on:
schedule:
- cron: "0 0 * * 1"
workflow_dispatch:

jobs:
check_message_updates:
name: Check message updates
runs-on: ubuntu-22.04
container: ros:humble
steps:
- name: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
path: scenario_simulator_v2

- name: Install dependencies
run: |
vcs import scenario_simulator_v2/external < scenario_simulator_v2/dependency_humble.repos

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"

- uses: abatilo/actions-poetry@v2
with:
poetry-version: "1.5.1"

- name: Install Python dependencies
run: |
cd scenario_simulator_v2
poetry install --no-interaction

- name: Check updates
shell: bash
run: |
cd scenario_simulator_v2
poetry run python3 .github/msgs_checker.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
ISSUE_NUMBER: 1203
52 changes: 50 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ mkdocs-codeinclude-plugin = "^0.2.1"
mkdocs-git-revision-date-localized-plugin = "^1.2.0"
plantuml = "^0.3.0"
mkdocs-redirects = "^1.2.1"
requests = "^2.31.0"
catkin-pkg = "^1.0.0"


[build-system]
Expand Down
Loading