License check tool for Apache ShenYu releases. Ensures every runtime dependency is properly declared in the project's LICENSE file.
Two tools are provided:
| Tool | Language | When to use | Dependencies |
|---|---|---|---|
shenyu_watcher.py | Python 3 | Before packaging — check directly from Maven project | Maven (optional, falls back to pom.xml parsing) |
ShenyuWatcher.java | Java 8 | After packaging — check from a .tar.gz release archive | Python ≥ 3.8, Java 8 |
The Python tool is recommended for development and CI workflows — it catches missing license declarations before you build, without needing to create a release archive.
shenyu_watcher.py)mvn dependency:list, no packaging needed--fix) — fetches license info from Maven Central POMs and appends missing entries to the LICENSE file in the correct section--pr) — after fixing, creates a git branch, commits only the LICENSE change, pushes, and opens a GitHub PR via the gh CLI (implies --fix)LICENSE, src/main/release-docs/LICENSE, etc.shiro-core:jakarta:1.13.0, netty-tcnative-boringssl-static:linux-x86_64:2.0.65.Final)lib/ directory instead. GAV is read from each jar’s embedded pom.properties, with filename parsing as a fallback.lib/ directory.gh) installed and authenticated, when using --pr.# Check only — report missing dependencies. # An assembled lib/ dir is auto-detected under <project>/target/*-bin/lib and # scanned in preference to running Maven; otherwise Maven is used. python shenyu_watcher.py /path/to/maven/project # Check with explicit LICENSE path python shenyu_watcher.py /path/to/maven/project /path/to/LICENSE # Check and auto-fix — append missing entries to LICENSE python shenyu_watcher.py --fix /path/to/maven/project # Short form python shenyu_watcher.py -f /path/to/maven/project # Scan a specific lib/ directory of jars instead of running Maven python shenyu_watcher.py --lib /path/to/lib /path/to/maven/project # Force Maven dependency resolution (disable lib/ auto-detection) python shenyu_watcher.py --no-lib /path/to/maven/project # Fix and open a GitHub PR for the LICENSE change (implies --fix) python shenyu_watcher.py --pr /path/to/maven/project # Fix + PR with a custom branch and base python shenyu_watcher.py --pr --branch fix/admin-license --base main /path/to/maven/project # Fix + PR pushing to a fork remote (when you lack push access to origin) python shenyu_watcher.py --pr --remote fork /path/to/maven/project
Check the shenyu-admin-dist module:
python shenyu_watcher.py /path/to/shenyu/shenyu-dist/shenyu-admin-dist
Output (the dist module's assembled lib/ is auto-detected and scanned):
📦 Scanning JARs in lib directory...
Found 324 unique dependencies (214 from pom.properties, 110 from filename).
(110 jars had no pom.properties — resolved from filename, groupId unknown; license auto-fix will skip these.)
📄 Using LICENSE: .../shenyu-admin-dist/src/main/release-docs/LICENSE
============================================================
License Check Report
============================================================
Total dependencies: 324
Checked: 294
Skipped (project): 30
Skipped (test/provided): 0
Matched in LICENSE: 290
NOT matched in LICENSE: 4
❌ 4 dependency(ies) NOT declared in LICENSE
(grouped by groupId, 3 groups)
────────────────────────────────────────────────────────────
(2 artifact(s))
• jna:5.18.1
• jna-platform:5.18.1
com.github.oshi (1 artifact(s))
• oshi-core:6.10.0
jakarta.mail (1 artifact(s))
• jakarta.mail-api:2.1.3
Result: FAIL — 4 dependency(ies) missing from LICENSE
jnaandjna-platformshow no groupId because those jars had no embeddedpom.propertiesand were resolved from their filenames. License matching still works for them (by artifactId + version). For--fix, the groupId is recovered from Maven Central (by artifactId + version) so the license can be fetched; if that lookup also fails, the dependency is skipped with a warning.
Auto-fix and re-verify:
python shenyu_watcher.py --fix /path/to/shenyu/shenyu-dist/shenyu-admin-dist
🔍 Resolving license info for 4 missing dependencies...
jna 5.18.1: groupId resolved from Maven Central -> net.java.dev.jna
jna 5.18.1 → LGPL licenses (LGPL-2.1-or-later)
jna-platform 5.18.1: groupId resolved from Maven Central -> net.java.dev.jna
jna-platform 5.18.1 → LGPL licenses (LGPL-2.1-or-later)
oshi-core 6.10.0 → MIT licenses (MIT)
jakarta.mail-api 2.1.3 → EPL licenses (EPL 2.0)
✅ Added 4 entries to LICENSE
Added entries by section:
EPL licenses:
+ jakarta.mail-api 2.1.3: https://mvnrepository.com/artifact/jakarta.mail/jakarta.mail-api, EPL 2.0
LGPL licenses:
+ jna 5.18.1: https://mvnrepository.com/artifact/net.java.dev.jna/jna, LGPL-2.1-or-later
+ jna-platform 5.18.1: https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform, LGPL-2.1-or-later
MIT licenses:
+ oshi-core 6.10.0: https://github.com/oshi/oshi, MIT
============================================================
Re-checking after fix...
============================================================
--fix Works<license><name>, <url>, and <scm><url> from the POM<url> (project homepage) over <scm><url> (may point to a personal fork) artifactId version: url, LicenseName to the correct sectionWith --pr, the flow additionally:
license-fix-<timestamp>, or the name passed to --branch) from the current HEAD — so the only commit on the new branch is the LICENSE changegit commit -- <relpath>) — unrelated dirty files in the working tree are never swept in--remote (default origin) with upstream trackinggh pr create with a title and a body listing the added entries and the check context--pr--pr turns the in-place LICENSE edit into a reviewable pull request. It implies --fix, so you can run it directly:
python shenyu_watcher.py --pr /path/to/shenyu/shenyu-dist/shenyu-admin-dist
What it does:
git rev-parse --show-toplevel), so it works when the project is a subdirectory of a larger repo (e.g. a shenyu monorepo module whose LICENSE lives at src/main/release-docs/LICENSE).gh. The PR body lists each added entry (grouped by LICENSE section) plus the check totals and any dependencies still unmatched after the fix (for manual review).Pre-flight checks (exit code 2 on failure): the project must be inside a git repository; the --remote (default origin) must exist; the LICENSE file must be inside the repo and not already have uncommitted changes; gh must be installed and authenticated.
If you lack push access to origin, fork the repo (gh repo fork --remote --remote-name fork) and re-run with --remote fork.
Example output:
🔧 PR target: /path/to/shenyu branch: license-fix-20260708-143012 (from main) remote: origin base: <repo default> ✅ Pull request opened: https://github.com/apache/shenyu/pull/NNNN branch: license-fix-20260708-143012 base: <repo default>
When a POM‘s <url> or <scm><url> points to a GitHub repository that appears to be a personal fork (e.g., at.yawk.lz4:lz4-java has https://github.com/yawkat/lz4-java), the tool detects this and uses https://mvnrepository.com/artifact/... instead, which always links to the canonical project information. The heuristic checks whether the GitHub owner matches the groupId or artifactId — if not, it’s treated as a likely fork.
The tool recognizes these section headers in the LICENSE file:
| POM License Name Contains | LICENSE Section |
|---|---|
Apache | Apache 2.0 licenses |
MIT | MIT licenses |
BSD / Go License | BSD licenses |
EPL / Eclipse Public License | EPL licenses |
EDL / Eclipse Distribution License | EDL License |
MPL / Mozilla Public License | MPL licenses |
CDDL | CDDL licenses |
CC0 | CC0 licenses |
Public Domain | Public Domain licenses |
Bouncy Castle | Bouncy Castle licenses |
ISC | ISC licenses |
| Unknown ⚠️ | Apache 2.0 licenses (with warning) |
When a license name cannot be mapped to any known section, the tool prints a warning and defaults to Apache 2.0 — review these entries manually.
Each appended entry follows the format used by the existing LICENSE file:
<artifactId> <version>[-<classifier>]: <url>, <LicenseName>
Examples:
zookeeper 3.9.5: https://gitbox.apache.org/repos/asf/zookeeper, Apache 2.0oshi-core 6.7.0: https://github.com/oshi/oshi, MITbcprov-jdk18on 1.84: https://github.com/bcgit/bc-java, Bouncy Castleshiro-core 1.13.0-jakarta: https://shiro.apache.org, Apache 2.0 (with classifier)| Code | Meaning |
|---|---|
| 0 | All dependencies matched (PASS) |
| 1 | Missing dependencies found (FAIL), or Maven/LICENSE not found |
| 2 | --pr requested but preconditions failed (not a git repo, gh missing/unauthenticated, LICENSE dirty or outside repo, push failed) — see message |
# In CI pipeline — exit code 1 if any dependency is missing python shenyu_watcher.py /path/to/project # Auto-fix in CI (modifies LICENSE in place — review the diff in version control) python shenyu_watcher.py --fix /path/to/project
ShenyuWatcher.java)mvn clean package
java -jar target/shenyu-watcher-1.0-SNAPSHOT.jar filePath/xxx.tar.gz
Checks a .tar.gz release archive: extracts it, scans lib/ for JAR filenames, parses each into package name + version, and verifies against the LICENSE file inside the archive.
commons-lang3-3.14.0.jar). in version strings matches any character, causing potential false matcheslib/ folder| Feature | Python (shenyu_watcher.py) | Java (ShenyuWatcher.java) |
|---|---|---|
| Check timing | Before packaging | After packaging |
| Dependency source | mvn dependency:list (all runtime deps) | JAR filenames in lib/ |
| Transitive deps | ✅ Yes | ❌ Only what's in lib/ |
| JAR name parsing | Not needed — Maven provides exact coordinates | Regex-based, breaks on names like commons-lang3 |
| LICENSE matching | Literal string match (no false positives) | Regex match (. matches any char) |
| Classifier support | ✅ Handles shiro-core:jakarta, native variants | ❌ Not supported |
| Auto-fix | ✅ --fix flag | ❌ None |
| Open PR | ✅ --pr (via gh) | ❌ None |
| License discovery | ✅ Auto-finds LICENSE in project | ❌ Must be in tar.gz |
| Fork detection | ✅ Detects fork POMs, uses mvnrepository fallback | ❌ N/A |
| Unknown license warning | ✅ Warns on unmappable licenses | ❌ N/A |
| Python dependency | None | Python ≥ 3.8 (checked but unused) |
| Maven dependency | Recommended (falls back to pom.xml) | None |