com.google.protobuf.maven.Protoc Maven / Gradle / Ivy
package com.google.protobuf.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 protoc}
* compiler. The actual executable is invoked using the plexus
* {@link Commandline}.
*
* This class currently only supports generating java source files.
*
* @author [email protected] (Gregory Kick)
*/
final class Protoc {
private final String executable;
private final ImmutableSet protoPathElements;
private final ImmutableSet protoFiles;
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 protoc} executable.
* @param protoPath The directories in which to search for imports.
* @param protoFiles The proto source files to compile.
* @param javaOutputDirectory The directory into which the java source files
* will be generated.
*/
private Protoc(String executable, ImmutableSet protoPath,
ImmutableSet protoFiles, File javaOutputDirectory) {
this.executable = checkNotNull(executable, "executable");
this.protoPathElements = checkNotNull(protoPath, "protoPath");
this.protoFiles = checkNotNull(protoFiles, "protoFiles");
this.javaOutputDirectory = checkNotNull(javaOutputDirectory, "javaOutputDirectory");
this.error = new CommandLineUtils.StringStreamConsumer();
this.output = new CommandLineUtils.StringStreamConsumer();
}
/**
* Invokes the {@code protoc} compiler using the configuration specified at
* construction.
*
* @return The exit status of {@code protoc}.
* @throws CommandLineException
*/
public int compile() throws CommandLineException {
Commandline cl = new Commandline();
cl.setExecutable(executable);
cl.addArguments(buildProtocCommand().toArray(new String[]{}));
return CommandLineUtils.executeCommandLine(cl, null, output, error);
}
/**
* Creates the command line arguments.
*
* This method has been made visible for testing only.
*
* @return A list consisting of the executable followed by any arguments.
*/
ImmutableList buildProtocCommand() {
final List command = newLinkedList();
// add the executable
for (File protoPathElement : protoPathElements) {
command.add("--proto_path=" + protoPathElement);
}
command.add("--java_out=" + javaOutputDirectory);
for (File protoFile : protoFiles) {
command.add(protoFile.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 Protoc} instances.
*
* @author [email protected] (Gregory Kick)
*/
static final class Builder {
private final String executable;
private final File javaOutputDirectory;
private Set protopathElements;
private Set protoFiles;
/**
* Constructs a new builder. The two parameters are present as they are
* required for all {@link Protoc} instances.
*
* @param executable The path to the {@code protoc} 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.protoFiles = newHashSet();
this.protopathElements = newHashSet();
}
/**
* Adds a proto file to be compiled. Proto files must be on the protopath
* and this method will fail if a proto file is added without first adding a
* parent directory to the protopath.
*
* @param protoFile
* @return The builder.
* @throws IllegalStateException If a proto file is added without first
* adding a parent directory to the protopath.
* @throws NullPointerException If {@code protoFile} is {@code null}.
*/
public Builder addProtoFile(File protoFile) {
checkNotNull(protoFile);
checkArgument(protoFile.isFile());
checkArgument(protoFile.getName().endsWith(".proto"));
checkProtoFileIsInProtopath(protoFile);
protoFiles.add(protoFile);
return this;
}
private void checkProtoFileIsInProtopath(File protoFile) {
assert protoFile.isFile();
checkState(checkProtoFileIsInProtopathHelper(protoFile.getParentFile()));
}
private boolean checkProtoFileIsInProtopathHelper(File directory) {
assert directory.isDirectory();
if (protopathElements.contains(directory)) {
return true;
} else {
final File parentDirectory = directory.getParentFile();
return (parentDirectory == null) ? false
: checkProtoFileIsInProtopathHelper(parentDirectory);
}
}
/**
* @see #addProtoFile(File)
*/
public Builder addProtoFiles(Iterable protoFiles) {
for (File protoFile : protoFiles) {
addProtoFile(protoFile);
}
return this;
}
/**
* Adds the {@code protopathElement} to the protopath.
*
* @param protopathElement A directory to be searched for imported protocol
* buffer definitions.
* @return The builder.
* @throws NullPointerException If {@code protopathElement} is {@code null}.
* @throws IllegalArgumentException If {@code protpathElement} is not a
* directory.
*/
public Builder addProtoPathElement(File protopathElement) {
checkNotNull(protopathElement);
checkArgument(protopathElement.isDirectory());
protopathElements.add(protopathElement);
return this;
}
/**
* @see #addProtoPathElement(File)
*/
public Builder addProtoPathElements(Iterable protopathElements) {
for (File protopathElement : protopathElements) {
addProtoPathElement(protopathElement);
}
return this;
}
/**
* @return A configured {@link Protoc} instance.
* @throws IllegalStateException If no proto files have been added.
*/
public Protoc build() {
checkState(!protoFiles.isEmpty());
return new Protoc(executable, ImmutableSet.copyOf(protopathElements),
ImmutableSet.copyOf(protoFiles), javaOutputDirectory);
}
}
}