roschema 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {roschema-0.1.2 → roschema-0.2.0}/.github/workflows/ci.yml +3 -1
- {roschema-0.1.2 → roschema-0.2.0}/.github/workflows/release.yml +1 -0
- roschema-0.2.0/CHANGELOG.md +120 -0
- {roschema-0.1.2 → roschema-0.2.0}/PKG-INFO +29 -7
- {roschema-0.1.2 → roschema-0.2.0}/README.md +28 -6
- roschema-0.2.0/action/action.yml +65 -0
- {roschema-0.1.2 → roschema-0.2.0}/docs/rules.md +9 -9
- {roschema-0.1.2 → roschema-0.2.0}/pyproject.toml +4 -5
- roschema-0.2.0/src/roschema/__init__.py +8 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/cli.py +146 -44
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/config.py +118 -55
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/deep.py +21 -7
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/differ.py +48 -30
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/discovery/discover.py +6 -5
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/discovery/model.py +3 -3
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/discovery/resolve.py +58 -17
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/freeze.py +1 -3
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/ir.py +2 -6
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/loader.py +1 -3
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/mcap_fs.py +109 -35
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/parser/__init__.py +1 -1
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/parser/core.py +17 -19
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/report/github.py +13 -3
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/report/json_report.py +15 -9
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/report/summary.py +2 -6
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs002_field_added.py +1 -1
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs006_array_kind_changed.py +5 -4
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs007_bound_tightened.py +1 -1
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs008_bound_loosened.py +1 -1
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs060_message_version.py +1 -4
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs110_transitive.py +2 -1
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/gendocs.py +3 -4
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl005_discouraged_types.py +6 -4
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl006_unbounded.py +2 -6
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/model.py +1 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/vfs.py +109 -8
- {roschema-0.1.2 → roschema-0.2.0}/tests/_helpers.py +9 -4
- roschema-0.2.0/tests/test_action.py +100 -0
- roschema-0.2.0/tests/test_check_cli.py +402 -0
- roschema-0.2.0/tests/test_cli_version.py +18 -0
- roschema-0.2.0/tests/test_config.py +281 -0
- roschema-0.2.0/tests/test_deep.py +153 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_differ.py +53 -20
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_discovery.py +19 -1
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_fmt.py +1 -5
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_freeze.py +11 -3
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_lint.py +54 -0
- roschema-0.2.0/tests/test_mcap.py +491 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_parser.py +19 -3
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_report_formats.py +13 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_vfs.py +56 -1
- roschema-0.1.2/CHANGELOG.md +0 -55
- roschema-0.1.2/action/action.yml +0 -51
- roschema-0.1.2/src/roschema/__init__.py +0 -3
- roschema-0.1.2/tests/test_check_cli.py +0 -182
- roschema-0.1.2/tests/test_config.py +0 -149
- roschema-0.1.2/tests/test_deep.py +0 -75
- roschema-0.1.2/tests/test_mcap.py +0 -196
- {roschema-0.1.2 → roschema-0.2.0}/.gitignore +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/LICENSE +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/discovery/__init__.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/formatter.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/parser/errors.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/py.typed +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/report/__init__.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/report/text.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/__init__.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/__init__.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs001_field_removed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs003_field_type_changed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs004_fields_reordered.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs005_field_renamed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs020_constant_removed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs021_constant_value_changed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs022_constant_type_changed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs030_default_changed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/breaking/rs040_interface_removed.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/__init__.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/_common.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl001_field_snake_case.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl002_constant_upper_snake.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl003_name_pascal_case.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl004_missing_doc.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/lint/rsl010_srv_shadow.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/src/roschema/rules/registry.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/conftest.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/AMENT_IGNORE +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/COLCON_IGNORE +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/golden_check.json +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/action/Move.action +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/ArrayKind.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundLoose.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundTight.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundedNest.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Constants.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Empty.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/FieldAdded.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/FieldRemoved.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Renamed.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Reordered.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Suppressed.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/TypeChanged.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/Unchanged.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/msg/WeirdSpacing.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/package.xml +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_new/demo_msgs/srv/DoThing.srv +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/action/Move.action +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/ArrayKind.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundLoose.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundTight.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundedNest.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Constants.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Empty.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/FieldAdded.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/FieldRemoved.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Removed.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Renamed.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Reordered.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Suppressed.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/TypeChanged.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/Unchanged.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/msg/WeirdSpacing.msg +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/package.xml +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/fixtures/ws_old/demo_msgs/srv/DoThing.srv +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_cli_ls.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_gendocs.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_golden.py +0 -0
- {roschema-0.1.2 → roschema-0.2.0}/tests/test_rules.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/), and the
|
|
4
|
+
project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.2.0] - 2026-11-03
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `bag-check --level` and `--config`.
|
|
11
|
+
- Git baselines read initialised submodules at their recorded commit, and warn
|
|
12
|
+
when that commit is not available locally.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `bag-check` reports at the `wire` level by default, since constant and other
|
|
17
|
+
source-level changes do not stop current code decoding a recording.
|
|
18
|
+
- Package-name targets resolve to the workspace source package before the
|
|
19
|
+
installed copy, with a warning when the installed copy is used.
|
|
20
|
+
- Adding or removing a sequence bound (`T[]` <-> `T[<=N]`) is reported as a
|
|
21
|
+
bound change (RS007/RS008) rather than an array-kind change (RS006).
|
|
22
|
+
- RS110 takes the severity of the change it traces back to and ignores
|
|
23
|
+
suppressed, allowlisted or disabled changes.
|
|
24
|
+
- Configuring a rule's severity no longer reports it outside the selected
|
|
25
|
+
level; only rules that are off by default (such as RS060) bypass the level.
|
|
26
|
+
- The GitHub Action passes `--level` only when set, so configured levels apply.
|
|
27
|
+
- RS002 is tagged `wire` only, and RSL005 explains why `char` and scalar `byte`
|
|
28
|
+
are ambiguous instead of calling them deprecated.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `bag-check` skips schemas recorded without a definition and compares service
|
|
33
|
+
event schemas (`SRV:` blocks) instead of failing.
|
|
34
|
+
- A same-package type written with its package (`pkg/Type` inside `pkg`) is no
|
|
35
|
+
longer reported as a type change.
|
|
36
|
+
- Duplicate field or constant names are parse errors, as in rosidl.
|
|
37
|
+
- Unknown configuration keys are configuration errors, with a suggestion.
|
|
38
|
+
- `lint --format json` no longer includes semver advisories.
|
|
39
|
+
- Text output no longer wraps long lines when not writing to a terminal.
|
|
40
|
+
- Files are read and written as UTF-8 regardless of locale.
|
|
41
|
+
- GitHub annotation text is escaped.
|
|
42
|
+
- `wchar` in MCAP IDL schemas is rejected instead of narrowed to `char`.
|
|
43
|
+
- The README's GitHub Action example references an existing ref (`@main`).
|
|
44
|
+
|
|
45
|
+
## [0.1.3] - 2026-11-03
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `--version` / `-v` to display the installed package version.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Pass GitHub Action inputs as literal arguments to prevent shell injection.
|
|
54
|
+
- Report tightened bounds even when another bound on the same field increases.
|
|
55
|
+
- Include deleted packages when checking glob targets against Git or directory
|
|
56
|
+
baselines, including globs with parent traversal; reject path globs for lockfile
|
|
57
|
+
and MCAP baselines.
|
|
58
|
+
- Reject unsupported, conflicting or unusable MCAP schemas instead of silently
|
|
59
|
+
checking incomplete definitions, while preserving supported IDL string constants.
|
|
60
|
+
- Accept equivalent NaN constants and defaults when comparing repeated MCAP schemas.
|
|
61
|
+
- Replace each installed package's full interface set with its source target in
|
|
62
|
+
`bag-check`, so deleted source interfaces are reported as removed.
|
|
63
|
+
- Honour workspace and package deep-check settings, with `--deep` / `--no-deep`
|
|
64
|
+
taking precedence.
|
|
65
|
+
- Inherit enclosing workspace configuration when targeting a package directly,
|
|
66
|
+
and apply package lint overrides from `roschema.toml` or `pyproject.toml`.
|
|
67
|
+
- Preserve root package settings in `bag-check` and after deleting its last interface.
|
|
68
|
+
- Reject unknown or misplaced rule identifiers and invalid configuration values.
|
|
69
|
+
- Detect unbounded string elements inside arrays and sequences with RSL006.
|
|
70
|
+
|
|
71
|
+
## [0.1.2] - 2026-11-03
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- Respect `COLCON_IGNORE` and `AMENT_IGNORE` during package discovery to avoid
|
|
76
|
+
reading generated build and install trees, including dangling package symlinks.
|
|
77
|
+
|
|
78
|
+
## [0.1.1] - 2026-07-25
|
|
79
|
+
|
|
80
|
+
Packaging and documentation release; no behavioural changes to the CLI.
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- `py.typed` marker (PEP 561) so downstream type-checkers use roschema's types.
|
|
85
|
+
- `Typing :: Typed` and Python 3.10-3.13 classifiers; `Repository` and `Issues`
|
|
86
|
+
project URLs.
|
|
87
|
+
- README: installation section, common-usage recipes, a demo screenshot, and a
|
|
88
|
+
link to the [roschema-demo](https://github.com/justagist/roschema-demo) repo.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Dropped the redundant Apache license classifier in favour of the SPDX
|
|
93
|
+
`license` expression.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- Removed a broken demo image reference from the README.
|
|
98
|
+
|
|
99
|
+
## [0.1.0] - 2026-07-24
|
|
100
|
+
|
|
101
|
+
First release.
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- `check`: breaking-change detection against a git ref, directory, MCAP bag or
|
|
106
|
+
lockfile baseline. Rules RS001-RS110 across the wire / source / introspection
|
|
107
|
+
/ behaviour compatibility levels, with cumulative `--level` presets and
|
|
108
|
+
per-package semver bump advice.
|
|
109
|
+
- `lint`: rosidl-aligned naming and style rules (RSL001-RSL010), with `--rules`
|
|
110
|
+
overrides.
|
|
111
|
+
- `fmt`: canonical, idempotent formatting that never changes what `check`
|
|
112
|
+
reports; inline `# roschema: ignore=` suppressions survive formatting.
|
|
113
|
+
- `bag-check`: check a recorded MCAP bag against the current environment
|
|
114
|
+
(ament index), with optional source targets overlaid.
|
|
115
|
+
- `freeze`: lockfile baselines for non-git distribution; `--deep` reports
|
|
116
|
+
transitive breakage (RS110).
|
|
117
|
+
- `ls`: discovery diagnostics.
|
|
118
|
+
- Configuration via `roschema.toml` / `pyproject.toml`, inline suppression and
|
|
119
|
+
reasoned allowlists; text / JSON / GitHub output formats; a composite GitHub
|
|
120
|
+
Action.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: roschema
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Breaking-change detection, linting and formatting for ROS 2 interface definitions
|
|
5
5
|
Project-URL: Homepage, https://github.com/justagist/roschema
|
|
6
6
|
Project-URL: Documentation, https://github.com/justagist/roschema#readme
|
|
@@ -124,7 +124,9 @@ roschema check [TARGET] --against REF [--level LEVEL] [--format text|json|github
|
|
|
124
124
|
`TARGET` is a path, a package name, or a glob (`'src/**'`) that expands to every
|
|
125
125
|
package underneath; omit it for the current workspace. Discovery skips trees
|
|
126
126
|
marked with `COLCON_IGNORE` or `AMENT_IGNORE`, including standard colcon build
|
|
127
|
-
and install directories.
|
|
127
|
+
and install directories. Glob targets include matching packages deleted from a Git
|
|
128
|
+
or directory baseline. Lockfile and MCAP baselines have synthetic paths; use a
|
|
129
|
+
workspace target with those baselines instead of a path glob.
|
|
128
130
|
|
|
129
131
|
`--against` accepts a git ref (validated with `git rev-parse`), a directory, an
|
|
130
132
|
MCAP bag (`.mcap`) or a lockfile (`roschema.lock`); the type is auto-detected. A
|
|
@@ -171,7 +173,7 @@ roschema fmt [TARGET] [--check]
|
|
|
171
173
|
|
|
172
174
|
`lint` checks a single tree (no baseline). Naming rules follow rosidl: field
|
|
173
175
|
names snake_case (RSL001), constants UPPER_SNAKE (RSL002), interface names
|
|
174
|
-
PascalCase (RSL003); it also flags
|
|
176
|
+
PascalCase (RSL003); it also flags ambiguous `char`/scalar-`byte` (RSL005) and
|
|
175
177
|
request/response field shadowing in services (RSL010). RSL004 (missing doc) and
|
|
176
178
|
RSL006 (unbounded) are off by default; enable via `[lint.rules]` or per run with
|
|
177
179
|
`--rules` (e.g. `--rules RSL004=warn --rules RSL001=off`; a bare `--rules RSL004`
|
|
@@ -200,10 +202,19 @@ definitions rather than reported as missing.
|
|
|
200
202
|
|
|
201
203
|
Optional `TARGETS` (paths or package names) are overlaid on top of the
|
|
202
204
|
environment, so a package you are editing can be checked from source instead of
|
|
203
|
-
its installed copy.
|
|
205
|
+
its installed copy. Each source package replaces its entire installed interface
|
|
206
|
+
set, so deleted source interfaces are reported as removed even when an installed
|
|
207
|
+
copy remains. If targets overlap, the last target wins for that package. A bag
|
|
208
|
+
type found in neither the environment nor any target
|
|
204
209
|
is reported as removed (RS040). `ros2msg` schemas (including concatenated
|
|
205
210
|
dependent definitions) are fully supported; `ros2idl` is a best-effort
|
|
206
|
-
conversion of the msg-equivalent subset.
|
|
211
|
+
conversion of the msg-equivalent subset. Unsupported encodings or IDL constructs,
|
|
212
|
+
conflicting definitions of the same type (including embedded dependencies), and
|
|
213
|
+
bags without usable ROS schemas produce a tool error (exit `2`). Equivalent
|
|
214
|
+
definitions that differ only in formatting or comments are accepted, including
|
|
215
|
+
repeated NaN constants and defaults. IDL struct inheritance and constants outside
|
|
216
|
+
structs (including generated `T_Constants` modules) are rejected because their
|
|
217
|
+
conversion is incomplete.
|
|
207
218
|
|
|
208
219
|
## Compatibility levels
|
|
209
220
|
|
|
@@ -221,11 +232,17 @@ conversion of the msg-equivalent subset.
|
|
|
221
232
|
|
|
222
233
|
`roschema.toml` at the workspace root, a per-package `roschema.toml`, or
|
|
223
234
|
`[tool.roschema]` in `pyproject.toml`. Precedence: CLI > package > workspace >
|
|
224
|
-
defaults.
|
|
235
|
+
defaults. Workspace discovery searches upwards to the Git repository root, or
|
|
236
|
+
the filesystem root outside Git. Package configurations override inherited
|
|
237
|
+
workspace values even when checking the package directly by path. At each
|
|
238
|
+
directory, `roschema.toml` takes precedence over `[tool.roschema]`. Unknown rule
|
|
239
|
+
IDs, rule IDs in the wrong section, and invalid settings produce configuration
|
|
240
|
+
errors (exit `2`).
|
|
225
241
|
|
|
226
242
|
```toml
|
|
227
243
|
[check]
|
|
228
244
|
level = "source"
|
|
245
|
+
deep = false # --deep / --no-deep overrides this
|
|
229
246
|
|
|
230
247
|
[check.rules]
|
|
231
248
|
RS008 = "off" # off | warn | error
|
|
@@ -290,9 +307,10 @@ jobs:
|
|
|
290
307
|
- uses: actions/checkout@v4
|
|
291
308
|
with:
|
|
292
309
|
fetch-depth: 0 # needed for the git-ref baseline
|
|
293
|
-
- uses: justagist/roschema/action@
|
|
310
|
+
- uses: justagist/roschema/action@main
|
|
294
311
|
with:
|
|
295
312
|
against: origin/${{ github.base_ref }}
|
|
313
|
+
# version: "roschema==0.2.0" # pin a release for reproducible results
|
|
296
314
|
# level: source
|
|
297
315
|
# deep: "true"
|
|
298
316
|
# exclude: "vendor_msgs* legacy_msgs/msg/Old"
|
|
@@ -302,6 +320,10 @@ jobs:
|
|
|
302
320
|
`roschema.lock`). A lockfile or directory baseline needs no `fetch-depth: 0`
|
|
303
321
|
because it is a plain file in the checkout.
|
|
304
322
|
|
|
323
|
+
By default the Action installs the latest roschema release, so new rules can
|
|
324
|
+
change results without any change on your side. Uncomment `version` to pin a
|
|
325
|
+
release instead.
|
|
326
|
+
|
|
305
327
|
## Rule reference
|
|
306
328
|
|
|
307
329
|
Every rule, its levels, default severity and rationale is documented in
|
|
@@ -90,7 +90,9 @@ roschema check [TARGET] --against REF [--level LEVEL] [--format text|json|github
|
|
|
90
90
|
`TARGET` is a path, a package name, or a glob (`'src/**'`) that expands to every
|
|
91
91
|
package underneath; omit it for the current workspace. Discovery skips trees
|
|
92
92
|
marked with `COLCON_IGNORE` or `AMENT_IGNORE`, including standard colcon build
|
|
93
|
-
and install directories.
|
|
93
|
+
and install directories. Glob targets include matching packages deleted from a Git
|
|
94
|
+
or directory baseline. Lockfile and MCAP baselines have synthetic paths; use a
|
|
95
|
+
workspace target with those baselines instead of a path glob.
|
|
94
96
|
|
|
95
97
|
`--against` accepts a git ref (validated with `git rev-parse`), a directory, an
|
|
96
98
|
MCAP bag (`.mcap`) or a lockfile (`roschema.lock`); the type is auto-detected. A
|
|
@@ -137,7 +139,7 @@ roschema fmt [TARGET] [--check]
|
|
|
137
139
|
|
|
138
140
|
`lint` checks a single tree (no baseline). Naming rules follow rosidl: field
|
|
139
141
|
names snake_case (RSL001), constants UPPER_SNAKE (RSL002), interface names
|
|
140
|
-
PascalCase (RSL003); it also flags
|
|
142
|
+
PascalCase (RSL003); it also flags ambiguous `char`/scalar-`byte` (RSL005) and
|
|
141
143
|
request/response field shadowing in services (RSL010). RSL004 (missing doc) and
|
|
142
144
|
RSL006 (unbounded) are off by default; enable via `[lint.rules]` or per run with
|
|
143
145
|
`--rules` (e.g. `--rules RSL004=warn --rules RSL001=off`; a bare `--rules RSL004`
|
|
@@ -166,10 +168,19 @@ definitions rather than reported as missing.
|
|
|
166
168
|
|
|
167
169
|
Optional `TARGETS` (paths or package names) are overlaid on top of the
|
|
168
170
|
environment, so a package you are editing can be checked from source instead of
|
|
169
|
-
its installed copy.
|
|
171
|
+
its installed copy. Each source package replaces its entire installed interface
|
|
172
|
+
set, so deleted source interfaces are reported as removed even when an installed
|
|
173
|
+
copy remains. If targets overlap, the last target wins for that package. A bag
|
|
174
|
+
type found in neither the environment nor any target
|
|
170
175
|
is reported as removed (RS040). `ros2msg` schemas (including concatenated
|
|
171
176
|
dependent definitions) are fully supported; `ros2idl` is a best-effort
|
|
172
|
-
conversion of the msg-equivalent subset.
|
|
177
|
+
conversion of the msg-equivalent subset. Unsupported encodings or IDL constructs,
|
|
178
|
+
conflicting definitions of the same type (including embedded dependencies), and
|
|
179
|
+
bags without usable ROS schemas produce a tool error (exit `2`). Equivalent
|
|
180
|
+
definitions that differ only in formatting or comments are accepted, including
|
|
181
|
+
repeated NaN constants and defaults. IDL struct inheritance and constants outside
|
|
182
|
+
structs (including generated `T_Constants` modules) are rejected because their
|
|
183
|
+
conversion is incomplete.
|
|
173
184
|
|
|
174
185
|
## Compatibility levels
|
|
175
186
|
|
|
@@ -187,11 +198,17 @@ conversion of the msg-equivalent subset.
|
|
|
187
198
|
|
|
188
199
|
`roschema.toml` at the workspace root, a per-package `roschema.toml`, or
|
|
189
200
|
`[tool.roschema]` in `pyproject.toml`. Precedence: CLI > package > workspace >
|
|
190
|
-
defaults.
|
|
201
|
+
defaults. Workspace discovery searches upwards to the Git repository root, or
|
|
202
|
+
the filesystem root outside Git. Package configurations override inherited
|
|
203
|
+
workspace values even when checking the package directly by path. At each
|
|
204
|
+
directory, `roschema.toml` takes precedence over `[tool.roschema]`. Unknown rule
|
|
205
|
+
IDs, rule IDs in the wrong section, and invalid settings produce configuration
|
|
206
|
+
errors (exit `2`).
|
|
191
207
|
|
|
192
208
|
```toml
|
|
193
209
|
[check]
|
|
194
210
|
level = "source"
|
|
211
|
+
deep = false # --deep / --no-deep overrides this
|
|
195
212
|
|
|
196
213
|
[check.rules]
|
|
197
214
|
RS008 = "off" # off | warn | error
|
|
@@ -256,9 +273,10 @@ jobs:
|
|
|
256
273
|
- uses: actions/checkout@v4
|
|
257
274
|
with:
|
|
258
275
|
fetch-depth: 0 # needed for the git-ref baseline
|
|
259
|
-
- uses: justagist/roschema/action@
|
|
276
|
+
- uses: justagist/roschema/action@main
|
|
260
277
|
with:
|
|
261
278
|
against: origin/${{ github.base_ref }}
|
|
279
|
+
# version: "roschema==0.2.0" # pin a release for reproducible results
|
|
262
280
|
# level: source
|
|
263
281
|
# deep: "true"
|
|
264
282
|
# exclude: "vendor_msgs* legacy_msgs/msg/Old"
|
|
@@ -268,6 +286,10 @@ jobs:
|
|
|
268
286
|
`roschema.lock`). A lockfile or directory baseline needs no `fetch-depth: 0`
|
|
269
287
|
because it is a plain file in the checkout.
|
|
270
288
|
|
|
289
|
+
By default the Action installs the latest roschema release, so new rules can
|
|
290
|
+
change results without any change on your side. Uncomment `version` to pin a
|
|
291
|
+
release instead.
|
|
292
|
+
|
|
271
293
|
## Rule reference
|
|
272
294
|
|
|
273
295
|
Every rule, its levels, default severity and rationale is documented in
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: roschema
|
|
2
|
+
description: Catch breaking ROS 2 interface changes in the pull request.
|
|
3
|
+
branding:
|
|
4
|
+
icon: git-pull-request
|
|
5
|
+
color: purple
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: Path or package to check.
|
|
10
|
+
default: "."
|
|
11
|
+
against:
|
|
12
|
+
description: Baseline -- a git ref, directory, .mcap bag or roschema.lock.
|
|
13
|
+
default: origin/main
|
|
14
|
+
level:
|
|
15
|
+
description: >-
|
|
16
|
+
Reporting level (wire, source, introspection, behavior). Empty uses the
|
|
17
|
+
level configured in roschema.toml, else source.
|
|
18
|
+
default: ""
|
|
19
|
+
format:
|
|
20
|
+
description: Output format (text, json, github).
|
|
21
|
+
default: github
|
|
22
|
+
deep:
|
|
23
|
+
description: Resolve nested types and report transitive breakage (RS110).
|
|
24
|
+
default: "false"
|
|
25
|
+
exclude:
|
|
26
|
+
description: >-
|
|
27
|
+
Whitespace-separated globs of packages/interfaces to skip. Split on
|
|
28
|
+
whitespace only so patterns reach roschema literally.
|
|
29
|
+
default: ""
|
|
30
|
+
version:
|
|
31
|
+
description: >-
|
|
32
|
+
roschema requirement for uvx. Unpinned runs the latest release (refreshed
|
|
33
|
+
on every run, even with a uv cache); pin it (e.g. "roschema==0.2.0") for
|
|
34
|
+
reproducible results.
|
|
35
|
+
default: roschema
|
|
36
|
+
|
|
37
|
+
runs:
|
|
38
|
+
using: composite
|
|
39
|
+
steps:
|
|
40
|
+
- uses: astral-sh/setup-uv@v5
|
|
41
|
+
- shell: bash
|
|
42
|
+
env:
|
|
43
|
+
ROSCHEMA_TARGET: ${{ inputs.target }}
|
|
44
|
+
ROSCHEMA_AGAINST: ${{ inputs.against }}
|
|
45
|
+
ROSCHEMA_LEVEL: ${{ inputs.level }}
|
|
46
|
+
ROSCHEMA_FORMAT: ${{ inputs.format }}
|
|
47
|
+
ROSCHEMA_DEEP: ${{ inputs.deep }}
|
|
48
|
+
ROSCHEMA_EXCLUDE: ${{ inputs.exclude }}
|
|
49
|
+
ROSCHEMA_VERSION: ${{ inputs.version }}
|
|
50
|
+
run: |
|
|
51
|
+
set -euo pipefail
|
|
52
|
+
args=(check "$ROSCHEMA_TARGET"
|
|
53
|
+
--against "$ROSCHEMA_AGAINST"
|
|
54
|
+
--format "$ROSCHEMA_FORMAT")
|
|
55
|
+
if [ -n "$ROSCHEMA_LEVEL" ]; then
|
|
56
|
+
args+=(--level "$ROSCHEMA_LEVEL")
|
|
57
|
+
fi
|
|
58
|
+
if [ "$ROSCHEMA_DEEP" = "true" ]; then
|
|
59
|
+
args+=(--deep)
|
|
60
|
+
fi
|
|
61
|
+
read -r -a excludes <<< "${ROSCHEMA_EXCLUDE//$'\n'/ }"
|
|
62
|
+
for glob in "${excludes[@]}"; do
|
|
63
|
+
args+=(--exclude "$glob")
|
|
64
|
+
done
|
|
65
|
+
uvx --refresh-package roschema --from "$ROSCHEMA_VERSION" roschema "${args[@]}"
|
|
@@ -26,7 +26,7 @@ int32 a
|
|
|
26
26
|
|
|
27
27
|
A field was added to an interface section.
|
|
28
28
|
|
|
29
|
-
- Levels: wire
|
|
29
|
+
- Levels: wire
|
|
30
30
|
- Default severity: error
|
|
31
31
|
|
|
32
32
|
*Rationale:* Appending a field is source-compatible but still changes the CDR wire layout, so a new publisher and an old subscriber cannot interoperate.
|
|
@@ -91,12 +91,12 @@ float32 yaw
|
|
|
91
91
|
|
|
92
92
|
### RS006 -- Array kind changed
|
|
93
93
|
|
|
94
|
-
A field changed between
|
|
94
|
+
A field changed between scalar, fixed-size array and sequence forms.
|
|
95
95
|
|
|
96
96
|
- Levels: wire, source
|
|
97
97
|
- Default severity: error
|
|
98
98
|
|
|
99
|
-
*Rationale:*
|
|
99
|
+
*Rationale:* A sequence carries a length prefix that a fixed-size array and a scalar do not, so the CDR layout and the generated container type both change. Adding or removing a sequence bound is reported as RS007 or RS008 instead.
|
|
100
100
|
|
|
101
101
|
```
|
|
102
102
|
# before
|
|
@@ -107,7 +107,7 @@ int32[8] data
|
|
|
107
107
|
|
|
108
108
|
### RS007 -- Bound tightened
|
|
109
109
|
|
|
110
|
-
A
|
|
110
|
+
A sequence or string gained a bound or had its bound reduced.
|
|
111
111
|
|
|
112
112
|
- Levels: wire
|
|
113
113
|
- Default severity: error
|
|
@@ -123,7 +123,7 @@ string<=16 name
|
|
|
123
123
|
|
|
124
124
|
### RS008 -- Bound loosened
|
|
125
125
|
|
|
126
|
-
A
|
|
126
|
+
A sequence or string lost its bound or had its bound increased.
|
|
127
127
|
|
|
128
128
|
- Levels: wire
|
|
129
129
|
- Default severity: warn
|
|
@@ -239,7 +239,7 @@ int32 b
|
|
|
239
239
|
|
|
240
240
|
### RS110 -- Transitively broken by an embedded type
|
|
241
241
|
|
|
242
|
-
An interface embeds (directly or through nested types) another interface whose wire layout changed, so it is broken even though its own definition did not change. Reported only with --deep.
|
|
242
|
+
An interface embeds (directly or through nested types) another interface whose wire layout changed, so it is broken even though its own definition did not change. Reported only with --deep, with the severity of the reported change it traces back to; suppressed changes are not followed.
|
|
243
243
|
|
|
244
244
|
- Levels: wire
|
|
245
245
|
- Default severity: error
|
|
@@ -290,15 +290,15 @@ A field has no trailing documentation comment.
|
|
|
290
290
|
|
|
291
291
|
### RSL005 -- Discouraged type
|
|
292
292
|
|
|
293
|
-
Uses `char`, or a scalar `byte`,
|
|
293
|
+
Uses `char`, or a scalar `byte`, whose generated types are ambiguous.
|
|
294
294
|
|
|
295
295
|
- Default severity: warn
|
|
296
296
|
|
|
297
|
-
*Rationale:* ROS 2
|
|
297
|
+
*Rationale:* rosidl generates `char` as `uint8` although the ROS 2 type table lists it as a DDS `char`, and a scalar `byte` becomes a Python `bytes` object rather than an integer; `uint8` states the intent. `byte[]` remains idiomatic for opaque binary data.
|
|
298
298
|
|
|
299
299
|
### RSL006 -- Unbounded string or array
|
|
300
300
|
|
|
301
|
-
A field is an unbounded array or unbounded
|
|
301
|
+
A field is an unbounded array or contains unbounded strings.
|
|
302
302
|
|
|
303
303
|
- Default severity: off
|
|
304
304
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "roschema"
|
|
7
|
-
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Breaking-change detection, linting and formatting for ROS 2 interface definitions"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -46,9 +46,6 @@ Documentation = "https://github.com/justagist/roschema#readme"
|
|
|
46
46
|
Repository = "https://github.com/justagist/roschema"
|
|
47
47
|
Issues = "https://github.com/justagist/roschema/issues"
|
|
48
48
|
|
|
49
|
-
[tool.hatch.version]
|
|
50
|
-
path = "src/roschema/__init__.py"
|
|
51
|
-
|
|
52
49
|
[tool.hatch.build.targets.wheel]
|
|
53
50
|
packages = ["src/roschema"]
|
|
54
51
|
|
|
@@ -67,4 +64,6 @@ files = ["src"]
|
|
|
67
64
|
|
|
68
65
|
[tool.pytest.ini_options]
|
|
69
66
|
testpaths = ["tests"]
|
|
70
|
-
|
|
67
|
+
# A sourced ROS environment auto-loads its launch testing plugins, which fail to
|
|
68
|
+
# import in a virtualenv without ROS's Python dependencies (e.g. PyYAML).
|
|
69
|
+
addopts = "-q -p no:launch_testing -p no:launch_ros -p no:launch_pytest"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""roschema: breaking-change detection, linting and formatting for ROS 2 interfaces."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("roschema")
|
|
7
|
+
except PackageNotFoundError: # Uninstalled source checkout.
|
|
8
|
+
__version__ = "0.0.0.dev"
|