diff --git a/ament_clang_format/ament_clang_format/main.py b/ament_clang_format/ament_clang_format/main.py index 87120628..b3d57ee9 100755 --- a/ament_clang_format/ament_clang_format/main.py +++ b/ament_clang_format/ament_clang_format/main.py @@ -19,6 +19,7 @@ import subprocess import sys import time +from typing import Literal from xml.etree import ElementTree from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -26,6 +27,27 @@ import yaml +class ClangFormatRunner: + + NAME = 'ament_clang_format' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.h', + '*.hh', + '*.hpp', + '*.hxx', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): config_file = os.path.join( os.path.dirname(__file__), 'configuration', '.clang-format') diff --git a/ament_clang_format/setup.py b/ament_clang_format/setup.py index 1510ea0b..d22109ec 100644 --- a/ament_clang_format/setup.py +++ b/ament_clang_format/setup.py @@ -44,5 +44,8 @@ 'console_scripts': [ 'ament_clang_format = ament_clang_format.main:main', ], + 'ament_lint': [ + 'ament_clang_format = ament_clang_format.main:ClangFormatRunner', + ], }, ) diff --git a/ament_clang_tidy/ament_clang_tidy/main.py b/ament_clang_tidy/ament_clang_tidy/main.py index 5faa8142..bd928c2a 100755 --- a/ament_clang_tidy/ament_clang_tidy/main.py +++ b/ament_clang_tidy/ament_clang_tidy/main.py @@ -24,6 +24,7 @@ import subprocess import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -31,6 +32,27 @@ import yaml +class ClangTidyRunner: + + NAME = 'ament_clang_tidy' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.h', + '*.hh', + '*.hpp', + '*.hxx', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): extensions = ['c', 'cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'hxx'] diff --git a/ament_clang_tidy/setup.py b/ament_clang_tidy/setup.py index 65a7d969..90be749e 100644 --- a/ament_clang_tidy/setup.py +++ b/ament_clang_tidy/setup.py @@ -44,5 +44,8 @@ 'console_scripts': [ 'ament_clang_tidy = ament_clang_tidy.main:main', ], + 'ament_lint': [ + 'ament_clang_tidy = ament_clang_tidy.main:ClangTidyRunner', + ], }, ) diff --git a/ament_copyright/ament_copyright/main.py b/ament_copyright/ament_copyright/main.py index 361f3b10..7a14cc85 100644 --- a/ament_copyright/ament_copyright/main.py +++ b/ament_copyright/ament_copyright/main.py @@ -18,6 +18,7 @@ import re import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -36,6 +37,20 @@ from ament_copyright.parser import search_copyright_information +class CopyrightRunner: + + NAME = 'ament_copyright' + FILE_TYPES = ( + '*', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): extensions = [ 'c', 'cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'hxx', diff --git a/ament_copyright/setup.py b/ament_copyright/setup.py index 702b24e8..59751e23 100644 --- a/ament_copyright/setup.py +++ b/ament_copyright/setup.py @@ -58,5 +58,8 @@ 'pytest11': [ 'ament_copyright = ament_copyright.pytest_marker', ], + 'ament_lint': [ + 'ament_copyright = ament_copyright.main:CopyrightRunner', + ], }, ) diff --git a/ament_cppcheck/ament_cppcheck/main.py b/ament_cppcheck/ament_cppcheck/main.py index 45017992..f83bc762 100755 --- a/ament_cppcheck/ament_cppcheck/main.py +++ b/ament_cppcheck/ament_cppcheck/main.py @@ -22,6 +22,7 @@ import subprocess import sys import time +from typing import Literal from xml.etree import ElementTree from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -51,6 +52,27 @@ def get_cppcheck_version(cppcheck_bin): return tokens[1] +class CPPCheckRunner: + + NAME = 'ament_cppcheck' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.h', + '*.hh', + '*.hpp', + '*.hxx', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): extensions = ['c', 'cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'hxx'] diff --git a/ament_cppcheck/setup.py b/ament_cppcheck/setup.py index 3b598670..be3caed4 100644 --- a/ament_cppcheck/setup.py +++ b/ament_cppcheck/setup.py @@ -40,5 +40,8 @@ 'console_scripts': [ 'ament_cppcheck = ament_cppcheck.main:main', ], + 'ament_lint': [ + 'ament_cppcheck = ament_cppcheck.main:CPPCheckRunner', + ], }, ) diff --git a/ament_cpplint/ament_cpplint/main.py b/ament_cpplint/ament_cpplint/main.py index fd8769b7..38afcd3f 100755 --- a/ament_cpplint/ament_cpplint/main.py +++ b/ament_cpplint/ament_cpplint/main.py @@ -21,6 +21,7 @@ import re import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -69,6 +70,27 @@ def custom_get_header_guard_cpp_variable(filename): cpplint.GetHeaderGuardCPPVariable = custom_get_header_guard_cpp_variable +class CPPLintRunner: + + NAME = 'ament_cpplint' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.h', + '*.hh', + '*.hpp', + '*.hxx', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): extensions = ['c', 'cc', 'cpp', 'cxx'] headers = ['h', 'hh', 'hpp', 'hxx'] diff --git a/ament_cpplint/setup.py b/ament_cpplint/setup.py index 53e3534a..aa056b1b 100644 --- a/ament_cpplint/setup.py +++ b/ament_cpplint/setup.py @@ -40,5 +40,8 @@ 'console_scripts': [ 'ament_cpplint = ament_cpplint.main:main', ], + 'ament_lint': [ + 'ament_cpplint = ament_cpplint.main:CPPLintRunner', + ], }, ) diff --git a/ament_flake8/ament_flake8/main.py b/ament_flake8/ament_flake8/main.py index ed22bdde..b72f800c 100755 --- a/ament_flake8/ament_flake8/main.py +++ b/ament_flake8/ament_flake8/main.py @@ -18,6 +18,7 @@ import os import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -27,6 +28,18 @@ from flake8.main import options as flake8_options +class Flake8Runner: + + NAME = 'ament_flake8' + FILE_TYPES = ('*.py', ) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): rc, _ = main_with_errors(argv=argv) return rc diff --git a/ament_flake8/setup.py b/ament_flake8/setup.py index b928050a..44fd7e28 100644 --- a/ament_flake8/setup.py +++ b/ament_flake8/setup.py @@ -45,5 +45,8 @@ 'pytest11': [ 'ament_flake8 = ament_flake8.pytest_marker', ], + 'ament_lint': [ + 'ament_flake8 = ament_flake8.main:Flake8Runner', + ], }, ) diff --git a/ament_lint_auto_py/CHANGELOG.rst b/ament_lint_auto_py/CHANGELOG.rst new file mode 100644 index 00000000..b20c0edb --- /dev/null +++ b/ament_lint_auto_py/CHANGELOG.rst @@ -0,0 +1,3 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ament_lint_auto_py +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/ament_lint_auto_py/ament_lint_auto_py/__init__.py b/ament_lint_auto_py/ament_lint_auto_py/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ament_lint_auto_py/ament_lint_auto_py/py.typed b/ament_lint_auto_py/ament_lint_auto_py/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/ament_lint_auto_py/ament_lint_auto_py/pytest_plugin.py b/ament_lint_auto_py/ament_lint_auto_py/pytest_plugin.py new file mode 100644 index 00000000..589955dc --- /dev/null +++ b/ament_lint_auto_py/ament_lint_auto_py/pytest_plugin.py @@ -0,0 +1,120 @@ +# Copyright 2026 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from importlib.metadata import entry_points +from importlib.metadata import EntryPoint +from pathlib import Path +from typing import Literal + +from ament_lint_auto_py.xml_helpers import find_package_xml +from ament_lint_auto_py.xml_helpers import get_depends_recursive +from ament_lint_auto_py.xml_helpers import package_has_ament_lint_auto_py +from pytest import Config +from pytest import Item +from pytest import Parser +from pytest import Session + + +def pytest_configure(config: Config) -> None: + config.addinivalue_line( + 'markers', 'ament_lint_auto_py: marks tests as running all linter checks' + ) + + +class AmentLintItem(Item): + def __init__( + self, + *, + entry_point: EntryPoint, + file_excludes: list[str], + **kwargs, + ) -> None: + super().__init__(**kwargs) + self.entry_point = entry_point + self.file_excludes = file_excludes + + def runtest(self) -> None: + runner = self.entry_point.load() + + args: list[str] = [] + if self.file_excludes: + args.append('--exclude') + args.extend(self.file_excludes) + + rc = runner(args)() + if rc != 0: + raise AssertionError( + f'Linter[{runner.NAME}] failed with exit code {rc}' + ) + + def reportinfo(self) -> tuple[Path, Literal[0], str]: + return self.path, 0, f'ament_lint: {self.name}' + + +def pytest_collection_modifyitems(session: Session, config: Config, items: list[Item]) -> None: + excluded = set(config.getini('ament_lint_auto_exclude')) + file_excludes = config.getini('ament_lint_auto_file_exclude') + + pkg_xml = find_package_xml(config.rootpath) + if pkg_xml is None: + return # Not in a ROS package + + if not package_has_ament_lint_auto_py(pkg_xml): + return # Package does not opt-in + + effective_depends = get_depends_recursive(pkg_xml) + linters = entry_points(group='ament_lint') + + for ep in linters: + runner = ep.load() + + if runner.NAME not in effective_depends: + continue # skipping linter if not declared in depends of a package.xml + + if runner.NAME in excluded: + continue # skipping linter if declared in ament_lint_auto_exclude + + # skip linters if no matching files exist + found_file = False + for pattern in runner.FILE_TYPES: + if any(config.rootpath.rglob(pattern)): + found_file = True + break + if not found_file: + continue + + items.append( + AmentLintItem.from_parent( + parent=session, + name=runner.NAME, + entry_point=ep, + file_excludes=file_excludes, + path=config.rootpath, + ) + ) + + +def pytest_addoption(parser: Parser): + parser.addini( + 'ament_lint_auto_exclude', + 'Linters to exclude from ament_lint_auto_py', + type='linelist', + default=[], + ) + parser.addini( + 'ament_lint_auto_file_exclude', + 'File globs to exclude from ament linters', + type='linelist', + default=[], + ) diff --git a/ament_lint_auto_py/ament_lint_auto_py/xml_helpers.py b/ament_lint_auto_py/ament_lint_auto_py/xml_helpers.py new file mode 100644 index 00000000..db1bc09f --- /dev/null +++ b/ament_lint_auto_py/ament_lint_auto_py/xml_helpers.py @@ -0,0 +1,89 @@ +# Copyright 2026 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Final +import xml.etree.ElementTree as ET + + +from ament_index_python.packages import get_package_share_directory + + +DEPEND_TAGS: Final = ( + 'depend', + 'build_depend', + 'test_depend', + 'exec_depend', +) + +PACKAGE_XML: Final = 'package.xml' + + +def should_expand_package(name: str) -> bool: + return name.startswith('ament_lint') + + +def get_package_xml_for_package(pkg_name: str) -> Path: + return Path(get_package_share_directory(pkg_name)) / PACKAGE_XML + + +def get_depends_recursive( + pkg_xml: Path, + seen: set[str] | None = None, +) -> set[str]: + seen = seen or set() + + tree = ET.parse(pkg_xml) + root = tree.getroot() + + deps: set[str] = set() + + for tag in DEPEND_TAGS: + for dep in root.findall(tag): + if not dep.text: + continue + + name = dep.text.strip() + if name in seen: + continue + + seen.add(name) + deps.add(name) + + if should_expand_package(name): + dep_xml = get_package_xml_for_package(name) + deps |= get_depends_recursive(dep_xml, seen) + + return deps + + +def find_package_xml(start: Path) -> Path | None: + for parent in [start, *start.parents]: + pkg_xml = parent / PACKAGE_XML + if pkg_xml.is_file(): + return pkg_xml + return None + + +def package_has_ament_lint_auto_py(pkg_xml: Path) -> bool: + tree = ET.parse(pkg_xml) + root = tree.getroot() + + # Adds name so this package can self lint + OPT_IN_TAGS = (*DEPEND_TAGS, 'name') + for tag in OPT_IN_TAGS: + for dep in root.findall(tag): + if dep.text and dep.text.strip() == 'ament_lint_auto_py': + return True + return False diff --git a/ament_lint_auto_py/doc/index.rst b/ament_lint_auto_py/doc/index.rst new file mode 100644 index 00000000..ffea95e4 --- /dev/null +++ b/ament_lint_auto_py/doc/index.rst @@ -0,0 +1,107 @@ +ament_lint_auto_py +================== + +The package simplifies using multiple linters as part of pytest tests. + +To have `ament_lint_auto_py` collect and run the linters include it in the `package.xml` + +``package.xml``: + +.. code:: xml + + ament_lint_auto_py + + +The set of linters to be used is then only specified in the package manifest as +test dependencies. + +``package.xml``: + +.. code:: xml + + ament_lint_auto_py + + + ament_clang_format + ament_cppcheck + ament_pycodestyle + +Since recursive dependencies are also being used a for packages starting with `ament_lint_*` a single test dependency is +sufficient to test with a set of common linters. + +``package.xml``: + +.. code:: xml + + ament_lint_auto_py + + + ament_lint_common_py + + +How to exclude linter modules with ament_lint_auto_py? +------------------------------------------------------ + +Linter modules can be excluded via the pytest configurable variables `ament_lint_auto_exclude` in pytest config files like `pytest.ini` or `pyproject.toml`. + +As an example to exclude the `copyright` linter: + +.. code:: + [pytest] + + ament_lint_auto_exclude = ament_copyright + + +How to exclude files with ament_lint_auto_py? +--------------------------------------------- + +Linter hooks shall conform to the ament_lint_auto_py convention of excluding files +specified in the environment list variable `ament_lint_auto_file_exclude`. + +.. code:: + [pytest] + + ament_lint_auto_file_exclude = /path/to/ignored_file + +For a more specific example, this excludes all python files matching a pattern using globbing. +Multiple expressions can be combined on multiple lines. + + +.. code:: + [pytest] + + ament_lint_auto_file_exclude = + src/* + test/*.cpp + +How to register 3rd party linters with ament_lint_auto_py? +--------------------------------------------------------- + +To register a third party linter implement class like the following. + +.. code:: python + + class CustomRunner: + + NAME = 'ament_custom' + FILE_TYPES = ('*.py',) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(): + # Custom linting + pass + +Then in a `setup.py` register an entry point for the `CustomRunner`. + +.. code:: python + + entry_points={ + 'ament_lint': [ + 'ament_lint_cmake = ament_lint_cmake.main:LintCMakeRunner' + ] + }, diff --git a/ament_lint_auto_py/package.xml b/ament_lint_auto_py/package.xml new file mode 100644 index 00000000..0cae5675 --- /dev/null +++ b/ament_lint_auto_py/package.xml @@ -0,0 +1,24 @@ + + + + ament_lint_auto_py + 0.20.3 + The auto-magic functions for ease to use of the ament linters in Python. + + + Chris Lalancette + + Apache License 2.0 + + Audrow Nash + + ament_index_python + python3-pytest + + ament_lint_common_py + ament_mypy + + + ament_python + + diff --git a/ament_lint_auto_py/resource/ament_lint_auto_py b/ament_lint_auto_py/resource/ament_lint_auto_py new file mode 100644 index 00000000..e69de29b diff --git a/ament_lint_auto_py/setup.py b/ament_lint_auto_py/setup.py new file mode 100644 index 00000000..ec8fb54b --- /dev/null +++ b/ament_lint_auto_py/setup.py @@ -0,0 +1,44 @@ +from setuptools import find_packages +from setuptools import setup + +package_name = 'ament_lint_auto_py' + +setup( + name=package_name, + version='0.20.3', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/' + package_name, ['package.xml']), + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ], + package_data={'': [ + 'py.typed' + ]}, + zip_safe=False, + author='Ted Kern', + author_email='ted.kern@canonical.com', + maintainer='Michael Jeronimo', + maintainer_email='michael.jeronimo@openrobotics.org', + url='https://github.com/ament/ament_lint', + download_url='https://github.com/ament/ament_lint/releases', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Run ament linters', + long_description='The auto-magic functions for ease to use of the ament linters in Python.', + license='Apache License, Version 2.0', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'pytest11': [ + 'ament_lint_auto_py = ament_lint_auto_py.pytest_plugin', + ], + }, +) diff --git a/ament_lint_cmake/ament_lint_cmake/main.py b/ament_lint_cmake/ament_lint_cmake/main.py index aa76fcbd..cbf8f186 100755 --- a/ament_lint_cmake/ament_lint_cmake/main.py +++ b/ament_lint_cmake/ament_lint_cmake/main.py @@ -18,6 +18,7 @@ import os import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -32,6 +33,19 @@ def is_valid_file(filename): cmakelint.IsValidFile = is_valid_file +class LintCMakeRunner: + + NAME = 'ament_lint_cmake' + FILE_TYPES = ('CMakeLists.txt', '*.cmake') + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( description='Check CMake code against the style conventions.', diff --git a/ament_lint_cmake/setup.py b/ament_lint_cmake/setup.py index 9d93996f..39e1bf7d 100644 --- a/ament_lint_cmake/setup.py +++ b/ament_lint_cmake/setup.py @@ -40,5 +40,8 @@ 'console_scripts': [ 'ament_lint_cmake = ament_lint_cmake.main:main', ], + 'ament_lint': [ + 'ament_lint_cmake = ament_lint_cmake.main:LintCMakeRunner' + ] }, ) diff --git a/ament_lint_common_py/CHANGELOG.rst b/ament_lint_common_py/CHANGELOG.rst new file mode 100644 index 00000000..b3307644 --- /dev/null +++ b/ament_lint_common_py/CHANGELOG.rst @@ -0,0 +1,7 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ament_lint_common_py +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.20.3 (2025-11-24) +------------------- + diff --git a/ament_lint_common_py/CMakeLists.txt b/ament_lint_common_py/CMakeLists.txt new file mode 100644 index 00000000..44cd31e1 --- /dev/null +++ b/ament_lint_common_py/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) + +project(ament_lint_common_py NONE) + +find_package(ament_cmake_core REQUIRED) +find_package(ament_cmake_export_dependencies REQUIRED) + +ament_package_xml() +ament_export_dependencies(${${PROJECT_NAME}_EXEC_DEPENDS}) +ament_package() diff --git a/ament_lint_common_py/doc/index.rst b/ament_lint_common_py/doc/index.rst new file mode 100644 index 00000000..4c7a0f64 --- /dev/null +++ b/ament_lint_common_py/doc/index.rst @@ -0,0 +1,24 @@ +ament_lint_common_py +==================== + +The pytest variant of ament_lint_common. The dependencies match to keep parity between them. + +A mechanism for running the following set of common linters: + +* `ament_copyright `_ : a copyright linter which checks that copyright statements and license headers are present and correct + +* `ament_cppcheck `_ : a C++ checker which can also find some logic tests + +* `ament_cpplint `_ : a C++ style checker (e.g. comment style) + +* `ament_flake8 `_ : a style checker for Python files + +* `ament_cmake_lint `_ : a cmake linter + +* `ament_pep257 `_ : a style checker for Python docstrings + +* `ament_uncrustify `_ : a C++ style checker + +* `ament_xmllint `_ : an xml linter + +The `ament_lint_auto_py `_ documentation provides information on using ament_lint_common_py. diff --git a/ament_lint_common_py/package.xml b/ament_lint_common_py/package.xml new file mode 100644 index 00000000..666ad19d --- /dev/null +++ b/ament_lint_common_py/package.xml @@ -0,0 +1,38 @@ + + + + ament_lint_common_py + 0.20.3 + The list of commonly used linters in the ament python build system. + + + Chris Lalancette + + Apache License 2.0 + + Audrow Nash + Brandon Ong + Claire Wang + Dirk Thomas + Michel Hidalgo + + ament_cmake_core + ament_cmake_export_dependencies + + ament_cmake_core + + + + ament_copyright + ament_cppcheck + ament_cpplint + ament_flake8 + ament_lint_cmake + ament_pep257 + ament_uncrustify + ament_xmllint + + + ament_cmake + + diff --git a/ament_mypy/ament_mypy/main.py b/ament_mypy/ament_mypy/main.py index 082d5943..7d4f6184 100755 --- a/ament_mypy/ament_mypy/main.py +++ b/ament_mypy/ament_mypy/main.py @@ -27,6 +27,18 @@ import mypy.api +class MypyRunner: + + NAME = 'ament_mypy' + FILE_TYPES = ('*.py', '*.pyi') + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> int: + return main(self.args) + + def main(argv: List[str] = sys.argv[1:]) -> int: """Command line tool for static type analysis with mypy.""" parser = argparse.ArgumentParser( diff --git a/ament_mypy/setup.py b/ament_mypy/setup.py index 4bcdc3ea..42b8ee88 100644 --- a/ament_mypy/setup.py +++ b/ament_mypy/setup.py @@ -45,5 +45,8 @@ 'pytest11': [ 'ament_mypy = ament_mypy.pytest_marker', ], + 'ament_lint': [ + 'ament_mypy = ament_mypy.main:MypyRunner', + ], }, ) diff --git a/ament_pclint/ament_pclint/main.py b/ament_pclint/ament_pclint/main.py index 960c58d6..3c1e2300 100755 --- a/ament_pclint/ament_pclint/main.py +++ b/ament_pclint/ament_pclint/main.py @@ -23,11 +23,31 @@ import subprocess import sys import time +from typing import Literal from xml.etree import ElementTree from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr +class PCLintRunner: + + NAME = 'ament_pclint' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.c++', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): extensions = ['c', 'cc', 'cpp', 'cxx', 'c++'] diff --git a/ament_pclint/setup.py b/ament_pclint/setup.py index f43d546e..b9dfe53b 100644 --- a/ament_pclint/setup.py +++ b/ament_pclint/setup.py @@ -61,5 +61,8 @@ 'console_scripts': [ 'ament_pclint = ament_pclint.main:main', ], + 'ament_lint': [ + 'ament_pclint = ament_pclint.main:PCLintRunner', + ], }, ) diff --git a/ament_pep257/ament_pep257/main.py b/ament_pep257/ament_pep257/main.py index a8f43800..307f592f 100755 --- a/ament_pep257/ament_pep257/main.py +++ b/ament_pep257/ament_pep257/main.py @@ -19,6 +19,7 @@ import os import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -55,6 +56,18 @@ ] +class Pep257Runner: + + NAME = 'ament_pep257' + FILE_TYPES = ('*.py',) + + def __init__(self, args: list[str]) -> None: + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( description='Check docstrings against the style conventions in PEP 257.', diff --git a/ament_pep257/setup.py b/ament_pep257/setup.py index 4a8f190b..87fe4cba 100644 --- a/ament_pep257/setup.py +++ b/ament_pep257/setup.py @@ -46,5 +46,8 @@ 'pytest11': [ 'ament_pep257 = ament_pep257.pytest_marker', ], + 'ament_lint': [ + 'ament_pep257 = ament_pep257.main:Pep257Runner', + ], }, ) diff --git a/ament_pycodestyle/ament_pycodestyle/main.py b/ament_pycodestyle/ament_pycodestyle/main.py index 57b28a47..ce0caf93 100755 --- a/ament_pycodestyle/ament_pycodestyle/main.py +++ b/ament_pycodestyle/ament_pycodestyle/main.py @@ -17,12 +17,26 @@ import argparse import os import sys +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr import pycodestyle +class PycodestyleRunner: + + NAME = 'ament_pycodestyle' + FILE_TYPES = ('*.py',) + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): config_file = os.path.join( os.path.dirname(__file__), 'configuration', 'ament_pycodestyle.ini') diff --git a/ament_pycodestyle/setup.py b/ament_pycodestyle/setup.py index 14e35114..6ad5789c 100644 --- a/ament_pycodestyle/setup.py +++ b/ament_pycodestyle/setup.py @@ -43,5 +43,8 @@ 'console_scripts': [ 'ament_pycodestyle = ament_pycodestyle.main:main', ], + 'ament_lint': [ + 'ament_pycodestyle = ament_pycodestyle.main:PycodestyleRunner', + ], }, ) diff --git a/ament_pyflakes/ament_pyflakes/main.py b/ament_pyflakes/ament_pyflakes/main.py index 1545c9c3..322f80da 100755 --- a/ament_pyflakes/ament_pyflakes/main.py +++ b/ament_pyflakes/ament_pyflakes/main.py @@ -18,6 +18,7 @@ import os import sys import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr @@ -26,6 +27,19 @@ from pyflakes.reporter import Reporter +class PyflakesRunner: + + NAME = 'ament_pyflakes' + FILE_TYPES = ('*.py',) + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( description='Check code using pyflakes.', diff --git a/ament_pyflakes/setup.py b/ament_pyflakes/setup.py index cec86989..94ea9547 100644 --- a/ament_pyflakes/setup.py +++ b/ament_pyflakes/setup.py @@ -40,5 +40,8 @@ 'console_scripts': [ 'ament_pyflakes = ament_pyflakes.main:main', ], + 'ament_lint': [ + 'ament_pyflakes = ament_pyflakes.main:PyflakesRunner', + ], }, ) diff --git a/ament_uncrustify/ament_uncrustify/main.py b/ament_uncrustify/ament_uncrustify/main.py index a3c3bf14..41047095 100755 --- a/ament_uncrustify/ament_uncrustify/main.py +++ b/ament_uncrustify/ament_uncrustify/main.py @@ -27,10 +27,33 @@ import sys import tempfile import time +from typing import Literal from xml.sax.saxutils import escape from xml.sax.saxutils import quoteattr +class UncrustifyRunner: + + NAME = 'ament_uncrustify' + FILE_TYPES = ( + '*.c', + '*.cc', + '*.cpp', + '*.cxx', + '*.h', + '*.hh', + '*.hpp', + '*.hxx', + ) + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + def main(argv=sys.argv[1:]): uncrustify_bin = find_executable('uncrustify') if not uncrustify_bin: diff --git a/ament_uncrustify/setup.py b/ament_uncrustify/setup.py index 039322af..f730a3f6 100644 --- a/ament_uncrustify/setup.py +++ b/ament_uncrustify/setup.py @@ -44,5 +44,8 @@ 'console_scripts': [ 'ament_uncrustify = ament_uncrustify.main:main', ], + 'ament_lint': [ + 'ament_uncrustify = ament_uncrustify.main:UncrustifyRunner', + ], }, ) diff --git a/ament_xmllint/ament_xmllint/main.py b/ament_xmllint/ament_xmllint/main.py index 261de0d6..870fb07d 100755 --- a/ament_xmllint/ament_xmllint/main.py +++ b/ament_xmllint/ament_xmllint/main.py @@ -22,6 +22,7 @@ import sys import tempfile import time +from typing import Literal import urllib.request from xml.etree import ElementTree from xml.sax import make_parser @@ -31,7 +32,20 @@ from xml.sax.saxutils import quoteattr -def main(argv=sys.argv[1:]): +class XmlLintRunner: + + NAME = 'ament_xmllint' + FILE_TYPES = ('*.xml',) + + def __init__(self, args: list[str]) -> None: + self.args = args + self.args = args + + def __call__(self) -> Literal[0, 1]: + return main(self.args) + + +def main(argv=sys.argv[1:]) -> Literal[0, 1]: default_extensions = ['xml'] parser = argparse.ArgumentParser( @@ -73,7 +87,8 @@ def main(argv=sys.argv[1:]): xmllint_bin = shutil.which('xmllint') if not xmllint_bin: - return "Could not find 'xmllint' executable" + print("Could not find 'xmllint' executable", file=sys.stderr) + return 1 report = [] diff --git a/ament_xmllint/setup.py b/ament_xmllint/setup.py index e8dbcf37..5f5fb3ee 100644 --- a/ament_xmllint/setup.py +++ b/ament_xmllint/setup.py @@ -43,5 +43,8 @@ 'pytest11': [ 'ament_xmllint = ament_xmllint.pytest_marker', ], + 'ament_lint': [ + 'ament_xmllint = ament_xmllint.main:XmlLintRunner', + ], }, )