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.
- Pre-build checking β resolves all runtime dependencies (including transitive) via
mvn dependency:list, no packaging needed - Auto-fix mode (
--fix) β fetches license info from Maven Central POMs and appends missing entries to the LICENSE file in the correct section - PR mode (
--pr) β after fixing, creates a git branch, commits only the LICENSE change, pushes, and opens a GitHub PR via theghCLI (implies--fix) - Smart LICENSE discovery β auto-locates LICENSE files in
LICENSE,src/main/release-docs/LICENSE, etc. - Classifier support β correctly handles Maven classifiers (e.g.,
shiro-core:jakarta:1.13.0,netty-tcnative-boringssl-static:linux-x86_64:2.0.65.Final) - Precise matching β literal string matching (not regex), avoids false positives from license boilerplate text
- Fork detection β when a POM's GitHub URL points to a personal fork rather than the canonical upstream, falls back to mvnrepository for accurate attribution
- Lib-directory scanning β for distribution modules that Maven can't resolve (e.g. pom-packaged aggregators depending on uninstalled reactor siblings), reads the exact shipped jar set from an assembled
lib/directory instead. GAV is read from each jar's embeddedpom.properties, with filename parsing as a fallback. - Fallback β works without Maven by parsing pom.xml directly (direct dependencies only)
- Zero dependencies β pure Python standard library, no pip install needed
- Python 3.6+
- Maven 3.x (recommended; falls back to pom.xml parsing if unavailable). Not required when scanning an assembled
lib/directory. - GitHub CLI (
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/projectCheck the shenyu-admin-dist module:
python shenyu_watcher.py /path/to/shenyu/shenyu-dist/shenyu-admin-distOutput (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...
============================================================
- Runs the normal check to find unmatched dependencies
- For each unmatched dependency, fetches its POM from Maven Central
- Resolves parent POMs recursively (up to 3 levels) since license info is often in parent POMs
- Extracts
<license><name>,<url>, and<scm><url>from the POM - Uses
<url>(project homepage) over<scm><url>(may point to a personal fork) - Detects GitHub fork URLs and falls back to mvnrepository for accurate attribution
- For dependencies resolved from filename only (no groupId), the groupId is recovered from Maven Central by artifactId + version before fetching the POM; if that lookup fails, the dependency is skipped with a warning
- Maps the POM license name to the correct LICENSE section header (e.g., "Apache License, Version 2.0" β "Apache 2.0 licenses")
- Appends the entry in the format
artifactId version: url, LicenseNameto the correct section - Re-runs the check to verify all entries now pass
With --pr, the flow additionally:
- Before writing to disk, creates a new git branch (auto-named
license-fix-<timestamp>, or the name passed to--branch) from the current HEAD β so the only commit on the new branch is the LICENSE change - Writes the LICENSE change, then commits only the LICENSE path (
git commit -- <relpath>) β unrelated dirty files in the working tree are never swept in - Pushes the branch to
--remote(defaultorigin) with upstream tracking - Runs
gh pr createwith a title and a body listing the added entries and the check context - If no entries were actually added (all duplicates or all skipped), the branch is rolled back and no PR is opened
--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-distWhat it does:
- Auto-detects the git root from the project directory (
git rev-parse --show-toplevel), so it works when the project is a subdirectory of a larger repo (e.g. ashenyumonorepo module whose LICENSE lives atsrc/main/release-docs/LICENSE). - Commits only the LICENSE file β a path-scoped commit means build artifacts or other uncommitted changes in the working tree are left alone. It only requires the LICENSE file itself to be clean beforehand.
- Pushes and opens the PR via
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/projectmvn clean packagejava -jar target/shenyu-watcher-1.0-SNAPSHOT.jar filePath/xxx.tar.gzChecks 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.
- Requires Python β₯ 3.8 installed (checked at startup but not actually used)
- JAR filename parsing uses a regex that can fail for artifact IDs containing digits (e.g.,
commons-lang3-3.14.0.jar) - Uses regex matching for LICENSE checks β
.in version strings matches any character, causing potential false matches - No transitive dependency resolution β only sees what's physically in the
lib/folder - No auto-fix capability
| 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 |