55

I'm looking for a general technique here, but let's give a specific example. I have a multi-module project and I'd like to run the exec:java goal from the command-line against one of the sub-modules of my project.

I know one approach is that I can run mvn install on the whole project and then just go into the sub-module directory, run the exec:java command from the command line, and have artifacts resolved to my local repository. But running mvn install all the time gets pretty tedious.

What I'd really like is the ability to run exec:java against the Maven reactor, where the classpath is constructed from the active modules of the project in the Maven reactor. The problem is that I'm not sure this is possible. A naive approach is to run the exec:java goal from the root of the project, but this tries to run the plugin against every module in the project, as opposed to the target module I'm interested in.

Any idea? I know my motivating example was exec:java, but really there are a number of single plugin goals that I'd like to run against my project from time to time outside of the scope of the full build lifecycle.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
Brian Ferris
  • 7,557
  • 5
  • 25
  • 27

4 Answers4

52

I have a multi-module project and I'd like to run the exec:java plugin from the command-line against one of the sub-modules of my project.

I'm not saying that this will fit your exact use case but it is possible to run a goal on a subset of a multi-modules build using the -pl, --projects <arg> option:

mvn exec:java -pl my-module

I know one approach is that I can run "mvn install" on the whole project and then just go into the sub-module directory, run the exec:java command from the command line, and have artifacts resolved to my local repository.

Dependency resolution is indeed done through the local repository.

What I'd really like is the ability to run exec:java against the Maven reactor, where the classpath is constructed from the active modules of the project in the Maven reactor.

That's not really what a reactor build does. A reactor build constructs an oriented graph of the modules, derives an appropriate build order from this graph and runs the goal / phase against the modules in the calculated order. A reactor build doesn't construct some "global" classpath.

A naive approach is to run the exec:java goal from the root of the project, but this tries to run the plugin against every module in the project, as opposed to the target module I'm interested in.

Well, this is the expected behavior. It just doesn't seem to be what you're actually looking for.

Any idea? I know my motivating example was exec:java, but really there are a number of single plugin goals that I'd like to run against my project from time to time outside of the scope of the full build lifecycle

A reactor build does allow this but, as I wrote, you seem to be looking for something different. Maybe If you clarify your exact need, I would be able to provide a better answer.

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • You wrote that "Dependency resolution is indeed done through the local repository" ... this is not always true ... in particular, it's not typically true for a reactor build ... to see this, try running `mvn package` from the root of a multi-module project. You will note that it succeeds, even though the local repo doesn't contain any modules of the modules we are trying to package. – Mark VY Jul 27 '23 at 02:58
  • Wait, this is awesome! Your code snippet actually works for me! `mvn exec:java -pl my-module` actually works! (Well, I wanted something other than `exec:java` but it worked for that too!) ... EDIT: I spoke too soon ... it works for running the "help" goal but not anything non-trivial. Sigh. – Mark VY Jul 27 '23 at 03:08
41

There is another way which lets you choose multiple modules to execute a plugin.

Many plugins have a skip option, which you can activate on the root project by setting its value to true. The plugin execution will be skipped by default for all sub-modules then. Sub-modules that should execute the plugin can explicitly set skip to false. You still need to configure any non-optional attributes in the root project.

Example of the exec-maven-plugin with configuration for the exec:exec goal:

<!-- root project -->
<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <skip>true</skip>
                    <executable>java</executable>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
<!-- any module that should execute the plugin -->
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <configuration>
                <skip>false</skip>
                <!-- ... -->
            </configuration>
        </plugin>
   </plugins>
</build>
kapex
  • 28,903
  • 6
  • 107
  • 121
  • 12
    This is the first and only answer that really worked for me without resorting to ugly hacks. If you use exec:java, remove the `` tag in the parent `pom.xml` and add `none` to suppress the error message. – Jodiug Mar 20 '17 at 12:37
  • 5
    I wish this answer was more prominent. It's the least 'hacky' way and really works. – Alexandros Aug 01 '17 at 18:13
  • Thanks that's what I was looking for – Mike Feb 20 '20 at 21:20
  • Will this approach work even if this module depends on other modules? Will exec-maven-plugin include them in the classpath? – haridsv Dec 16 '21 at 14:00
  • And this is why Gradle is much better than Maven. This is such a basic functionality /facepalm – Martin Vysny Aug 17 '22 at 12:50
  • 1
    This works for me! For future readers, note that: 1) you need to run from root; 2) include `-am` in conjunction with the `-pl `; 3) include `compile` before the `exec:java` target. All in all, my command to run `exec:java` for a class in a submodule that depends on a sibling module looks something like: `mvn -s ~/.m2/settings.xml compile exec:java -Dexec.mainClass="org.unicode.text.UCA.Main" -Dexec.args="ICU" -am -pl unicodetools ` – Elango Oct 05 '22 at 16:50
  • 1
    More details for the previous comment at https://github.com/unicode-org/unicodetools/issues/317 – Elango Oct 05 '22 at 18:59
7

A somewhat general technique that I've used in this circumstance is to define a profile in the submodule POM in question that binds exec:java to the test phase. For example:

<profiles>                                                                                                                      
  <profile>                                                                                                                     
    <id>test-java</id>                                                                                                          
    <build>                                                                                                                     
      <plugins>                                                                                                                 
        <plugin>                                                                                                                
          <groupId>org.codehaus.mojo</groupId>                                                                                  
          <artifactId>exec-maven-plugin</artifactId>                                                                            
          <version>1.1.1</version>                                                                                              
          <executions>                                                                                                          
            <execution>                                                                                                         
              <phase>test</phase>                                                                                               
              <goals>                                                                                                           
                <goal>java</goal>                                                                                               
              </goals>                                                                                                          
              <configuration>                                                                                                   
                <mainClass>com.foo.bar.MyClass</mainClass>                                                                      
              </configuration>                                                                                                  
            </execution>                                                                                                        
          </executions>                                                                                                         
        </plugin>                                                                                                               
      </plugins>                                                                                                                
    </build>                                                                                                                    
  </profile>                                                                                                                    
</profiles>                                                                                                                     

Then from the top of your project, run:

mvn test -Ptest-java

This will set up the inter-module classpath as usual, and attempt to run the test-java profile in all of your subprojects. But since only the one you care about has the profile defined, it's the only one that will do anything.

It does take a wee bit of extra time for Maven to grind through your other subprojects NOOPing, but it's not so bad.

One thing to note is that the subproject is run with the top-level directory as the current working directory (not the subproject directory). There's not much you can do to work around that, but hopefully that won't cause you trouble.

samskivert
  • 3,694
  • 20
  • 22
2

Pascal's suggestion is probably what you want. Note that it is not currently possible to first compile the dependencies, then run (exec:exec etc.) the app, in a single Maven command: https://jira.codehaus.org/browse/MNG-5059

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112