David Tonhofer
David Tonhofer

Reputation: 15316

Building Antlr4's Java distribution fails because the test for the "antlr4 maven plugin" fails with a NullPointerException

I want to compile the ANTLR4 Java distribution using Maven. But it won't work.

I am probably going about this problem wrongly. There may be something very simple I am missing. Probably the way I'm calling Maven in the first place. The fact that absolutely no-one on the Internet seems to have this problem indicates as much.

What happens

First, my Java version:

$ java -version
openjdk version "17.0.3" 2022-04-19
OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7)
OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)

(But note that ANLTR4 is compiled to Java 11, except for the plugin which is compiled to Java 8, according to the POM)

So. Get the distribution from github:

$ git clone https://github.com/antlr/antlr4.git antlr4_30`
$ cd antlr4_30
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Compile the projects (Maven modules)

PROJECTS="runtime/Java,tool,antlr4-maven-plugin"
mvn --projects "$PROJECTS" clean
mvn --projects "$PROJECTS" verify

Compilation works, then the tests for the maven plugin kick off.

These tests are run using another maven plugin by "Takari" for plugin unit testing: Plugin Unit Testing.

The ANTLR4 Maven plugin tests fail:

java.lang.NullPointerException: Cannot invoke
"org.apache.maven.model.Plugin.getGroupId()"
because the return value of 
"org.apache.maven.plugin.MojoExecution.getPlugin()" 
is null

with the interesting part of the stack trace:

org.apache.maven.lifecycle.internal.
DefaultMojoExecutionConfigurator.configure(DefaultMojoExecutionConfigurator.java:44)
(called by) io.takari.maven.testing.
            Maven331Runtime.lookupConfiguredMojo(Maven331Runtime.java:37)
(called by) io.takari.maven.testing.
            Maven325Runtime.executeMojo(Maven325Runtime.java:34)
(called by) io.takari.maven.testing.
            TestMavenRuntime.executeMojo(TestMavenRuntime.java:269)
(called by) org.antlr.mojo.antlr4.
            Antlr4MojoTest.processWhenDependencyRemoved(Antlr4MojoTest.java:326)

So what's going on?

After a lengthy trawl through the source code (code which is not straightforward to read but unfortunately lacks comments, logging or assertions, these would be useful), we find:

In class org.antlr.mojo.antlr4.Antlr4MojoTest (file antlr4-maven-plugin/src/test/java/org/antlr/mojo/antlr4/Antlr4MojoTest.java), the failing test cases perform the following (example of test case processWhenDependencyRemoved(), slightly modified & commented):

// Junit4 rule
@Rule
public final TestMavenRuntime maven = new TestMavenRuntime();

@Test
public void processWhenDependencyRemoved() throws Exception {
   Path baseDir = resources.getBasedir("dependencyRemoved").toPath();
   Path antlrDir = baseDir.resolve("src/main/antlr4");
   Path baseGrammar = antlrDir.resolve("imports/HelloBase.g4");
   MavenProject project = maven.readMavenProject(baseDir.toFile());
   MavenSession session = maven.newMavenSession(project);
   String goal = "antlr4";

   // Get a "org.apache.maven.plugin.MojoExecution" from the 
   // "io.takari.maven.testing.TestMavenRuntime" instance.

   assertTrue(maven instanceof io.takari.maven.testing.TestMavenRuntime);

   MojoExecution exec = maven.newMojoExecution(goal); 

   maven.executeMojo(session, project, exec);

   ...

Now, org.apache.maven.plugin.MojoExecution (the exec instance) provides a .getPlugin() method which returns null instead of a valid instance of org.apache.maven.model.Plugin (a class generated by Modello when Maven itself is compiled).

After two stack frames, org.apache.maven.lifecycle.internal.DefaultMojoExecutionConfigurator.configure() is called, but then fails immediately in a chained call:

public void configure( MavenProject project, MojoExecution mojoExecution, boolean allowPluginLevelConfig )
{
   String g = mojoExecution.getPlugin().getGroupId();
   String a = mojoExecution.getPlugin().getArtifactId();
   Plugin plugin = findPlugin( g, a, project.getBuildPlugins() );
   ...

Why does getPlugin() return null? From maven's org.apache.maven.plugin.MojoExecution, file core/maven-3/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java:

public Plugin getPlugin()
{
    if ( mojoDescriptor != null )
    {
        return mojoDescriptor.getPluginDescriptor().getPlugin();
    }
    return plugin;
}

I our case, mojoDescriptor is not null, but mojoDescriptor.getPluginDescriptor().getPlugin() is, which is bad.

I have no idea why the Plugin instance is not set, or even where it is supposed to be set.

At some point, Maven's org.apache.maven.plugin.descriptor.PluginDescriptorBuilder is called from Takari's io.takari.maven.testing.Maven30xRuntime (source) to build org.apache.maven.plugin.descriptor.PluginDescriptor from the plugin.xml resource and that works nicely but the plugin member is not set at that time. The data structures are not straightforward. Below, for what it's worth, the relationship between a few of the classes.

Some classes of interest

Upvotes: 2

Views: 476

Answers (1)

Mike Cargal
Mike Cargal

Reputation: 6785

It looks like the "correct" way to build ANTLR4 from the repo is:

$ export MAVEN_OPTS="-Xmx1G"   # don't forget this on linux
cd /tmp/antlr4 # or wherever you have the software
rm -rf ~/.m2/repository/org/antlr*
mvn clean
mvn -DskipTests install

see here for explanation.

This worked for me.

Upvotes: 2

Related Questions