From e70d33da7a44d3f47faf5388aa62e7a9200e9255 Mon Sep 17 00:00:00 2001 From: David Pratt Date: Thu, 29 Aug 2019 14:47:55 -0500 Subject: [PATCH] Allow for custom plugins to be supplied to the main invocation of protoc. 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 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 org.xolstice.maven.plugins protobuf-maven-plugin 0.7.0-SNAPSHOT com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java io.grpc protoc-gen-grpc-java ${grpc.version} exe ${os.detected.classifier} kotlin com.github.marcoferrer.krotoplus protoc-gen-kroto-plus ${kroto-plus.version} jvm8 compile 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. --- .../protobuf/AbstractProtocCompileMojo.java | 1 + .../plugin/protobuf/AbstractProtocMojo.java | 110 +++++++++++++----- .../maven/plugin/protobuf/Protoc.java | 17 +-- .../maven/plugin/protobuf/ProtocPlugin.java | 51 ++++++-- .../protobuf/ProtocPluginAssembler.java | 68 +++++------ 5 files changed, 160 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocCompileMojo.java b/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocCompileMojo.java index 8a21602f..1767c6e6 100644 --- a/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocCompileMojo.java +++ b/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocCompileMojo.java @@ -62,4 +62,5 @@ protected List getDependencyArtifacts() { protected File getProtoSourceRoot() { return protoSourceRoot; } + } diff --git a/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocMojo.java b/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocMojo.java index 146f1d96..a48a5f70 100644 --- a/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocMojo.java +++ b/src/main/java/org/xolstice/maven/plugin/protobuf/AbstractProtocMojo.java @@ -428,18 +428,23 @@ public void execute() throws MojoExecutionException, MojoFailureException { } else { final List 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) { @@ -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(), @@ -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()); + } + } } @@ -956,22 +995,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); @@ -992,14 +1031,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 @@ -1026,6 +1061,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. diff --git a/src/main/java/org/xolstice/maven/plugin/protobuf/Protoc.java b/src/main/java/org/xolstice/maven/plugin/protobuf/Protoc.java index 1823e335..17ace693 100644 --- a/src/main/java/org/xolstice/maven/plugin/protobuf/Protoc.java +++ b/src/main/java/org/xolstice/maven/plugin/protobuf/Protoc.java @@ -257,13 +257,6 @@ public List 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); @@ -294,6 +287,16 @@ public List 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()); } diff --git a/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPlugin.java b/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPlugin.java index fab66814..31e32c2a 100644 --- a/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPlugin.java +++ b/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPlugin.java @@ -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; @@ -48,10 +49,14 @@ 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 @@ -59,6 +64,11 @@ public class ProtocPlugin { // Windows where we need to pick the right version of the WinRun4J executable. private String winJvmDataModel; + @Parameter( + required = false + ) + private File outputDirectory; + private List args; private List jvmArgs; @@ -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. * @@ -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. * @@ -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. * @@ -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(); + } } } @@ -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 + diff --git a/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPluginAssembler.java b/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPluginAssembler.java index aded96db..193e4eb3 100644 --- a/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPluginAssembler.java +++ b/src/main/java/org/xolstice/maven/plugin/protobuf/ProtocPluginAssembler.java @@ -66,12 +66,8 @@ public class ProtocPluginAssembler { private final List remoteRepositories; - private final File pluginDirectory; - private final List resolvedJars = new ArrayList<>(); - private final File pluginExecutableFile; - private final Log log; public ProtocPluginAssembler( @@ -83,7 +79,6 @@ public ProtocPluginAssembler( final ResolutionErrorHandler resolutionErrorHandler, final ArtifactRepository localRepository, final List remoteRepositories, - final File pluginDirectory, final Log log) { this.pluginDefinition = pluginDefinition; this.session = session; @@ -93,34 +88,35 @@ public ProtocPluginAssembler( this.resolutionErrorHandler = resolutionErrorHandler; this.localRepository = localRepository; this.remoteRepositories = remoteRepositories; - this.pluginDirectory = pluginDirectory; - this.pluginExecutableFile = pluginDefinition.getPluginExecutableFile(pluginDirectory); this.log = log; } /** * Resolves the plugin's dependencies to the local Maven repository and builds the plugin executable. */ - public void execute() { + public void execute(File destinationFile) { pluginDefinition.validate(log); if (log.isDebugEnabled()) { log.debug("plugin definition: " + pluginDefinition); } + if(!"jar".equalsIgnoreCase(pluginDefinition.getType())) { + throw new MojoInitializationException("Cannot create a wrapper executable for a plugin of type " + pluginDefinition.getType()); + } + if(pluginDefinition.getMainClass() == null) { + throw new MojoInitializationException("Wrapped java plugins must specify a main class."); + } resolvePluginDependencies(); if (Os.isFamily(Os.FAMILY_WINDOWS)) { - buildWindowsPlugin(); - copyWinRun4JExecutable(); + buildWindowsPlugin(destinationFile); } else { - buildUnixPlugin(); - pluginExecutableFile.setExecutable(true); + buildUnixPlugin(destinationFile); } } - private void buildWindowsPlugin() { - createPluginDirectory(); + private void buildWindowsPlugin(File destinationFile) { // Try to locate jvm.dll based on pluginDefinition's javaHome property final File javaHome = new File(pluginDefinition.getJavaHome()); @@ -129,7 +125,7 @@ private void buildWindowsPlugin() { "bin/server/jvm.dll", "jre/bin/client/jvm.dll", "bin/client/jvm.dll"); - final File winRun4JIniFile = new File(pluginDirectory, pluginDefinition.getPluginName() + ".ini"); + final File winRun4JIniFile = new File(destinationFile.getParentFile(), pluginDefinition.getPluginName() + ".ini"); if (log.isDebugEnabled()) { log.debug("javaHome=" + javaHome.getAbsolutePath()); @@ -171,43 +167,29 @@ private void buildWindowsPlugin() { throw new MojoInitializationException( "Could not write WinRun4J ini file: " + winRun4JIniFile.getAbsolutePath(), e); } - } - - private static File findJvmLocation(final File javaHome, final String... paths) { - for (final String path : paths) { - final File jvmLocation = new File(javaHome, path); - if (jvmLocation.isFile()) { - return jvmLocation; - } - } - return null; - } - private void copyWinRun4JExecutable() { final String executablePath = getWinrun4jExecutablePath(); final URL url = Thread.currentThread().getContextClassLoader().getResource(executablePath); if (url == null) { throw new MojoInitializationException( - "Could not locate WinRun4J executable at path: " + executablePath); + "Could not locate WinRun4J executable at path: " + executablePath); } try { - FileUtils.copyURLToFile(url, pluginExecutableFile); + FileUtils.copyURLToFile(url, destinationFile); } catch (IOException e) { throw new MojoInitializationException( - "Could not copy WinRun4J executable to: " + pluginExecutableFile.getAbsolutePath(), e); + "Could not copy WinRun4J executable to: " + destinationFile.getAbsolutePath(), e); } } - private void buildUnixPlugin() { - createPluginDirectory(); - + private void buildUnixPlugin(File destinationFile) { final File javaLocation = new File(pluginDefinition.getJavaHome(), "bin/java"); if (log.isDebugEnabled()) { log.debug("javaLocation=" + javaLocation.getAbsolutePath()); } - try (final PrintWriter out = new PrintWriter(new FileWriter(pluginExecutableFile))) { + try (final PrintWriter out = new PrintWriter(new FileWriter(destinationFile))) { out.println("#!/bin/sh"); out.println(); out.print("CP="); @@ -230,21 +212,23 @@ private void buildUnixPlugin() { out.println("\""); out.println(); out.println("\"" + javaLocation.getAbsolutePath() + "\" $JVMARGS -cp $CP " - + pluginDefinition.getMainClass() + " $ARGS"); + + pluginDefinition.getMainClass() + " $ARGS"); out.println(); } catch (IOException e) { - throw new MojoInitializationException("Could not write plugin script file: " + pluginExecutableFile, e); + throw new MojoInitializationException("Could not write plugin script file: " + destinationFile, e); } + destinationFile.setExecutable(true); } - private void createPluginDirectory() { - pluginDirectory.mkdirs(); - if (!pluginDirectory.isDirectory()) { - throw new MojoInitializationException("Could not create protoc plugin directory: " - + pluginDirectory.getAbsolutePath()); + private static File findJvmLocation(final File javaHome, final String... paths) { + for (final String path : paths) { + final File jvmLocation = new File(javaHome, path); + if (jvmLocation.isFile()) { + return jvmLocation; + } } + return null; } - private void resolvePluginDependencies() { final VersionRange versionSpec;