Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maven: partial compilation before code generation

Tags:

java

maven

tl;dr-edition: I have a compilation I know that will fail, but want the subset of classes that are still compilable in my target/classes folder after compilation. I have configured <failOnError>false</failOnError>, but no classes are generated, not even a dummy class that is independent of any other classes except Object. Is there some configuration to achieve this?


I have a maven-powered project whose workflow consists of basically the following (relevant) goals:

  • ...
  • init-compile

    The code generator (below) uses a config that is reflection-based, so, in a first pass, I want to try compile as much of the project as possible so that no ClassNotFoundExceptions are thrown there. This compilation is configured with <failOnError>false</failOnError> so that the build continues.

    Unfortunately (you could call it a design flaw), the config is used both for code generation (specifying the OWL file and namespace to package mappings) and at runtime, so it also contains other elements that are not needed for the code generator, but are still read and therefore needed on the classpath to succeed.

  • generate-model

    In this step, some model classes are generated from an OWL-Ontology, creating the code that makes the rest of the project completely compileable.

  • default-compile

    Now, the rest of the classes should be compiled, obviously

  • save-model

    Now, the instances from the ontology are read and serialized to a file for runtime

  • ...

Side note: both generate and save model use maven-exec-plugin, but I sincerely don't think that matters at all.

Question:

When I run my build with mvn -e -U clean package source:jar javadoc:jar install:install, it fails during the generate-model goal with the errors I'm trying to avoid. target/classes is empty, so it seems that the compiler doesn't spit out the subset of classes it could/should have been able to process. Is there a way to achieve this?

I have two workarounds in mind which I both don't like:

  • Editing the config file "AST" before parsing it into Java-Objects, so that only the part relevant for the code generator is parsed (needs tweaking of code that I have access to but should be considered immutable by my project);
  • and to configure the init-compile goal to only include the needed classes (too inflexible, because the POM should/could be a template for future applications using the same model).

If you can imagine another way to work around my problem that you can see from my description, I would be glad to hear them, too!

like image 538
Silly Freak Avatar asked Oct 05 '11 18:10

Silly Freak


People also ask

Does maven do incremental builds?

4) Apache Maven doesn't support incremental builds very well. Because of that fact, most people use mvn clean compile instead of mvn compile for example. This is pretty time consuming and can be improved a lot. The goal is to make mvn compile , mvn verify , mvn install , ...

What is annotationProcessorPaths in Maven?

<annotationProcessorPaths>The detection itself depends on the configuration of annotationProcessors. Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier, type). Transitive dependencies are added automatically.

What does mvn clean do?

mvn clean: Cleans the project and removes all files generated by the previous build. mvn compile: Compiles source code of the project. mvn test-compile: Compiles the test source code.

What is testCompile in Maven?

Full name: org.apache.maven.plugins:maven-compiler-plugin:3.10.1:testCompile. Description: Compiles application test sources.


2 Answers

First, let me recapitulate your problem to be sure I've grasped it properly.

  • You have a set of classes whose function, in their compiled form, is to configure both the code generator and the runtime. (A subset of these are relevant to the code generator, but generation will fail unless the complete configuration is present. So we can treat it as though the entire configuration were necessary.)

  • You then have a set of classes which will be generated as source code. These have a generation-time, perhaps a compile-time, and a run-time dependency upon the configuration classes.

  • Finally you have some other code which has a compile-time dependency upon the generated classes, and a runtime dependency upon both the generated classes and the configuration classes.

  • However, your configuration classes don't have any compile-time dependencies upon the generated classes nor upon the other code. You don't explicitly say this, but I'm assuming it, otherwise you've got a circular dependency problem.

Here's my suggestion: Divide the project into a multi-module ("reactor") project. Your current project will be a module of the reactor project. Create a new module called "config" or similar and move your config classes into it. Have the main module depend upon it.

If you don't like multi-module projects, you could achieve the same thing by declaring an extra execution of the compile plugin, bound to the generate-sources phase. (You don't say, but I assume you're doing the code generation in this phase. If you declare the compile plugin before the code generator plugin within the POM, Maven will execute them in the same order.) You would use the "include" filter of the compile plugin to compile only the config classes. For this, you would need to have the config classes in a separate package from all the rest, which is good practice anyway.

like image 110
Andrew Spencer Avatar answered Oct 08 '22 03:10

Andrew Spencer


There is one highly convenient solution - use Eclipse Java Compiler (EJC) instead of standard Oracle javac! One of ECJ advantages over javac is that it is permissible of errors, it tries to compile as much as possible and keeps already generated class files. EJC was developed for use in IDE, for highly interactive work where partial compilation is a must, but it can also be used as CLI or Maven plugin. Plexus guys provide EJC as a handy Maven dependency.

Compilers are pluggable in Maven. You can have multiple compilation (compilation executions) defined in one POM and you can use different compilers for each giving the developer broad range of options.

Example POM code:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <executions>
        <execution>
            <id>pre-compilation</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <compilerId>eclipse</compilerId>
                <!-- IMPORTANT. Select EJC as compiler instead of javac -->
                <failOnError>false</failOnError>
                <!-- IMPORTANT. When ECJ is used errors are reported 
                only as warnings, it continues in compilation, 
                try to compile as much as possible, keeps already 
                generated classes in target/classes -->
            </configuration>
        </execution>
        <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <!-- in the end recompile everything with standard javac.
                 This time no compilation errors are expected or tolerated. -->
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <useIncrementalCompilation>false</useIncrementalCompilation>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.plexus</groupId>
            <artifactId>plexus-compiler-eclipse</artifactId>
            <version>2.3</version>
        </dependency>
    </dependencies>
</plugin>

<!-- generate sources. This plugin executes in facet BETWEEN the compilations due to 
    'generate-sources' phase binding and relative position in POM -->
<plugin>
    <groupId>org.eclipse.xtext</groupId>
    <artifactId>xtext-maven-plugin</artifactId>
    <version>2.5.0</version>
    <executions>
        <execution>
            <id>generate-the-stuff</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Credits to Gabriel Axel for his article http://www.gabiaxel.com/2011/10/replacing-javac-with-eclipse-compiler.html

This approach can solve some tricky "source to generated source to source" circular dependencies, potentially unsolvable by splitting into separate modules.

Also, I wanted to integrate source generation in the most transparent way. Having to reshuffle the code based on dependencies on generated sources would definitively break it. I want to group my code based on logical design not because of technicalities.

If you code generator happens to xtext, like in my case, and you use xtext-maven-plugin, let's say freshly released 2.5.0, you don't have to configure nothing like in the example above, the plugin does exactly that under the hood.

like image 27
Espinosa Avatar answered Oct 08 '22 03:10

Espinosa