diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/contrib/thrift-maven-plugin | |
parent | Initial commit. (diff) | |
download | ceph-upstream/16.2.11+ds.tar.xz ceph-upstream/16.2.11+ds.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
9 files changed, 1379 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/pom.xml b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/pom.xml new file mode 100644 index 000000000..4179b1d91 --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/pom.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + --> +<project + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.thrift</groupId> + <artifactId>thrift-maven-plugin</artifactId> + <packaging>maven-plugin</packaging> + <name>thrift-maven-plugin</name> + <version>1.0.0</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.14.1</version> + <configuration> + <systemPropertyVariables> + <thriftExecutable>${thrift.compiler}</thriftExecutable> + </systemPropertyVariables> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.8</version> + <executions> + <execution> + <id>generate-jar-for-test</id> + <phase>generate-test-resources</phase> + <configuration> + <target> + <jar destfile="${project.build.directory}/SharedIdl.jar" basedir="${basedir}/src/test/resources" includes="**/*.thrift" /> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-plugin-api</artifactId> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-project</artifactId> + <version>2.2.1</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>14.0.1</version> + </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-utils</artifactId> + <version>3.0.14</version> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.10.19</version> + <scope>test</scope> + </dependency> + </dependencies> + <properties> + <thrift.root>${basedir}/../..</thrift.root> + <thrift.compiler>${thrift.root}/compiler/cpp/thrift</thrift.compiler> + <thrift.test.home>${thrift.root}/test</thrift.test.home> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> +</project> 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(); + } +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java new file mode 100644 index 000000000..6eea95448 --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/Thrift.java @@ -0,0 +1,262 @@ +/* + * 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.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.codehaus.plexus.util.cli.CommandLineException; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; +import java.io.File; +import java.util.List; +import java.util.Set; +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.Lists.newLinkedList; +import static com.google.common.collect.Sets.newHashSet; + +/** + * This class represents an invokable configuration of the {@code thrift} + * compiler. The actual executable is invoked using the plexus + * {@link Commandline}. + * <p/> + * This class currently only supports generating java source files. + */ +final class Thrift { + + final static String GENERATED_JAVA = "gen-java"; + + private final String executable; + private final String generator; + private final ImmutableSet<File> thriftPathElements; + private final ImmutableSet<File> thriftFiles; + private final File javaOutputDirectory; + private final CommandLineUtils.StringStreamConsumer output; + private final CommandLineUtils.StringStreamConsumer error; + + /** + * Constructs a new instance. This should only be used by the {@link Builder}. + * + * @param executable The path to the {@code thrift} executable. + * @param generator The value for the {@code --gen} option. + * @param thriftPath The directories in which to search for imports. + * @param thriftFiles The thrift source files to compile. + * @param javaOutputDirectory The directory into which the java source files + * will be generated. + */ + private Thrift(String executable, String generator, ImmutableSet<File> thriftPath, + ImmutableSet<File> thriftFiles, File javaOutputDirectory) { + this.executable = checkNotNull(executable, "executable"); + this.generator = checkNotNull(generator, "generator"); + this.thriftPathElements = checkNotNull(thriftPath, "thriftPath"); + this.thriftFiles = checkNotNull(thriftFiles, "thriftFiles"); + this.javaOutputDirectory = checkNotNull(javaOutputDirectory, "javaOutputDirectory"); + this.error = new CommandLineUtils.StringStreamConsumer(); + this.output = new CommandLineUtils.StringStreamConsumer(); + } + + /** + * Invokes the {@code thrift} compiler using the configuration specified at + * construction. + * + * @return The exit status of {@code thrift}. + * @throws CommandLineException + */ + public int compile() throws CommandLineException { + + for (File thriftFile : thriftFiles) { + Commandline cl = new Commandline(); + cl.setExecutable(executable); + cl.addArguments(buildThriftCommand(thriftFile).toArray(new String[]{})); + final int result = CommandLineUtils.executeCommandLine(cl, null, output, error); + + if (result != 0) { + return result; + } + } + + // result will always be 0 here. + return 0; + } + + /** + * Creates the command line arguments. + * <p/> + * This method has been made visible for testing only. + * + * @param thriftFile + * @return A list consisting of the executable followed by any arguments. + */ + ImmutableList<String> buildThriftCommand(final File thriftFile) { + final List<String> command = newLinkedList(); + // add the executable + for (File thriftPathElement : thriftPathElements) { + command.add("-I"); + command.add(thriftPathElement.toString()); + } + command.add("-out"); + command.add(javaOutputDirectory.toString()); + command.add("--gen"); + command.add(generator); + command.add(thriftFile.toString()); + return ImmutableList.copyOf(command); + } + + /** + * @return the output + */ + public String getOutput() { + return output.getOutput(); + } + + /** + * @return the error + */ + public String getError() { + return error.getOutput(); + } + + /** + * This class builds {@link Thrift} instances. + */ + static final class Builder { + private final String executable; + private final File javaOutputDirectory; + private Set<File> thriftPathElements; + private Set<File> thriftFiles; + private String generator; + + /** + * Constructs a new builder. The two parameters are present as they are + * required for all {@link Thrift} instances. + * + * @param executable The path to the {@code thrift} executable. + * @param javaOutputDirectory The directory into which the java source files + * will be generated. + * @throws NullPointerException If either of the arguments are {@code null}. + * @throws IllegalArgumentException If the {@code javaOutputDirectory} is + * not a directory. + */ + public Builder(String executable, File javaOutputDirectory) { + this.executable = checkNotNull(executable, "executable"); + this.javaOutputDirectory = checkNotNull(javaOutputDirectory); + checkArgument(javaOutputDirectory.isDirectory()); + this.thriftFiles = newHashSet(); + this.thriftPathElements = newHashSet(); + } + + /** + * Adds a thrift file to be compiled. Thrift files must be on the thriftpath + * and this method will fail if a thrift file is added without first adding a + * parent directory to the thriftpath. + * + * @param thriftFile + * @return The builder. + * @throws IllegalStateException If a thrift file is added without first + * adding a parent directory to the thriftpath. + * @throws NullPointerException If {@code thriftFile} is {@code null}. + */ + public Builder addThriftFile(File thriftFile) { + checkNotNull(thriftFile); + checkArgument(thriftFile.isFile()); + checkArgument(thriftFile.getName().endsWith(".thrift")); + checkThriftFileIsInThriftPath(thriftFile); + thriftFiles.add(thriftFile); + return this; + } + + /** + * Adds the option string for the Thrift executable's {@code --gen} parameter. + * + * @param generator + * @return The builder + * @throws NullPointerException If {@code generator} is {@code null}. + */ + public Builder setGenerator(String generator) { + checkNotNull(generator); + this.generator = generator; + return this; + } + + private void checkThriftFileIsInThriftPath(File thriftFile) { + assert thriftFile.isFile(); + checkState(checkThriftFileIsInThriftPathHelper(thriftFile.getParentFile())); + } + + private boolean checkThriftFileIsInThriftPathHelper(File directory) { + assert directory.isDirectory(); + if (thriftPathElements.contains(directory)) { + return true; + } else { + final File parentDirectory = directory.getParentFile(); + return (parentDirectory == null) ? false + : checkThriftFileIsInThriftPathHelper(parentDirectory); + } + } + + /** + * @see #addThriftFile(File) + */ + public Builder addThriftFiles(Iterable<File> thriftFiles) { + for (File thriftFile : thriftFiles) { + addThriftFile(thriftFile); + } + return this; + } + + /** + * Adds the {@code thriftPathElement} to the thriftPath. + * + * @param thriftPathElement A directory to be searched for imported thrift message + * buffer definitions. + * @return The builder. + * @throws NullPointerException If {@code thriftPathElement} is {@code null}. + * @throws IllegalArgumentException If {@code thriftPathElement} is not a + * directory. + */ + public Builder addThriftPathElement(File thriftPathElement) { + checkNotNull(thriftPathElement); + checkArgument(thriftPathElement.isDirectory()); + thriftPathElements.add(thriftPathElement); + return this; + } + + /** + * @see #addThriftPathElement(File) + */ + public Builder addThriftPathElements(Iterable<File> thriftPathElements) { + for (File thriftPathElement : thriftPathElements) { + addThriftPathElement(thriftPathElement); + } + return this; + } + + /** + * @return A configured {@link Thrift} instance. + * @throws IllegalStateException If no thrift files have been added. + */ + public Thrift build() { + checkState(!thriftFiles.isEmpty()); + return new Thrift(executable, generator, ImmutableSet.copyOf(thriftPathElements), + ImmutableSet.copyOf(thriftFiles), javaOutputDirectory); + } + } +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java new file mode 100644 index 000000000..b4f75714b --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftCompileMojo.java @@ -0,0 +1,78 @@ +/* + * 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 java.io.File; +import java.util.List; +import org.apache.maven.artifact.Artifact; +import com.google.common.collect.ImmutableList; + +/** + * This mojo executes the {@code thrift} compiler for generating java sources + * from thrift definitions. It also searches dependency artifacts for + * thrift files and includes them in the thriftPath so that they can be + * referenced. Finally, it adds the thrift files to the project as resources so + * that they are included in the final artifact. + * + * @phase generate-sources + * @goal compile + * @requiresDependencyResolution compile + */ +public final class ThriftCompileMojo extends AbstractThriftMojo { + + /** + * The source directories containing the sources to be compiled. + * + * @parameter default-value="${basedir}/src/main/thrift" + * @required + */ + private File thriftSourceRoot; + + /** + * This is the directory into which the {@code .java} will be created. + * + * @parameter default-value="${project.build.directory}/generated-sources/thrift" + * @required + */ + private File outputDirectory; + + @Override + protected List<Artifact> getDependencyArtifacts() { + List<Artifact> compileArtifacts = project.getCompileArtifacts(); + return compileArtifacts; + } + + @Override + protected File getOutputDirectory() { + return outputDirectory; + } + + @Override + protected File getThriftSourceRoot() { + return thriftSourceRoot; + } + + @Override + protected void attachFiles() { + project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); + projectHelper.addResource(project, thriftSourceRoot.getAbsolutePath(), + ImmutableList.of("**/*.thrift"), null); + } +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java new file mode 100644 index 000000000..1b1d99e0d --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/main/java/org/apache/thrift/maven/ThriftTestCompileMojo.java @@ -0,0 +1,93 @@ +/* + * 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 java.io.File; +import java.util.List; +import org.apache.maven.artifact.Artifact; +import com.google.common.collect.ImmutableList; +import org.apache.maven.artifact.repository.ArtifactRepository; + +/** + * @phase generate-test-sources + * @goal testCompile + * @requiresDependencyResolution test + */ +public final class ThriftTestCompileMojo extends AbstractThriftMojo { + + /** + * The source directories containing the sources to be compiled. + * + * @parameter default-value="${basedir}/src/test/thrift" + * @required + */ + private File thriftTestSourceRoot; + + /** + * This is the directory into which the {@code .java} will be created. + * + * @parameter default-value="${project.build.directory}/generated-test-sources/thrift" + * @required + */ + private File outputDirectory; + + @Override + protected void attachFiles() { + project.addTestCompileSourceRoot(outputDirectory.getAbsolutePath()); + projectHelper.addTestResource(project, thriftTestSourceRoot.getAbsolutePath(), + ImmutableList.of("**/*.thrift"), null); + } + + @Override + protected List<Artifact> getDependencyArtifacts() { + // TODO(gak): maven-project needs generics + @SuppressWarnings("unchecked") + List<Artifact> testArtifacts = project.getTestArtifacts(); + return testArtifacts; + } + + @Override + protected File getOutputDirectory() { + return outputDirectory; + } + + @Override + protected File getThriftSourceRoot() { + return thriftTestSourceRoot; + } + + /** + * Set the local maven ArtifactRepository. Exposed only to allow testing outside of Maven itself. + * + * @param localRepository local ArtifactRepository + */ + public void setLocalMavenRepository(final ArtifactRepository localRepository) { + this.localRepository = localRepository; + } + + /** + * Set the option to hash dependent JAR paths. Exposed only to allow testing outside of Maven itself. + * + * @param hashDependentPaths whether or not to hash paths to dependent JARs + */ + public void setHashDependentPaths(final boolean hashDependentPaths) { + this.hashDependentPaths = hashDependentPaths; + } +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestAbstractThriftMojo.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestAbstractThriftMojo.java new file mode 100644 index 000000000..7c90a990e --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestAbstractThriftMojo.java @@ -0,0 +1,102 @@ +/* + * 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.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.util.Set; + +import static junit.framework.TestCase.assertEquals; + + +public class TestAbstractThriftMojo { + + private ThriftTestCompileMojo mojo; + private File testRootDir; + private ArtifactRepository mavenRepository; + + + @Before + public void setUp() throws Exception { + final File tmpDir = new File(System.getProperty("java.io.tmpdir")); + testRootDir = new File(tmpDir, "thrift-test"); + + // the truncatePath method assumes a maven repository, but it only cares about the base dir + mavenRepository = Mockito.mock(ArtifactRepository.class); + Mockito.when(mavenRepository.getBasedir()).thenReturn("/test/maven/repo/basedir"); + + mojo = new ThriftTestCompileMojo(); + mojo.setLocalMavenRepository(mavenRepository); + } + + @Test + public void testMakeThriftPathFromJars() throws Throwable { + final File temporaryThriftFileDirectory = testRootDir; + + // The SharedIdl.jar file contains the same idl/shared.thrift and idl/tutorial.thrift hierarchy + // used by other tests. It's used here to represent a dependency of the project maven is building, + // one that is contributing .thrift IDL files as well as any other artifacts. + final Iterable<File> classpathElementFiles = Lists.newArrayList( + new File("target/SharedIdl.jar") + ); + + final Set<File> thriftDirectories = mojo.makeThriftPathFromJars(temporaryThriftFileDirectory, classpathElementFiles); + + // The results should be a path to a directory named after the JAR itself (assuming no path hashing, + // but see below for a separate test of that) representing the root of a hierarchy containing thrift + // files, suitable for providing to the thrift compiler as an include directory. In this case, that + // means it points to the directory containing the "idl" hierarchy rather than to the idl directory + // itself. + final Set<File> expected = Sets.newHashSet( + new File(testRootDir, "target/SharedIdl.jar") + ); + + assertEquals("makeThriftPathFromJars should return thrift IDL base path from within JAR", expected, thriftDirectories); + } + + @Test + public void testTruncatePath() throws Throwable { + // JAR path is unrelated to maven repo, and should be unchanged + assertEquals("/path/to/somejar.jar", mojo.truncatePath("/path/to/somejar.jar")); + + // JAR path is within maven repo, and should be made relative to the repo + assertEquals("path/to/somejar.jar", mojo.truncatePath("/test/maven/repo/basedir/path/to/somejar.jar")); + + // JAR path contains forward slashes that should be normalized + assertEquals("/path/to/somejar.jar", mojo.truncatePath("\\path\\to\\somejar.jar")); + } + + @Test + public void testTruncatePathWithDependentPathHashing() throws Throwable { + mojo.setHashDependentPaths(true); + + // hashDependentPaths set to true, the JAR path is immediately hashed (MD5) and converted to a hex string + + assertEquals("1c85950987b23493462cf3c261d9510a", mojo.truncatePath("/path/to/somejar.jar")); + assertEquals("39fc2b4c34cb6cb0da38bed5d8b5fc67", mojo.truncatePath("/test/maven/repo/basedir/path/to/somejar.jar")); + assertEquals("25b6924f5b0e19486d0ff88448e999d5", mojo.truncatePath("\\path\\to\\somejar.jar")); + } + +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java new file mode 100644 index 000000000..7c7891edf --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/java/org/apache/thrift/maven/TestThrift.java @@ -0,0 +1,163 @@ +/* + * 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 org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.cli.CommandLineException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TestThrift { + + private File testRootDir; + private File idlDir; + private File genJavaDir; + private Thrift.Builder builder; + + @Before + public void setup() throws Exception { + final File tmpDir = new File(System.getProperty("java.io.tmpdir")); + testRootDir = new File(tmpDir, "thrift-test"); + + if (testRootDir.exists()) { + FileUtils.cleanDirectory(testRootDir); + } else { + assertTrue("Failed to create output directory for test: " + testRootDir.getPath(), testRootDir.mkdir()); + } + + File testResourceDir = new File("src/test/resources"); + assertTrue("Unable to find test resources", testRootDir.exists()); + + String thriftExecutable = System.getProperty("thriftExecutable", "thrift"); + if (!(new File(thriftExecutable).exists())) { + thriftExecutable = "thrift"; + } + System.out.println("Thrift compiler: " + thriftExecutable); + + idlDir = new File(testResourceDir, "idl"); + genJavaDir = new File(testRootDir, Thrift.GENERATED_JAVA); + builder = new Thrift.Builder(thriftExecutable, testRootDir); + builder + .setGenerator("java") + .addThriftPathElement(idlDir); + } + + @Test + public void testThriftCompile() throws Exception { + executeThriftCompile(); + } + + @Test + public void testThriftCompileWithGeneratorOption() throws Exception { + builder.setGenerator("java:private-members"); + executeThriftCompile(); + } + + private void executeThriftCompile() throws CommandLineException { + final File thriftFile = new File(idlDir, "shared.thrift"); + + builder.addThriftFile(thriftFile); + + final Thrift thrift = builder.build(); + + assertTrue("File not found: shared.thrift", thriftFile.exists()); + assertFalse("gen-java directory should not exist", genJavaDir.exists()); + + // execute the compile + final int result = thrift.compile(); + assertEquals(0, result); + + assertFalse("gen-java directory was not removed", genJavaDir.exists()); + assertTrue("generated java code doesn't exist", + new File(testRootDir, "shared/SharedService.java").exists()); + } + + @Test + public void testThriftMultipleFileCompile() throws Exception { + final File sharedThrift = new File(idlDir, "shared.thrift"); + final File tutorialThrift = new File(idlDir, "tutorial.thrift"); + + builder.addThriftFile(sharedThrift); + builder.addThriftFile(tutorialThrift); + + final Thrift thrift = builder.build(); + + assertTrue("File not found: shared.thrift", sharedThrift.exists()); + assertFalse("gen-java directory should not exist", genJavaDir.exists()); + + // execute the compile + final int result = thrift.compile(); + assertEquals(0, result); + + assertFalse("gen-java directory was not removed", genJavaDir.exists()); + assertTrue("generated java code doesn't exist", + new File(testRootDir, "shared/SharedService.java").exists()); + assertTrue("generated java code doesn't exist", + new File(testRootDir, "tutorial/InvalidOperation.java").exists()); + } + + @Test + public void testBadCompile() throws Exception { + final File thriftFile = new File(testRootDir, "missing.thrift"); + builder.addThriftPathElement(testRootDir); + + // Hacking around checks in addThrift file. + assertTrue(thriftFile.createNewFile()); + builder.addThriftFile(thriftFile); + assertTrue(thriftFile.delete()); + + final Thrift thrift = builder.build(); + + assertTrue(!thriftFile.exists()); + assertFalse("gen-java directory should not exist", genJavaDir.exists()); + + // execute the compile + final int result = thrift.compile(); + assertEquals(1, result); + } + + @Test + public void testFileInPathPreCondition() throws Exception { + final File thriftFile = new File(testRootDir, "missing.thrift"); + + // Hacking around checks in addThrift file. + assertTrue(thriftFile.createNewFile()); + try { + builder.addThriftFile(thriftFile); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + } + } + + @After + public void cleanup() throws Exception { + if (testRootDir.exists()) { + FileUtils.cleanDirectory(testRootDir); + assertTrue("Failed to delete output directory for test: " + testRootDir.getPath(), testRootDir.delete()); + } + } +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift new file mode 100644 index 000000000..475e7f803 --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/shared.thrift @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/** + * This Thrift file can be included by other Thrift files that want to share + * these definitions. + */ + +namespace cpp shared +namespace java shared +namespace perl shared + +struct SharedStruct { + 1: i32 key + 2: string value +} + +service SharedService { + SharedStruct getStruct(1: i32 key) +} diff --git a/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift new file mode 100644 index 000000000..86e433dd3 --- /dev/null +++ b/src/jaegertracing/thrift/contrib/thrift-maven-plugin/src/test/resources/idl/tutorial.thrift @@ -0,0 +1,152 @@ +/* + * 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. + */ + +# Thrift Tutorial +# Mark Slee (mcslee@facebook.com) +# +# This file aims to teach you how to use Thrift, in a .thrift file. Neato. The +# first thing to notice is that .thrift files support standard shell comments. +# This lets you make your thrift file executable and include your Thrift build +# step on the top line. And you can place comments like this anywhere you like. +# +# Before running this file, you will need to have installed the thrift compiler +# into /usr/local/bin. + +/** + * The first thing to know about are types. The available types in Thrift are: + * + * bool Boolean, one byte + * byte Signed byte + * i16 Signed 16-bit integer + * i32 Signed 32-bit integer + * i64 Signed 64-bit integer + * double 64-bit floating point value + * string String + * binary Blob (byte array) + * map<t1,t2> Map from one type to another + * list<t1> Ordered list of one type + * set<t1> Set of unique elements of one type + * + * Did you also notice that Thrift supports C style comments? + */ + +// Just in case you were wondering... yes. We support simple C comments too. + +/** + * Thrift files can reference other Thrift files to include common struct + * and service definitions. These are found using the current path, or by + * searching relative to any paths specified with the -I compiler flag. + * + * Included objects are accessed using the name of the .thrift file as a + * prefix. i.e. shared.SharedObject + */ +include "shared.thrift" + +/** + * Thrift files can namespace, package, or prefix their output in various + * target languages. + */ +namespace cpp tutorial +namespace java tutorial +namespace php tutorial +namespace perl tutorial +namespace smalltalk.category Thrift.Tutorial + +/** + * Thrift lets you do typedefs to get pretty names for your types. Standard + * C style here. + */ +typedef i32 MyInteger + +/** + * Thrift also lets you define constants for use across languages. Complex + * types and structs are specified using JSON notation. + */ +const i32 INT32CONSTANT = 9853 +const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} + +/** + * You can define enums, which are just 32 bit integers. Values are optional + * and start at 1 if not supplied, C style again. + */ +enum Operation { + ADD = 1, + SUBTRACT = 2, + MULTIPLY = 3, + DIVIDE = 4 +} + +/** + * Structs are the basic complex data structures. They are comprised of fields + * which each have an integer identifier, a type, a symbolic name, and an + * optional default value. + * + * Fields can be declared "optional", which ensures they will not be included + * in the serialized output if they aren't set. Note that this requires some + * manual management in some languages. + */ +struct Work { + 1: i32 num1 = 0, + 2: i32 num2, + 3: Operation op, + 4: optional string comment, +} + +/** + * Structs can also be exceptions, if they are nasty. + */ +exception InvalidOperation { + 1: i32 what, + 2: string why +} + +/** + * Ahh, now onto the cool part, defining a service. Services just need a name + * and can optionally inherit from another service using the extends keyword. + */ +service Calculator extends shared.SharedService { + + /** + * A method definition looks like C code. It has a return type, arguments, + * and optionally a list of exceptions that it may throw. Note that argument + * lists and exception lists are specified using the exact same syntax as + * field lists in struct or exception definitions. + */ + + void ping(), + + i32 add(1:i32 num1, 2:i32 num2), + + i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), + + /** + * This method has a oneway modifier. That means the client only makes + * a request and does not listen for any response at all. Oneway methods + * must be void. + */ + oneway void zip() + +} + +/** + * That just about covers the basics. Take a look in the test/ folder for more + * detailed examples. After you run this file, your generated code shows up + * in folders with names gen-<language>. The generated code isn't too scary + * to look at. It even has pretty indentation. + */ |