Skip to content
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

review fix(MavenLauncher): Improving MavenLauncher dependancy resolution #2112

Merged
merged 31 commits into from
Aug 9, 2018
Merged

review fix(MavenLauncher): Improving MavenLauncher dependancy resolution #2112

merged 31 commits into from
Aug 9, 2018

Conversation

nharrand
Copy link
Collaborator

Based on @tdurieux 's work. (WiP)

  • Handles cases where a dependancy's version is not not specified in the targeted pom, but instead is defined earlier or in parent pom.
  • Handles version with weird name
  • Handles the properties pom.groupId, pom.artifactId, pom.version
    This could probably benefit some more testing and/or some refactoring.
    Help welcome!

@surli
Copy link
Collaborator

surli commented Jun 27, 2018

is it ready for review ?

@tdurieux
Copy link
Collaborator

This class needs a major refactoring because it is a real mess.

@surli surli changed the title fix(MavenLauncher): Improving MavenLauncher dependancy resolution WIP fix(MavenLauncher): Improving MavenLauncher dependancy resolution Jun 27, 2018
@surli
Copy link
Collaborator

surli commented Jun 27, 2018

OK it's a WIP then, I edited the title.

@nharrand
Copy link
Collaborator Author

nharrand commented Jul 12, 2018

I propose to change MavenLauncher's behavior: Instead of implementing every single rules of maven dependency resolution to build a classpath, I propose to build it by invoking maven.

  • On the pro side: If maven can run the project, then spoon should at least find the bytecode for every classes used by the project.
  • On the con side: This requires the user to specify the path to maven home (if it is not a common one). So it might break retro compatibility.

LOGGER.log(Level.ERROR, "Unable to read the pom of the dependency:" + dependence.toString(), e);
}
return dependence;
private File getDefaultMavenHome() throws SpoonException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outch! This method looks really ugly!
Can't you rely on environment variable instead? The M2_HOME was considered as a standard one, but it's not heavily used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you saw that...Fine, I'll correct it.

@surli
Copy link
Collaborator

surli commented Jul 12, 2018

Instead of implementing every single rules of maven dependency resolution to build a classpath

Maven is written in Java. Shouldn't we get the implementation of Maven concerning their dependency resolution?

@nharrand
Copy link
Collaborator Author

nharrand commented Jul 12, 2018

I agree, but I could not find the documentation to do that. I'll look better.

@surli
Copy link
Collaborator

surli commented Jul 12, 2018

It's maven: the documentation is the source ;) https://github.com/apache/maven-dependency-plugin/tree/maven-dependency-plugin-3.1.1

@nharrand
Copy link
Collaborator Author

nharrand commented Jul 12, 2018

I've tried to implement a version that uses directly MavenCli programatically (No calls to MavenInvoker). But to be honest, I am unsure whether it is a good idea.
It relies on maven packages that I cannot declare (or at least did not succeed to) in the pom because it generates some version conflict (as far as I understand).
I think that currently, it works on my machine, because it happens that in my classpath the maven classes follow an api that is the same as the one I expect, but I am unsure if it will work on a machine with a different version of maven installed, or if spoon is used in other circumstances.

I am starting to think that there is a reason why most people seem to use MavenInvoker. I think it is more stable because it isolate the nested call to maven.

What do you think? (Am I missing something?)

@surli
Copy link
Collaborator

surli commented Jul 13, 2018

So if I sum up: the problem is Maven dependencies are difficult to resolve, so the good way to do it is to use Maven directly.

One solution is then to use a Maven Invoker, but clients would have to specify their Maven path.
Another solution is to use Maven dependency resolver but then we cannot guarantee a good behaviour, depending on the version of Maven the clients use.

Then yeah maybe it makes sense to use your first solution...
I propose that you change the API:

  • by default MavenLauncher checks for the env var M2_HOME and try to use it
  • another constructor is created with an argument for the Maven executable path
  • if M2_HOME is not available and the argument is not used an expection is thrown

WDYT?

@nharrand
Copy link
Collaborator Author

One solution is then to use a Maven Invoker, but clients would have to specify their Maven path.
Another solution is to use Maven dependency resolver but then we cannot guarantee a good behaviour, depending on the version of Maven the clients use.

Yes, that is at least how I see it.

by default MavenLauncher checks for the env var M2_HOME and try to use it
another constructor is created with an argument for the Maven executable path
if M2_HOME is not available and the argument is not used an expection is thrown

I think we could also try to exec directly the command mvn -version to determine the path to maven home, before throwing an error.

@nharrand
Copy link
Collaborator Author

nharrand commented Jul 19, 2018

Could someone who has access to travis CI add the following environment variable M2_HOME="/usr/local/maven" ? (If I understand correctly I can't change that from the configuration file.)

@nharrand
Copy link
Collaborator Author

nharrand commented Aug 1, 2018

This code is ready for review!
(Please don't judge the last ten commits of struggle ending up on on just adding -c in one travis script...)

@surli surli changed the title WIP fix(MavenLauncher): Improving MavenLauncher dependancy resolution review fix(MavenLauncher): Improving MavenLauncher dependancy resolution Aug 2, 2018
@@ -8,6 +8,6 @@ wget https://raw.githubusercontent.com/sormuras/bach/master/install-jdk.sh
chmod +x install-jdk.sh

export JAVA_HOME=$HOME/openjdk8
source ./install-jdk.sh -f 10
source ./install-jdk.sh -f 10 -c
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why you needed this argument?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! So basically it tells install-jdk.sh to use the default (the one present in the base conatiner) collection of trusted certificate authority (CA) certificates for java. Without this option, calling maven from java results in SSL errors.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, can you add a comment on the script please?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pom.xml Outdated
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>2.2</version>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you have one version 3.1.1 of maven-invoker and one version 2.2? Does it make sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. This is a leftover from previous trials. I'll fix this ASAP.

properties.setProperty("mdep.outputFile", "spoon.classpath.tmp");
request.setProperties(properties);

//FIXME Should the standard output made silent and error verbose?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be interesting to use a logger debug for this one? WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'll add it ASAP.

*
* @param classPathFiles File[] containing the classpath elements separated with ':'
*/
public static String[] readClassPath(File ... classPathFiles) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it take an array of files here? You should create only one classpath file, don't you?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I thought too. But this is the behavior of maven-dependency-plugin for multi module projects. It creates one classpath file per module.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK it worthes a comment then

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover I'm not sure the method should be public here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the comment, agreed.
About the public, my idea of it was to expose a way for the user to manually add its classpath. But if you think it is not relevent, I can totally make it private.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the user to manually add its classpath

You can still use the environment.setSourceClasspath

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but his method gives you what content to feed in environment.setSourceClasspath should you choose to generate it with maven.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but the purpose of this method is to be called internally to feed automatically the environment. Then you can use it to improve the classpath by adding/removing some libs.
So I'm still not sure there's a strong reason to let this one open.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I made it private.

} catch (NumberFormatException ignore) {
br.close();
} catch (IOException e) {
e.printStackTrace(); //These Exceptions occur after the classpath has been read, so it should not prevent normal execution.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw instead a SpoonException maybe?

Copy link
Collaborator Author

@nharrand nharrand Aug 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can. But do we really want to throw an excepetion for a temporary file that we failed to close? I mean the error happen after reading the information we need.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually you can use a try with resources to be sure it's closed at the end.
Something like:

try (br = new BufferedReader(new FileReader(classPathFile));) {
...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And maybe add the throws IOException to the method itself if it's not public anymore. You'll manage those elsewhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this should be fixed now.

}
if (splitVersion.length > 2) {
} catch (Exception e) {
e.printStackTrace();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove that one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}
}
while ((line = output.readLine()) != null) {
if (line.contains("Maven home: ")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use a constant for "Maven home: " please?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed.

return dependence;
p.waitFor();
} catch (IOException e) {
e.printStackTrace();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again: declaring two exception for the same behaviour is useless. You can catch both in the same block and throw a SpoonException here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

try {
classPathPrints = Files.find(Paths.get(pom.getParentFile().getAbsolutePath()),
Integer.MAX_VALUE,
(filePath, fileAttr) -> filePath.endsWith("spoon.classpath.tmp"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again: use a constant for the name of the file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

.map(p -> p.toFile())
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No printstacktrace

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// with the tests
launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.ALL_SOURCE);

assertEquals(30, launcher.getEnvironment().getSourceClasspath().length);
assertEquals(194, launcher.getEnvironment().getSourceClasspath().length);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a new test to check this new behaviour: you did this PR because on some projects dependency resolution was not working. So could you put pom.xml files that wasn't working previously and that is working now, as a test?

Copy link
Collaborator Author

@nharrand nharrand Aug 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not that it used to not work on certain poms but more than some types were not resolved because not existing in the classpath where they could have been if MavenLauncher generated a classpath more similar to Maven.
I don't think it is testable just with a pom. The problem appears when analyzing an AST.
I could add a test that check that for a maven project (may be spoon itself), all types are resolvable, even those defined in dependencies. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could add a test that check that for a maven project (may be spoon itself), all types are resolvable, even those defined in dependencies. WDYT?

Sounds good yes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@surli surli merged commit eed3e53 into INRIA:master Aug 9, 2018
@surli
Copy link
Collaborator

surli commented Aug 9, 2018

thanks @nharrand :)

surli added a commit that referenced this pull request Aug 10, 2018
One test has been added by @nharrand in #2112 to show the usage of the new dependency resolver. However this test consumes too much memory on our CI, on the job used to deploy maven artefacts: 
> [ERROR] Tests run: 7, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 68.03 s <<< FAILURE! - in spoon.MavenLauncherTest
[ERROR] testTypeResolution(spoon.MavenLauncherTest)  Time elapsed: 38.801 s  <<< ERROR!
java.lang.OutOfMemoryError: GC overhead limit exceeded
	at spoon.MavenLauncherTest.testTypeResolution(MavenLauncherTest.java:25)

https://ci.inria.fr/sos/job/Spoon-Snapshot-Deployer/1689/console

I propose to ignore the test for now.
@monperrus monperrus mentioned this pull request Sep 20, 2018
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants