简体   繁体   中英

Executing individual maven plugin goals in multi-module project

I'm trying to overcome well-known maven issue, described in various SO questions, for example:

before now I was familiar with following workarounds:

  • mvn install - that is exactly what I would like to avoid
  • extremely comprehensive project configuration involving <skip> / <properties> / <profiles>
  • copy dependencies using maven-dependency-plugin into module folder whilst packaging and setup classpath

and all of those workarounds looks very poor from my perspective.

Today I have read about root-reactor aware subfolder builds in maven-4 , however maven-4 is not yet released and I'm interested to get a solution for maven-3 . I have performed some research and have found a couple useful extension points in maven-3 :

DefaultArtifactResolver.java :

if ( workspace != null )
{
    File file = workspace.findArtifact( artifact );
    if ( file != null )
    {
        artifact = artifact.setFile( file );
        result.setArtifact( artifact );
        result.setRepository( workspace.getRepository() );
        artifactResolved( session, trace, artifact, result.getRepository(), null );
        continue;
    }
}

DefaultProjectDependenciesResolver.java

for ( RepositorySessionDecorator decorator : decorators )
{
    RepositorySystemSession decorated = decorator.decorate( project, session );
    if ( decorated != null )
    {
        session = decorated;
    }
}

and finally I have implemented a very simple maven extension (full source code on github ):

@Component(role = RepositorySessionDecorator.class)
public class FakeRepositorySessionDecorator implements RepositorySessionDecorator {

    @Requirement
    protected ArtifactHandlerManager artifactHandlerManager;

    @Override
    public RepositorySystemSession decorate(MavenProject project, RepositorySystemSession session) {
        String enabled = session.getUserProperties().get("fakerepo");
        if (!"true".equalsIgnoreCase(enabled)) {
            return null;
        }
        MavenProject root = project;
        while (root != null && !root.isExecutionRoot()) {
            root = root.getParent();
        }
        if (root != null) {
            WorkspaceReader workspaceReader = session.getWorkspaceReader();
            workspaceReader = new FakeWorkspaceReader(workspaceReader, root, artifactHandlerManager);
            return new DefaultRepositorySystemSession(session)
                    .setWorkspaceReader(workspaceReader);
        }
        return null;
    }

}

The idea is if developer specifies -Dfakeroot when executing maven plugin goal my extension expands workspace scope from single module to the project root and when requested new expanded workspace tries to find packaged artifact among submodule folders, thus the sequence of commands like:

mvn clean package
mvn exec:exec -pl submodule -Dfakeroot

leads developer to the expected result.

The question is : what I may brake if I remove requirement to specify -Dfakerepo and enable the behaviour described above by default (ie apply new behaviour for all maven goals and lifecycle phases)? From my perspective it is always more reasonable to lookup packaged artifacts among submodule folders rather than in local repository. Or am I missing something?


UPD.

I have found a following hypothetical scenario when my extension may work not like "expected":

  • let there are two submodules A and B in multi-module project, and B depends on A
  • developer have modified at least A and issues something like mvn -am test -pl B

in that case if A was packaged previously my extension forces maven to use stale artifact, however default implementation would use A/target/classes as classpath entry, on the other hand A/target/classes may contain stale classes (we are not issuing clean ), thus the behaviour of "default implementation" is also far from ideal in that case.


UPD2.

It seems that it is worth to explain why I that issue is bothering me. Actually, there are a couple of "typical" scenarios:

  1. developers would like to maintain their own infrastructure (in particular that is primarily a DB), ie: start and stop multiple instances, perform DB migrations, debug, etc - hereby we would like to avoid CI issues like "something went wrong in CI pipeline - guess what". And the goal is to make it as simple as possible, for example we have a special exec goal in dev submodule, which performs DB migrations:

<dependencies>

    <dependency>
        <groupId>tld.project</groupId>
        <artifactId>another-submodule</artifactId>
    </dependency>
    
</dependencies>

<execution>
    <id>liquibase-update-primary</id>
    <phase>install</phase>
    <goals>
        <goal>exec</goal>
    </goals>
    <configuration>
        <executable>java</executable>
        <arguments>
            <argument>-classpath</argument>
            <!-- expecting to get module dependencies there -->
            <classpath/>
            <!-- main class -->
            <argument>liquibase.integration.commandline.Main</argument>
            <!-- covered by project properties -->
            <argument>--changeLogFile=${primary.changeLogFile}</argument>
            <!-- covered by profile properties -->
            <argument>--url=${jdbc.url}</argument>
            <argument>--driver=${jdbc.driver}</argument>
            <argument>--username=${jdbc.username}</argument>
            <argument>--password=${jdbc.password}</argument>
            <argument>--logLevel=info</argument>
            <argument>update</argument>
        </arguments>
    </configuration>
</execution>

and that obviously does not work in maven-3 , because it expects to find tld.project-another-submodule artifact in local repository, however it is possible to perform the following trick with maven-dependency-plugin :

<execution>
    <id>liquibase-dependencies</id>
    <phase>package</phase>
    <goals>
        <goal>copy</goal>
    </goals>
    <configuration>
        <artifactItems>
            <artifactItem>
                <!-- 
                    now we may tell liquibase to load extra jars
                    from  ${project.build.directory}/liquibase
                -->
                <groupId>tld.project</groupId>
                <artifactId>another-submodule</artifactId>
                <type>jar</type>
                <destFileName>another-submodule.jar</destFileName>
                <outputDirectory>${project.build.directory}/liquibase</outputDirectory>
            </artifactItem>
        </artifactItems>
    </configuration>
</execution>
  1. We would like to run integration tests individually without recompiling/packaging the entire project ie issuing something like mvn verify -pl it-submodule , that is both useful from developer and CI perspective:

    • Developers and DevOps may perform infrastructure-related steps somewhere between package and verify phases
    • CI may run verify multiple times (yep, someone may think about how is it possible to reiterate failed tests in CI pipeline, however our goal is to run verify phase multiple times in a row to make sure there are no flapping tests)
  2. In case of large projects every extra lifecycle step takes a lot of time

Well,

I have taken a look on how that implemented in maven-4 and got a conclusion that it won't work as expected:

  1. maven team have expanded the scope of reactor workspace, now the scope of reactor workspace is an entire project, regardless whether -f or -pl was specified - exactly the same what I'm doing.
  2. maven team have added some heuristics to determine whether packaged artifact is stale or not (simply comparing modification dates of packaged artifact and classes in target/classes , nothing related to sources though) - that is actually the answer to my Q.
  3. there are a issue which make "root-reactor aware subfolder builds" feature completely useless: classifiers are not supported - only main artifact gets discovered, otherwise it fallbacks to ~/.m2/repository :(
  4. if maven fails to discover main artifact it tries either target/classes or target/test-classes - for artifacts with classifiers maven throws "Artifact has not been packaged yet. When used on reactor artifact, copy should be executed after packaging: see MDEP-187"

Finally I end up with the following:

  1. I doubt this feature will be implemented in maven in the observable future, so implementing my own maven extension makes sense, here it is: GitHub , Maven Central
  2. Two modes were implemented:
    • installing reactor artifacts into "temporary local repository" - by doing that we do not poison local repository and performing mvn install is now "safe". -Dimh.repository system property enables this mode
    • expanding reactor workspace to the entire project, like maven-4 does, but also paying attention to supplemental artifacts, mvn install is not required in this case and mvn package is enough. -Dimh.workspace system property enables this mode

Examples below are based on MNG-7527 project

-Dimh.repository mode:

% mvn clean install -Dimh.repository| grep Installing
[INFO] Installing MNG-7527/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527/0.0.1-SNAPSHOT/mng7527-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-war/target/mng7527-war-0.0.1-SNAPSHOT.war to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT.war
[INFO] Installing MNG-7527/mng7527-war/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-war/target/mng7527-war-0.0.1-SNAPSHOT-classes.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT-classes.jar
[INFO] Installing MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep1/0.0.1-SNAPSHOT/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] Installing MNG-7527/mng7527-dep1/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep1/0.0.1-SNAPSHOT/mng7527-dep1-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-dep2/target/mng7527-dep2-0.0.1-SNAPSHOT.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep2/0.0.1-SNAPSHOT/mng7527-dep2-0.0.1-SNAPSHOT.jar
[INFO] Installing MNG-7527/mng7527-dep2/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep2/0.0.1-SNAPSHOT/mng7527-dep2-0.0.1-SNAPSHOT.pom

% mvn clean package -f mng7527-dep1 -Dimh.repository
[INFO] [IMH] workspace extension disabled
[INFO] [IMH] setting up overlay repository
[INFO] [IMH] using root project target folder as overlay repository: MNG-7527/target/local-repo
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< tel.panfilov.maven:mng7527-dep1 >-------------------
[INFO] Building mng7527-dep1 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mng7527-dep1 ---
[INFO] Deleting MNG-7527/mng7527-dep1/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mng7527-dep1 ---
[INFO] No tests to run.
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mng7527-dep1 ---
[INFO] Building jar: MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

-Dimh.workspace mode:

% mvn clean package -f mng7527-dep1 -Dimh.workspace 
[INFO] [IMH] repository extension disabled
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< tel.panfilov.maven:mng7527-dep1 >-------------------
[INFO] Building mng7527-dep1 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mng7527-dep1 ---
[INFO] Deleting MNG-7527/mng7527-dep1/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mng7527-dep1 ---
[INFO] No tests to run.
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mng7527-dep1 ---
[INFO] Building jar: MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM