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.
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.
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.xmlIf you haven't already installed pre-commit hooks in your repository:
pip install pre-commit
pre-commit installNow, package.xml files will be checked and formatted automatically on every git commit.
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>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."
- 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 matchesMAJOR.MINOR.PATCH, and every<maintainer>has a non-emptyemail="β¦"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 toFalseagainstos.environare 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-conditionsif 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-depsit 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-depsis on.
- Launch File Scanning: Scans
.py,.yaml, and.xmllaunch files. If a package is used in a launch file but missing frompackage.xml, it adds it as an<exec_depend>or<test_depend>. Can be disabled with--skip-launch-dep-checkwhen launch scanning produces false positives or is not desired for a given package. - CMake Synchronization: Compares
package.xmlagainstCMakeLists.txtto ensure build dependencies match, adding missing entries as<depend>or<test_depend>. Calls of the formfind_package(<pkg> QUIET)are treated as optional and skipped. all other forms such asfind_package(<pkg>),find_package(<pkg> REQUIRED), andfind_package(<pkg> REQUIRED QUIET)are enforced inpackage.xml. - Rosdep Validation: Verifies that your dependency names exist as valid keys in the rosdep database.
- 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>.
If you need to run the validator manually or in a CI environment without pre-commit, you can install it via pip.
pip install package-xml-validator
# OR install from source
pip install .Check only (Don't modify files)
package-xml-validator . --check-onlyAuto-fill missing dependencies from CMake
package-xml-validator . --compare-with-cmake --auto-fill-missing-deps| 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. |
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_descriptionpackage containing both URDF/xacro files and an RViz visualization launch file, split it intorobot_description(model files) androbot_description_visualization(RViz launch files). Each package then declares only what it truly needs, and deployingrobot_descriptionto a robot won't drag inrviz2.
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.xmlmanages 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_guiThe 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.1If rosdep is not initialized in your CI image, add --skip-rosdep-key-validation to the hook's args in .pre-commit-config.yaml.
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.
To enable tab autocompletion for CLI arguments:
- Install:
pip install . - Enable (Temporary):
eval "$(register-python-argcomplete package-xml-validator)" - Enable (Permanent):
echo 'eval "$(register-python-argcomplete package-xml-validator)"' >> ~/.bashrc