Manually attach main artifact if built by maven-assembly-plugin - java

I am having problems building a maven project. I have a requirement to produce deterministic jar files, which must be binary-consistent across different builds and versions, in case there are no source code changes in between these builds. For the purpose, I have used this article for guidance.
I have successfully managed to build my jars and they are consistent up to my requirements. Here is my configuration:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>step-1-remove-timestamp</id>
<phase>prepare-package</phase>
<configuration>
<target>
<touch datetime="01/01/2015 00:10:00 am">
<fileset dir="target/classes"/>
<fileset dir="src"/>
</touch>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
<execution>
<id>step-3-rename-assembly</id>
<phase>package</phase>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}-deterministic.zip"
tofile="${project.build.directory}/${project.build.finalName}.jar"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/zip.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>step-2-make-assembly</id>
<phase>prepare-package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
In the above code I build and package the jar as a zip, then copy the zip over the expected jar artifact.
The problem with the above, is that maven still executes the maven-jar-plugin, so the manually assembled jar is overwritten by the one of the maven-jar-plugin. I do not want to use this jar, since it is not consistent with my requirements.
So, I have disabled the maven-jar-plugin execution, by explicitly setting it to run for an invalid phase, like below: (saw that from other posts here)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-jar</id>
<phase>never</phase>
<configuration>
<finalName>unwanted</finalName>
<classifier>unwanted</classifier>
</configuration>
</execution>
</executions>
</plugin>
Everything seems fine, until I discovered my main jar artifact is never installed in the .m2 directory. To correct this, I also added the maven-helper-plugin so that I manually attach any artifacts I produce from the assembler plugin:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>attach-instrumented-jar</id>
<phase>verify</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${project.build.directory}/${project.build.finalName}.jar</file>
<type>jar</type>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
This leads to an error I am unable to solve:
[ERROR] Failed to execute goal org.codehaus.mojo:build-helper-maven-plugin:1.9.1:attach-artifact (attach-instrumented-jar) on project my-project: Execution attach-instrumented-jar of goal org.codehaus.mojo:build-helper-maven-plugin:1.9.1:attach-artifact failed: For artifact {full-name-of-my-project.jar}: An attached artifact must have a different ID than its corresponding main artifact. -> [Help 1]
Is there a way to overcome this issue? I've checked for solutions, and most recommend using classifiers, but I want to install the main artifact as would the maven-jar-plugin. Other software we are developing will require the standard jar dependency, and we want to avoid complicating our setup by introducing classifiers unreasonably.

After some more trial and failures I happened to come with a working solution.
I am posting it here with the hopes to either be useful, or to have any issues with pointed to me, as I am not really confident if this is a reliable approach.
So, the error I received
An attached artifact must have a different ID than its corresponding main artifact.
meant to me that I cannot manually install "again" the main artifact. Since that artifact is not produced anymore by the maven-jar-plugin, it never gets scheduled for installation even if the file is present (the antrun copy task produces a jar with the same name).
Surprisingly, it needed a few little tricks to make this work again:
Re-enabled the maven-jar-plugin as it should be:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
</execution>
</executions>
</plugin>
This will produce the standard jar on the package phase, and most importantly, makes maven aware for it to be installed during the install phase.
Tweak the maven-antrun-plugin copy tasks to overwrite the already produced jar with the deterministic zip. The setup is nearly identical as in my question, so I am only adding the differences:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>...</execution>
<execution>
<id>step-3-rename-assembly-and-sources</id>
<phase>package</phase>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}-deterministic.zip"
tofile="${project.build.directory}/${project.build.finalName}.jar"
overwrite="true"/>
</target>
</configuration>
</execution>
. . .
</executions>
</plugin>
The copy operation now has overwrite="true" specified. Originally, the copy operation seemed to ignore files in the destination if they already exist, and what happened is that the maven-jar-plugin had already produced the default jar artifact when the copying occured. With this option set, the maven-antrun-plugin now overrides the former jar with the deterministic one, and the latter becomes a subject of the maven install phase.
Removed the setup from the build-helper-maven-plugin, so that the main jar artifact is not copied a second time:
<artifact>
<file>${project.build.directory}/${project.build.finalName}.jar</file>
<type>jar</type>
</artifact>
That's it, the correct jar is installed in the .m2 dir.

In case you don't have a plugin to create and attach main artifact for you, there is a more generic solution with Groovy Maven plugin:
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<id>set-main-artifact</id>
<phase>package</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
project.artifact.setFile(new File(project.build.directory, project.build.finalName + ".zip"))
</source>
</configuration>
</execution>
</executions>
</plugin>
Inspired by post https://stackoverflow.com/a/31513690/2053580 many thanks to https://stackoverflow.com/users/1314907/lukasz-guminski

Related

why do I not find generated source files for grpc and protobuf when using maven and java in vscode

I have a pom file that correctly generates the grpc and protobuf source files I need in target/generated-sources when run from the command line. But when I build in vscode those directories are empty and references to the protobufs are undefined. Here's the section of my pom file that builds the grpc source.
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
I added the following based on what I read in other posts to prevent vscode/eclipse from removing the generated source directories
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources/protobuf/grpc-java</source>
<source>/target/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
When vs-code builds, the target generated-source directories are there, but there is no source in them. We had a similar problem with intellij but were able to work around it by running the maven command line build before opening intellij but that does not seem to work for vscode.
Use protoc-jar-maven-plugin instead.
Sample usage please view protoc-jar-maven-plugin.

How to review file names format with maven

I am trying to find a way to review the files generated by running mvn install, my task basically is, review if the total characters in the name are correct, if at least one file name is not complying, I should make certain task.
Is this possible in a pom with maven?
you can use dependency:analyze for that,
[https://maven.apache.org/plugins/maven-dependency-plugin/analyze-mojo.html]
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id> <!-- notice the execution id "analyze" -->
<goals>
<goal>analyze</goal>
</goals>
<configuration>
<ignoreNonCompile>true</ignoreNonCompile>
</configuration>
</execution>
</executions>

Maven and JavaDoc: install additional (generated) files

My project can generate some additional help files automatically from the source code.
How do I have maven install these files into the generated JavaDoc package?
I couldn't fingure out how to have Maven:
run some class to generate the additional documentation, e.g. compile and launch src/main/java/mypackage/ListOptions.java aka mypackage.ListOptions to produce a list of all available options/modules/examples/etc. .
install the output files (I only could get Maven to install files from src/main/javadoc/resources into the site/apidocs/resources subfolder; but I want this documentation to live in the top level site/apidocs folder; more precisely I don't care about the site part at all, but about having a complete documetation in mypackage-0.1.0-SNAPSHOT-javadoc.jar; including non-Javadoc-generated auxillary information linked from javadoc).
Minimal example - Plugin configuration for maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<docfilessubdirs>true</docfilessubdirs>
</configuration>
</plugin>
Directory layout:
./pom.xml
./src/main/java/foobar/GenerateNonstatic.java
./src/main/javadoc/staticfile.js
./src/main/javadoc/resources/insubfolder.png
mvn package produces: javadoc in ./target/apidocs and ./target/foobar-0.0.1-SNAPSHOT-javadoc.jar. The file target/apidocs/resources/insubfolder.png ends up in folder target/apidocs/resources (and the .jar), but the staticfile.js is not installed.
How to run GenerateNonstatic.java to have the output included in the javadoc .jar is unclear to me. I don't see a hook to have exec:exec it run after javadoc, and before ./target/foobar-0.0.1-SNAPSHOT-javadoc.jar is built...
Long story, short answer. Both the javadoc:javadoc and javadoc:jar mojos have their drawbacks. The first is meant to build the javadoc for reporting; the latter will build the javadoc (into a different directory) and produce a jar package.
Some of the suggested answers did not work well because of this - they would execute javadoc twice.
But I noticed that javadoc does not care if the folder already exists or contains files.
So my solution is simple: generate the desired additional files in prepare-package, and the regular javadoc:jar task adds the regular javadoc and pacakges it nicely.
This will work as long as you don't intend to modify files generated by javadoc (which I, fortunately, don't - I only add additional documentation.
The resulting pom.xml is like this:
<plugins>
<!-- copy additional javadoc resources -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/apidocs</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/javadoc</directory>
<excludes>
<!-- overview.html is integrated by javadoc -->
<exclude>${basedir}/src/main/javadoc/overview.html</exclude>
</excludes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Generate additional files for javadoc -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>generate-extra-javadoc</id>
<phase>prepare-package</phase>
<goals>
<!-- java is okay, but you only can have one -->
<goal>exec</goal>
</goals>
<configuration>...</configuration>
</execution>
</executions>
</plugin>
<!-- Build JavaDoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
Eric,
If you are uncomfortable modifying the javadoc executable, another option is to break apart your javadoc call into 2 separate steps (javadoc and jar) and make your call between them by manipulating the Maven build lifecycle via <phase> tag:
phase: generate-sources => maven-javadoc-plugin:javadoc
phase: compile => exec-maven-plugin:java
phase: package => maven-javadoc-plugin:jar
note: use exec:java, not exec:exec to run java classes
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>generate-javadocs</id>
<phase>generate-sources</phase>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>generate-nonstatic-javadocs</id>
<phase>compile</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>foobar.GenerateNonstatic</mainClass>
</configuration>
</execution>
</executions>
</plugin>
To answer your first question: Run a commandline command with the Exec-Maven-Plugin or use the Maven-Ant-Plugin and embed custom tasks.
Eric,
Your example is indeed helpful. Please do try to always include concrete examples as much as possible when asking the question.
The simplest approach would be to write your own Alternate Javadoc tool and pass that into Maven via the <javadocExecutable> tag. This example comes from the link above:
<project>
...
<reporting> (or <build>)
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<configuration>
<javadocExecutable>C:\jdk1.6.0\bin\javadoc.exe</javadocExecutable>
...
</configuration>
</plugin>
...
</plugins>
</reporting> (or </build>)
...
</project>
Hope that helps.

Maven custom packaging

I am using a library (RootBeer), which requires an additional build step: after creating my JAR, I have to run the RootBeer JAR with my JAR as its parameter to create the final RootBeer-enabled JAR.
E.g., if my jar is myjar.jar, this is how I have to create the final artefact myjar-final.jar with RootBeer:
java -jar rootbeer.jar myjar.jar myjar-final.jar
I would like to know if there is a mechanism in Maven which would enable me to build an artifact in this way.
Right now I'm using the gmaven-plugin with a Groovy script, but this just feels too hacky, and I'm pretty sure I couldn't use the resulting artefact as a Maven dependency in other projects:
<plugin>
<groupId>org.codehaus.groovy.maven</groupId>
<artifactId>gmaven-plugin</artifactId>
<executions>
<execution>
<id>groovy-magic</id>
<phase>package</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
println """java -jar target/rootbeer-1.2.0.jar target/myjar.jar target/myjar-final.jar"""
.execute().in.eachLine {
line -> println line
}
</source>
</configuration>
</execution>
</executions>
</plugin>
Any suggestions?
You can use the exec-maven-plugin to execute the final step what you have implemented into Groovy furthermore you need to added build-helper-maven-plugin to add the supplemental artifact to Maven to get it deployed with the rest of your artifacts.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- The main class of rootbeer.jar -->
<mainClass>org.trifort.rootbeer.entry.Main</mainClass>
<!-- by setting equal source and target jar names, the main artefact is
replaced with the one built in the final step, which is exactly what I need. -->
<arguments>
<argument>${project.build.directory}/${project.artifactId}.jar</argument>
<argument>${project.build.directory}/${project.artifactId}.jar</argument>
<argument>-nodoubles</argument>
</arguments>
</configuration>
</plugin>

Generating sources by running a project's java class in Maven

I'm converting a largish Ant build to Maven. As part of the Ant build, we have several steps which created Java classes by invoking one of the project's classes, simplified as:
javac SomeGenerator.java
java SomeGenerator generated # generate classes in generated/
javac generated/*.java
I've split each generator in its own Maven module, but I have the problem of not being able to run the generator since it's not yet compiled in the generate-sources phase.
I've tried something similar to
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<id>generate-model</id>
<goals>
<goal>java</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<mainClass>DTOGenerator</mainClass>
<arguments>
<argument>${model.generated.dir}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
Which sadly does not work, for the reasons outlined above. Splitting the code generators into two projects each, one for compiling the generator and another for generating the DTOs seems overkill.
What alternatives are there?
Using Maven 2.2.1.
You can execute the maven-compile-plugin in the generate-sources phase. Just add another execution before the existing execution and configure it so that it just picks up the sources for the generator.
Or split the project in two: build the generator with a separate POM and include the generator library as a dependency to the POM that's generating the sources.
Personally I would split the project. Keeps the build files cleaner and easier to maintain.
I didn't want to have 2 different projects, so I tried to setup Maven for adding the generated compiled code to the final jar package.
This is the working solution I've used:
In process-classes phase (executed just after the compile phase):
exec-maven-plugin for executing a main class able to generate my source files in target/generated-sources/java folder (in my specific case I used the Roaster library for source code generation);
build-helper-maven-plugin for adding the generated sources in the correct location
In prepare-package phase:
maven-compiler-plugin, in order to detect the changes and recompile the module
maven-jar-plugin for producing the jar package
This is my pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.example.MyClassWriter</mainClass>
<arguments>
<argument>${project.basedir}</argument>
<argument>${project.build.directory}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
In order to do this in one project, there are 3 steps:
Compile generator code
We can do it in generate-sources phase, using maven-compiler-plugin. You can also exclude other source files.
Run generator to generate code
We can do it in process-sources phase, using exec-maven-plugin.
Compile project
Below is the key part of pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<execution>
<id>compile-generator</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<includes>
<include>source/file/of/generator/*.java</include>
</includes>
<excludes>
<exclude>other/source/files/*.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>generate-codes</id>
<goals>
<goal>java</goal>
</goals>
<phase>process-sources</phase>
<configuration>
<mainClass>your.main.class.of.generator</mainClass>
</configuration>
</execution>
</executions>
</plugin>
We faced the same problem. We wanted to respect Maven's behavior as closely as possible, to have no problems with plugins and so on... Fighting Maven is just too expensive!
We realized that the update frequency of the generated code was usually very different from the one of the code that we manually write, so separating the code had very good performance characteristics for the build. So we accepted to have our generated classes as a dependency of the manually written.
We adopted the following structure, that had just one little change from a regular maven config, a change in the source directory.
Parent project : Generations
We created a parent project for all our generations.
It has a JAR type if it contains code to be compiled, otherwise POM.
There we have our generating code, in /src.
It can compile in /target as usual.
It runs the generation, each generator producing code in a sub-directory of /target.
Note: if you want several generated results in the same jar, just put them in the same sub-directory.
Child jar projects : Generateds
It is a subdirectory of the Generations project.
It has a JAR type.
The source directory points to the sub-directory in the parent's target.
<sourceDirectory>../target/generated1</sourceDirectory>
It compiles normally in its own /target directory.
That structure allows us to :
have as little modification to the standard maven layout as possible, so every maven command and plugin keeps working nicely.
scale nicely if you have several generators,
scale nicely if you want to generate several jars (we had a case wsdl2java where one generator produced code that should be split into several jars ; each child generated project would have the same source directory, but would be configured with an <includes> to handle only some of the classes).
I posted a minimal working setup here https://github.com/baloise/inlinesourcecodegenerator
It uses build-helper compiler and exec plugins and has all code in the same project.

Categories