Fail builds if multiple main classes are found
Update the maven and gradle plugins to fail the build if a single unique main class cannot be found. Additionally plugins will warn if the search is taking too long. Fixes gh-210
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
@@ -29,11 +30,13 @@ import org.springframework.boot.loader.tools.Repackager;
|
||||
|
||||
/**
|
||||
* Repackage task.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class Repackage extends DefaultTask {
|
||||
|
||||
private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
private String customConfiguration;
|
||||
|
||||
private Object withJarTask;
|
||||
@@ -74,7 +77,22 @@ public class Repackage extends DefaultTask {
|
||||
if ("".equals(archive.getClassifier())) {
|
||||
File file = archive.getArchivePath();
|
||||
if (file.exists()) {
|
||||
Repackager repackager = new Repackager(file);
|
||||
Repackager repackager = new Repackager(file) {
|
||||
protected String findMainMethod(java.util.jar.JarFile source) throws IOException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
return super.findMainMethod(source);
|
||||
}
|
||||
finally {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
if (duration > FIND_WARNING_TIMEOUT) {
|
||||
getLogger().warn(
|
||||
"Searching for the main-class is taking some time, "
|
||||
+ "consider using setting 'springBoot.mainClass'");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
repackager.setMainClass(extension.getMainClass());
|
||||
if (extension.convertLayout() != null) {
|
||||
repackager.setLayout(extension.convertLayout());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -77,27 +77,42 @@ public abstract class MainClassFinder {
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String findMainClass(File rootFolder) throws IOException {
|
||||
return doWithMainClasses(rootFolder, new ClassNameCallback<String>() {
|
||||
@Override
|
||||
public String doWith(String className) {
|
||||
return className;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given callback operation on all main classes from the given root
|
||||
* folder.
|
||||
* @param rootFolder the root folder
|
||||
* @param callback the callback
|
||||
* @return the first callback result or {@code null}
|
||||
* @throws IOException
|
||||
*/
|
||||
public static <T> T doWithMainClasses(File rootFolder, ClassNameCallback<T> callback)
|
||||
throws IOException {
|
||||
if (!rootFolder.isDirectory()) {
|
||||
throw new IllegalArgumentException("Inavlid root folder '" + rootFolder + "'");
|
||||
}
|
||||
File mainClassFile = findMainClassFile(rootFolder);
|
||||
if (mainClassFile == null) {
|
||||
return null;
|
||||
}
|
||||
String mainClass = mainClassFile.getAbsolutePath();
|
||||
return convertToClassName(mainClass, rootFolder.getAbsolutePath() + "/");
|
||||
}
|
||||
|
||||
private static File findMainClassFile(File root) throws IOException {
|
||||
String prefix = rootFolder.getAbsolutePath() + "/";
|
||||
Deque<File> stack = new ArrayDeque<File>();
|
||||
stack.push(root);
|
||||
stack.push(rootFolder);
|
||||
while (!stack.isEmpty()) {
|
||||
File file = stack.pop();
|
||||
if (file.isFile()) {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
try {
|
||||
if (isMainClass(inputStream)) {
|
||||
return file;
|
||||
String className = convertToClassName(file.getAbsolutePath(),
|
||||
prefix);
|
||||
T result = callback.doWith(className);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -133,6 +148,24 @@ public abstract class MainClassFinder {
|
||||
*/
|
||||
public static String findMainClass(JarFile jarFile, String classesLocation)
|
||||
throws IOException {
|
||||
return doWithMainClasses(jarFile, classesLocation,
|
||||
new ClassNameCallback<String>() {
|
||||
@Override
|
||||
public String doWith(String className) {
|
||||
return className;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given callback operation on all main classes from the given jar.
|
||||
* @param jarFile the jar file to search
|
||||
* @param classesLocation the location within the jar containing classes
|
||||
* @return the first callback result or {@code null}
|
||||
* @throws IOException
|
||||
*/
|
||||
public static <T> T doWithMainClasses(JarFile jarFile, String classesLocation,
|
||||
ClassNameCallback<T> callback) throws IOException {
|
||||
List<JarEntry> classEntries = getClassEntries(jarFile, classesLocation);
|
||||
Collections.sort(classEntries, new ClassEntryComparator());
|
||||
for (JarEntry entry : classEntries) {
|
||||
@@ -140,9 +173,12 @@ public abstract class MainClassFinder {
|
||||
jarFile.getInputStream(entry));
|
||||
try {
|
||||
if (isMainClass(inputStream)) {
|
||||
String name = entry.getName();
|
||||
name = convertToClassName(name, classesLocation);
|
||||
return name;
|
||||
String className = convertToClassName(entry.getName(),
|
||||
classesLocation);
|
||||
T result = callback.doWith(className);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -238,6 +274,20 @@ public abstract class MainClassFinder {
|
||||
public boolean isFound() {
|
||||
return this.found;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface used to receive class names.
|
||||
*/
|
||||
public static interface ClassNameCallback<T> {
|
||||
|
||||
/**
|
||||
* Handle the specified class name
|
||||
* @param className the class name
|
||||
* @return a non-null value if processing should end or {@code null} to continue
|
||||
*/
|
||||
T doWith(String className);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,9 +18,13 @@ package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.springframework.boot.loader.tools.MainClassFinder.ClassNameCallback;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to repackage an archive so that it can be executed using
|
||||
* '{@literal java -jar}'.
|
||||
@@ -171,8 +175,7 @@ public class Repackager {
|
||||
startClass = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);
|
||||
}
|
||||
if (startClass == null) {
|
||||
startClass = MainClassFinder.findMainClass(source,
|
||||
this.layout.getClassesLocation());
|
||||
startClass = findMainMethod(source);
|
||||
}
|
||||
String launcherClassName = this.layout.getLauncherClassName();
|
||||
if (launcherClassName != null) {
|
||||
@@ -193,6 +196,13 @@ public class Repackager {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
protected String findMainMethod(JarFile source) throws IOException {
|
||||
MainClassesCallback callback = new MainClassesCallback();
|
||||
MainClassFinder.doWithMainClasses(source, this.layout.getClassesLocation(),
|
||||
callback);
|
||||
return callback.getMainClass();
|
||||
}
|
||||
|
||||
private void renameFile(File file, File dest) {
|
||||
if (!file.renameTo(dest)) {
|
||||
throw new IllegalStateException("Unable to rename '" + file + "' to '" + dest
|
||||
@@ -206,4 +216,24 @@ public class Repackager {
|
||||
}
|
||||
}
|
||||
|
||||
private static class MainClassesCallback implements ClassNameCallback<Object> {
|
||||
|
||||
private final List<String> classNames = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
public Object doWith(String className) {
|
||||
this.classNames.add(className);
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getMainClass() {
|
||||
if (this.classNames.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to find a single main class from the following candidates "
|
||||
+ this.classNames);
|
||||
}
|
||||
return this.classNames.isEmpty() ? null : this.classNames.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,12 +17,14 @@
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.loader.tools.MainClassFinder;
|
||||
import org.springframework.boot.loader.tools.MainClassFinder.ClassNameCallback;
|
||||
import org.springframework.boot.loader.tools.sample.ClassWithMainMethod;
|
||||
import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod;
|
||||
|
||||
@@ -106,4 +108,42 @@ public class MainClassFinderTests {
|
||||
assertThat(actual, equalTo("a.B"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doWithFolderMainMethods() throws Exception {
|
||||
this.testJarFile.addClass("a/b/c/D.class", ClassWithMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/c/E.class", ClassWithoutMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/G.class", ClassWithMainMethod.class);
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
MainClassFinder.doWithMainClasses(this.testJarFile.getJarSource(), callback);
|
||||
assertThat(callback.getClassNames().toString(), equalTo("[a.b.G, a.b.c.D]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doWithJarMainMethods() throws Exception {
|
||||
this.testJarFile.addClass("a/b/c/D.class", ClassWithMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/c/E.class", ClassWithoutMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/G.class", ClassWithMainMethod.class);
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
MainClassFinder.doWithMainClasses(this.testJarFile.getJarFile(), "", callback);
|
||||
assertThat(callback.getClassNames().toString(), equalTo("[a.b.G, a.b.c.D]"));
|
||||
}
|
||||
|
||||
private static class ClassNameCollector implements ClassNameCallback<Object> {
|
||||
|
||||
private final List<String> classNames = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
public Object doWith(String className) {
|
||||
this.classNames.add(className);
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<String> getClassNames() {
|
||||
return this.classNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -130,6 +130,18 @@ public class RepackagerTests {
|
||||
assertThat(hasLauncherClasses(file), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleMainClassFound() throws Exception {
|
||||
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
|
||||
this.testJarFile.addClass("a/b/D.class", ClassWithMainMethod.class);
|
||||
File file = this.testJarFile.getFile();
|
||||
Repackager repackager = new Repackager(file);
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Unable to find a single main class "
|
||||
+ "from the following candidates [a.b.C, a.b.D]");
|
||||
repackager.repackage(NO_LIBRARIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMainClass() throws Exception {
|
||||
this.testJarFile.addClass("a/b/C.class", ClassWithoutMainMethod.class);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.boot.maven;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
@@ -46,6 +48,8 @@ import org.springframework.boot.loader.tools.Repackager;
|
||||
@Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
|
||||
public class RepackageMojo extends AbstractMojo {
|
||||
|
||||
private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
/**
|
||||
* The Maven project.
|
||||
*/
|
||||
@@ -95,7 +99,24 @@ public class RepackageMojo extends AbstractMojo {
|
||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||
File source = this.project.getArtifact().getFile();
|
||||
File target = getTargetFile();
|
||||
Repackager repackager = new Repackager(source);
|
||||
Repackager repackager = new Repackager(source) {
|
||||
@Override
|
||||
protected String findMainMethod(JarFile source) throws IOException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
return super.findMainMethod(source);
|
||||
}
|
||||
finally {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
if (duration > FIND_WARNING_TIMEOUT) {
|
||||
getLog().warn(
|
||||
"Searching for the main-class is taking some time, "
|
||||
+ "consider using the mainClass configuration "
|
||||
+ "parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
repackager.setMainClass(this.mainClass);
|
||||
if (this.layout != null) {
|
||||
getLog().info("Layout: " + this.layout);
|
||||
|
||||
Reference in New Issue
Block a user