Feasibility of getting Maven aggregator pom to inj

2020-07-17 16:10发布

问题:

A bit of a feasibility question for you regarding Maven. Particular, on whether we can define properties in an aggregating pom and then inject them into the referenced modules, thus allowing that module to locally overwrite the default properties defined in the inheritance hierarchy.

If you're interested in specifics I'll describe my setup. Before I do though, let me just say that we have discussed our project structure as a team extensively and it fits our needs very well. We are not looking for suggestions on other structures at this point, but exclusively exploring whether maven can fulfil our needs.

So, our setup; I'll boil it down to the essentials. We have two source projects, A and B. Each of these is actually a child module of another, ParentA and ParentB respectively. ParentA and ParentB technically have a number of child modules, but in this example I'll only explicitly reference one each for simplicity. So far so good. ParentA references A as a sub-module and A references ParentA as its parent. The same relationship applies between B and ParentB.

Now comes the fun. We would like a super parent pom for both ParentA and ParentB to inherit shared properties and config such as dependencyManagement and plugins etc. But we do NOT want this super parent pom to be responsible for builds. Instead, we would like to define a number of build projects which selectively build the various modules. In this example I'll introduce BuildAB and BuildB. The first builds A and then B, whereas the second builds just B. In reality we have quite a few of these interleaving module groups and dependencies. Finally, just to complete the picture, we have a dependency from B to A.

Let me try and draw this using some ascii art ;)

Inheritance

A --> ParentA --> parent
B --> ParentB --> parent

Sub-Module relationships

BuildAB ==> { (ParentA ==> A) (ParentB ==> B) }
BuildB ==> (ParentB ==> B)

Dependencies

B > A

Now, as it stand it is impossible to use properties from the BuildAB and BuildB files to define dependencies; these Build files are not part of any inheritance tree so nothing will pick up the properties. But we DO want to control the dependency versions differently when running BuildAB and BuildB; simply putting the dependencies in the super-parent is not going to cut it for our requirements.

If you're wondering why this might be consider that one team might be developing the B modules and possibly making minor modifications to A as well. Other developers might be working on the latest and greatest for project A which has repercussions on B thanks to the dependency. We have excellent mechanisms for handling this in sourcecode thanks to Mercurial. But we're really struggling to make this work with Maven.

Ideally, each Build file would in the first instance rely on sub-modules inheriting from the Parent. But when we need to override this inheritance, we would like to be able to specify injectable properties in the Build file, which would act exactly as if they had been specified in the module originally. Of course, all without actually modifying the pom which is being source-controlled.

What we would like to assess is whether there is any scope to modify maven to do this, via a plugin or patch.

We've never written plugins before (and frankly the tutorials and stuff online regarding this are scanty and not really developer-friendly - unless someone has a good tutorial I've missed :)) but we would be willing to give it a try if it seems feasible.

So, basically,

  • have you dealt with similar requirements yourself before and got it working with existing plugins?
  • Is there a simple trick we're missing?
  • Have you written a similar plugin and can recommend a place to start?
  • Do you know of any practical reason why such a plugin may not work?
  • Do you work on the maven source code and know whether we might be able to contribute any resulting code... and where should we start looking if we would like to.

One last comment. We develop in Eclipse, so we also need the build to work without property injection. I expect this would be via the normal inheritance tree.

Many thanks all, I know it's a bit of a tricky question.

回答1:

For all kinds of special magic: use a maven build extension.

This is a not well known (and as usual for maven, sigh) not well documented mechanism, but as far as I can see, it is indeed an officially approved way to influence the build process as a whole.

import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.execution.MavenSession;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;

@Component(role = AbstractMavenLifecycleParticipant.class, hint = "mySpecialService")
public class MySpecialExtension
    extends AbstractMavenLifecycleParticipant
{

    @Requirement
    private Logger logger;

    @Override
    public void afterProjectsRead( MavenSession session ) {
        // ...do you magic here

        // for example, to set some POM properties
        Properties sysProps = session.getSystemProperties();
        ....
        Properties projProps = session.getCurrentProject().getProperties();
        projProps.setProperty("..",val);

This function is called right after parsing the pom.xml files and building the basic POM in memory, but before any further build activity starts. In a multi module project, the extension gets called from the root project, even if it is defined in just some submodule. At this point you could in theory do anything with your build process, like just injecting some properties into the pom, loading further projects from the artefact manager and adding them to the build reactor, look up some specific plug-ins, reshape the POM of some module or even build things which aren't declared anywhere (!)

To build such an extension, you place your code into a separate maven project

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <prerequisites>
        <maven>3.0</maven>
    </prerequisites>

    <name>my-special-service</name>
    <groupId>my.group</groupId>
    <artifactId>my-special-service</artifactId>
    <packaging>jar</packaging>

    <parent>
       ....
    </parent>

    <properties>
        <mavenApiVer>3.0.5</mavenApiVer>
        <mavenModelVer>2.2.1</mavenModelVer>
    </properties>

<build>
    <plugins>
        <!-- Maven Build Extension -->
        <plugin>
            <groupId>org.codehaus.plexus</groupId>
            <artifactId>plexus-component-metadata</artifactId>
            <version>1.5.5</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate-metadata</goal>
                        <!-- goal>generate-test-metadata</goal -->
                    </goals>
                </execution>
            </executions>
            </plugin>
            <!-- Maven Build Extension -->
        </plugins>
    </build>


    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-project</artifactId>
            <version>${mavenModelVer}</version>
        </dependency>

        <!-- Maven Build Extension -->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-compat</artifactId>
            <version>${mavenApiVer}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-core</artifactId>
            <version>${mavenApiVer}</version>
        </dependency>
        <!-- Maven Build Extension -->

        ....

    </dependencies>
</project>

In order to use your extension in some other project, just add the following

<build>
    <extensions>
        <extension><!-- Maven Build Extension: my Special Service -->
            <groupId>my.group</groupId>
            <artifactId>my-special-service</artifactId>
            <version>.....</version>
        </extension>
    </extensions>

    <pluginManagement>
    ....

In our specific use case, we had some general services, (esp. database URLs used from specific plug-ins in the build process) which we need to retrieve from a configuration management system transparently. Rolling out property files to every developer and every build server would not be practical, since the environment is way to heterogenous.