add list verb and rosdistro_loader
This commit is contained in:
58
buildfarm/command.py
Normal file
58
buildfarm/command.py
Normal 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
12
buildfarm/package.py
Normal 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
|
||||||
86
buildfarm/rosdistro_loader.py
Normal file
86
buildfarm/rosdistro_loader.py
Normal 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
|
||||||
0
buildfarm/verb/__init__.py
Normal file
0
buildfarm/verb/__init__.py
Normal file
41
buildfarm/verb/list.py
Normal file
41
buildfarm/verb/list.py
Normal 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)
|
||||||
@@ -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>
|
||||||
|
|||||||
14
setup.py
14
setup.py
@@ -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",
|
||||||
# ],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user