diff --git a/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java new file mode 100644 index 0000000..5b3607a --- /dev/null +++ b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java @@ -0,0 +1,105 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +/* ======================================================================== + * This file was adapted from the no-package-cycles-enforcer-rule project: + * https://github.com/andrena/no-package-cycles-enforcer-rule + * + * Copyright 2013 - 2018 David Burkhart, Ben Romberg, Daniel Galan y Martins, + * Bastian Feigl, Marc Philipp, and Carsten Otto. + * + * Licensed 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.scijava.maven.plugin.enforcer; + +import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.project.MavenProject; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.Objects; +import java.util.Properties; + +/** + * Ensures a main class exists if the POM declares one. + * + * @author Gabriel Selzer + */ +@Named("mainClassExistsRule") +public class MainClassExistsRule extends AbstractEnforcerRule { + + // Inject needed Maven components + private final MavenProject project; + + @Inject + public MainClassExistsRule(MavenProject project) { + this.project = Objects.requireNonNull(project); + } + + @Override + public void execute() throws EnforcerRuleException { + Properties properties = project.getProperties(); + if (!properties.containsKey("main-class")) { + return; + } + String mainClass = properties.getProperty("main-class"); + if (mainClass.trim().isEmpty()) { + return; + } + + // Get the build output directory (e.g., target/classes) + String outputDirectory = project.getBuild().getOutputDirectory(); + + // Convert class name to file path (e.g., com.example.Main -> com/example/Main.class) + String classFilePath = mainClass.replace('.', File.separatorChar) + ".class"; + // Check if e.g. target/classes/com/example/Main.class exists + File classFile = new File(outputDirectory, classFilePath); + if (!classFile.exists()) { + throw new EnforcerRuleException( + "Main class '" + mainClass + "' declared in POM does not exist. " + + "Expected to find: " + classFile.getAbsolutePath() + ); + } + + getLog().info("Main class '" + mainClass + "' exists at: " + classFile.getAbsolutePath()); + } +} diff --git a/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java b/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java new file mode 100644 index 0000000..a62f04a --- /dev/null +++ b/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java @@ -0,0 +1,92 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.maven.plugin.enforcer; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.maven.enforcer.rule.api.EnforcerLogger; + +public class EnforcerLoggerMock implements EnforcerLogger { + private final List info = new ArrayList(); + + public List getInfo() { + return info; + } + + public void debug(CharSequence message) { + } + + public void debug(Supplier messageSupplier) { + } + + public void info(CharSequence message) { + info.add(message.toString()); + } + + public void info(Supplier messageSupplier) { + info.add(messageSupplier.get().toString()); + } + + public void warn(CharSequence message) { + } + + public void warn(Supplier messageSupplier) { + } + + public void error(CharSequence message) { + } + + public void error(Supplier messageSupplier) { + } + + public void warnOrError(CharSequence message) { + } + + public void warnOrError(Supplier messageSupplier) { + } + + public boolean isDebugEnabled() { + return false; + } + + public boolean isInfoEnabled() { + return true; + } + + public boolean isWarnEnabled() { + return false; + } + + public boolean isErrorEnabled() { + return false; + } +} diff --git a/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java new file mode 100644 index 0000000..0a1dd31 --- /dev/null +++ b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java @@ -0,0 +1,146 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.maven.plugin.enforcer; + +import org.apache.maven.enforcer.rule.api.EnforcerLogger; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link MainClassExistsRule}. + * + * @author Gabriel Selzer + */ +public class MainClassExistsRuleTest { + + /** + * Test subclass that provides a mock logger. + */ + private class MainClassExistsRuleMock extends MainClassExistsRule { + public MainClassExistsRuleMock(MavenProject project) { + super(project); + } + + @Override + public EnforcerLogger getLog() { + return logMock; + } + } + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private MavenProject project; + private File outputDirectory; + private EnforcerLoggerMock logMock; + + @Before + public void setUp() throws Exception { + // Create a temporary output directory + outputDirectory = temporaryFolder.newFolder("target", "classes"); + + // Create a mock MavenProject + project = new MavenProject(); + Build build = new Build(); + build.setOutputDirectory(outputDirectory.getAbsolutePath()); + project.setBuild(build); + + // Create a mock logger + logMock = new EnforcerLoggerMock(); + } + + @Test + public void TestNoMainClassProperty() throws Exception { + // No "main-class" property set + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + // Execute the rule and make sure it passes + rule.execute(); + } + + @Test + public void TestEmptyMainClassProperty() throws Exception { + // No "main-class" property set + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + // Execute the rule and make sure it passes + rule.execute(); + } + + @Test + public void TestMainClassExists() throws Exception { + // Set main-class property + project.getProperties().setProperty("main-class", "com.example.Main"); + + // Create the corresponding .class file + createClassFile("com.example.Main"); + + // Execute the rule and make sure it passes + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + rule.execute(); + } + + @Test + public void TestMainClassDoesNotExist() { + // Set main-class property... + project.getProperties().setProperty("main-class", "com.example.NonExistent"); + + // ...but don't create the file + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + + // ...and assert an Exception is thrown. + Assert.assertThrows(EnforcerRuleException.class, rule::execute); + } + + /** + * Helper method to create a .class file at the appropriate location + * based on the fully qualified class name. + */ + private void createClassFile(String fullyQualifiedClassName) throws IOException { + String classFilePath = fullyQualifiedClassName.replace('.', File.separatorChar) + ".class"; + File classFile = new File(outputDirectory, classFilePath); + + // Create parent directories if needed + classFile.getParentFile().mkdirs(); + + // Create the .class file + assertTrue("Failed to create class file: " + classFile.getAbsolutePath(), + classFile.createNewFile()); + } +}