add list verb and rosdistro_loader

This commit is contained in:
Maik Knof
2025-12-29 16:25:08 +00:00
parent 523f3cf782
commit 4b9b7b6336
7 changed files with 205 additions and 8 deletions

58
buildfarm/command.py Normal file
View File

@@ -0,0 +1,58 @@
import os
import sys
from ros2cli.command import CommandExtension
from .rosdistro_loader import fetch_rosdistro_packages
from .verb.list import add_list_verb
class BuildfarmCommand(CommandExtension):
"""https://git.maikknof.de/ros2/buildfarm"""
def add_arguments(self, parser, cli_name):
parser.add_argument(
"--base-url",
default="https://git.maikknof.de",
help="Base URL of Git instance",
)
parser.add_argument(
"--owner", default="ros2", help="Repo owner or organization"
)
parser.add_argument("--repo", default="rosdistro", help="Repository name")
parser.add_argument("--branch", default="main", help="Branch name")
env_rosdistro = os.environ.get("ROS_DISTRO") or os.environ.get("ROSDISTRO")
parser.add_argument(
"--rosdistro",
default=env_rosdistro,
required=(env_rosdistro is None),
help="ROS distro name (defaults to env ROS_DISTRO/ROSDISTRO)",
)
subparsers = parser.add_subparsers(dest="verb", metavar="verb")
subparsers.required = True
add_list_verb(subparsers)
self._parser = parser
def main(self, *, parser, args):
if not hasattr(args, "main"):
self._parser.print_help()
return 0
try:
packages = fetch_rosdistro_packages(
base_url=args.base_url,
owner=args.owner,
repo=args.repo,
branch=args.branch,
rosdistro=args.rosdistro,
)
except Exception as e:
print(f"Error loading rosdistro packages: {e}", file=sys.stderr)
return 1
setattr(args, "packages", packages)
return args.main(args)

12
buildfarm/package.py Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python3
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Package:
name: str
owner: str
repo: str
ref: str

View File

@@ -0,0 +1,86 @@
from __future__ import annotations
import re
from typing import Dict
import requests
import yaml
from .package import Package
def _raw_url(base_url: str, owner: str, repo: str, branch: str, file_path: str) -> str:
return (
f"{base_url.rstrip('/')}/"
f"{owner}/{repo}/raw/branch/{branch}/{file_path.lstrip('/')}"
)
def _parse_package_ref(value: str) -> tuple[str, str, str]:
"""
Parses "owner/repo@ref" into (owner, repo, ref).
"""
value = value.strip()
if "@" not in value:
raise ValueError(f"Invalid package reference '{value}': missing '@'")
left, ref = value.rsplit("@", 1)
left = left.strip()
ref = ref.strip()
if "/" not in left:
raise ValueError(f"Invalid package reference '{value}': missing '/'")
owner, repo = left.split("/", 1)
owner = owner.strip()
repo = repo.strip()
if not owner or not repo or not ref:
raise ValueError(f"Invalid package reference '{value}': empty owner/repo/ref")
if re.search(r"\s", value):
raise ValueError(f"Invalid package reference '{value}': contains whitespace")
return owner, repo, ref
def fetch_rosdistro_packages(
*,
base_url: str,
owner: str,
repo: str,
branch: str,
rosdistro: str,
timeout_s: float = 20.0,
) -> Dict[str, Package]:
"""
Fetches rosdistro/<rosdistro>/distro.yaml from a Gitea repo and parses it into:
{ package_name: Package(name, owner, repo, ref) }
"""
file_path = f"rosdistro/{rosdistro}/distro.yaml"
url = _raw_url(base_url, owner, repo, branch, file_path)
resp = requests.get(url, timeout=timeout_s)
if resp.status_code != 200:
raise RuntimeError(
f"Failed to fetch '{url}' (HTTP {resp.status_code}): {resp.text}"
)
data = yaml.safe_load(resp.text)
if not isinstance(data, dict):
raise ValueError(
f"Invalid YAML structure in {file_path}: expected mapping at root"
)
packages: Dict[str, Package] = {}
for name, ref in data.items():
if not isinstance(name, str):
raise ValueError(f"Invalid package key in YAML: {name!r} (expected string)")
if not isinstance(ref, str):
raise ValueError(f"Invalid ref for '{name}': {ref!r} (expected string)")
pkg_owner, pkg_repo, pkg_ref = _parse_package_ref(ref)
packages[name] = Package(name=name, owner=pkg_owner, repo=pkg_repo, ref=pkg_ref)
return packages

View File

41
buildfarm/verb/list.py Normal file
View File

@@ -0,0 +1,41 @@
from __future__ import annotations
from typing import Any, Dict
from ros2cli.verb import VerbExtension
from ..package import Package
class ListVerb(VerbExtension):
"""List packages from rosdistro/<rosdistro>/distro.yaml."""
def add_arguments(self, parser, cli_name):
parser.add_argument(
"--format",
default="plain",
choices=("plain", "yaml"),
help="Output format",
)
def main(self, *, args):
packages: Dict[str, Package] = getattr(args, "packages", {})
if args.format == "yaml":
for name in sorted(packages.keys()):
pkg = packages[name]
print(f"{name}: {pkg.owner}/{pkg.repo}@{pkg.ref}")
return 0
for name in sorted(packages.keys()):
pkg = packages[name]
print(f"{name}: {pkg.owner}/{pkg.repo}@{pkg.ref}")
return 0
def add_list_verb(subparsers):
parser = subparsers.add_parser("list", help="List packages from rosdistro")
verb = ListVerb()
verb.add_arguments(parser, "buildfarm")
parser.set_defaults(main=verb.main)

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="3"> <package format="3">
<name>buildfarm</name> <name>buildfarm</name>
<version>0.0.0</version> <version>0.0.1</version>
<description>Scripts to build ROS2 packages as .deb packages.</description> <description>Scripts to build ROS2 packages as .deb packages.</description>
<maintainer email="mail@maikknof.de">Maik Knof</maintainer> <maintainer email="mail@maikknof.de">Maik Knof</maintainer>
<license>TODO</license> <license>TODO</license>

View File

@@ -9,7 +9,7 @@ data_files = [
setup( setup(
name=package_name, name=package_name,
version="0.0.0", version="0.0.1",
packages=[package_name], packages=[package_name],
data_files=data_files, data_files=data_files,
install_requires=["setuptools", "ros2cli", "bringup_cli"], install_requires=["setuptools", "ros2cli", "bringup_cli"],
@@ -20,11 +20,11 @@ setup(
license="TODO: License declaration", license="TODO: License declaration",
tests_require=["pytest"], tests_require=["pytest"],
entry_points={ entry_points={
# "ros2cli.command": [ "ros2cli.command": [
# "buildfarm = buildfarm.command:BuildfarmCommand", "buildfarm = buildfarm.command:BuildfarmCommand",
# ], ],
# "ros2cli.extension_point": [ "ros2cli.extension_point": [
# "buildfarm = ros2cli.command.CommandExtension", "buildfarm = ros2cli.command.CommandExtension",
# ], ],
}, },
) )