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

6

15

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.

Shipmaster answered 27/1, 2010 at 13:27 Comment(0)
S
9

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.

Shiah answered 27/1, 2010 at 13:41 Comment(2)
Two projects it is then. Thanks.Shipmaster
Go with Andreas' second suggestion. Split the generation code into another project, tie them together with a third, parent project. This might seem like overkill at first, but, trust me, the solution that KLE is really overkill. Creating a strange dependency (using sourceDirectory) between a parent and child project, and trying to shoehorn a POM project to create a JAR is the definition of overkill.Murrelet
A
9

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>
Aerodontia answered 3/4, 2017 at 14:47 Comment(0)
A
3

In order to do this in one project, there are 3 steps:

  1. Compile generator code

    We can do it in generate-sources phase, using maven-compiler-plugin. You can also exclude other source files.

  2. Run generator to generate code

    We can do it in process-sources phase, using exec-maven-plugin.

  3. 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>
Avid answered 1/11, 2019 at 13:39 Comment(1)
It is not working. Phase process-sources is simply ignored and not running at all. Do I need to do something special here?Hoodoo
M
1

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).
Mcadoo answered 27/1, 2010 at 13:42 Comment(3)
This answer violates many core assumptions of Maven. Implementing the solution this way will give you a Maven project that is unsupportable. Adding a source directory that "tunnels" out into the target directory of a parent project is a recipe for disaster. For starters, your parent project has packaging "pom". There is no compilation in a "POM" project, so you have to manually configure the compile plugin goal in a parent project. You are creating a dependency between two projects (parent-child) that isn't defined using a real dependency. You are making an end-run around Maven.Murrelet
@tobrien You are right about compiling in the parent, I forgot this. That was the less intrusive solution we found, and no disaster happened, contrary to what happened for several other configurations we tried. We are open for better ideas though, as you must know maven better than we do.Mcadoo
@tobrien Actually, our parent project has no code to compile, it only triggers generations through wsdl2java and others... That's why it has POM type.Mcadoo
D
1

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.

Demount answered 13/11, 2017 at 9:56 Comment(0)
K
1

Following is a minimal and well-commented pom.xml that will allow code generation within the same project, while making sure to clean-up properly afterwards:

    <build>
        <plugins>
            <!-- Compile XyzGenerator.java to XyzGenerator.class -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <executions>
                    <execution>
                        <id>code-generator</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <includes>
                                <include>com/project/codegen/XyzGenerator.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Execute XyzGenerator.class to generate Xyz.Java -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <id>code-generator</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>java</goal>
                        </goals>
                        <configuration>
                            <classpathScope>compile</classpathScope>
                            <mainClass>com.project.codegen.CodeGenerator</mainClass>
                            <commandlineArgs>target/codegen/com/project/</commandlineArgs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Include the path to Xyz.java for compilation -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.5.0</version>
                <executions>
                    <execution>
                        <id>code-generator</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>target/codegen</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Package the application in a JAR file -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <executions>
                    <execution>
                        <id>default-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <configuration>
                            <excludes>
                                <!-- Exclude the XyzGenerator.class file from the JAR -->
                                <exclude>com/project/codegen/*.*</exclude>
                                <exclude>com/project/codegen</exclude>
                            </excludes>
                            <archive>
                                <manifest>
                                    <!-- Add the name of main class -->
                                    <addClasspath>true</addClasspath>
                                    <mainClass>com.project.Main</mainClass>
                                </manifest>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

The main function in CodeGenerator class inside XyzGenerator.java file takes the output directory as the first command line argument; above, this is passed in the <commandlineArgs> tag.

The file structure is:

src
'- main
   '- java
      '- com
         '- project
            '- Main.class
            '- codegen
               '- XyzGenerator.java
target
'- codegen
   '- com
      '- project
Kalmar answered 30/12, 2023 at 13:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.