diff options
Diffstat (limited to '')
-rw-r--r-- | src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java new file mode 100644 index 000000000..869be95e4 --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/AbstractThriftMojo.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.thrift.maven; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.ArtifactRepository; +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.cli.CommandLineException; +import org.codehaus.plexus.util.io.RawInputStreamFacade; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Sets.newHashSet; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.list; +import static org.codehaus.plexus.util.FileUtils.cleanDirectory; +import static org.codehaus.plexus.util.FileUtils.copyStreamToFile; +import static org.codehaus.plexus.util.FileUtils.getFiles; + +/** + * Abstract Mojo implementation. + * <p/> + * This class is extended by {@link org.apache.thrift.maven.ThriftCompileMojo} and + * {@link org.apache.thrift.maven.ThriftTestCompileMojo} in order to override the specific configuration for + * compiling the main or test classes respectively. + */ +abstract class AbstractThriftMojo 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; + + /** + * This is the path to the {@code thrift} executable. By default it will search the {@code $PATH}. + * + * @parameter default-value="thrift" + * @required + */ + private String thriftExecutable; + + /** + * This string is passed to the {@code --gen} option of the {@code thrift} parameter. By default + * it will generate Java output. The main reason for this option is to be able to add options + * to the Java generator - if you generate something else, you're on your own. + * + * @parameter default-value="java" + */ + private String generator; + + /** + * @parameter + */ + private File[] additionalThriftPathElements = new File[]{}; + + /** + * Since {@code thrift} cannot access jars, thrift files in dependencies are extracted to this location + * and deleted on exit. This directory is always cleaned during execution. + * + * @parameter default-value="${project.build.directory}/thrift-dependencies" + * @required + */ + private File temporaryThriftFileDirectory; + + /** + * This is the path to the local maven {@code repository}. + * + * @parameter default-value="${localRepository}" + * @required + */ + protected ArtifactRepository localRepository; + + /** + * Set this to {@code false} to disable hashing of dependent jar paths. + * <p/> + * This plugin expands jars on the classpath looking for embedded .thrift files. + * Normally these paths are hashed (MD5) to avoid issues with long file names on windows. + * However if this property is set to {@code false} longer paths will be used. + * + * @parameter default-value="true" + * @required + */ + protected boolean hashDependentPaths; + + /** + * @parameter + */ + private Set<String> includes = ImmutableSet.of(DEFAULT_INCLUDES); + + /** + * @parameter + */ + private Set<String> excludes = ImmutableSet.of(); + + /** + * @parameter + */ + private long staleMillis = 0; + + /** + * @parameter + */ + private boolean checkStaleness = false; + + /** + * Executes the mojo. + */ + public void execute() throws MojoExecutionException, MojoFailureException { + checkParameters(); + final File thriftSourceRoot = getThriftSourceRoot(); + if (thriftSourceRoot.exists()) { + try { + ImmutableSet<File> thriftFiles = findThriftFilesInDirectory(thriftSourceRoot); + final File outputDirectory = getOutputDirectory(); + ImmutableSet<File> outputFiles = findGeneratedFilesInDirectory(getOutputDirectory()); + + if (thriftFiles.isEmpty()) { + getLog().info("No thrift files to compile."); + } else if (checkStaleness && ((lastModified(thriftFiles) + staleMillis) < lastModified(outputFiles))) { + getLog().info("Skipping compilation because target directory newer than sources."); + attachFiles(); + } else { + ImmutableSet<File> derivedThriftPathElements = + makeThriftPathFromJars(temporaryThriftFileDirectory, getDependencyArtifactFiles()); + outputDirectory.mkdirs(); + + // Quick fix to fix issues with two mvn installs in a row (ie no clean) + // cleanDirectory(outputDirectory); + + Thrift thrift = new Thrift.Builder(thriftExecutable, outputDirectory) + .setGenerator(generator) + .addThriftPathElement(thriftSourceRoot) + .addThriftPathElements(derivedThriftPathElements) + .addThriftPathElements(asList(additionalThriftPathElements)) + .addThriftFiles(thriftFiles) + .build(); + final int exitStatus = thrift.compile(); + if (exitStatus != 0) { + getLog().error("thrift failed output: " + thrift.getOutput()); + getLog().error("thrift failed error: " + thrift.getError()); + throw new MojoFailureException( + "thrift did not exit cleanly. Review output for more information."); + } + attachFiles(); + } + } catch (IOException e) { + throw new MojoExecutionException("An IO error occurred", e); + } catch (IllegalArgumentException e) { + throw new MojoFailureException("thrift failed to execute because: " + e.getMessage(), e); + } catch (CommandLineException e) { + throw new MojoExecutionException("An error occurred while invoking thrift.", e); + } + } else { + getLog().info(format("%s does not exist. Review the configuration or consider disabling the plugin.", + thriftSourceRoot)); + } + } + + ImmutableSet<File> findGeneratedFilesInDirectory(File directory) throws IOException { + if (directory == null || !directory.isDirectory()) + return ImmutableSet.of(); + + List<File> javaFilesInDirectory = getFiles(directory, "**/*.java", null); + return ImmutableSet.copyOf(javaFilesInDirectory); + } + + private long lastModified(ImmutableSet<File> files) { + long result = 0; + for (File file : files) { + if (file.lastModified() > result) + result = file.lastModified(); + } + return result; + } + + private void checkParameters() { + checkNotNull(project, "project"); + checkNotNull(projectHelper, "projectHelper"); + checkNotNull(thriftExecutable, "thriftExecutable"); + checkNotNull(generator, "generator"); + final File thriftSourceRoot = getThriftSourceRoot(); + checkNotNull(thriftSourceRoot); + checkArgument(!thriftSourceRoot.isFile(), "thriftSourceRoot is a file, not a directory"); + checkNotNull(temporaryThriftFileDirectory, "temporaryThriftFileDirectory"); + checkState(!temporaryThriftFileDirectory.isFile(), "temporaryThriftFileDirectory is a file, not a directory"); + final File outputDirectory = getOutputDirectory(); + checkNotNull(outputDirectory); + checkState(!outputDirectory.isFile(), "the outputDirectory is a file, not a directory"); + } + + protected abstract File getThriftSourceRoot(); + + protected abstract List<Artifact> getDependencyArtifacts(); + + protected abstract File getOutputDirectory(); + + protected abstract void attachFiles(); + + /** + * Gets the {@link File} for each dependency artifact. + * + * @return A set of all dependency artifacts. + */ + private ImmutableSet<File> getDependencyArtifactFiles() { + Set<File> dependencyArtifactFiles = newHashSet(); + for (Artifact artifact : getDependencyArtifacts()) { + dependencyArtifactFiles.add(artifact.getFile()); + } + return ImmutableSet.copyOf(dependencyArtifactFiles); + } + + /** + * @throws IOException + */ + ImmutableSet<File> makeThriftPathFromJars(File temporaryThriftFileDirectory, Iterable<File> classpathElementFiles) + throws IOException, MojoExecutionException { + checkNotNull(classpathElementFiles, "classpathElementFiles"); + // clean the temporary directory to ensure that stale files aren't used + if (temporaryThriftFileDirectory.exists()) { + cleanDirectory(temporaryThriftFileDirectory); + } + + Set<File> thriftDirectories = newHashSet(); + + for (File classpathElementFile : classpathElementFiles) { + // for some reason under IAM, we receive poms as dependent files + // I am excluding .xml rather than including .jar as there may be other extensions in use (sar, har, zip) + if (classpathElementFile.isFile() && classpathElementFile.canRead() && + !classpathElementFile.getName().endsWith(".xml")) { + + // create the jar file. the constructor validates. + JarFile classpathJar; + try { + classpathJar = new JarFile(classpathElementFile); + } catch (IOException e) { + throw new IllegalArgumentException(format( + "%s was not a readable artifact", classpathElementFile)); + } + + /** + * Copy each .thrift file found in the JAR into a temporary directory, preserving the + * directory path it had relative to its containing JAR. Add the resulting root directory + * (unique for each JAR processed) to the set of thrift include directories to use when + * compiling. + */ + for (JarEntry jarEntry : list(classpathJar.entries())) { + final String jarEntryName = jarEntry.getName(); + if (jarEntry.getName().endsWith(THRIFT_FILE_SUFFIX)) { + final String truncatedJarPath = truncatePath(classpathJar.getName()); + final File thriftRootDirectory = new File(temporaryThriftFileDirectory, truncatedJarPath); + final File uncompressedCopy = + new File(thriftRootDirectory, jarEntryName); + uncompressedCopy.getParentFile().mkdirs(); + copyStreamToFile(new RawInputStreamFacade(classpathJar + .getInputStream(jarEntry)), uncompressedCopy); + thriftDirectories.add(thriftRootDirectory); + } + } + + } else if (classpathElementFile.isDirectory()) { + File[] thriftFiles = classpathElementFile.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(THRIFT_FILE_SUFFIX); + } + }); + + if (thriftFiles.length > 0) { + thriftDirectories.add(classpathElementFile); + } + } + } + + return ImmutableSet.copyOf(thriftDirectories); + } + + ImmutableSet<File> findThriftFilesInDirectory(File directory) throws IOException { + checkNotNull(directory); + checkArgument(directory.isDirectory(), "%s is not a directory", directory); + List<File> thriftFilesInDirectory = getFiles(directory, + Joiner.on(",").join(includes), + Joiner.on(",").join(excludes)); + return ImmutableSet.copyOf(thriftFilesInDirectory); + } + + /** + * Truncates the path of jar files so that they are relative to the local repository. + * + * @param jarPath the full path of a jar file. + * @return the truncated path relative to the local repository or root of the drive. + */ + String truncatePath(final String jarPath) throws MojoExecutionException { + + if (hashDependentPaths) { + try { + return toHexString(MessageDigest.getInstance("MD5").digest(jarPath.getBytes())); + } catch (NoSuchAlgorithmException e) { + throw new MojoExecutionException("Failed to expand dependent jar", e); + } + } + + String repository = localRepository.getBasedir().replace('\\', '/'); + if (!repository.endsWith("/")) { + repository += "/"; + } + + String path = jarPath.replace('\\', '/'); + int repositoryIndex = path.indexOf(repository); + if (repositoryIndex != -1) { + path = path.substring(repositoryIndex + repository.length()); + } + + // By now the path should be good, but do a final check to fix windows machines. + int colonIndex = path.indexOf(':'); + if (colonIndex != -1) { + // 2 = :\ in C:\ + path = path.substring(colonIndex + 2); + } + + return path; + } + + private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + + public static String toHexString(byte[] byteArray) { + final StringBuilder hexString = new StringBuilder(2 * byteArray.length); + for (final byte b : byteArray) { + hexString.append(HEX_CHARS[(b & 0xF0) >> 4]).append(HEX_CHARS[b & 0x0F]); + } + return hexString.toString(); + } +} |