diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4007e4c..5da0437 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,3 +68,14 @@ repos: files: \.(c|cc|cxx|cpp|h|hh|hpp|hxx|ipp)$ # -i arg is included by default by the hook args: ["-fallback-style=none"] + + # The repo is versioned as a whole; VERSION is the authoritative copy and + # every package.xml has to agree with it. See dev/version.py. + - repo: local + hooks: + - id: version-consistency + name: package.xml versions match VERSION + entry: dev/version.py --check + language: system + files: '(^VERSION$|package\.xml$)' + pass_filenames: false diff --git a/README.md b/README.md index 3dfc63a..18b76ae 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ROS packages for Robotiq grippers and sensors. | Package | Description | ROS Version | |---|---|---| | [robotiq_tsf](robotiq_tsf/) | TSF-85 tactile sensor driver | ROS 2 Humble / Jazzy / Lyrical ([main](https://github.com/robotiq/ros/tree/main)) / ROS 1 Noetic ([noetic](https://github.com/robotiq/ros/tree/noetic)) | -| [grippers](grippers/) | ROS 2 driver for Robotiq grippers (2F-85/140, Hand-E) — vendored from [PickNik](https://github.com/PickNikRobotics/ros2_robotiq_gripper) | ROS 2 Humble / Jazzy / Lyrical | +| [grippers](grippers/) | ROS 2 `ros2_control` driver for Robotiq grippers (2F-85/140, Hand-E), on the [Robotiq C++ SDK](https://github.com/Robotiq/grippers) | ROS 2 Humble / Jazzy / Lyrical | ## Supported ROS 2 distros @@ -32,6 +32,29 @@ is identical across all three. On Humble that makes this repo a drop-in replacement for PickNik's `humble` branch — see [Migrating from PickNik's ros2_robotiq_gripper](#migrating-from-picknik-ros2_robotiq_gripper). +## Versioning + +The repository is versioned as a whole: every package carries the same version +and each release is one tag, `v`, covering both the gripper and TSF +stacks. A release touching only one stack still moves the other's version. + +[`VERSION`](VERSION) at the repo root is the authoritative copy, on its first +line. `package.xml` must hold a literal version string — ament, rosdep and bloom +parse it statically — so the value is necessarily duplicated there; +`dev/version.py` propagates it, and a pre-commit hook fails on drift: + +```bash +dev/version.py # show the version, report any drift +dev/version.py --set 1.2.0 # bump VERSION and every package.xml +``` + +`VERSION` also lists the `package.xml` files holding a copy, as a reminder of +what a bump touches. That list is generated by `--set` and checked by the hook, +so adding or removing a package fails until it is regenerated. + +Tags predating this scheme were per-stack: `V1.0.0` released the TSF packages, +and `0.0.1` is PickNik's original gripper release. + ## Legacy | Repository | Description | ROS Version | @@ -101,7 +124,21 @@ In the combined launch the pad frames are TF-mounted on the gripper fingertip li ROS 2 `ros2_control` driver for Robotiq grippers (2F-85 / 2F-140, Hand-E), under [`grippers/`](grippers/). -`grippers/` is a vendored copy of PickNik Robotics' [`ros2_robotiq_gripper`](https://github.com/PickNikRobotics/ros2_robotiq_gripper) (BSD-3-Clause), imported via `git subtree` at upstream commit `3b6cf8f` with its history preserved, and maintained here in-tree. Upstream copyright and `` tags are retained. +The driver runs on the [Robotiq C++ grippers SDK](https://github.com/Robotiq/grippers), which arrives as +the `extern/grippers` submodule — so clone with `--recurse-submodules`. The SDK owns the serial link +and the Modbus exchange (one FC 0x17 transaction per cycle, on its own thread); `robotiq_driver` is +the `ros2_control` layer above it, and its `read()` / `write()` copy the SDK's process image rather +than touching the bus. + +**Robotiq maintains this driver.** It started from PickNik Robotics' +[`ros2_robotiq_gripper`](https://github.com/PickNikRobotics/ros2_robotiq_gripper) (BSD-3-Clause), +imported with `git subtree` at upstream commit `3b6cf8f` so its history came along, and has been +developed here since: the in-tree Modbus implementation and the `vcs import`ed `serial` package it +depended on were replaced by the SDK, three distros build from one branch, and bugs still open +upstream ([#114](https://github.com/PickNikRobotics/ros2_robotiq_gripper/issues/114), +[#88](https://github.com/PickNikRobotics/ros2_robotiq_gripper/issues/88)) are fixed here. Upstream +copyright and `` tags are retained; issues and support go to +[robotiq/ros/issues](https://github.com/robotiq/ros/issues), not to PickNik. ### Migrating from PickNik's ros2_robotiq_gripper diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..7b33ac7 --- /dev/null +++ b/VERSION @@ -0,0 +1,15 @@ +1.1.0 + +# Authoritative version for the whole repository. Every package.xml below +# carries a copy, because ament, rosdep and bloom parse that version +# statically and it cannot reference a variable: +# +# grippers/robotiq_controllers/package.xml +# grippers/robotiq_description/package.xml +# grippers/robotiq_driver/package.xml +# grippers/robotiq_hardware_tests/package.xml +# robotiq_tsf/package.xml +# +# Bump with `dev/version.py --set X.Y.Z`, which rewrites this file and every +# package.xml above. A pre-commit hook fails if they drift, or if this list +# stops matching the packages in the repo. diff --git a/dev/version.py b/dev/version.py new file mode 100755 index 0000000..da0e4fc --- /dev/null +++ b/dev/version.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Keep every package.xml equal to the repo-root VERSION file. + +The repository is versioned as a whole: one version for all packages, one tag +per release. package.xml has to carry a literal version string — ament, rosdep +and bloom parse it statically, so it cannot reference a variable — which means +the version is necessarily duplicated. VERSION is the authoritative copy and +this script is what propagates it; the --check mode runs in pre-commit and CI so +the copies cannot drift. + + dev/version.py # report the current version and any drift + dev/version.py --check # exit non-zero on drift (pre-commit, CI) + dev/version.py --set 1.2.0 # write VERSION and every package.xml + dev/version.py --check-tag v1.1.0 # assert a release tag matches VERSION +""" + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +VERSION_FILE = REPO / "VERSION" + +# appears once per package.xml, and only the first occurrence is the +# package's own version, so the substitution is deliberately count-limited. +VERSION_TAG = re.compile(r"()([^<]*)()") +SEMVER = re.compile(r"^\d+\.\d+\.\d+$") + + +def package_xmls(): + """Every package.xml we own. + + Enumerated through git rather than a glob: tracked files only, so colcon + build/install trees carrying copies of package.xml stay out, and submodule + contents (gitlinks, not trees) are excluded for free. + """ + listed = subprocess.run( + ["git", "-C", str(REPO), "ls-files", "*package.xml"], + capture_output=True, + text=True, + check=True, + ) + return sorted(REPO / line for line in listed.stdout.split()) + + +def read_version(): + """The version from VERSION: first line that is not blank or a comment. + + The version stays on line 1 so `head -1 VERSION` remains a valid way to read + it; the comment block listing the package.xml copies follows below. + """ + for line in VERSION_FILE.read_text().splitlines(): + stripped = line.strip() + if stripped and not stripped.startswith("#"): + return stripped + raise SystemExit(f"{VERSION_FILE} holds no version line") + + +def listed_package_xmls(): + """The package.xml paths named in VERSION's reminder block.""" + listed = [] + for line in VERSION_FILE.read_text().splitlines(): + stripped = line.lstrip("#").strip() + if stripped.endswith("package.xml"): + listed.append(stripped) + return sorted(listed) + + +def render_version_file(version): + """VERSION's full contents: the version, then the generated reminder. + + The list is generated rather than hand-kept, and --check compares it against + the tracked package.xml files, so it cannot quietly go stale when a package + is added or removed. + """ + lines = [ + version, + "", + "# Authoritative version for the whole repository. Every package.xml below", + "# carries a copy, because ament, rosdep and bloom parse that version", + "# statically and it cannot reference a variable:", + "#", + ] + lines += [f"# {p.relative_to(REPO)}" for p in package_xmls()] + lines += [ + "#", + "# Bump with `dev/version.py --set X.Y.Z`, which rewrites this file and every", + "# package.xml above. A pre-commit hook fails if they drift, or if this list", + "# stops matching the packages in the repo.", + ] + return "\n".join(lines) + "\n" + + +def package_version(path): + match = VERSION_TAG.search(path.read_text()) + return match.group(2) if match else None + + +def drift(expected): + return [ + (p.relative_to(REPO), package_version(p)) + for p in package_xmls() + if package_version(p) != expected + ] + + +def write_version(path, version): + text = path.read_text() + updated = VERSION_TAG.sub(rf"\g<1>{version}\g<3>", text, count=1) + if updated != text: + path.write_text(updated) + return True + return False + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + group = parser.add_mutually_exclusive_group() + group.add_argument("--check", action="store_true", help="fail on drift") + group.add_argument("--set", metavar="X.Y.Z", help="set the repo version") + group.add_argument("--check-tag", metavar="TAG", help="assert TAG matches VERSION") + args = parser.parse_args() + + if args.set: + if not SEMVER.match(args.set): + parser.error(f"not a semantic version: {args.set}") + VERSION_FILE.write_text(render_version_file(args.set)) + for path in package_xmls(): + if write_version(path, args.set): + print(f"updated {path.relative_to(REPO)}") + print(f"repo version is now {args.set}") + return 0 + + expected = read_version() + + if args.check_tag: + # Release tags are the repo version with a leading v. + if args.check_tag.lstrip("vV") != expected: + print( + f"tag {args.check_tag} does not match VERSION ({expected})", + file=sys.stderr, + ) + return 1 + print(f"tag {args.check_tag} matches VERSION") + return 0 + + tracked = [str(p.relative_to(REPO)) for p in package_xmls()] + stale = listed_package_xmls() != sorted(tracked) + mismatched = drift(expected) + + if not mismatched and not stale: + print(f"{expected}: {len(tracked)} package.xml files agree") + return 0 + + if stale: + listed = set(listed_package_xmls()) + print("VERSION's package.xml list no longer matches the repo:", file=sys.stderr) + for path in sorted(listed ^ set(tracked)): + side = "listed, no longer in the repo" if path in listed else "not listed" + print(f" {path}: {side}", file=sys.stderr) + + if mismatched: + print(f"VERSION says {expected}, but:", file=sys.stderr) + for path, found in mismatched: + print(f" {path}: {found}", file=sys.stderr) + if args.check: + print("run dev/version.py --set to fix", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/extern/grippers b/extern/grippers index 7620865..ce63844 160000 --- a/extern/grippers +++ b/extern/grippers @@ -1 +1 @@ -Subproject commit 7620865369e84c190e1ceba178b5c81fad53b1c1 +Subproject commit ce6384455c6802d89329ea56b5d7e2eaf26b3654 diff --git a/grippers/robotiq_controllers/CHANGELOG.rst b/grippers/robotiq_controllers/CHANGELOG.rst deleted file mode 100644 index 467d0a3..0000000 --- a/grippers/robotiq_controllers/CHANGELOG.rst +++ /dev/null @@ -1,9 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package robotiq_controllers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.0.1 (2023-07-17) ------------------- -* Initial ROS 2 release of robotiq_controllers - * This package is not supported by Robotiq but is being maintained by PickNik Robotics -* Contributors: Alex Moriarty, Cory Crean diff --git a/grippers/robotiq_controllers/package.xml b/grippers/robotiq_controllers/package.xml index c50091b..4834285 100644 --- a/grippers/robotiq_controllers/package.xml +++ b/grippers/robotiq_controllers/package.xml @@ -2,7 +2,7 @@ robotiq_controllers - 0.0.1 + 1.1.0 Controllers for the Robotiq gripper. Robotiq BSD-3-Clause diff --git a/grippers/robotiq_description/CHANGELOG.rst b/grippers/robotiq_description/CHANGELOG.rst deleted file mode 100644 index 8ece7f4..0000000 --- a/grippers/robotiq_description/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package robotiq_description -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.0.1 (2023-07-17) ------------------- -* Initial ROS 2 release of robotiq_description - * includes support for Robotiq 2F 85 - * This package is not supported by Robotiq but is being maintained by PickNik Robotics -* Contributors: Alex Moriarty, Anthony Baker, Chance Cardona, Cory Crean, Erik Holum, Marq Rasmussen, Sakai Hibiki, Sebastian Castro, marqrazz diff --git a/grippers/robotiq_description/package.xml b/grippers/robotiq_description/package.xml index 2e1858f..1e4fd90 100644 --- a/grippers/robotiq_description/package.xml +++ b/grippers/robotiq_description/package.xml @@ -2,7 +2,7 @@ robotiq_description - 0.0.1 + 1.1.0 URDF and xacro description package for the Robotiq gripper. Cory Crean Robotiq diff --git a/grippers/robotiq_driver/CHANGELOG.rst b/grippers/robotiq_driver/CHANGELOG.rst deleted file mode 100644 index 785e85e..0000000 --- a/grippers/robotiq_driver/CHANGELOG.rst +++ /dev/null @@ -1,11 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package robotiq_driver -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.0.1 (2023-07-17) ------------------- -* Initial ROS 2 release of robotiq_driver - * This package should be ignored by initial bloom release until serial or cxx_serial is released - * includes support for Robotiq 2F 85 - * This package is not supported by Robotiq but is being maintained by PickNik Robotics -* Contributors: Alex Moriarty, Anthony Baker, Cory Crean, Erik Holum, Ezra Brooks, Marq Rasmussen, marqrazz diff --git a/grippers/robotiq_driver/package.xml b/grippers/robotiq_driver/package.xml index 57f4620..d0518b7 100644 --- a/grippers/robotiq_driver/package.xml +++ b/grippers/robotiq_driver/package.xml @@ -2,7 +2,7 @@ robotiq_driver - 0.0.1 + 1.1.0 ROS2 driver package for the Robotiq gripper. Robotiq BSD-3-Clause diff --git a/grippers/robotiq_hardware_tests/package.xml b/grippers/robotiq_hardware_tests/package.xml index 2b60f0e..a7b50e0 100644 --- a/grippers/robotiq_hardware_tests/package.xml +++ b/grippers/robotiq_hardware_tests/package.xml @@ -2,7 +2,7 @@ robotiq_hardware_tests - 0.0.1 + 1.1.0 ROS2 driver for the Robotiq gripper. Robotiq BSD-3-Clause diff --git a/robotiq_tsf/package.xml b/robotiq_tsf/package.xml index 84478dc..207f5e8 100644 --- a/robotiq_tsf/package.xml +++ b/robotiq_tsf/package.xml @@ -1,7 +1,7 @@ robotiq_tsf - 1.0.0 + 1.1.0 ROS 2 tactile sensing package for the Robotiq TSF-85 (Humble, Jazzy, Lyrical). Jean-Philippe Roberge