Skip to content

Commit

Permalink
refactor(MavenLauncher): improve the maven launcher and remove unused…
Browse files Browse the repository at this point in the history
… files (#2437)

* refactor(MavenLauncher)

* vanity

* removes parameter m2repository as it has become useless

* override last modified date when classpath is evaluated but has not changed

* doc(MavenLauncher)

* untrack .tmp files and cosmetic

* fixes a bug when compiler plugin exist but has no configuration child, and add read default profile if it exists to get source version

* fixes a bug which made InheritanceModel incorectly read properties composed of the concatenation of a string and another property

* re add deprecated MavenLauncher constructor and add a forceRefresh option

* style
  • Loading branch information
nharrand authored and tdurieux committed Sep 6, 2018
1 parent 6a176de commit 21a5527
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 599 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ maven-eclipse.xml
spooned-classes/
spooned/
doc/_jekyll/_site/
*.tmp

# Nodejs
node_modules/
node_modules/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Alphabetical order of last names
* Christophe Dufour
* Thomas Durieux
* Alcides Fonseca
* Nicolas Harrand
* Sebastian Lamelas Marcote
* Romain Leventov
* Matias Martinez
Expand Down
13 changes: 12 additions & 1 deletion doc/launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ for(CtType<?> s : model.getAllTypes()) {

```


Note that by default, MavenLauncher relies on an existing local maven binary to build the project's classpath. But a constructor allowing the user to skip this step and to provide a custom classpath is available.
```java
MavenLauncher launcher = new MavenLauncher("<path_to_maven_project>",
MavenLauncher.SOURCE_TYPE.APP_SOURCE,
new String[] {
"/home/user/.m2/repository/org/my/jar/1.0/org-my-jar-1.0.jar"
}
);
launcher.buildModel();
CtModel model = launcher.getModel();
```
To avoid invoking maven over and over to build a classpath that has not changed, it is stored in a file `spoon.classpath.tmp` (or depending on the scope `spoon.classpath-app.tmp` or `spoon.classpath-test.tmp`) in the same folder as the `pom.xml`. This classpath will be refreshed is the file is deleted or if it has not been modified since 1h.

## About the classpath

Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
<role>Contributor</role>
</roles>
</developer>
<developer>
<name>Nicolas Harrand</name>
<roles>
<role>Contributor</role>
</roles>
</developer>
<developer>
<name>Sebastian Lamelas Marcote</name>
<roles>
Expand Down
162 changes: 118 additions & 44 deletions src/main/java/spoon/MavenLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,26 @@
import java.io.IOException;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.Date;

/**
* Create a Spoon launcher from a maven pom file
*/
public class MavenLauncher extends Launcher {
static String mavenVersionParsing = "Maven home: ";
static String spoonClasspathTmpFileName = "spoon.classpath.tmp";
private String m2RepositoryPath;
static String spoonClasspathTmpFileNameApp = "spoon.classpath-app.tmp";
static String spoonClasspathTmpFileNameTest = "spoon.classpath-test.tmp";
static long classpathTmpFilesTTL = 60 * 60 * 1000; // 1h in ms
private String mvnHome;
private SOURCE_TYPE sourceType;
private InheritanceModel model;
private boolean forceRefresh = false;

/**
* The type of source to consider in the model
Expand All @@ -58,50 +62,100 @@ public enum SOURCE_TYPE {
ALL_SOURCE
}

/**
* MavenLauncher constructor assuming either an environment
* variable M2_HOME, or that mvn command exist in PATH.
*
* @param mavenProject the path to the root of the project
* @param sourceType the source type (App, test, or all)
*/
public MavenLauncher(String mavenProject, SOURCE_TYPE sourceType) {
this(mavenProject, Paths.get(System.getProperty("user.home"), ".m2", "repository").toString(), sourceType);
this(mavenProject, sourceType, System.getenv().get("M2_HOME"), false);
}

/**
* MavenLauncher constructor assuming either an environment
* variable M2_HOME, or that mvn command exist in PATH.
*
* @param mavenProject the path to the root of the project
* @param sourceType the source type (App, test, or all)
* @param forceRefresh force the regeneration of classpath
*/
public MavenLauncher(String mavenProject, SOURCE_TYPE sourceType, boolean forceRefresh) {
this(mavenProject, sourceType, System.getenv().get("M2_HOME"), forceRefresh);
}


/**
* MavenLauncher constructor assuming either an environment
* variable M2_HOME, or that mvn command exist in PATH.
*
* @param mavenProject the path to the root of the project
* @param m2RepositoryPath the path to the m2repository
* @param m2RepositoryPath unused
* @param sourceType the source type (App, test, or all)
*/
@Deprecated
public MavenLauncher(String mavenProject, String m2RepositoryPath, SOURCE_TYPE sourceType) {
this(mavenProject, m2RepositoryPath, sourceType, System.getenv().get("M2_HOME"));
this(mavenProject, sourceType);
}

/**
*
* @param mavenProject the path to the root of the project
* @param m2RepositoryPath the path to the m2repository
* @param m2RepositoryPath unused
* @param sourceType the source type (App, test, or all)
* @param mvnHome Path to maven install
*/
@Deprecated
public MavenLauncher(String mavenProject, String m2RepositoryPath, SOURCE_TYPE sourceType, String mvnHome) {
this(mavenProject, m2RepositoryPath, sourceType, buildClassPath(mvnHome, mavenProject, sourceType));
this(mavenProject, sourceType, mvnHome);
}

/**
*
* @param mavenProject the path to the root of the project
* @param sourceType the source type (App, test, or all)
* @param mvnHome Path to maven install
*/
public MavenLauncher(String mavenProject, SOURCE_TYPE sourceType, String mvnHome) {
this(mavenProject, sourceType, mvnHome, false);
}

/**
*
* @param mavenProject the path to the root of the project
* @param sourceType the source type (App, test, or all)
* @param mvnHome Path to maven install
* @param forceRefresh force the regeneration of classpath
*/
public MavenLauncher(String mavenProject, SOURCE_TYPE sourceType, String mvnHome, boolean forceRefresh) {
this.sourceType = sourceType;
this.mvnHome = mvnHome;
this.forceRefresh = forceRefresh;
init(mavenProject, null);
}

/**
* MavenLauncher constructor that skips maven invocation building
* classpath.
*
* @param mavenProject the path to the root of the project
* @param m2RepositoryPath the path to the m2repository
* @param sourceType the source type (App, test, or all)
* @param classpath String array containing the classpath elements
*/
public MavenLauncher(String mavenProject, String m2RepositoryPath, SOURCE_TYPE sourceType, String[] classpath) {
this.m2RepositoryPath = m2RepositoryPath;
public MavenLauncher(String mavenProject, SOURCE_TYPE sourceType, String[] classpath) {
this.sourceType = sourceType;
init(mavenProject, classpath);
}

private void init(String mavenProject, String[] classpath) {
File mavenProjectFile = new File(mavenProject);
if (!mavenProjectFile.exists()) {
throw new SpoonException(mavenProject + " does not exist.");
}

InheritanceModel model;
try {
model = InheritanceModel.readPOM(mavenProject, null, m2RepositoryPath, sourceType, getEnvironment());
model = InheritanceModel.readPOM(mavenProject, null, sourceType, getEnvironment());
} catch (Exception e) {
throw new SpoonException("Unable to read the pom", e);
}
Expand All @@ -125,39 +179,50 @@ public MavenLauncher(String mavenProject, String m2RepositoryPath, SOURCE_TYPE s
}
}

if (classpath == null) {
classpath = buildClassPath(mvnHome, mavenProject, sourceType);
}

// dependencies
this.getModelBuilder().setSourceClasspath(classpath);

// compliance level
this.getEnvironment().setComplianceLevel(model.getSourceVersion());
}

private static void generateClassPathFile(File pom, File mvnHome, SOURCE_TYPE sourceType) {
//Run mvn dependency:build-classpath -Dmdep.outputFile="spoon.classpath.tmp"
//This should write the classpath used by maven in spoon.classpath.tmp
InvocationRequest request = new DefaultInvocationRequest();
request.setBatchMode(true);
request.setPomFile(pom);
request.setGoals(Collections.singletonList("dependency:build-classpath"));
Properties properties = new Properties();
if (sourceType == SOURCE_TYPE.APP_SOURCE) {
properties.setProperty("includeScope", "runtime");
}
properties.setProperty("mdep.outputFile", spoonClasspathTmpFileName);
request.setProperties(properties);
private static void generateClassPathFile(File pom, File mvnHome, SOURCE_TYPE sourceType, boolean forceRefresh) {
// Check if classpath file already exist and is recent enough (1h)
File classpathFile = new File(pom.getParentFile(), getSpoonClasspathTmpFileName(sourceType));
Date date = new Date();
long time = date.getTime();
if (forceRefresh || !classpathFile.exists() || ((time - classpathFile.lastModified()) > classpathTmpFilesTTL)) {
//Run mvn dependency:build-classpath -Dmdep.outputFile="spoon.classpath.tmp"
//This should write the classpath used by maven in spoon.classpath.tmp
InvocationRequest request = new DefaultInvocationRequest();
request.setBatchMode(true);
request.setPomFile(pom);
request.setGoals(Collections.singletonList("dependency:build-classpath"));
Properties properties = new Properties();
if (sourceType == SOURCE_TYPE.APP_SOURCE) {
properties.setProperty("includeScope", "runtime");
}
properties.setProperty("mdep.outputFile", getSpoonClasspathTmpFileName(sourceType));
request.setProperties(properties);

request.getOutputHandler(s -> LOGGER.debug(s));
request.getErrorHandler(s -> LOGGER.debug(s));
request.getOutputHandler(s -> LOGGER.debug(s));
request.getErrorHandler(s -> LOGGER.debug(s));

Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(mvnHome);
invoker.setWorkingDirectory(pom.getParentFile());
invoker.setErrorHandler(s -> LOGGER.debug(s));
invoker.setOutputHandler(s -> LOGGER.debug(s));
try {
InvocationResult ir = invoker.execute(request);
} catch (MavenInvocationException e) {
throw new SpoonException("Maven invocation failed to build a classpath.");
Invoker invoker = new DefaultInvoker();
invoker.setMavenHome(mvnHome);
invoker.setWorkingDirectory(pom.getParentFile());
invoker.setErrorHandler(s -> LOGGER.debug(s));
invoker.setOutputHandler(s -> LOGGER.debug(s));
try {
InvocationResult ir = invoker.execute(request);
} catch (MavenInvocationException e) {
throw new SpoonException("Maven invocation failed to build a classpath.");
}
classpathFile.setLastModified(time);
}
}

Expand Down Expand Up @@ -224,7 +289,7 @@ static String guessMavenHome() {
* @param mavenProject the path to the root of the project
* @param sourceType the source type (App, test, or all)
*/
public static String[] buildClassPath(String mvnHome, String mavenProject, SOURCE_TYPE sourceType) {
public String[] buildClassPath(String mvnHome, String mavenProject, SOURCE_TYPE sourceType) {
if (mvnHome == null) {
mvnHome = guessMavenHome();
if (mvnHome == null) {
Expand All @@ -236,16 +301,12 @@ public static String[] buildClassPath(String mvnHome, String mavenProject, SOURC
projectPath = Paths.get(projectPath, "pom.xml").toString();
}
File pom = new File(projectPath);
generateClassPathFile(pom, new File(mvnHome), sourceType);
generateClassPathFile(pom, new File(mvnHome), sourceType, forceRefresh);

List<File> classPathPrints;
String[] classpath;
try {
classPathPrints = Files.find(Paths.get(pom.getParentFile().getAbsolutePath()),
Integer.MAX_VALUE,
(filePath, fileAttr) -> filePath.endsWith(spoonClasspathTmpFileName))
.map(p -> p.toFile())
.collect(Collectors.toList());
classPathPrints = model.getClasspathTmpFiles(getSpoonClasspathTmpFileName(sourceType));
File[] classPathPrintFiles = new File[classPathPrints.size()];
classPathPrintFiles = classPathPrints.toArray(classPathPrintFiles);
classpath = readClassPath(classPathPrintFiles);
Expand All @@ -254,4 +315,17 @@ public static String[] buildClassPath(String mvnHome, String mavenProject, SOURC
}
return classpath;
}

static String getSpoonClasspathTmpFileName(SOURCE_TYPE sourceType) {
// As the temporary file containing the classpath is re-generated only
// once per hour, we need a different file for different dependency
// resolution scopes.
if (SOURCE_TYPE.TEST_SOURCE == sourceType) {
return spoonClasspathTmpFileNameTest;
} else if (SOURCE_TYPE.APP_SOURCE == sourceType) {
return spoonClasspathTmpFileNameApp;
} else {
return spoonClasspathTmpFileName;
}
}
}
Loading

0 comments on commit 21a5527

Please sign in to comment.