All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.os72.protocjar.maven.ProtocJarMojo Maven / Gradle / Ivy

There is a newer version: 3.11.4
Show newest version
/*
 * 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy