Skip to content

Commit

Permalink
Allow for custom plugins to be supplied to the main invocation of pro…
Browse files Browse the repository at this point in the history
…toc.

The protobuf plugin does not allow for custom plugins to be supplied to
a single protoc that includes the main java stub generation. This ends
up being a problem for plugins that utilize features like protobuf's
extension point support, since they rely on protoc to supply info about
where it's main phase has emitted Java code.

Instead of relying on the compile-custom goal to execute a new, distinct
follow-on execution of protoc, allow for <plugin> elements in the
configuration of the main compile task to augment the arguments passed
to protoc. For example, after this commit, you can do the following

      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.7.0-SNAPSHOT</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <protocPlugins>
            <plugin>
              <id>grpc-java</id>
              <groupId>io.grpc</groupId>
              <artifactId>protoc-gen-grpc-java</artifactId>
              <version>${grpc.version}</version>
              <type>exe</type>
              <classifier>${os.detected.classifier}</classifier>
            </plugin>
            <plugin>
              <id>kotlin</id>
              <groupId>com.github.marcoferrer.krotoplus</groupId>
              <artifactId>protoc-gen-kroto-plus</artifactId>
              <version>${kroto-plus.version}</version>
              <classifier>jvm8</classifier>
            </plugin>
          </protocPlugins>
        </configuration>
        <executions>
          <execution><goals><goal>compile</goal></goals></execution>
        </executions>
      </plugin>

With the above configuration, maven generates and executes a command line that looks like

protoc --java_out=... --plugin=protoc-gen-grpc-java --grpc-java_out=... --plugin=protoc-gen-kroto-plus --kroto-plus_out=...

which allows for plugins to have full access to the entire protoc
generation context.
  • Loading branch information
dpratt committed Aug 29, 2019
1 parent 824424c commit f9a5d2e
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ protected List<Artifact> getDependencyArtifacts() {
protected File getProtoSourceRoot() {
return protoSourceRoot;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -428,18 +428,23 @@ public void execute() throws MojoExecutionException, MojoFailureException {
} else {
final List<File> derivedProtoPathElements =
makeProtoPathFromJars(temporaryProtoFileDirectory, getDependencyArtifactFiles());

FileUtils.mkdir(outputDirectory.getAbsolutePath());
createProtocPlugins();

if (clearOutputDirectory) {
try {
cleanDirectory(outputDirectory);
if(protocPlugins != null) {
for(ProtocPlugin plugin : protocPlugins) {
cleanDirectory(plugin.getOutputDirectory());
}
}
} catch (final IOException e) {
throw new MojoInitializationException("Unable to clean output directory", e);
}
}

createProtocPlugins();

//get toolchain from context
final Toolchain tc = toolchainManager.getToolchainFromBuildContext("protobuf", session); //NOI18N
if (tc != null) {
Expand Down Expand Up @@ -541,15 +546,35 @@ protected void createProtocPlugins() {

for (final ProtocPlugin plugin : protocPlugins) {

if (plugin.getJavaHome() != null) {
getLog().debug("Using javaHome defined in plugin definition: " + javaHome);
String pluginid = plugin.getId();

if (pluginid.equals("java")
|| pluginid.equals("js")
|| pluginid.equals("python")
|| pluginid.equals("csharp")
|| pluginid.equals("cpp")
|| pluginid.equals("descriptor_set")) {
throw new MojoConfigurationException("custom plugin ID matches one of the built-in protoc plugins: " + pluginid);
}

File pluginOutputDir = plugin.getOutputDirectory();
if(pluginOutputDir == null) {
plugin.setOutputDirectory(getOutputDirectory());
} else {
getLog().debug("Setting javaHome for plugin: " + javaHome);
plugin.setJavaHome(javaHome);
FileUtils.mkdir(pluginOutputDir.getAbsolutePath());
}

getLog().info("Building protoc plugin: " + plugin.getId());
final ProtocPluginAssembler assembler = new ProtocPluginAssembler(
if(plugin.getType().equalsIgnoreCase("jar") && plugin.getMainClass() != null) {
//we need to generate an executable wrapper for this plugin
if (plugin.getJavaHome() != null) {
getLog().debug("Using javaHome defined in plugin definition: " + javaHome);
} else {
getLog().debug("Setting javaHome for plugin: " + javaHome);
plugin.setJavaHome(javaHome);
}

getLog().info("Building protoc plugin: " + plugin.getId());
final ProtocPluginAssembler assembler = new ProtocPluginAssembler(
plugin,
session,
project.getArtifact(),
Expand All @@ -558,9 +583,23 @@ protected void createProtocPlugins() {
resolutionErrorHandler,
localRepository,
remoteRepositories,
protocPluginDirectory,
getLog());
assembler.execute();
File destinationFile = new File(protocPluginDirectory, plugin.getPluginExecutableName());
assembler.execute(destinationFile);

} else {
//it's a native plugin - just resolve and download it
final Artifact artifact = createDependencyArtifact(
plugin.getGroupId(),
plugin.getArtifactId(),
plugin.getVersion(),
plugin.getType(),
plugin.getClassifier()
);
File sourceFile = resolveArtifact(artifact);
copyToPluginDir(sourceFile, plugin.getPluginExecutableName());
}

}
}

Expand Down Expand Up @@ -957,22 +996,22 @@ protected static String toHexString(final byte[] byteArray) {
return hexString.toString();
}

protected File resolveBinaryArtifact(final Artifact artifact) {
protected File resolveArtifact(Artifact artifact) {
final ArtifactResolutionResult result;
try {
final ArtifactResolutionRequest request = new ArtifactResolutionRequest()
.setArtifact(project.getArtifact())
.setResolveRoot(false)
.setResolveTransitively(false)
.setArtifactDependencies(singleton(artifact))
.setManagedVersionMap(emptyMap())
.setLocalRepository(localRepository)
.setRemoteRepositories(remoteRepositories)
.setOffline(session.isOffline())
.setForceUpdate(session.getRequest().isUpdateSnapshots())
.setServers(session.getRequest().getServers())
.setMirrors(session.getRequest().getMirrors())
.setProxies(session.getRequest().getProxies());
.setArtifact(project.getArtifact())
.setResolveRoot(false)
.setResolveTransitively(false)
.setArtifactDependencies(singleton(artifact))
.setManagedVersionMap(emptyMap())
.setLocalRepository(localRepository)
.setRemoteRepositories(remoteRepositories)
.setOffline(session.isOffline())
.setForceUpdate(session.getRequest().isUpdateSnapshots())
.setServers(session.getRequest().getServers())
.setMirrors(session.getRequest().getMirrors())
.setProxies(session.getRequest().getProxies());

result = repositorySystem.resolve(request);

Expand All @@ -993,14 +1032,10 @@ protected File resolveBinaryArtifact(final Artifact artifact) {
}

// Copy the file to the project build directory and make it executable
final File sourceFile = resolvedBinaryArtifact.getFile();
final String sourceFileName = sourceFile.getName();
final String targetFileName;
if (Os.isFamily(Os.FAMILY_WINDOWS) && !sourceFileName.endsWith(".exe")) {
targetFileName = sourceFileName + ".exe";
} else {
targetFileName = sourceFileName;
}
return resolvedBinaryArtifact.getFile();
}

protected File copyToPluginDir(File sourceFile, String targetFileName) {
final File targetFile = new File(protocPluginDirectory, targetFileName);
if (targetFile.exists()) {
// The file must have already been copied in a prior plugin execution/invocation
Expand All @@ -1027,6 +1062,19 @@ protected File resolveBinaryArtifact(final Artifact artifact) {
return targetFile;
}

protected File resolveBinaryArtifact(final Artifact artifact) {
// Copy the file to the project build directory and make it executable
final File sourceFile = resolveArtifact(artifact);
final String sourceFileName = sourceFile.getName();
final String targetFileName;
if (Os.isFamily(Os.FAMILY_WINDOWS) && !sourceFileName.endsWith(".exe")) {
targetFileName = sourceFileName + ".exe";
} else {
targetFileName = sourceFileName;
}
return copyToPluginDir(sourceFile, targetFileName);
}

/**
* Creates a dependency artifact from a specification in
* {@code groupId:artifactId:version[:type[:classifier]]} format.
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/org/xolstice/maven/plugin/protobuf/Protoc.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,6 @@ public List<String> buildProtocCommand() {
}
if (javaOutputDirectory != null) {
command.add("--java_out=" + javaOutputDirectory);

// For now we assume all custom plugins produce Java output
for (final ProtocPlugin plugin : plugins) {
final File pluginExecutable = plugin.getPluginExecutableFile(pluginDirectory);
command.add("--plugin=protoc-gen-" + plugin.getId() + '=' + pluginExecutable);
command.add("--" + plugin.getId() + "_out=" + javaOutputDirectory);
}
}
if (cppOutputDirectory != null) {
command.add("--cpp_out=" + cppOutputDirectory);
Expand Down Expand Up @@ -294,6 +287,16 @@ public List<String> buildProtocCommand() {
outputOption += customOutputDirectory;
command.add(outputOption);
}
for (final ProtocPlugin plugin : plugins) {
final File pluginExecutable = new File(pluginDirectory, plugin.getPluginExecutableName());
command.add("--plugin=protoc-gen-" + plugin.getId() + '=' + pluginExecutable);
String pluginOutOption = "--" + plugin.getId() + "_out=";
if(plugin.getParameter() != null) {
pluginOutOption += plugin.getParameter() + ':';
}
pluginOutOption += plugin.getOutputDirectory();
command.add(pluginOutOption);
}
for (final File protoFile : protoFiles) {
command.add(protoFile.toString());
}
Expand Down
51 changes: 44 additions & 7 deletions src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.Os;

import java.io.File;
Expand Down Expand Up @@ -48,17 +49,26 @@ public class ProtocPlugin {

private String version;

private String type = "jar";

private String classifier;

private String mainClass;

private String parameter;

private String javaHome;

// Assuming we're running a HotSpot JVM, use the data model of the
// current JVM as the default. This property is only relevant on
// Windows where we need to pick the right version of the WinRun4J executable.
private String winJvmDataModel;

@Parameter(
required = false
)
private File outputDirectory;

private List<String> args;

private List<String> jvmArgs;
Expand Down Expand Up @@ -100,6 +110,13 @@ public String getVersion() {
return version;
}

/**
* The type of the artifact (eg. 'exe' or 'jar').
*
* @return the plugin artifact type
*/
public String getType() { return type; }

/**
* Returns an optional classifier of the plugin's artifact for dependency resolution.
*
Expand All @@ -118,6 +135,13 @@ public String getMainClass() {
return mainClass;
}

/**
* Returns the plugin's argument to be passed to protoc.
*
* @return protoc argument
*/
public String getParameter() { return parameter; }

/**
* Returns optional command line arguments to pass to the {@code main()} method.
*
Expand Down Expand Up @@ -152,6 +176,14 @@ public String getPluginName() {
return "protoc-gen-" + id;
}

public void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}

public File getOutputDirectory() {
return outputDirectory;
}

/**
* Validate the state of this plugin specification.
*
Expand Down Expand Up @@ -217,16 +249,19 @@ private boolean archDirectoryExists(String arch) {
}

/**
* Returns the generated plugin executable path.
* Returns the generated plugin executable name.
*
* @param pluginDirectory directory where plugins will be created
* @return file handle for the plugin executable.
*/
public File getPluginExecutableFile(final File pluginDirectory) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
return new File(pluginDirectory, getPluginName() + ".exe");
public String getPluginExecutableName() {
if("jar".equalsIgnoreCase(type) && mainClass == null) {
//this is an uberjar plugin
return getPluginName() + ".jar";
} else {
return new File(pluginDirectory, getPluginName());
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
return getPluginName() + ".exe";
} else {
return getPluginName();
}
}
}

Expand All @@ -237,8 +272,10 @@ public String toString() {
", groupId='" + groupId + '\'' +
", artifactId='" + artifactId + '\'' +
", version='" + version + '\'' +
", type='" + type + '\'' +
", classifier='" + classifier + '\'' +
", mainClass='" + mainClass + '\'' +
", parameter='" + parameter + '\'' +
", javaHome='" + javaHome + '\'' +
", winJvmDataModel='" + winJvmDataModel + '\'' +
", args=" + args +
Expand Down
Loading

0 comments on commit f9a5d2e

Please sign in to comment.