diff --git a/repology-schemacheck.py b/repology-schemacheck.py index 7ca35f90..2fee0ee3 100755 --- a/repology-schemacheck.py +++ b/repology-schemacheck.py @@ -107,6 +107,7 @@ 'pypi', 'ravenports', 'reactos', + 'ros', 'rosa', 'rubygems', 'rudix', diff --git a/repology/packagemaker/names.py b/repology/packagemaker/names.py index 9ec4ca99..49fc63e9 100644 --- a/repology/packagemaker/names.py +++ b/repology/packagemaker/names.py @@ -246,6 +246,8 @@ class NameType: OPAM_NAME: ClassVar[int] = GENERIC_SRC_NAME + ROS_NAME: ClassVar[int] = GENERIC_SRC_NAME + @dataclass class _NameMapping: diff --git a/repology/parsers/parsers/ros.py b/repology/parsers/parsers/ros.py new file mode 100644 index 00000000..397f8cb9 --- /dev/null +++ b/repology/parsers/parsers/ros.py @@ -0,0 +1,83 @@ +# Copyright (C) 2024 Guilhem Saurel +# +# This file is part of repology +# +# repology is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# repology is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with repology. If not, see . + +from typing import Iterable + +from repology.logger import Logger +from repology.package import LinkType +from repology.packagemaker import NameType, PackageFactory, PackageMaker +from repology.parsers import Parser +from yaml import load + +try: + from yaml import CLoader as Loader +except ImportError: + from yaml import Loader + + +def ros_extract_recipe_url(key, /, *, url, tags, version, **kwargs): + release = tags["release"].format( + package=key, + version=version, + upstream_version=version, + ) + + if "github.com" in url: + return url.removesuffix(".git") + f"/blob/{release}/package.xml" + if "gitlab" in url: + return url.removesuffix(".git") + f"/-/blob/{release}/package.xml" + + +class RosYamlParser(Parser): + def iter_parse(self, path: str, factory: PackageFactory) -> Iterable[PackageMaker]: + with open(path) as f: + data = load(f, Loader=Loader) + for key, packagedata in data["repositories"].items(): + with factory.begin(key) as pkg: + # Some included packages are not yet released, + # and only available as source + if "release" not in packagedata: + pkg.log(f"dropping {pkg}: no release", severity=Logger.ERROR) + continue + + release = packagedata["release"] + + if "version" not in release: + pkg.log(f"dropping {pkg}: has no version.", severity=Logger.ERROR) + continue + + if "bitbucket" in release["url"]: + pkg.log(f"dropping {pkg}: RIP bitbucket", severity=Logger.ERROR) + continue + + pkg.add_name(key, NameType.ROS_NAME) + pkg.set_version(release["version"].split("-")[0]) + + if recipe_url := ros_extract_recipe_url(key, **release): + pkg.add_links(LinkType.PACKAGE_RECIPE, recipe_url) + else: + pkg.log(f"{pkg} has no known recipe url", severity=Logger.WARNING) + + if source := packagedata.get("source"): + pkg.add_links(LinkType.UPSTREAM_HOMEPAGE, source["url"]) + else: + pkg.log(f"{pkg} has no source", severity=Logger.WARNING) + + if doc := packagedata.get("doc"): + pkg.add_links(LinkType.UPSTREAM_DOCUMENTATION, doc["url"]) + + yield pkg diff --git a/repos.d/ros.yaml b/repos.d/ros.yaml new file mode 100644 index 00000000..f30e8ca8 --- /dev/null +++ b/repos.d/ros.yaml @@ -0,0 +1,56 @@ +########################################################################### +# ROS +########################################################################### +{% macro ros(version, name, minpackages, valid_till) %} +- name: ros_{{version}}_{{name}} + type: repository + desc: ROS {{name}} {% if version > 1 %}(ROS {{version}}){% endif %} + statsgroup: ros + family: ros + ruleset: ros + color: '22314E' + minpackages: {{minpackages}} + {% if valid_till %} + valid_till: {{valid_till}} + {% endif %} + default_maintainer: fallback-mnt-ros@repology + sources: + - name: distribution.yaml + fetcher: + class: FileFetcher + url: https://raw.githubusercontent.com/ros/rosdistro/refs/heads/master/{{name}}/distribution.yaml + parser: + class: RosYamlParser + repolinks: + - desc: ROS home + url: https://ros.org/ + - desc: ROS packages + url: https://index.ros.org/ + - desc: {{name}} GitHub repository + url: https://github.com/ros/rosdistro/tree/master/{{name}} + - desc: {{name}} home + url: https://{% if version > 1 %}docs.ros.org/en{% else %}wiki.ros.org{% endif %}/{{name}} + groups: [ all, production, ros, ros{{version}} ] +{% endmacro %} + +{{ ros(1, 'groovy', minpackages=200, valid_till='2014-07-31') }} +{{ ros(1, 'hydro', minpackages=550, valid_till='2015-06-30') }} +{{ ros(1, 'indigo', minpackages=950, valid_till='2019-04-30') }} +{{ ros(1, 'jade', minpackages=450, valid_till='2017-05-31') }} +{{ ros(1, 'kinetic', minpackages=950, valid_till='2021-04-30') }} +{{ ros(1, 'lunar', minpackages=350, valid_till='2019-05-31') }} +{{ ros(1, 'melodic', minpackages=850, valid_till='2023-06-27') }} +{{ ros(1, 'noetic', minpackages=750, valid_till='2025-05-31') }} + +{{ ros(2, 'ardent', minpackages=50, valid_till='2018-12-31') }} +{{ ros(2, 'bouncy', minpackages=50, valid_till='2019-06-30') }} +{{ ros(2, 'crystal', minpackages=100, valid_till='2019-12-31') }} +{{ ros(2, 'dashing', minpackages=200, valid_till='2021-05-31') }} +{{ ros(2, 'eloquent', minpackages=200, valid_till='2020-11-30') }} +{{ ros(2, 'foxy', minpackages=450, valid_till='2023-05-31') }} +{{ ros(2, 'galactic', minpackages=350, valid_till='2022-11-30') }} +{{ ros(2, 'humble', minpackages=650, valid_till='2027-05-31') }} +{{ ros(2, 'iron', minpackages=500, valid_till='2024-11-30') }} +{{ ros(2, 'jazzy', minpackages=550, valid_till='2029-05-31') }} + +{{ ros(2, 'rolling', minpackages=500) }}