Skip to content

Joschi3/package_xml_validation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

105 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ROS 2 Package XML Validator & Formatter

CI Lint codecov

Automate package.xml consistency in your ROS 2 projects.

This tool checks your package manifests for required tags, schema-defined ordering, REP-149 invariants, and missing dependencies discovered in CMakeLists.txt and launch files, then automatically formats the XML to standard conventions. Designed primarily as a pre-commit hook.

Requires Python β‰₯ 3.9 and an initialized rosdep install (any ROS 2 distro). Tested on Ubuntu 22.04 / 24.04.


πŸš€ Quick Start: Pre-commit Hook

The recommended way to use this tool is to integrate it into your pre-commit workflow. This ensures that every commit is automatically validated and formatted without manual intervention.

1. Add to .pre-commit-config.yaml

repos:
  - repo: https://github.com/Joschi3/package_xml_validation.git
    rev: v1.4.2  # Use the latest tag
    hooks:
      - id: format-package-xml
        name: Format package.xml

2. Install the Hook

If you haven't already installed pre-commit hooks in your repository:

pip install pre-commit
pre-commit install

Now, package.xml files will be checked and formatted automatically on every git commit.


πŸ” Visual Example

This tool enforces the standard ROS 2 element order: name β†’ version β†’ description β†’ maintainer β†’ license β†’ dependencies β†’ export.

Before (Disorganized & Missing Build Type): Contains valid tags, but the order is random, grouping is missing, and the export tag is absent.

<package format="3">
  <name>my_package</name>
  <description>My cool package</description>
  <version>0.0.0</version>
  <license>Apache-2.0</license>
  <maintainer email="me@example.com">Me</maintainer>
  <test_depend>ament_lint_auto</test_depend>
  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>std_msgs</depend>
  <depend>rclcpp</depend>
</package>

After (Standardized, Sorted & Fixed): Elements are reordered to match the schema, dependencies are grouped alphabetically, and missing dependencies detected in CMakeLists.txt or launch files are automatically added.

<package format="3">
  <name>my_package</name>
  <version>0.0.0</version>
  <description>My cool package</description>
  <maintainer email="me@example.com">Me</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>example_from_cmake</depend> <!-- Automatically added missing dep from the CMakeLists.txt -->
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>test_launch_example</test_depend> <!-- Automatically added missing dep from a test launch file-->

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

πŸ–₯️ Sample Output

Running the validator on the Before manifest above with --auto-fill-missing-deps produces:

Processing my_package...
	Element order in my_package/package.xml is incorrect.
	Misplaced elements: version, maintainer, buildtool_depend
	Corrected dependency order in my_package/package.xml.
	Check element order corrected in my_package/package.xml.
	Check dependency order corrected in my_package/package.xml.
	Auto-filling <export><build_type>ament_cmake</build_type></export> in my_package/package.xml.
βœ… Corrected `package.xml` files successfully. πŸŽ‰

The on-disk file now matches the After snippet above. The exit code is non-zero because the file was modified β€” pre-commit treats that as "please re-stage and re-commit."


✨ Features

XML Formatting & Standards

  • Required Tags: Enforces the presence of required tags (name, version, description, maintainer, license) and rejects unknown top-level child tags. Accepts every REP-149 dependency tag including the anti-dependency <conflict> and <replace> tags.
  • Strict Ordering: Reorders elements to match the official ROS 2 standard (package_format3.xsd).
  • Intelligent Sorting: Groups dependencies (e.g., build_depend, exec_depend) and sorts them alphabetically.
  • Non-Destructive: Preserves your existing comments and indentation.

REP-149 Conformance

  • Manifest Invariants: Validates that the root element is <package>, that <package format="3"> is present, <name> syntax matches ^[a-z][a-z0-9_]*$, <version> syntax matches MAJOR.MINOR.PATCH, and every <maintainer> has a non-empty email="…" attribute. These can't be safely auto-filled, so they're report-only.
  • Conditional Dependencies: Honours REP-149 condition="…" attributes on dependency tags. Entries whose condition evaluates to False against os.environ are skipped during rosdep checks, CMake comparison, launch-file scanning, build-type matching, and <member_of_group> checks β€” matching what colcon does at build time. Disable with --ignore-conditions if your validation environment differs from build time.
  • <depend> Exclusivity: REP-149 forbids declaring the same key in both <depend> and any of <build_depend>/<build_export_depend>/<exec_depend>. The validator reports overlaps; with --auto-fill-missing-deps it collapses redundant granular tags into the canonical <depend> form.
  • Multiple <build_type>: When more than one <build_type> is active after condition evaluation, the validator picks the last one (REP-149 last-wins rule) and warns β€” multiple actives almost always indicate a config mistake.
  • Interface Packages: Message/service/action packages are required to declare <exec_depend>rosidl_default_runtime</exec_depend> (or the unified <depend> equivalent), per the rolling "Custom interfaces" tutorial. Auto-filled when --auto-fill-missing-deps is on.

Dependency Integrity

  • Launch File Scanning: Scans .py, .yaml, and .xml launch files. If a package is used in a launch file but missing from package.xml, it adds it as an <exec_depend> or <test_depend>. Can be disabled with --skip-launch-dep-check when launch scanning produces false positives or is not desired for a given package.
  • CMake Synchronization: Compares package.xml against CMakeLists.txt to ensure build dependencies match, adding missing entries as <depend> or <test_depend>. Calls of the form find_package(<pkg> QUIET) are treated as optional and skipped. all other forms such as find_package(<pkg>), find_package(<pkg> REQUIRED), and find_package(<pkg> REQUIRED QUIET) are enforced in package.xml.
  • Rosdep Validation: Verifies that your dependency names exist as valid keys in the rosdep database.

Build Configuration

  • Export Validation: Ensures the correct <build_type> (e.g., ament_cmake) is exported.
  • Test Dependencies: Parses test/ folders to ensure testing libraries are declared as <test_depend>.

πŸ› οΈ Manual Usage (CLI)

If you need to run the validator manually or in a CI environment without pre-commit, you can install it via pip.

Installation

pip install package-xml-validator
# OR install from source
pip install .

Usage Examples

Check only (Don't modify files)

package-xml-validator . --check-only

Auto-fill missing dependencies from CMake

package-xml-validator . --compare-with-cmake --auto-fill-missing-deps

CLI Options

Option Description
--check-only Report errors/formatting issues without modifying files (Exit code 1 on failure).
--compare-with-cmake Check if dependencies used in CMakeLists.txt are declared in package.xml.
--auto-fill-missing-deps Automatically add dependencies found in CMake/Launch files to package.xml.
--strict-cmake-checking Treat unresolved CMake dependencies as errors instead of warnings.
--skip-rosdep-key-validation Skip verifying if dependency names exist in the rosdep database.
--missing-deps-only Skips formatting checks; only looks for missing dependencies.
--ignore-cmake-key KEY Treat find_package(KEY ...) in CMakeLists.txt as not requiring a package.xml <depend> entry. Repeatable. Merged with the built-in defaults (Threads, OpenMP, ament_cmake).
--ignore-deps dep1,dep2 Comma-separated list of dependency names to globally ignore in validation.
--skip-launch-dep-check Skip checking for missing dependencies in launch and test files.
--ignore-conditions Disable evaluation of REP-149 condition="…" attributes; every entry is then evaluated regardless of its condition.

Ignoring Dependencies

Some dependencies detected in CMakeLists.txt or launch files should not be declared in package.xml β€” typically when a package pulls in heavy transitive dependencies (e.g. rviz2 and its GUI stack) that should not be installed on every target machine.

Prefer splitting the package first. For example, rather than a single robot_description package containing both URDF/xacro files and an RViz visualization launch file, split it into robot_description (model files) and robot_description_visualization (RViz launch files). Each package then declares only what it truly needs, and deploying robot_description to a robot won't drag in rviz2.

If splitting is not practical, the tool recognizes a validator:ignore directive inside XML comments:

<package format="3">
  <name>robot_description</name>
  <!-- ... -->

  <!-- validator:ignore rviz2 joint_state_publisher_gui -->

  <buildtool_depend>ament_cmake</buildtool_depend>
  <!-- ... -->
</package>
  • Names are space-separated after validator:ignore.
  • The directive may appear anywhere inside the <package> element; multiple directives in the same file are merged.
  • Listed dependencies are neither flagged as missing nor added by --auto-fill-missing-deps.
  • Scope is per-file β€” each package.xml manages its own ignore list.

For a global override β€” typically in CI β€” use the --ignore-deps CLI argument:

package-xml-validator . --compare-with-cmake --ignore-deps rviz2,joint_state_publisher_gui

πŸ§ͺ CI Integration

The simplest CI integration is to run your existing pre-commit config in GitHub Actions:

# .github/workflows/lint.yml
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - uses: pre-commit/action@v3.0.1

If rosdep is not initialized in your CI image, add --skip-rosdep-key-validation to the hook's args in .pre-commit-config.yaml.


🧭 Architecture

The validator is structured as a small pipeline. For each package.xml, PackageXmlValidator parses the file once, runs a list of validation steps against the in-memory tree, and writes back only if a step actually mutated.

Module Responsibility
package_xml_validator.py CLI entry point and per-file orchestration (parse β†’ run steps β†’ optionally write).
helpers/validation_steps/ One *Step class per validation rule. Each docstring states the rule, inputs, and when (if ever) it mutates the tree.
helpers/formatter/ Pure structural checks (structural_checks.py), tree mutators (mutations.py), indentation/pretty-print helpers, and shared schema constants. PackageXmlFormatter is a thin facade.
helpers/cmake_parsers.py Lightweight regex-based CMake parser used by CMakeComparisonStep.
helpers/find_launch_dependencies.py Extracts package names referenced from launch files for LaunchDependencyStep.
helpers/rosdep_validator.py, rosdep_wrapper.py Resolve rosdep keys + workspace packages; the wrapper is the single boundary against the untyped rosdep2.
helpers/workspace.py ROS workspace layout discovery (locate the <ws>/src for a given path).

To add a new validation rule, create a new file under helpers/validation_steps/ exporting a subclass of ValidationStep, then register it in PackageXmlValidator._build_steps.


⌨️ Autocompletion

To enable tab autocompletion for CLI arguments:

  1. Install: pip install .
  2. Enable (Temporary): eval "$(register-python-argcomplete package-xml-validator)"
  3. Enable (Permanent): echo 'eval "$(register-python-argcomplete package-xml-validator)"' >> ~/.bashrc

About

Validate and auto-format ROS 2 package.xml files with schema checks, non-destructive fixes, and optional CMake/launch dependency sync.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors