com.github.os72.protocjar.maven.ProtocJarMojo Maven / Gradle / Ivy
Show all versions of protoc-jar-maven-plugin Show documentation
/*
* Copyright 2014 protoc-jar developers
*
* Incorporates code derived from https://github.com/igor-petruk/protobuf-maven-plugin
* Copyright 2012, by Yet another Protobuf Maven Plugin Developers
*
* Licensed 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 com.github.os72.protocjar.maven;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.sonatype.plexus.build.incremental.BuildContext;
import com.github.os72.protocjar.PlatformDetector;
import com.github.os72.protocjar.Protoc;
import com.github.os72.protocjar.ProtocVersion;
/**
* Compiles .proto files using protoc-jar embedded protoc compiler. Also supports pre-installed protoc binary, and downloading binaries (protoc and protoc plugins) from maven repo
*
* @goal run
* @phase generate-sources
* @requiresDependencyResolution
*/
public class ProtocJarMojo extends AbstractMojo
{
private static final String DEFAULT_INPUT_DIR = "/src/main/protobuf/".replace('/', File.separatorChar);
/**
* Specifies the protoc version (default: latest version).
*
* @parameter property="protocVersion"
*/
private String protocVersion;
/**
* Input directories that have *.proto files (or the configured extension).
* If none specified then src/main/protobuf is used.
*
* @parameter property="inputDirectories"
*/
private File[] inputDirectories;
/**
* This parameter lets you specify additional include paths to protoc.
*
* @parameter property="includeDirectories"
*/
private File[] includeDirectories;
/**
* If "true", extract the included google.protobuf standard types and add them to protoc import path.
*
* @parameter property="includeStdTypes" default-value="false"
*/
private boolean includeStdTypes;
/**
* Specifies output type.
* Options: "java", "cpp", "python", "descriptor" (default: "java"); for proto3 also: "javanano", "csharp", "objc", "ruby", "js"
*
* Ignored when {@code } is given
*
* @parameter property="type" default-value="java"
*/
private String type;
/**
* Specifies whether to add outputDirectory to sources that are going to be compiled.
* Options: "main", "test", "none" (default: "main")
*
* Ignored when {@code } is given
*
* @parameter property="addSources" default-value="main"
*/
private String addSources;
/**
* If this parameter is set to "true" output folder is cleaned prior to the
* build. This will not let old and new classes coexist after package or
* class rename in your IDE cache or after non-clean rebuild. Set this to
* "false" if you are doing multiple plugin invocations per build and it is
* important to preserve output folder contents
*
* Ignored when {@code } is given
*
* @parameter property="cleanOutputFolder" default-value="false"
*/
private boolean cleanOutputFolder;
/**
* Path to the protoc plugin that generates code for the specified {@link #type}.
*
* Ignored when {@code } is given
*
* @parameter property="pluginPath"
*/
private String pluginPath;
/**
* Maven artifact coordinates of the protoc plugin that generates code for the specified {@link #type}.
* Format: "groupId:artifactId:version" (eg, "io.grpc:protoc-gen-grpc-java:1.0.1")
*
* Ignored when {@code } is given
*
* @parameter property="pluginArtifact"
*/
private String pluginArtifact;
/**
* Output directory for the generated java files. Defaults to
* "${project.build.directory}/generated-sources" or
* "${project.build.directory}/generated-test-sources"
* depending on the addSources parameter
*
* Ignored when {@code } is given
*
* @parameter property="outputDirectory"
*/
private File outputDirectory;
/**
* If this parameter is set, append its value to the {@link #outputDirectory} path
* For example, value "protobuf" will set output directory to
* "${project.build.directory}/generated-sources/protobuf" or
* "${project.build.directory}/generated-test-sources/protobuf"
*
* Ignored when {@code } is given
*
* @parameter property="outputDirectorySuffix"
*/
private String outputDirectorySuffix;
/**
* Output options. Used for example with type "js" to create protoc argument --js_out=[OPTIONS]:output_dir
*
* Ignored when {@code } is given
*
* @parameter property="outputOptions"
*/
private String outputOptions;
/**
* This parameter lets you specify multiple protoc output targets.
* OutputTarget parameters: "type", "addSources", "cleanOutputFolder", "outputDirectory", "outputDirectorySuffix", "outputOptions", "pluginPath", "pluginArtifact".
* Type options: "java", "cpp", "python", "descriptor" (default: "java"); for proto3 also: "javanano", "csharp", "objc", "ruby", "js"
*
*
* {@code
*
*
* java
* none
* src/main/java
*
*
* grpc-java
* none
* src/main/java
* io.grpc:protoc-gen-grpc-java:1.0.1
*
*
* python
* none
* src/main/python
*
*
* dart
* none
* lib/protobuf
* protoc-gen-dart.exe
*
*
* }
*
*
* @parameter property="outputTargets"
*/
private OutputTarget[] outputTargets;
/**
* Default extension for protobuf files
*
* @parameter property="extension" default-value=".proto"
*/
private String extension;
/**
* This parameter allows to use a protoc binary instead of the protoc-jar bundle
*
* @parameter property="protocCommand"
*/
private String protocCommand;
/**
* Maven artifact coordinates of the protoc binary to use
* Format: "groupId:artifactId:version" (eg, "com.google.protobuf:protoc:3.1.0")
*
* @parameter property="protocArtifact"
*/
private String protocArtifact;
/**
* The Maven project.
*
* @parameter property="project"
* @readonly
* @required
*/
private MavenProject project;
/**
* @parameter default-value="${localRepository}"
* @readonly
* @required
*/
private ArtifactRepository localRepository;
/**
* @parameter default-value="${project.remoteArtifactRepositories}"
* @readonly
* @required
*/
private List remoteRepositories;
/** @component */
private BuildContext buildContext;
/** @component */
private ArtifactFactory artifactFactory;
/** @component */
private ArtifactResolver artifactResolver;
public void execute() throws MojoExecutionException {
if (project.getPackaging() != null && "pom".equals(project.getPackaging().toLowerCase())) {
getLog().info("Skipping 'pom' packaged project");
return;
}
if (outputTargets == null || outputTargets.length == 0) {
OutputTarget target = new OutputTarget();
target.type = type;
target.addSources = addSources;
target.cleanOutputFolder = cleanOutputFolder;
target.pluginPath = pluginPath;
target.pluginArtifact = pluginArtifact;
target.outputDirectory = outputDirectory;
target.outputDirectorySuffix = outputDirectorySuffix;
target.outputOptions = outputOptions;
outputTargets = new OutputTarget[] {target};
}
for (OutputTarget target : outputTargets) {
target.addSources = target.addSources.toLowerCase().trim();
if ("true".equals(target.addSources)) target.addSources = "main";
if (target.outputDirectory == null) {
String subdir = "generated-" + ("test".equals(target.addSources) ? "test-" : "") + "sources";
target.outputDirectory = new File(project.getBuild().getDirectory() + File.separator + subdir + File.separator);
}
if (target.outputDirectorySuffix != null) {
target.outputDirectory = new File(target.outputDirectory, target.outputDirectorySuffix);
}
}
performProtoCompilation();
}
private void performProtoCompilation() throws MojoExecutionException {
if (protocCommand != null) {
try {
Protoc.runProtoc(protocCommand, new String[]{"--version"});
}
catch (Exception e) {
protocCommand = null;
}
}
String protocTemp = null;
if ((protocCommand == null && protocArtifact == null) || includeStdTypes) {
if (protocVersion == null || protocVersion.length() < 1) protocVersion = ProtocVersion.PROTOC_VERSION.mVersion;
getLog().info("Protoc version: " + protocVersion);
try {
File protocFile = Protoc.extractProtoc(ProtocVersion.getVersion("-v"+protocVersion), includeStdTypes);
protocTemp = protocFile.getAbsolutePath();
}
catch (IOException e) {
throw new MojoExecutionException("Error extracting protoc for version " + protocVersion, e);
}
if (protocCommand == null && protocArtifact == null) protocCommand = protocTemp;
}
if (protocCommand == null && protocArtifact != null) {
protocCommand = resolveArtifact(protocArtifact).getAbsolutePath();
}
getLog().info("Protoc command: " + protocCommand);
if (inputDirectories == null || inputDirectories.length == 0) {
File inputDir = new File(project.getBasedir().getAbsolutePath() + DEFAULT_INPUT_DIR);
inputDirectories = new File[] { inputDir };
}
getLog().info("Input directories:");
for (File input : inputDirectories) getLog().info(" " + input);
if (includeStdTypes) {
File stdTypeDir = new File(new File(protocTemp).getParentFile().getParentFile(), "include");
if (includeDirectories != null && includeDirectories.length > 0) {
List includeDirList = new ArrayList();
includeDirList.add(stdTypeDir);
includeDirList.addAll(Arrays.asList(includeDirectories));
includeDirectories = includeDirList.toArray(new File[0]);
}
else {
includeDirectories = new File[] { stdTypeDir };
}
}
if (includeDirectories != null && includeDirectories.length > 0) {
getLog().info("Include directories:");
for (File include : includeDirectories) getLog().info(" " + include);
}
getLog().info("Output targets:");
for (OutputTarget target : outputTargets) getLog().info(" " + target);
for (OutputTarget target : outputTargets) preprocessTarget(target);
for (OutputTarget target : outputTargets) processTarget(target);
}
private void preprocessTarget(OutputTarget target) throws MojoExecutionException {
if (target.pluginArtifact != null && target.pluginArtifact.length() > 0) {
target.pluginPath = resolveArtifact(target.pluginArtifact).getAbsolutePath();
}
File f = target.outputDirectory;
if (!f.exists()) {
getLog().info(f + " does not exist. Creating...");
f.mkdirs();
}
if (target.cleanOutputFolder) {
try {
getLog().info("Cleaning " + f);
FileUtils.cleanDirectory(f);
}
catch (IOException e) {
e.printStackTrace();
}
}
}
private void processTarget(OutputTarget target) throws MojoExecutionException {
boolean shaded = false;
String targetType = target.type;
if (targetType.equals("java-shaded") || targetType.equals("java_shaded")) {
targetType = "java";
shaded = true;
}
FileFilter fileFilter = new FileFilter(extension);
for (File input : inputDirectories) {
if (input == null) continue;
if (input.exists() && input.isDirectory()) {
Collection protoFiles = FileUtils.listFiles(input, fileFilter, TrueFileFilter.INSTANCE);
for (File protoFile : protoFiles) {
if (target.cleanOutputFolder || buildContext.hasDelta(protoFile.getPath())) {
processFile(protoFile, protocVersion, targetType, target.pluginPath, target.outputDirectory, target.outputOptions);
}
else {
getLog().info("Not changed " + protoFile);
}
}
}
else {
if (input.exists()) getLog().warn(input + " is not a directory");
else getLog().warn(input + " does not exist");
}
}
if (shaded) {
try {
getLog().info(" Shading (version " + protocVersion + "): " + target.outputDirectory);
Protoc.doShading(target.outputDirectory, protocVersion.replace(".", ""));
}
catch (IOException e) {
throw new MojoExecutionException("Error occurred during shading", e);
}
}
boolean mainAddSources = "main".endsWith(target.addSources);
boolean testAddSources = "test".endsWith(target.addSources);
if (mainAddSources) {
getLog().info("Adding generated classes to classpath");
project.addCompileSourceRoot(target.outputDirectory.getAbsolutePath());
}
if (testAddSources) {
getLog().info("Adding generated classes to test classpath");
project.addTestCompileSourceRoot(target.outputDirectory.getAbsolutePath());
}
if (mainAddSources || testAddSources) {
buildContext.refresh(target.outputDirectory);
}
}
private void processFile(File file, String version, String type, String pluginPath, File outputDir, String outputOptions) throws MojoExecutionException {
getLog().info(" Processing ("+ type + "): " + file.getName());
Collection cmd = buildCommand(file, version, type, pluginPath, outputDir, outputOptions);
try {
int ret = 0;
if (protocCommand == null) ret = Protoc.runProtoc(cmd.toArray(new String[0]));
else ret = Protoc.runProtoc(protocCommand, cmd.toArray(new String[0]));
if (ret != 0) throw new MojoExecutionException("protoc-jar failed for " + file + ". Exit code " + ret);
}
catch (InterruptedException e) {
throw new MojoExecutionException("Interrupted", e);
}
catch (IOException e) {
throw new MojoExecutionException("Unable to execute protoc-jar for " + file, e);
}
}
private Collection buildCommand(File file, String version, String type, String pluginPath, File outputDir, String outputOptions) throws MojoExecutionException {
Collection cmd = new ArrayList();
populateIncludes(cmd);
cmd.add("-I" + file.getParentFile().getAbsolutePath());
if ("descriptor".equals(type)) {
File outFile = new File(outputDir, file.getName());
cmd.add("--descriptor_set_out=" + FilenameUtils.removeExtension(outFile.toString()) + ".desc");
cmd.add("--include_imports");
if (outputOptions != null) {
for (String arg : outputOptions.split("\\s+")) cmd.add(arg);
}
}
else {
if (outputOptions != null) {
cmd.add("--" + type + "_out=" + outputOptions + ":" + outputDir);
}
else {
cmd.add("--" + type + "_out=" + outputDir);
}
if (pluginPath != null) {
getLog().info(" Plugin path: " + pluginPath);
cmd.add("--plugin=protoc-gen-" + type + "=" + pluginPath);
}
}
cmd.add(file.toString());
if (version != null) cmd.add("-v" + version);
return cmd;
}
private void populateIncludes(Collection args) throws MojoExecutionException {
for (File include : includeDirectories) {
if (!include.exists()) throw new MojoExecutionException("Include path '" + include.getPath() + "' does not exist");
if (!include.isDirectory()) throw new MojoExecutionException("Include path '" + include.getPath() + "' is not a directory");
args.add("-I" + include.getPath());
}
}
private File resolveArtifact(String artifactSpec) throws MojoExecutionException {
try {
Properties detectorProps = new Properties();
new PlatformDetector().detect(detectorProps, null);
String platform = detectorProps.getProperty("os.detected.classifier");
getLog().info("Resolving artifact: " + artifactSpec + ", platform: " + platform);
String[] as = artifactSpec.split(":");
Artifact artifact = artifactFactory.createDependencyArtifact(as[0], as[1], VersionRange.createFromVersionSpec(as[2]), "exe", platform, Artifact.SCOPE_RUNTIME);
artifactResolver.resolve(artifact, remoteRepositories, localRepository);
File tempFile = File.createTempFile(as[1], ".exe");
copyFile(artifact.getFile(), tempFile);
tempFile.setExecutable(true);
tempFile.deleteOnExit();
return tempFile;
}
catch (Exception e) {
throw new MojoExecutionException("Error resolving artifact: " + artifactSpec, e);
}
}
static File copyFile(File srcFile, File destFile) throws IOException {
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(srcFile);
os = new FileOutputStream(destFile);
int read = 0;
byte[] buf = new byte[4096];
while ((read = is.read(buf)) > 0) os.write(buf, 0, read);
}
finally {
if (is != null) is.close();
if (os != null) os.close();
}
return destFile;
}
static class FileFilter implements IOFileFilter
{
String extension;
public FileFilter(String extension) {
this.extension = extension;
}
public boolean accept(File dir, String name) {
return name.endsWith(extension);
}
public boolean accept(File file) {
return file.getName().endsWith(extension);
}
}
}