-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Infer main class if it isn't configured anywhere #278
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff!
import java.util.stream.Stream; | ||
import javax.annotation.Nullable; | ||
|
||
/** Class used for inferring the main class in an application. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: usually can omit phrases like Class used for
so it's like Infers the main...
/** Helper class for loading a .class file. */ | ||
private static class ClassFileLoader extends ClassLoader { | ||
|
||
private Path classFile; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
} | ||
|
||
/** | ||
* Searches for a class containing a main method given an absolute root directory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should prob have a note that this is searching for .class
files in the directory.
} | ||
|
||
String className = null; | ||
try (Stream<Path> pathStream = Files.walk(Paths.get(rootDirectory))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use DirectoryWalker
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this much of an improvement? Looks like implementing a DirectoryWalker would be more complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mostly as a way to abstract away the strategy for walking into a testable class. The use would like something like:
new DirectoryWalker(rootDirectory).filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".class")).walk(classFile -> {...});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, the one you implemented. I was thinking you meant something like this https://commons.apache.org/proper/commons-io/javadocs/api-1.4/org/apache/commons/io/DirectoryWalker.html
List<Path> classFiles = | ||
pathStream | ||
.filter(Files::isRegularFile) | ||
.filter(path -> path.toString().endsWith(".class")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path
has endsWith
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right that makes sense. Looks like we used Filesystems#getPathMatcher
in MavenSourceFilesConfiguration
.
@@ -15,7 +15,6 @@ jib { | |||
image = 'gcr.io/jib-integration-testing/emptyimage:gradle' | |||
credHelper = 'gcr' | |||
} | |||
mainClass = 'com.test.Empty' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😃
throw new GradleException( | ||
HelpfulSuggestionsProvider.get("Could not find main class specified in a 'jar' task") | ||
.suggest("add a `mainClass` configuration to jib")); | ||
gradleBuildLogger.info( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could prob just be at the debug level.
try { | ||
mainClass = | ||
MainClassFinder.findMainClass( | ||
project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use GradleSourceConfiguration#getClassesFiles
, which will be the files that go into the container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns a list of Paths, how would I use it to get the root directory?
} catch (MultipleClassesFoundException | IOException ex) { | ||
throw new GradleException( | ||
HelpfulSuggestionsProvider.get("Failed to get main class: " + ex.getMessage()) | ||
.suggest("add a `mainClass` configuration to jib")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ex
should go as a second argument to GradleException
rather than a string addition so there is stack hierarchy.
} catch (MultipleClassesFoundException | IOException ex) { | ||
throw new GradleException( | ||
HelpfulSuggestionsProvider.get("Failed to get main class: " + ex.getMessage()) | ||
.suggest("add a `mainClass` configuration to jib")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion can be a constant together with below.
It would need to do the findMainClass for all the paths since there could
be multiple sourcesets.
…On Wed, May 16, 2018 at 6:25 PM Tad Cordle ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In
jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ProjectProperties.java
<#278 (comment)>
:
> @@ -46,21 +48,38 @@ String ***@***.*** String mainClass) {
if (mainClass == null) {
mainClass = getMainClassFromJarTask();
if (mainClass == null) {
- throw new GradleException(
- HelpfulSuggestionsProvider.get("Could not find main class specified in a 'jar' task")
- .suggest("add a `mainClass` configuration to jib"));
+ gradleBuildLogger.info(
+ "Could not find main class specified in a 'jar' task; attempting to "
+ + "infer main class.");
+ try {
+ mainClass =
+ MainClassFinder.findMainClass(
+ project
This returns a list of Paths, how would I use it to get the root directory?
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#278 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEoLKvBO-ZtYuQWH90MK318HSTR6gLfIks5tzKd1gaJpZM4UB6PN>
.
|
public static String findMainClass(String rootDirectory) | ||
throws MultipleClassesFoundException, IOException { | ||
public static List<String> findMainClass(Path rootDirectory) throws IOException { | ||
List<String> results = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more descriptive variable name like classNames
// Got an invalid class file, keep searching | ||
continue; | ||
} | ||
// Get all .class files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Get/Searches (or Looks at)
.filter(Files::isRegularFile) | ||
.filter(path -> path.toString().endsWith(".class")) | ||
.walk( | ||
classFile -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to put this in a separate method since it is getting to almost 6 levels of tabbing around line 91.
&& main.getReturnType() == void.class | ||
&& Modifier.isStatic(main.getModifiers()) | ||
&& Modifier.isPublic(main.getModifiers())) { | ||
results.add(name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is it possible to not have to do the path-to-class conversion and instead just get the class from the file via reflection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look like it, I think I need to pass the full name into the class loader.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe if you pass in null
to defineClass
it would resolve the class name from the file.
} | ||
} | ||
Class<?> fileClass = new ClassFileLoader(classFile).findClass(name); | ||
if (fileClass != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An early return here would help reduce the tabbing.
"Could not find main class specified in a 'jar' task; attempting to " | ||
+ "infer main class."); | ||
|
||
final String mainClassSuggestion = "add a `mainClass` configuration to jib"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh by constant, I meant like a private static final
field.
} | ||
if (mainClass == null) { | ||
// Adds each file in each classes output directory to the classes files list. | ||
JavaPluginConvention javaPluginConvention = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I meant here to just directly use the classes files from SourceFilesConfiguration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was either this or something like getSourceFilesConfiguration().getClassesFiles().get(0).getParent()
since I need the root directory to resolve the class names correctly, and getClassesFiles
seems to return a list of the files in the root directory. Plus using getSourceFilesConfiguration()
was weird in testing, since I would either need to refactor a bit, or mock a bunch of the GradleSourceFilesConfiguration
internals to avoid null pointer exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, though we do need some way to guarantee that the classes we are searching in are the classes that will be in the container.
project.getConvention().getPlugin(JavaPluginConvention.class); | ||
SourceSet mainSourceSet = javaPluginConvention.getSourceSets().getByName("main"); | ||
Path classesDirs = Paths.get(mainSourceSet.getOutput().getClassesDirs().getAsPath()); | ||
List<String> mainClasses = new ArrayList<>(MainClassFinder.findMainClass(classesDirs)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ArrayList<>
doesn't seem to be necessary.
mainClass = mainClasses.get(0); | ||
} else if (mainClasses.size() == 0) { | ||
throw new GradleException( | ||
HelpfulSuggestionsProvider.get("Main class was not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these messages are the same as in Maven, they can be added to HelpfulSuggestions
.
@@ -48,29 +51,36 @@ String getMainClass(@Nullable String mainClass) { | |||
if (mainClass == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the logic is getting a bit more complex, it'd be good to add a description to the Javadoc like:
If {@code mainClass} is {@code null}, tries to infer main class in this order:
1. Looks in a {@code jar} task.
2. Searches for a class defined with a main method.
Warns if main class is not valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there!
&& main.getReturnType() == void.class | ||
&& Modifier.isStatic(main.getModifiers()) | ||
&& Modifier.isPublic(main.getModifiers())) { | ||
classNames.add(fileClass.getCanonicalName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you would want getName
since the canonical name would replace a subclass like package.Hello$Main
with package.Hello.Main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
K, wasn't sure which one we wanted.
|
||
ProjectProperties(Project project, GradleBuildLogger gradleBuildLogger) { | ||
this.project = project; | ||
this.gradleBuildLogger = gradleBuildLogger; | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
This should prob go in a static method like ProjectProperties#getForProject
since constructors generally should be lightweight.
@@ -54,32 +78,29 @@ String getMainClass(@Nullable String mainClass) { | |||
gradleBuildLogger.debug( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was talking to @patflynn and Patrick mentioned that it might be better to log something like Searching for main class... Add a 'mainClass' configuration to 'jib' to improve build speed.
at the info
level to help users in case they have a ton of classes to search.
try { | ||
List<String> mainClasses = | ||
MainClassFinder.findMainClass(Paths.get(project.getBuild().getOutputDirectory())); | ||
MainClassFinder.findMainClasses(Paths.get(project.getBuild().getOutputDirectory())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use MavenSourceFilesConfiguration
like you did in Gradle.
HelpfulSuggestionsProvider.get("Could not find main class specified in a 'jar' task") | ||
.suggest("add a `mainClass` configuration to jib")); | ||
gradleBuildLogger.debug( | ||
"Could not find main class specified in a 'jar' task; attempting to " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a todo to consolidate this code with the Maven one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already added it to #262, but I can add a TODO in the code as well if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay that's fine then.
Searches the build output for .class files with a
main
method. If more than one is found, or if one isn't found, it throws an error.Fixes #189.