Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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<version>`, 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 |
Expand Down Expand Up @@ -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 `<author>` 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 `<author>` 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

Expand Down
15 changes: 15 additions & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -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.
175 changes: 175 additions & 0 deletions dev/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Keep every package.xml <version> 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"

# <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"(<version>)([^<]*)(</version>)")
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 <version> to fix", file=sys.stderr)
return 1
return 0


if __name__ == "__main__":
sys.exit(main())
9 changes: 0 additions & 9 deletions grippers/robotiq_controllers/CHANGELOG.rst

This file was deleted.

2 changes: 1 addition & 1 deletion grippers/robotiq_controllers/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>robotiq_controllers</name>
<version>0.0.1</version>
<version>1.1.0</version>
<description>Controllers for the Robotiq gripper.</description>
<maintainer email="noreply@robotiq.com">Robotiq</maintainer>
<license>BSD-3-Clause</license>
Expand Down
10 changes: 0 additions & 10 deletions grippers/robotiq_description/CHANGELOG.rst

This file was deleted.

2 changes: 1 addition & 1 deletion grippers/robotiq_description/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>robotiq_description</name>
<version>0.0.1</version>
<version>1.1.0</version>
<description>URDF and xacro description package for the Robotiq gripper.</description>
<author>Cory Crean</author>
<maintainer email="noreply@robotiq.com">Robotiq</maintainer>
Expand Down
11 changes: 0 additions & 11 deletions grippers/robotiq_driver/CHANGELOG.rst

This file was deleted.

2 changes: 1 addition & 1 deletion grippers/robotiq_driver/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>robotiq_driver</name>
<version>0.0.1</version>
<version>1.1.0</version>
<description>ROS2 driver package for the Robotiq gripper.</description>
<maintainer email="noreply@robotiq.com">Robotiq</maintainer>
<license>BSD-3-Clause</license>
Expand Down
2 changes: 1 addition & 1 deletion grippers/robotiq_hardware_tests/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>robotiq_hardware_tests</name>
<version>0.0.1</version>
<version>1.1.0</version>
<description>ROS2 driver for the Robotiq gripper.</description>
<maintainer email="noreply@robotiq.com">Robotiq</maintainer>
<license>BSD-3-Clause</license>
Expand Down
2 changes: 1 addition & 1 deletion robotiq_tsf/package.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<package format="3">
<name>robotiq_tsf</name>
<version>1.0.0</version>
<version>1.1.0</version>
<description>ROS 2 tactile sensing package for the Robotiq TSF-85 (Humble, Jazzy, Lyrical).</description>

<maintainer email="jean-philippe.roberge@etsmtl.ca">Jean-Philippe Roberge</maintainer>
Expand Down
Loading