If distribution of {@code .proto} source files is undesirable for security reasons
- * or because of other considerations, then this parameter should be set to {@code false}.
- *
- * @since 0.4.1
- */
- @Parameter(
- required = true,
- defaultValue = "true"
- )
- protected boolean attachProtoSources;
-
- /**
- * The descriptor set file name. Only used if {@code writeDescriptorSet} is set to {@code true}.
- *
- * @since 0.3.0
- */
- @Parameter(
- required = true,
- defaultValue = "${project.build.finalName}.protobin"
- )
- protected String descriptorSetFileName;
-
- /**
- * If set to {@code true}, the compiler will generate a binary descriptor set file for the
- * specified {@code .proto} files.
- *
- * @since 0.3.0
- */
- @Parameter(
- required = true,
- defaultValue = "false"
- )
- protected boolean writeDescriptorSet;
-
- /**
- * If set to {@code true}, the generated descriptor set will be attached to the build.
- *
- * @since 0.4.1
- */
- @Parameter(
- required = true,
- defaultValue = "false"
- )
- protected boolean attachDescriptorSet;
-
- /**
- * If {@code true} and {@code writeDescriptorSet} has been set, the compiler will include
- * all dependencies in the descriptor set making it "self-contained".
- *
- * @since 0.3.0
- */
- @Parameter(
- required = false,
- defaultValue = "false"
- )
- protected boolean includeDependenciesInDescriptorSet;
-
- /**
- * If {@code true} and {@code writeDescriptorSet} has been set, do not strip SourceCodeInfo
- * from the FileDescriptorProto. This results in vastly larger descriptors that include information
- * about the original location of each decl in the source file as well as surrounding comments.
- *
- * @since 0.4.4
- */
- @Parameter(
- required = false,
- defaultValue = "false"
- )
- protected boolean includeSourceInfoInDescriptorSet;
-
- /**
- * Specifies one of more custom protoc plugins, written in Java
- * and available as Maven artifacts. An executable plugin will be created
- * at execution time. On UNIX the executable is a shell script and on
- * Windows it is a WinRun4J .exe and .ini.
- */
- @Parameter(
- required = false
- )
- private List protocPlugins;
-
- /**
- * Sets the granularity in milliseconds of the last modification date
- * for testing whether source protobuf definitions need recompilation.
- *
- * This parameter is only used when {@link #checkStaleness} parameter is set to {@code true}.
- *
- *
If the project is built on NFS it's recommended to set this parameter to {@code 10000}.
- */
- @Parameter(
- required = false,
- defaultValue = "0"
- )
- private long staleMillis;
-
- /**
- * Normally {@code protoc} is invoked on every execution of the plugin.
- * Setting this parameter to {@code true} will enable checking
- * timestamps of source protobuf definitions vs. generated sources.
- *
- * @see #staleMillis
- */
- @Parameter(
- required = false,
- defaultValue = "false"
- )
- private boolean checkStaleness;
-
- /**
- * When {@code true}, skip the execution.
- *
- * @since 0.2.0
- */
- @Parameter(
- required = false,
- property = "protoc.skip",
- defaultValue = "false"
- )
- private boolean skip;
-
- /**
- * Usually most of protobuf mojos will not get executed on parent poms
- * (i.e. projects with packaging type 'pom').
- * Setting this parameter to {@code true} will force
- * the execution of this mojo, even if it would usually get skipped in this case.
- *
- * @since 0.2.0
- */
- @Parameter(
- required = false,
- property = "protoc.force",
- defaultValue = "false"
- )
- private boolean forceMojoExecution;
-
- /**
- * When {@code true}, the output directory will be cleared out prior to code generation.
- * With the latest versions of protoc (2.5.0 or later) this is generally not required,
- * although some earlier versions reportedly had issues with running
- * two code generations in a row without clearing out the output directory in between.
- *
- * @since 0.4.0
- */
- @Parameter(
- required = false,
- defaultValue = "true"
- )
- private boolean clearOutputDirectory;
-
- /**
- * Executes the mojo.
- */
- @Override
- public void execute() throws MojoExecutionException, MojoFailureException {
-
- if (skipMojo()) {
- return;
- }
-
- checkParameters();
- final File protoSourceRoot = getProtoSourceRoot();
- if (protoSourceRoot.exists()) {
- try {
- final ImmutableSet protoFiles = findProtoFilesInDirectory(protoSourceRoot);
- final File outputDirectory = getOutputDirectory();
- final ImmutableSet outputFiles = findGeneratedFilesInDirectory(getOutputDirectory());
-
- if (protoFiles.isEmpty()) {
- getLog().info("No proto files to compile.");
- } else if (!hasDelta(protoFiles)) {
- getLog().info("Skipping compilation because build context has no changes.");
- doAttachFiles();
- } else if (checkStaleness && checkFilesUpToDate(protoFiles, outputFiles)) {
- getLog().info("Skipping compilation because target directory newer than sources.");
- doAttachFiles();
- } else {
- final ImmutableSet derivedProtoPathElements =
- makeProtoPathFromJars(temporaryProtoFileDirectory, getDependencyArtifactFiles());
- FileUtils.mkdir(outputDirectory.getAbsolutePath());
-
- if (clearOutputDirectory) {
- cleanDirectory(outputDirectory);
- }
-
- if (writeDescriptorSet) {
- final File descriptorSetOutputDirectory = getDescriptorSetOutputDirectory();
- FileUtils.mkdir(descriptorSetOutputDirectory.getAbsolutePath());
- if (clearOutputDirectory) {
- cleanDirectory(descriptorSetOutputDirectory);
- }
- }
-
- if (protocPlugins != null) {
- createProtocPlugins();
- }
-
- //get toolchain from context
- final Toolchain tc = toolchainManager.getToolchainFromBuildContext("protobuf", session); //NOI18N
- if (tc != null) {
- getLog().info("Toolchain in protoc-plugin: " + tc);
- //when the executable to use is explicitly set by user in mojo's parameter, ignore toolchains.
- if (protocExecutable != null) {
- getLog().warn(
- "Toolchains are ignored, 'protocExecutable' parameter is set to " + protocExecutable);
- } else {
- //assign the path to executable from toolchains
- protocExecutable = tc.findTool("protoc"); //NOI18N
- }
- }
- if (protocExecutable == null && protocArtifact != null) {
- final Artifact artifact = createDependencyArtifact(protocArtifact);
- final File file = resolveBinaryArtifact(artifact);
- protocExecutable = file.getAbsolutePath();
- }
- if (protocExecutable == null) {
- // Try to fall back to 'protoc' in $PATH
- getLog().warn("No 'protocExecutable' parameter is configured, using the default: 'protoc'");
- protocExecutable = "protoc";
- }
-
- final Protoc.Builder protocBuilder =
- new Protoc.Builder(protocExecutable)
- .addProtoPathElement(protoSourceRoot)
- .addProtoPathElements(derivedProtoPathElements)
- .addProtoPathElements(asList(additionalProtoPathElements))
- .addProtoFiles(protoFiles);
- addProtocBuilderParameters(protocBuilder);
- final Protoc protoc = protocBuilder.build();
-
- if (getLog().isDebugEnabled()) {
- getLog().debug("Proto source root:");
- getLog().debug(" " + protoSourceRoot);
-
- if (derivedProtoPathElements != null && !derivedProtoPathElements.isEmpty()) {
- getLog().debug("Derived proto paths:");
- for (final File path : derivedProtoPathElements) {
- getLog().debug(" " + path);
- }
- }
-
- if (additionalProtoPathElements != null && additionalProtoPathElements.length > 0) {
- getLog().debug("Additional proto paths:");
- for (final File path : additionalProtoPathElements) {
- getLog().debug(" " + path);
- }
- }
- }
- protoc.logExecutionParameters(getLog());
-
- getLog().info(format("Compiling %d proto file(s) to %s", protoFiles.size(), outputDirectory));
-
- final int exitStatus = protoc.execute();
- if (StringUtils.isNotBlank(protoc.getOutput())) {
- getLog().info("PROTOC: " + protoc.getOutput());
- }
- if (exitStatus != 0) {
- getLog().error("PROTOC FAILED: " + protoc.getError());
- for (File pf : protoFiles) {
- buildContext.removeMessages(pf);
- buildContext.addMessage(pf, 0, 0, protoc.getError(), BuildContext.SEVERITY_ERROR, null);
- }
- throw new MojoFailureException(
- "protoc did not exit cleanly. Review output for more information.");
- } else if (StringUtils.isNotBlank(protoc.getError())) {
- getLog().warn("PROTOC: " + protoc.getError());
- }
- doAttachFiles();
- }
- } catch (IOException e) {
- throw new MojoExecutionException("An IO error occured", e);
- } catch (IllegalArgumentException e) {
- throw new MojoFailureException("protoc failed to execute because: " + e.getMessage(), e);
- } catch (CommandLineException e) {
- throw new MojoExecutionException("An error occurred while invoking protoc.", e);
- }
- } else {
- getLog().info(format("%s does not exist. Review the configuration or consider disabling the plugin.",
- protoSourceRoot));
- }
- }
-
- /**
- * Generates native launchers for java protoc plugins.
- * These launchers will later be added as parameters for protoc compiler.
- *
- * @throws MojoExecutionException if plugins launchers could not be created.
- *
- * @since 0.3.0
- */
- protected void createProtocPlugins() throws MojoExecutionException {
- final String javaHome = detectJavaHome();
-
- for (final ProtocPlugin plugin : protocPlugins) {
-
- 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(),
- artifactFactory,
- repositorySystem,
- resolutionErrorHandler,
- localRepository,
- remoteRepositories,
- protocPluginDirectory,
- getLog());
- assembler.execute();
- }
- }
-
- /**
- * Attempts to detect java home directory, using {@code jdk} toolchain if available,
- * with a fallback to {@code java.home} system property.
- *
- * @return path to java home directory.
- *
- * @since 0.3.0
- */
- protected String detectJavaHome() {
- String javaHome = null;
-
- final Toolchain tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
- if (tc != null) {
- if (tc instanceof DefaultJavaToolChain) {
- javaHome = ((DefaultJavaToolChain) tc).getJavaHome();
- if (javaHome != null) {
- getLog().debug("Using javaHome from toolchain: " + javaHome);
- }
- } else {
- // Try to infer JAVA_HOME from location of 'java' tool in toolchain, if available.
- // We don't use 'java' directly because for Windows we need to find the path to
- // jvm.dll instead, which the assembler tries to figure out relative to JAVA_HOME.
- final String javaExecutable = tc.findTool("java");
- if (javaExecutable != null) {
- File parent = new File(javaExecutable).getParentFile();
- if (parent != null) {
- parent = parent.getParentFile();
- if (parent != null && parent.isDirectory()) {
- javaHome = parent.getAbsolutePath();
- getLog().debug(
- "Using javaHome based on 'java' location returned by toolchain: " + javaHome);
- }
- }
- }
- }
- }
- if (javaHome == null) {
- // Default location is the current JVM's JAVA_HOME.
- javaHome = System.getProperty("java.home");
- getLog().debug("Using javaHome from java.home system property: " + javaHome);
- }
- return javaHome;
- }
-
- /**
- * Adds mojo-specific parameters to the protoc builder.
- *
- * @param protocBuilder the builder to be modified.
- * @throws MojoExecutionException if parameters cannot be resolved or configured.
- */
- protected void addProtocBuilderParameters(final Protoc.Builder protocBuilder) throws MojoExecutionException {
- if (protocPlugins != null) {
- for (final ProtocPlugin plugin : protocPlugins) {
- protocBuilder.addPlugin(plugin);
- }
- protocPluginDirectory.mkdirs();
- protocBuilder.setPluginDirectory(protocPluginDirectory);
- }
- if (writeDescriptorSet) {
- final File descriptorSetFile = new File(getDescriptorSetOutputDirectory(), descriptorSetFileName);
- getLog().info("Will write descriptor set:");
- getLog().info(" " + descriptorSetFile.getAbsolutePath());
- protocBuilder.withDescriptorSetFile(
- descriptorSetFile,
- includeDependenciesInDescriptorSet,
- includeSourceInfoInDescriptorSet);
- }
- }
-
- /**
- * Determine if the mojo execution should get skipped.
- * This is the case if:
- *
- * - {@link #skip} is
true
- * - if the mojo gets executed on a project with packaging type 'pom' and
- * {@link #forceMojoExecution} is
false
- *
- *
- * @return true
if the mojo execution should be skipped.
- *
- * @since 0.2.0
- */
- protected boolean skipMojo() {
- if (skip) {
- getLog().info("Skipping protoc mojo execution");
- return true;
- }
-
- if (!forceMojoExecution && "pom".equals(this.project.getPackaging())) {
- getLog().info("Skipping protoc mojo execution for project with packaging type 'pom'");
- return true;
- }
-
- return false;
- }
-
- protected static ImmutableSet findGeneratedFilesInDirectory(final File directory) throws IOException {
- if (directory == null || !directory.isDirectory()) {
- return ImmutableSet.of();
- }
-
- final List generatedFilesInDirectory = getFiles(directory, "**/*", getDefaultExcludesAsString());
- return ImmutableSet.copyOf(generatedFilesInDirectory);
- }
-
- /**
- * Returns timestamp for the most recently modified file in the given set.
- *
- * @param files a set of file descriptors.
- * @return timestamp of the most recently modified file.
- */
- protected static long lastModified(final ImmutableSet files) {
- long result = 0;
- for (final File file : files) {
- result = max(result, file.lastModified());
- }
- return result;
- }
-
- /**
- * Checks that the source files don't have modification time that is later than the target files.
- *
- * @param sourceFiles a set of source files.
- * @param targetFiles a set of target files.
- * @return {@code true}, if source files are not later than the target files; {@code false}, otherwise.
- */
- protected boolean checkFilesUpToDate(final ImmutableSet sourceFiles, final ImmutableSet targetFiles) {
- return lastModified(sourceFiles) + staleMillis < lastModified(targetFiles);
- }
-
- /**
- * Checks if the injected build context has changes in any of the specified files.
- *
- * @param files files to be checked for changes.
- * @return {@code true}, if at least one file has changes; {@code false}, if no files have changes.
- *
- * @since 0.3.0
- */
- protected boolean hasDelta(final ImmutableSet files) {
- for (final File file : files) {
- if (buildContext.hasDelta(file)) {
- return true;
- }
- }
- return false;
- }
-
- protected void checkParameters() {
- checkNotNull(project, "project");
- checkNotNull(projectHelper, "projectHelper");
- final File protoSourceRoot = getProtoSourceRoot();
- checkNotNull(protoSourceRoot);
- checkArgument(!protoSourceRoot.isFile(), "protoSourceRoot is a file, not a directory");
- checkNotNull(temporaryProtoFileDirectory, "temporaryProtoFileDirectory");
- checkState(!temporaryProtoFileDirectory.isFile(), "temporaryProtoFileDirectory is a file, not a directory");
- final File outputDirectory = getOutputDirectory();
- checkNotNull(outputDirectory);
- checkState(!outputDirectory.isFile(), "the outputDirectory is a file, not a directory");
- }
-
- protected abstract File getProtoSourceRoot();
-
- protected Set getIncludes() {
- return includes;
- }
-
- protected Set getExcludes() {
- return excludes;
- }
-
- // TODO add artifact filtering (inclusions and exclusions)
- // TODO add filtering for proto definitions in included artifacts
- protected abstract List getDependencyArtifacts();
-
- /**
- * Returns the output directory for generated sources. Depends on build phase so must
- * be defined in concrete implementation.
- *
- * @return output directory for generated sources.
- */
- protected abstract File getOutputDirectory();
-
- /**
- * Returns output directory for descriptor set file. Depends on build phase so must
- * be defined in concrete implementation.
- *
- * @return output directory for generated descriptor set.
- *
- * @since 0.3.0
- */
- protected abstract File getDescriptorSetOutputDirectory();
-
- protected void doAttachFiles() {
- if (attachProtoSources) {
- doAttachProtoSources();
- }
- doAttachGeneratedFiles();
- }
-
- protected abstract void doAttachProtoSources();
-
- protected abstract void doAttachGeneratedFiles();
-
- /**
- * Gets the {@link File} for each dependency artifact.
- *
- * @return A set of all dependency artifacts.
- */
- protected ImmutableSet getDependencyArtifactFiles() {
- final Set dependencyArtifactFiles = new LinkedHashSet();
- for (final Artifact artifact : getDependencyArtifacts()) {
- dependencyArtifactFiles.add(artifact.getFile());
- }
- return ImmutableSet.copyOf(dependencyArtifactFiles);
- }
-
- /**
- * Unpacks proto descriptors that are bundled inside dependent artifacts into a temporary directory.
- * This is needed because protobuf compiler cannot handle imported descriptors that are packed inside jar files.
- *
- * @param temporaryProtoFileDirectory temporary directory to serve as root for unpacked structure.
- * @param classpathElementFiles classpath elements, can be either jar files or directories.
- * @throws IOException if one of the file operations fails.
- * @throws MojoExecutionException if an internal error happens.
- * @return a set of import roots for protobuf compiler
- * (these will all be subdirectories of the temporary directory).
- */
- protected ImmutableSet makeProtoPathFromJars(
- final File temporaryProtoFileDirectory,
- final Iterable classpathElementFiles)
- throws IOException, MojoExecutionException {
- checkNotNull(classpathElementFiles, "classpathElementFiles");
- if (!classpathElementFiles.iterator().hasNext()) {
- return ImmutableSet.of(); // Return an empty set
- }
- // clean the temporary directory to ensure that stale files aren't used
- if (temporaryProtoFileDirectory.exists()) {
- cleanDirectory(temporaryProtoFileDirectory);
- }
- final Set protoDirectories = new LinkedHashSet