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
66 changes: 55 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/shitikanth/enforcer-rules.svg?label=License)](http://www.apache.org/licenses/) [![Github CI](https://github.com/shitikanth/enforcer-rules/actions/workflows/ci.yml/badge.svg)](https://github.com/mojohaus/extra-enforcer-rules/actions/workflows/ci.yml)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.shitikanth/enforcer-rules.svg?label=Maven%20Central)](https://search.maven.org/artifact/io.github.shitikanth/enforcer-rules)
# Motivation

## Ban Empty Java Files
# Enforcer Rules

Empty Java source files (or all commented or just not containing a class with the same name) are
detected as stale source files
by [Apache Maven Compiler Plugin](https://maven.apache.org/plugins/maven-compiler-plugin/), so modules containing such
files get unnecessarily recompiled every single time.
Custom [Maven Enforcer](https://maven.apache.org/enforcer/maven-enforcer-plugin/) rules.

This plugin adds an enforcer rule to detect and ban such files.
## Rules

- [banEmptyJavaFiles](#banemptyjavafiles)
- [requireDependencyManagement](#requiredependencymanagement)

# Usage

```xml
Add `enforcer-rules` as a dependency of the `maven-enforcer-plugin` and configure the desired rules inside an `enforce` execution:

```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
Expand All @@ -23,21 +23,65 @@ This plugin adds an enforcer rule to detect and ban such files.
<dependency>
<groupId>io.github.shitikanth</groupId>
<artifactId>enforcer-rules</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce-java-rules</id>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<banEmptyJavaFiles/>
<requireDependencyManagement/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
```
```

---

## banEmptyJavaFiles

Empty Java source files — or files that contain no top-level type declaration whose name matches the file name — are detected as stale by the [Maven Compiler Plugin](https://maven.apache.org/plugins/maven-compiler-plugin/), causing unnecessary recompilation on every build.

This rule fails the build if any such file is found.

```xml
<banEmptyJavaFiles/>
```

---

## requireDependencyManagement

Fails the build if any project dependency declares its version inline rather than inheriting it from `<dependencyManagement>`. This encourages centralised version management and prevents version drift across modules.

```xml
<requireDependencyManagement/>
```

### Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `excludes` | `List<String>` | *(empty)* | Dependency coordinates to exempt from the check, in `groupId:artifactId` format. `*` is a glob wildcard and may appear anywhere within either segment. |

### Excluding dependencies

Use `<excludes>` to exempt specific dependencies. The most common use case is a multi-module project where modules depend on each other with `<version>${project.version}</version>` — these do not need a `<dependencyManagement>` entry.

```xml
<requireDependencyManagement>
<excludes>
<!-- exempt all sibling modules in this reactor -->
<exclude>${project.groupId}:*</exclude>
</excludes>
</requireDependencyManagement>
```


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.buildResult = failure
36 changes: 36 additions & 0 deletions src/it/fail-require-dependency-management/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.shitikanth</groupId>
<artifactId>fail-require-dependency-management</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>@maven-enforcer-plugin.version@</version>
<dependencies>
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
</dependency>
</dependencies>
<configuration>
<rules>
<requireDependencyManagement/>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
7 changes: 7 additions & 0 deletions src/it/fail-require-dependency-management/verify.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
File file = new File( basedir, "build.log" )
assert file.exists()
String text = file.getText("utf-8")

assert text.contains('[ERROR] Rule 0: io.github.shitikanth.enforcerrules.RequireDependencyManagement failed with message:')
assert text.contains('[ERROR] Dependencies without dependency management:')
assert text.contains('\t- org.junit.jupiter:junit-jupiter:5.11.0')
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.buildResult = success
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.shitikanth.it</groupId>
<artifactId>success-require-dependency-management-multimodule</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>module-a</artifactId>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.shitikanth.it</groupId>
<artifactId>success-require-dependency-management-multimodule</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>module-b</artifactId>

<dependencies>
<dependency>
<groupId>io.github.shitikanth.it</groupId>
<artifactId>module-a</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
37 changes: 37 additions & 0 deletions src/it/success-require-dependency-management-multimodule/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.shitikanth.it</groupId>
<artifactId>success-require-dependency-management-multimodule</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>module-a</module>
<module>module-b</module>
</modules>

<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>@maven-enforcer-plugin.version@</version>
<dependencies>
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
</dependency>
</dependencies>
<configuration>
<rules>
<requireDependencyManagement>
<excludes>
<exclude>${project.groupId}:*</exclude>
</excludes>
</requireDependencyManagement>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
File file = new File( basedir, "build.log" )
assert file.exists()
String text = file.getText("utf-8")
assert !text.contains('Dependencies without dependency management:')
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.buildResult = success
45 changes: 45 additions & 0 deletions src/it/success-require-dependency-management/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.shitikanth</groupId>
<artifactId>success-require-dependency-management</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>@maven-enforcer-plugin.version@</version>
<dependencies>
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
</dependency>
</dependencies>
<configuration>
<rules>
<requireDependencyManagement/>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
2 changes: 2 additions & 0 deletions src/it/success-require-dependency-management/verify.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
File file = new File( basedir, "build.log" )
assert file.exists()
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.github.shitikanth.enforcerrules;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.project.MavenProject;

@Named("requireDependencyManagement")
class RequireDependencyManagement extends AbstractEnforcerRule {

private final MavenProject project;

private List<String> excludes = new ArrayList<>();

@Inject
public RequireDependencyManagement(MavenProject project) {
this.project = project;
}

void setExcludes(List<String> excludes) {
this.excludes = excludes;
}

@Override
public void execute() throws EnforcerRuleException {

Set<String> managedKeys = new HashSet<>();
DependencyManagement dependencyManagement = project.getDependencyManagement();
if (dependencyManagement != null) {
for (Dependency managed : dependencyManagement.getDependencies()) {
managedKeys.add(managed.getGroupId() + ":" + managed.getArtifactId());
}
}

List<Dependency> violations = new ArrayList<>();
for (Dependency dep : project.getDependencies()) {
String key = dep.getGroupId() + ":" + dep.getArtifactId();
if (!managedKeys.contains(key) && !isExcluded(dep.getGroupId(), dep.getArtifactId())) {
violations.add(dep);
}
}

if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder("Dependencies without dependency management:\n");
for (Dependency dep : violations) {
sb.append("\t- ")
.append(dep.getGroupId())
.append(":")
.append(dep.getArtifactId())
.append(":")
.append(dep.getVersion())
.append("\n");
}
throw new EnforcerRuleException(sb.toString());
}
}

private boolean isExcluded(String groupId, String artifactId) {
for (String exclude : excludes) {
int colon = exclude.indexOf(':');
if (colon < 0) {
continue;
}
String groupPattern = exclude.substring(0, colon);
String artifactPattern = exclude.substring(colon + 1);
if (matchesGlob(groupPattern, groupId) && matchesGlob(artifactPattern, artifactId)) {
return true;
}
}
return false;
}

private static boolean matchesGlob(String pattern, String input) {
String[] parts = pattern.split("\\*", -1);
StringBuilder regex = new StringBuilder("^");
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
regex.append(".*");
}
regex.append(java.util.regex.Pattern.quote(parts[i]));
}
regex.append("$");
return input.matches(regex.toString());
}
}
Loading
Loading