/**
* Copyright (C) 2013-2014 Telenor Digital AS
*
* 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.comoyo.maven.plugins.protoc;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.List;
/**
* Maven Plugin Mojo for compiling Protobuf schema files. Protobuf
* compiler binaries for various platforms and protobuf versions are
* bundled with the plugin and used as required.
* <p>
* The included ``protoc'' binary is:
* <pre>
* Copyright 2008, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Code generated by the Protocol Buffer compiler is owned by the owner
* of the input file used when generating it. This code is not
* standalone and requires a support library to be linked with it. This
* support library is itself covered by the above license.
* </pre>
*
* @goal run
* @phase generate-sources
* @requiresDependencyResolution
*/
public class ProtocBundledMojo extends AbstractMojo
{
/**
* Plugin descriptor.
*
* @component role="org.apache.maven.plugin.descriptor.PluginDescriptor"
* @required
* @readonly
*/
private PluginDescriptor pluginDescriptor;
/**
* The Maven project.
*
* @parameter property="project"
* @required
* @readonly
*/
private MavenProject project;
/**
* Used to look up Artifacts in the remote repository.
*
* @component role="org.apache.maven.repository.RepositorySystem"
* @required
* @readonly
*/
protected RepositorySystem repositorySystem;
/**
* List of Remote Repositories used by the resolver
*
* @parameter property="project.remoteArtifactRepositories"
* @readonly
* @required
*/
protected List<ArtifactRepository> remoteRepositories;
/**
* Protobuf version to compile schema files for. If omitted,
* version is inferred from the project's depended-on
* com.google.com:protobuf-java artifact, if any. (If both are
* present, the version must match.)
*
* @parameter property="protobufVersion"
*/
private String protobufVersion;
/**
* Directories containing *.proto files to compile.
*
* @parameter
* property="inputDirectories"
*/
private File[] inputDirectories;
/**
* Output directory for generated Java class files.
*
* @parameter
* property="outputDirectory"
* default-value="${project.build.directory}/generated-sources/protobuf"
*/
private File outputDirectory;
/**
* Directories containing *.proto files to compile for test.
*
* @parameter
* property="testInputDirectories"
*/
private File[] testInputDirectories;
/**
* Output directory for generated Java class files.
*
* @parameter
* property="testOutputDirectory"
* default-value="${project.build.directory}/generated-test-sources/protobuf"
*/
private File testOutputDirectory;
/**
* Path to existing protoc to use. Overrides auto-detection and
* use of bundled protoc.
*
* @parameter property="protocExec"
*/
private File protocExec;
private static final Map<String, String> osNamePrefix = new HashMap<String, String>();
private static final Map<String, String> osArchCanon = new HashMap<String, String>();
static {
osNamePrefix.put("linux", "linux");
osNamePrefix.put("mac os x", "mac_os_x");
osNamePrefix.put("windows", "win32");
osArchCanon.put("x86_64", "x86_64");
osArchCanon.put("amd64", "x86_64");
osArchCanon.put("x86", "x86");
osArchCanon.put("i386", "x86");
osArchCanon.put("i686", "x86");
}
/**
* Dynamically determine name of protoc variant suitable for
* current system.
*
*/
private String determineProtocForSystem()
throws MojoExecutionException
{
final String osName = System.getProperty("os.name");
if (osName == null) {
throw new MojoExecutionException("Unable to determine OS platform");
}
String os = null;
for (String prefix : osNamePrefix.keySet()) {
if (osName.toLowerCase().startsWith(prefix)) {
os = osNamePrefix.get(prefix);
break;
}
}
if (os == null) {
throw new MojoExecutionException("Unable to determine OS class for " + osName);
}
final String archName = System.getProperty("os.arch");
if (archName == null) {
throw new MojoExecutionException("Unable to determine CPU architecture");
}
String arch = osArchCanon.get(archName.toLowerCase());
if (arch == null) {
throw new MojoExecutionException("Unable to determine CPU arch id for " + archName);
}
// Pre-built protoc binaries for Windows use the same image
// for 32- and 64-bit systems.
if ("win32".equals(os) && "x86_64".equals(arch)) {
arch = "x86";
}
return protobufVersion + "-" + os + "-" + arch;
}
/**
* Return reference to suitable protoc binary artifact, download
* from remote repository if necessary.
*
* @param protocName protoc specifier
*/
private File resolveProtocArtifact(String protocName)
throws MojoExecutionException
{
Artifact artifact
= repositorySystem.createArtifactWithClassifier(
pluginDescriptor.getGroupId(),
pluginDescriptor.getArtifactId(),
pluginDescriptor.getVersion(),
"exe", protocName);
getLog().info("Using protoc " + artifact);
ArtifactResolutionRequest request = new ArtifactResolutionRequest()
.setArtifact(artifact)
.setRemoteRepositories(remoteRepositories);
ArtifactResolutionResult result = repositorySystem.resolve(request);
if (!result.isSuccess()) {
throw new MojoExecutionException(
"Unable to resolve dependency on protoc binary artifact, sorry: "
+ result.toString());
}
Set<Artifact> artifacts = result.getArtifacts();
if (artifacts.size() != 1) {
throw new MojoExecutionException(
"Unexpected number of artifacts returned when resolving protoc binary (" + artifacts.size() + ")");
}
Artifact protocArtifact = artifacts.iterator().next();
File file = protocArtifact.getFile();
file.setExecutable(true, false);
return file;
}
/**
* Get version of specified artifact from the current project's
* dependencies, if it exists.
*
* @param groupId
* @param artifactId
*/
private String getArtifactVersion(String groupId, String artifactId)
throws MojoExecutionException
{
Set<Artifact> artifacts = project.getArtifacts();
for (Artifact artifact : artifacts) {
if (groupId.equals(artifact.getGroupId())
&& artifactId.equals(artifact.getArtifactId()))
{
return artifact.getVersion();
}
}
return null;
}
/**
* Ensure we have a suitable protoc binary available. If
* protocExec is explicitly given, use that. Otherwise find and
* extract suitable protoc from plugin bundle.
*
*/
private void ensureProtocBinaryPresent()
throws MojoExecutionException
{
if (protocExec != null) {
return;
}
final String protobufArtifactVersion
= getArtifactVersion("com.google.protobuf", "protobuf-java");
if (protobufVersion == null) {
if (protobufArtifactVersion == null) {
throw new MojoExecutionException(
"protobufVersion not specified and unable to derive version "
+ "from protobuf-java dependency");
}
protobufVersion = protobufArtifactVersion;
}
else {
if (protobufArtifactVersion != null
&& !protobufVersion.equals(protobufArtifactVersion))
{
getLog().warn("Project includes protobuf-java artifact of version "
+ protobufArtifactVersion
+ " while protoc is set to compile for version "
+ protobufVersion);
}
}
final String protocName = determineProtocForSystem();
protocExec = resolveProtocArtifact(protocName);
}
/**
* Compile single protobuf schema file.
*
* @param dir base dir for input file, used to resolve includes
* @param input input file to compile
*/
private void compileFile(File inputDir, File input, File outputDir)
throws MojoExecutionException
{
try {
final Process proc
= new ProcessBuilder(protocExec.toString(),
"--proto_path=" + inputDir.getAbsolutePath(),
"--java_out=" + outputDir,
input.getAbsolutePath())
.redirectErrorStream(true)
.start();
final BufferedReader procOut
= new BufferedReader(new InputStreamReader(proc.getInputStream()));
while (true) {
final String line = procOut.readLine();
if (line == null) {
break;
}
getLog().info("[protoc] " + line);
}
final int status = proc.waitFor();
procOut.close();
if (status != 0) {
throw new MojoExecutionException(
"Compilation failure signalled by protoc exit status: " + status);
}
}
catch (Exception e) {
throw new MojoExecutionException("Unable to compile " + input.toString(), e);
}
}
/**
* Compile all *.proto files found under inputDirectories.
*
*/
private boolean compileAllFiles(String tag, File[] inputDirs, File outputDir)
throws MojoExecutionException
{
final IOFileFilter filter = new SuffixFileFilter(".proto");
boolean seen = false;
for (File inputDir : inputDirs) {
if (!inputDir.exists()) {
continue;
}
if (!outputDir.exists()) {
outputDir.mkdirs();
}
getLog().info("Compiling " + inputDir + " to " + outputDir + " [" + tag + "]");
Iterator<File> files
= FileUtils.iterateFiles(inputDir, filter, TrueFileFilter.INSTANCE);
while (files.hasNext()) {
final File input = files.next();
compileFile(inputDir, input, outputDir);
seen = true;
}
}
return seen;
}
/**
* Plugin invocation point.
*
*/
@Override
public void execute()
throws MojoExecutionException
{
ensureProtocBinaryPresent();
if (inputDirectories.length == 0) {
inputDirectories = new File[]{new File(project.getBasedir(), "src/main/protobuf")};
}
if (compileAllFiles("main", inputDirectories, outputDirectory)) {
project.addCompileSourceRoot(outputDirectory.getAbsolutePath());
}
if (testInputDirectories.length == 0) {
testInputDirectories = new File[]{new File(project.getBasedir(), "src/test/protobuf")};
}
if (compileAllFiles("test", testInputDirectories, testOutputDirectory)) {
project.addTestCompileSourceRoot(testOutputDirectory.getAbsolutePath());
}
}
}