[MENFORCER-500] New rule to enforce that Maven coordinates match given patterns(#309)
Optionally allows to enforce the module directory name being equal to
the module's artifactId.
diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireMatchingCoordinates.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireMatchingCoordinates.java
new file mode 100644
index 0000000..f5aff82
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireMatchingCoordinates.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.enforcer.rules;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.project.MavenProject;
+
+/**
+ * This rule checks that the Maven coordinates (i.e. the project's {@code groupId} and {@code artifactId}) each match a given pattern.
+ * @since 3.5.0
+ */
+@Named("requireMatchingCoordinates")
+public final class RequireMatchingCoordinates extends AbstractStandardEnforcerRule {
+
+ private Pattern groupIdPattern;
+
+ private Pattern artifactIdPattern;
+
+ private boolean moduleNameMustMatchArtifactId;
+
+ private final MavenProject project;
+
+ @Inject
+ public RequireMatchingCoordinates(MavenProject project) {
+ this.project = Objects.requireNonNull(project);
+ }
+
+ @Override
+ public void execute() throws EnforcerRuleException {
+ StringBuilder msgBuilder = new StringBuilder();
+ if (groupIdPattern != null
+ && !groupIdPattern.matcher(project.getGroupId()).matches()) {
+ msgBuilder
+ .append("Group ID must match pattern \"")
+ .append(groupIdPattern)
+ .append("\" but is \"")
+ .append(project.getGroupId())
+ .append("\"");
+ }
+ if (artifactIdPattern != null
+ && !artifactIdPattern.matcher(project.getArtifactId()).matches()) {
+ if (msgBuilder.length() > 0) {
+ msgBuilder.append(System.lineSeparator());
+ }
+ msgBuilder
+ .append("Artifact ID must match pattern \"")
+ .append(artifactIdPattern)
+ .append("\" but is \"")
+ .append(project.getArtifactId())
+ .append("\"");
+ }
+ if (moduleNameMustMatchArtifactId
+ && !project.isExecutionRoot()
+ && !project.getBasedir().getName().equals(project.getArtifactId())) {
+ if (msgBuilder.length() > 0) {
+ msgBuilder.append(System.lineSeparator());
+ }
+ msgBuilder
+ .append("Module directory name must be equal to its artifact ID \"")
+ .append(project.getArtifactId())
+ .append("\" but is \"")
+ .append(project.getBasedir().getName())
+ .append("\"");
+ }
+ if (msgBuilder.length() > 0) {
+ throw new EnforcerRuleException(msgBuilder.toString());
+ }
+ }
+
+ public void setGroupIdPattern(String groupIdPattern) {
+ this.groupIdPattern = Pattern.compile(groupIdPattern);
+ }
+
+ public void setArtifactIdPattern(String artifactIdPattern) {
+ this.artifactIdPattern = Pattern.compile(artifactIdPattern);
+ }
+
+ public void setModuleNameMustMatchArtifactId(boolean moduleNameMustMatchArtifactId) {
+ this.moduleNameMustMatchArtifactId = moduleNameMustMatchArtifactId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "requireMatchingCoordinates[groupIdPattern=%s, artifactIdPattern=%s, moduleNameMustMatchArtifactId=%b]",
+ groupIdPattern, artifactIdPattern, moduleNameMustMatchArtifactId);
+ }
+}
diff --git a/enforcer-rules/src/site/apt/index.apt b/enforcer-rules/src/site/apt/index.apt
index ac384ba..bdf21ae 100644
--- a/enforcer-rules/src/site/apt/index.apt
+++ b/enforcer-rules/src/site/apt/index.apt
@@ -73,6 +73,8 @@
* {{{./requireJavaVersion.html}requireJavaVersion}} - enforces the JDK version.
+ * {{{./requireMatchingCoordinates.html}requireMatchingCoordinates}} - enforces specific group ID and/or artifact ID patterns.
+
* {{{./requireMavenVersion.html}requireMavenVersion}} - enforces the Maven version.
* {{{./requireNoRepositories.html}requireNoRepositories}} - enforces to not include repositories.
diff --git a/enforcer-rules/src/site/apt/requireMatchingCoordinates.apt.vm b/enforcer-rules/src/site/apt/requireMatchingCoordinates.apt.vm
new file mode 100644
index 0000000..906d61f
--- /dev/null
+++ b/enforcer-rules/src/site/apt/requireMatchingCoordinates.apt.vm
@@ -0,0 +1,75 @@
+~~ Licensed to the Apache Software Foundation (ASF) under one
+~~ or more contributor license agreements. See the NOTICE file
+~~ distributed with this work for additional information
+~~ regarding copyright ownership. The ASF licenses this file
+~~ to you under the Apache License, Version 2.0 (the
+~~ "License"); you may not use this file except in compliance
+~~ with the License. You may obtain a copy of the License at
+~~
+~~ http://www.apache.org/licenses/LICENSE-2.0
+~~
+~~ Unless required by applicable law or agreed to in writing,
+~~ software distributed under the License is distributed on an
+~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+~~ KIND, either express or implied. See the License for the
+~~ specific language governing permissions and limitations
+~~ under the License.
+
+ ------
+ Require Matching Coordinates
+ ------
+ Konrad Windszus
+ ------
+ 2024-03-22
+ ------
+
+Require Matching Coordinates
+
+ This rule checks that the Maven coordinates (i.e. the project's <<<groupId>>> and <<<artifactId>>>) each match a given pattern.
+ Optionally one can also enforce that in a multi-module build the module directory name is always equal to the module's <<<artifactId>>>.
+
+ The following parameters are supported by this rule:
+
+ * <<message>> - an optional message to the user if the rule fails. If not set a default message will be used.
+
+ * <<groupIdPattern>> - an optional {{{https://docs.oracle.com/javase/tutorial/essential/regex/}regular expression}}, which must match the project's <<<groupId>>>. If not set there is no check on the <<<groupId>>>.
+
+ * <<artifactIdPattern>> - an optional {{{https://docs.oracle.com/javase/tutorial/essential/regex/}regular expression}}, which must match the project's <<<artifactId>>>. If not set there is no check on the <<<artifactId>>>.
+
+ * <<moduleNameMustMatchArtifactId>> - boolean flag to enforce that the the module's directory name is always equal to the module's <<<artifactId>>>. By default <<<false>>>.
+
+ []
+
+
+ Sample Plugin Configuration:
+
++---+
+<project>
+ [...]
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>${project.version}</version>
+ <executions>
+ <execution>
+ <id>enforce-group-id</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireMatchingCoordinates>
+ <groupIdPattern>com\.example.\namespace\..*</groupIdPattern>
+ </requireTextFileChecksum>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ [...]
+</project>
++---+
diff --git a/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/mod2/pom.xml b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/mod2/pom.xml
new file mode 100644
index 0000000..baa5856
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/mod2/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.enforcer</groupId>
+ <artifactId>test-multimodule-project</artifactId>
+ <version>1.0</version>
+ </parent>
+
+ <artifactId>invalid-test-multimodule-mod2</artifactId>
+
+ <description>
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>test-multimodule-mod1</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/pom.xml b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/pom.xml
new file mode 100644
index 0000000..0797df8
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.its.enforcer</groupId>
+ <artifactId>test-multimodule-project</artifactId>
+ <version>1.0</version>
+ <packaging>pom</packaging>
+
+ <description>
+ </description>
+
+ <modules>
+ <module>test-multimodule-mod1</module>
+ <module>mod2</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>test-multimodule-mod1</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>@project.version@</version>
+ <executions>
+ <execution>
+ <id>test</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireMatchingCoordinates>
+ <groupIdPattern>org\.apache\.maven\.its\.enforcer\.somepackage</groupIdPattern>
+ <artifactIdPattern>test-.*</artifactIdPattern>
+ <moduleNameMustMatchArtifactId>true</moduleNameMustMatchArtifactId>
+ </requireMatchingCoordinates>
+ </rules>
+ <fail>false</fail>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/test-multimodule-mod1/pom.xml b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/test-multimodule-mod1/pom.xml
new file mode 100644
index 0000000..4165af7
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/test-multimodule-mod1/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.enforcer</groupId>
+ <artifactId>test-multimodule-project</artifactId>
+ <version>1.0</version>
+ </parent>
+
+ <groupId>org.apache.maven.its.enforcer.somepackage</groupId>
+ <artifactId>test-multimodule-mod1</artifactId>
+
+ <description>
+ </description>
+
+</project>
diff --git a/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/verify.groovy b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/verify.groovy
new file mode 100644
index 0000000..d9ce9f0
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-matching-coordinates/verify.groovy
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+File buildLog = new File(basedir, 'build.log')
+def log = buildLog.text.normalize()
+assert log.contains('[WARNING] Rule 0: org.apache.maven.enforcer.rules.RequireMatchingCoordinates failed with message:\nGroup ID must match pattern "org\\.apache\\.maven\\.its\\.enforcer\\.somepackage" but is "org.apache.maven.its.enforcer"\n')
+assert log.contains('[WARNING] Rule 0: org.apache.maven.enforcer.rules.RequireMatchingCoordinates failed with message:\nGroup ID must match pattern "org\\.apache\\.maven\\.its\\.enforcer\\.somepackage" but is "org.apache.maven.its.enforcer"\nArtifact ID must match pattern "test-.*" but is "invalid-test-multimodule-mod2"\nModule directory name must be equal to its artifact ID "invalid-test-multimodule-mod2" but is "mod2"\n')