
com.twitter.AbstractMavenScroogeMojo Maven / Gradle / Ivy
The newest version!
package com.twitter;
import com.google.common.collect.ImmutableSet;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.util.io.RawInputStreamFacade;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static com.google.common.base.Join.join;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static java.util.Collections.list;
import static org.codehaus.plexus.util.FileUtils.*;
/**
* Abstract Mojo implementation.
* This class is extended by {@link MavenScroogeCompileMojo} and
* {@link MavenScroogeTestCompileMojo} in order to override the specific configuration for
* compiling the main or test classes respectively.
*
* @requiresDependencyResolution
*/
abstract class AbstractMavenScroogeMojo extends AbstractMojo {
private static final String THRIFT_FILE_SUFFIX = ".thrift";
private static final String DEFAULT_INCLUDES = "**/*" + THRIFT_FILE_SUFFIX;
/**
* The current Maven project.
*
* @parameter default-value="${project}"
* @readonly
* @required
*/
protected MavenProject project;
/**
* A helper used to add resources to the project.
*
* @component
* @required
*/
protected MavenProjectHelper projectHelper;
/**
* A set of include directories to pass to the thrift compiler.
* @parameter
*/
private Set thriftIncludes = new HashSet();
/**
* Which language the generated files should be ("java" or "scala")
* @parameter default-value="scala"
* {@code
*
* java
*
* }
*/
private String language;
/**
* Command line options to pass to scrooge, e.g.
* {@code
*
* --finagle
* --ostrich
*
*}
* @parameter
*/
private Set thriftOpts = new HashSet();
/**
* List of dependencies to extract thrift files from, even if they
* do not have idl classifier. Make sure to include the
* correct artifact name (eg. finagle-thrift, not just finagle)
* {@code
*
* finagle-thrift
*
* }
* @parameter
*/
private Set dependencyIncludes = new HashSet();
/**
* A set of namespace mappings to pass to the thrift compiler, e.g.
* {@code
*
*
* com.twitter
* com.twitter.thriftscala
*
*
* }
*
* Only used by the scrooge generator, usually to avoid clashes with Java namespaces.
* @parameter
*/
private Set thriftNamespaceMappings = new HashSet();
/**
* A set of include patterns used to filter thrift files.
* @parameter
*/
private Set includes = ImmutableSet.of(DEFAULT_INCLUDES);
/**
* A set of exclude patterns used to filter thrift files.
* @parameter
*/
private Set excludes = ImmutableSet.of();
/**
* Whether or not to fix hashcode being default 0
* @parameter
*/
private boolean fixHashcode = false;
/**
* Whether or not to skip thrift generation if generated files are newer than source files.
* @parameter
*/
private boolean checkStaleness = true;
/**
* Delta to use for triggering thrift regeneration
* @parameter
*/
private long staleMillis = 0;
private static Object lock = new Object();
/**
* Executes the mojo.
*/
public void execute() throws MojoExecutionException, MojoFailureException {
try {
Set thriftFiles = findThriftFiles();
final File outputDirectory = getOutputDirectory();
ImmutableSet outputFiles = findGeneratedFilesInDirectory(getOutputDirectory());
Set compileRoots = new HashSet();
compileRoots.add("scrooge");
if (thriftFiles.isEmpty()) {
getLog().info("No thrift files to compile.");
} else if (checkStaleness && ((lastModified(thriftFiles) + staleMillis) < lastModified(outputFiles))) {
getLog().info("Generated thrift files up to date, skipping compile.");
attachFiles(compileRoots);
} else {
outputDirectory.mkdirs();
// Quick fix to fix issues with two mvn installs in a row (ie no clean)
cleanDirectory(outputDirectory);
getLog().info(format("compiling thrift files %s with Scrooge", thriftFiles));
synchronized(lock) {
ScroogeRunner runner = new ScroogeRunner();
Map thriftNamespaceMap = new HashMap();
for (ThriftNamespaceMapping mapping : thriftNamespaceMappings) {
thriftNamespaceMap.put(mapping.getFrom(), mapping.getTo());
}
// Include thrifts from resource as well.
Set includes = thriftIncludes;
includes.add(getResourcesOutputDirectory());
runner.compile(
getLog(),
new File(outputDirectory, "scrooge"),
thriftFiles,
includes,
thriftNamespaceMap,
language,
thriftOpts);
}
attachFiles(compileRoots);
}
} catch (IOException e) {
throw new MojoExecutionException("An IO error occured", e);
}
}
/**
* Where our local thrift files live.
*/
protected abstract File getThriftSourceRoot();
/**
* Where our generated files go.
*/
protected abstract File getOutputDirectory();
/**
* Where all our thrift files (from references, dependencies, local) get copied.
*/
protected abstract File getResourcesOutputDirectory();
/**
* Add newly created files to the project.
* @return A set of directories that contain generated source files.
*/
protected abstract void attachFiles(Set compileRoots);
/**
* What scope should we look at for dependent thrift files in {@link #getDependencyArtifacts()}.
* @return A string used to filter scope, e.g. "compile".
*/
protected abstract String getDependencyScopeFilter();
/**
* Thrift files from referenced projects.
*/
protected abstract List getReferencedThriftFiles() throws IOException;
/**
* Get the last modified time for a set of files.
*/
private long lastModified(Set files) {
long result = 0;
for (File file : files) {
if (file.lastModified() > result)
result = file.lastModified();
}
return result;
}
/**
* build a complete set of local files, files from referenced projects, and dependencies.
*/
private Set findThriftFiles() throws IOException {
final File thriftSourceRoot = getThriftSourceRoot();
Set thriftFiles = new HashSet();
if (thriftSourceRoot != null && thriftSourceRoot.exists()) {
thriftFiles.addAll(findThriftFilesInDirectory(thriftSourceRoot));
}
getLog().info("finding thrift files in dependencies");
extractFilesFromDependencies(findThriftDependencies(dependencyIncludes), getResourcesOutputDirectory());
if (getResourcesOutputDirectory().exists()) {
thriftFiles.addAll(findThriftFilesInDirectory(getResourcesOutputDirectory()));
}
getLog().info("finding thrift files in referenced (reactor) projects");
thriftFiles.addAll(getReferencedThriftFiles());
return thriftFiles;
}
/**
* Iterate through dependencies, find those specified in the whitelist
*/
private Set findThriftDependencies(Set whitelist) throws IOException {
Set thriftDependencies = new HashSet();
Set allDependencies = new HashSet();
allDependencies.addAll(project.getArtifacts());
allDependencies.addAll(project.getDependencyArtifacts());
for(Artifact artifact : allDependencies) {
String artifactId = artifact.getArtifactId();
if (whitelist.contains(artifactId)) {
thriftDependencies.add(artifact);
}
}
return thriftDependencies;
}
/**
* Copy thrift files from dependency jars to {@link #getResourcesOutputDirectory()}.
* @param dependencies A set of jar artifacts ths project depends on.
* @param output The directory to copy any found files into.
*/
private void extractFilesFromDependencies(Collection dependencies, File output) throws IOException {
for (Artifact artifact : dependencies) {
File dep = artifact.getFile();
getLog().info("extracting thrift files from " + dep.getCanonicalPath());
File destFolder = new File(output, artifact.getArtifactId());
destFolder.mkdirs();
if (dep.isFile() && dep.canRead() && dep.getName().endsWith(".jar")) {
JarFile jar = new JarFile(dep);
for (JarEntry entry : list(jar.entries())) {
if (entry.getName().endsWith(".thrift")) {
File destination = new File(destFolder, entry.getName());
getLog().info(format("extracting %s to %s", entry.getName(), destination.getCanonicalPath()));
copyStreamToFile(new RawInputStreamFacade(jar.getInputStream(entry)), destination);
if (!destination.setLastModified(dep.lastModified()))
getLog().warn(format("fail to set last modified time for %s", destination.getCanonicalPath()));
}
}
} else {
getLog().warn(format("dep %s isn't a file or can't be read", dep.getCanonicalPath()));
}
}
}
/**
* Find all {@code .java} and {@code .scala} files in the given directory.
*/
private ImmutableSet findGeneratedFilesInDirectory(File directory) throws IOException {
if (directory == null || !directory.isDirectory())
return ImmutableSet.of();
List sourceFilesInDirectory = getFiles(directory, "**/*.java", null);
sourceFilesInDirectory.addAll(getFiles(directory, "**/*.scala", null));
return ImmutableSet.copyOf(sourceFilesInDirectory);
}
/**
* Find all {@code .thrift} files in the given directory.
*/
private ImmutableSet findThriftFilesInDirectory(File directory) throws IOException {
checkNotNull(directory);
checkArgument(directory.isDirectory(), "%s is not a directory", directory);
List thriftFilesInDirectory = getFiles(directory, join(",", includes), join(",", excludes));
return ImmutableSet.copyOf(thriftFilesInDirectory);
}
/**
* Walk project references recursively, building up a list of thrift files they provide, starting
* with an empty file list.
*/
protected List getRecursiveThriftFiles(MavenProject project, String outputDirectory) throws IOException {
return getRecursiveThriftFiles(project, outputDirectory, new ArrayList());
}
/**
* Walk project references recursively, adding thrift files to the provided list.
*/
List getRecursiveThriftFiles(MavenProject project, String outputDirectory, List files) throws IOException {
if (dependencyIncludes.contains(project.getArtifactId())) {
File dir = new File(new File(project.getFile().getParent(), "target"), outputDirectory);
if (dir.exists()) {
try {
URI baseDir = new URI("file://" + dir.getCanonicalPath());
for (File f : findThriftFilesInDirectory(dir)) {
URI fileURI = new URI("file://" + f.getCanonicalPath());
String relPath = baseDir.relativize(fileURI).getPath();
File destFolder = new File(getResourcesOutputDirectory(), project.getArtifactId());
destFolder.mkdirs();
File destFile = new File(destFolder, relPath);
getLog().info(format("copying %s to %s", f.getCanonicalPath(), destFile.getCanonicalPath()));
copyFile(f, destFile);
files.add(destFile);
}
} catch (URISyntaxException urie) {
throw new IOException("error forming URI for file transfer: " + urie);
}
}
}
Map refs = project.getProjectReferences();
for (String name : refs.keySet()) {
getRecursiveThriftFiles(refs.get(name), outputDirectory, files);
}
return files;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy