ascheman opened a new issue, #997:
URL: https://github.com/apache/maven-enforcer/issues/997

   Follow-up to #995 (the `module-info` module rules: `requireExplicitModules`, 
`requireMinimalExports`, `banUnjustifiedOpens`).
   
   ## Problem
   
   The Java module system requires every package to belong to exactly one 
module. When two modules on the module path
   contain the same package, the boot layer refuses to resolve:
   
   ```
   java.lang.module.ResolutionException: Module a.consumer contains package 
com.acme.p,
   module a.provider exports package com.acme.p to a.consumer
   ```
   
   Such a split package is easy to introduce by accident — most often when a 
build-time code generator, an unpacked or
   shaded dependency, or a copied resource puts classes of a *foreign* package 
into the current project's output. The
   failure surfaces late (at packaging, `jlink`, or first boot), and the 
message describes the symptom rather than the
   build step that caused it.
   
   Two further shapes make it worse:
   
   - **Silent variant.** If the project does not `requires` the split module, 
the resolver never pulls it in — so it
     never complains. The build is green and the application boots, but a type 
or an endpoint that was supposed to come
     from that module is silently absent (a 404, a late 
`LayerInstantiationException`, a `NoClassDefFoundError`).
   - **On the classpath.** A split package is legal there and the JVM never 
complains at all — but it is precisely what
     will break a later migration to the module path.
   
   There is currently no Enforcer rule that catches any of these at build time.
   
   ## Proposed rule: `banSplitPackages`
   
   Primary target is the **module path**, but the rule is useful on the 
**classpath** too (as a modularization aid), so
   it covers both. Bind it after compilation (e.g. `process-classes`, or 
`verify`); it fails or warns when a package
   produced by the current project also belongs to one of its dependencies.
   
   Detection:
   
   - **Project packages** — enumerate the packages actually present under the 
compiled output directory (the
     directories that hold `.class` files). This is intentionally based on the 
real output, not the compiled
     `module-info` descriptor: the offending classes are frequently added 
*after* `compile` (generation, copy), and the
     descriptor's `ModulePackages` attribute is only finalized at packaging 
time.
   - **Dependency packages** — resolve the project's dependency artifacts with 
`java.lang.module.ModuleFinder`, whose
     `ModuleReference.descriptor().packages()` yields the full package set and 
transparently handles automatic modules;
     fall back to scanning jar entries for plain (non-modular) artifacts.
   - **Intersect against the full declared-dependency set**, not only the 
modules the project `requires`. This is what
     lets the rule catch the **silent variant** that the resolver misses: the 
split provider is a real Maven dependency,
     so it is in the set even when the module graph would never pull it in.
   - **Report** every package owned by more than one place, naming both owners.
   
   The rule handles the Maven 4 module source hierarchy (POM model 4.1.0), 
where one project compiles several modules,
   each to `${project.build.outputDirectory}/<module-name>/`.
   
   ## Severity model
   
   The severity depends on how real the problem is, and is configurable:
   
   | Overlap kind | Default | Rationale |
   | --- | --- | --- |
   | Project **module** vs. a dependency **module** (incl. the silent, 
not-`requires`d case) | **error** | An actual `ResolutionException` (or a 
silent missing type) — a real defect. |
   | Any overlap involving a **non-modular / classpath** artifact | **warn** | 
Legal on the classpath today; matters only for a future move to modules — flag 
it, don't fail. |
   
   The classpath check is **on by default at `warn`** (at least a warning is 
emitted); set `classpathSeverity=ignore`
   to opt out entirely, or `error` to enforce module-readiness strictly. The 
module-path/severity defaults can still be
   lowered globally through the plugin's per-rule `<level>`.
   
   ## Configuration
   
   | Parameter | Default | Meaning |
   | --- | --- | --- |
   | `classpathSeverity` | `warn` | Severity for overlaps involving non-modular 
artifacts: `warn`, `error`, or `ignore` (opt-out). Module-vs-module overlaps 
are always errors. |
   | `allowedSplitPackages` | – | Packages permitted to overlap (escape hatch). 
|
   | `ignoredModules` / `ignoredArtifacts` | – | Dependencies to exclude from 
the check. |
   | `message` | – | Custom failure message. |
   
   ## Relationship to existing rules
   
   - Complements MojoHaus `banDuplicateClasses` (the classpath 
duplicate-*class* check); this rule is the
     *package*-level counterpart and is module-aware. The real classpath bug is 
a duplicate class (handled there); a
     split package *without* a duplicate class is the module-migration hazard 
this rule surfaces.
   - Sibling of the `module-info` rules proposed in #995 
(`requireExplicitModules`, `requireMinimalExports`,
     `banUnjustifiedOpens`) and can reuse their `module-info` reader.
   
   ## Notes and non-goals
   
   - A guard is a **safety net, not a fix**: the durable cure is placing 
generated or copied classes in the module that
     owns their package.
   - Only **declared dependencies** are inspected. A provider pulled in purely 
at run time (not a Maven dependency)
     cannot be seen at build time and is out of scope; an 
`additionalModulePath` / `scanArtifacts` parameter could let
     advanced users feed such paths in.
   - On a large legacy classpath the `warn` default may be chatty; 
`allowedSplitPackages` / `ignoredArtifacts` /
     `classpathSeverity=ignore` are the escape hatches.
   
   ## Motivation
   
   This surfaced from a real multi-module modular application: a code generator 
wrote classes into a consumer module's
   output, producing a split package that failed `jlink` and boot — and, in one 
configuration, **silently** returned
   404 because the affected module was never resolved. Write-up:
   
<https://vidocq.dev/posts/multi-module-jpms-apps-on-vidocq-one-opens-one-split-package-and-a-fix/>.
   
   I'm happy to contribute the implementation.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to