maven icon indicating copy to clipboard operation
maven copied to clipboard

[MNG-5102] Add support for POM mixins

Open gnodet opened this issue 2 years ago • 5 comments

  • JIRA issue: https://issues.apache.org/jira/browse/MNG-5102

Maven POM Mixins: Enhancing Project Composition

Overview

Maven 4.1.0 introduces POM Mixins, a powerful new feature that allows for more flexible and modular project composition. Mixins enable you to extract common configurations into reusable components that can be included in your projects, promoting better organization and reducing duplication across your build configurations.

What are Mixins?

Mixins are reusable POM fragments that can be included in your project, similar to parent POMs but with more flexibility. Unlike parent POMs which establish a strict hierarchy, mixins allow for composition from multiple sources without inheritance constraints. This enables a more modular approach to build configuration.

Key Benefits

  • Modularity: Extract common configurations into reusable components
  • Flexibility: Include multiple mixins in a single project
  • Reduced Duplication: Define configurations once and reuse them across projects
  • Simplified Maintenance: Update configurations in one place

Usage Examples

Including a Mixin by Path

Mixins can be included from the file system using a relative path:

<project xmlns="http://maven.apache.org/POM/4.2.0">
  <modelVersion>4.2.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0.0</version>
  
  <mixins>
    <mixin>
      <relativePath>mixins/mixin-1.xml</relativePath>
    </mixin>
  </mixins>
</project>

Including a Mixin by GAV Coordinates

Mixins can also be retrieved from repositories using standard Maven coordinates:

<mixins>
  <mixin>
    <groupId>org.example</groupId>
    <artifactId>my-mixin</artifactId>
    <version>1.0.0</version>
  </mixin>
</mixins>

Using Mixins with Classifiers

For more specialized configurations, mixins can be referenced with classifiers:

<mixins>
  <mixin>
    <groupId>org.example</groupId>
    <artifactId>my-mixin</artifactId>
    <version>1.0.0</version>
    <classifier>special</classifier>
  </mixin>
</mixins>

Compatibility

Mixins are only available in Maven 4.1.0 and later, corresponding to modelVersion 4.2.0 or higher. Projects using mixins must specify <modelVersion>4.2.0</modelVersion> in their POM.

Implementation Details

The mixins feature extends the Maven model by adding a new <mixins> element that can contain multiple <mixin> declarations. Each mixin can be specified either by a relative path or by GAV coordinates, optionally with a classifier.

When Maven processes a project with mixins, it resolves each mixin, validates it, and merges its contents into the project model according to well-defined merging rules, similar to how parent POMs are processed but with important differences in precedence and inheritance behavior.

gnodet avatar Jul 20 '23 06:07 gnodet

One challenge we have with Maven Tiles is that plugin configurations for the same plugin will overwrite each other. How is this handled with mixins?

jimisola avatar Apr 29 '25 08:04 jimisola

One challenge we have with Maven Tiles is that plugin configurations for the same plugin will overwrite each other. How is this handled with mixins?

They use the same mechanism as parents, so it will be like if one plugin definition is in the parent and the other one in the grand-parent. They end up being merged. The order will have to be clearly specified though.

gnodet avatar Apr 29 '25 15:04 gnodet

One challenge we have with Maven Tiles is that plugin configurations for the same plugin will overwrite each other. How is this handled with mixins?

They use the same mechanism as parents, so it will be like if one plugin definition is in the parent and the other one in the grand-parent. They end up being merged. The order will have to be clearly specified though.

When you say merged - in what way?

A (parent):

    <build>
        <plugins>
            <plugin>
                <groupId>plugin</groupId>
                <artifactId>plugin</artifactId>
                <configuration>
                    <a>a</a>
                    <c>a</c>
                </configuration>
            </plugin>
        </plugins>
    </build>

B (child)

    <build>
        <plugins>
            <plugin>
                <groupId>plugin</groupId>
                <artifactId>plugin</artifactId>
                <configuration>
                    <b>b</b>
                    <c>b</c>
                </configuration>
            </plugin>
        </plugins>
    </build>

What would be the outcome of the above?

  1. The entire plugin is configured by A or B depending on order
  2. Everything is merged but A has priority so a b a
  3. Everything is merged but B has priority so a b b
  4. other outcome

lfvjimisola avatar Apr 29 '25 17:04 lfvjimisola

One challenge we have with Maven Tiles is that plugin configurations for the same plugin will overwrite each other. How is this handled with mixins?

They use the same mechanism as parents, so it will be like if one plugin definition is in the parent and the other one in the grand-parent. They end up being merged. The order will have to be clearly specified though.

When you say merged - in what way?

A (parent):

    <build>
        <plugins>
            <plugin>
                <groupId>plugin</groupId>
                <artifactId>plugin</artifactId>
                <configuration>
                    <a>a</a>
                    <c>a</c>
                </configuration>
            </plugin>
        </plugins>
    </build>

B (child)

    <build>
        <plugins>
            <plugin>
                <groupId>plugin</groupId>
                <artifactId>plugin</artifactId>
                <configuration>
                    <b>b</b>
                    <c>b</c>
                </configuration>
            </plugin>
        </plugins>
    </build>

What would be the outcome of the above?

  1. The entire plugin is configured by A or B depending on order
  2. Everything is merged but A has priority so a b a
  3. Everything is merged but B has priority so a b b
  4. other outcome

Maven identifies plugins by their groupId and artifactId. Since both parent and child define the same plugin, their configurations are merged.

Configuration Merging:

For non-conflicting elements (elements present in one POM but not the other), Maven includes both elements in the final configuration. For conflicting elements (elements with the same name in both parent and child), the child's configuration takes precedence and overrides the parent's configuration. By default, Maven does not append or merge the content of elements unless explicitly instructed (e.g., using combine.children="append").

Non-conflicting elements:

Parent has a, which is not present in the child. This element is included in the final configuration. Child has b, which is not present in the parent. This element is included in the final configuration.

Conflicting elements:

Both parent and child define . The parent has a, and the child has b. The child's b overrides the parent's a.

Resulting Configuration:

The merged configuration for the plugin in the child project will include:

<a>a</a> (from parent, non-conflicting).
<b>b</b> (from child, non-conflicting).
<c>b</c> (from child, overriding parent's <c>a</c>).

Final Outcome The effective configuration for the plugin in the child project (B) will be:

<build>
    <plugins>
        <plugin>
            <groupId>plugin</groupId>
            <artifactId>plugin</artifactId>
            <configuration>
                <a>a</a>
                <b>b</b>
                <c>b</c>
            </configuration>
        </plugin>
    </plugins>
</build>

Notes

If you want to append configurations (e.g., combine <c> values from parent and child), you can use the combine.children="append" attribute in the child POM. For example:

<configuration combine.children="append">
    <b>b</b>
    <c>b</c>
</configuration>

This would require a more complex structure for <c> (e.g., as a list), which depends on the plugin’s configuration schema. If you want to prevent inheritance of the parent’s configuration, you can add <inherited>false</inherited> to the child’s plugin definition:

<plugin>
    <groupId>plugin</groupId>
    <artifactId>plugin</artifactId>
    <inherited>false</inherited>
    <configuration>
        <b>b</b>
        <c>b</c>
    </configuration>
</plugin>

This would result in only <b>b</b> and <c>b</c> in the child’s configuration, ignoring <a>a</a> and <c>a</c> from the parent.

gnodet avatar Apr 29 '25 18:04 gnodet

@gnodet Thank you for the very thorough description with examples.

Been using Maven for decades (literally!) and I just realized that I've never seen the advanced configuration inheritance with combine.children and combine.self.

Really looking forward to mixins in Maven 4.

jimisola avatar Apr 29 '25 18:04 jimisola

Resolve #6814

jira-importer avatar Jul 06 '25 06:07 jira-importer

@gnodet Will this be included in Maven 4 (4.0.0-rc and final)?

jimisola avatar Jul 21 '25 07:07 jimisola

@gnodet Will this be included in Maven 4 (4.0.0-rc and final)?

We're trying to freeze 4.0, so it's scheduled for 4.1.

gnodet avatar Jul 21 '25 07:07 gnodet

@gnodet Will this be included in Maven 4 (4.0.0-rc and final)?

We're trying to freeze 4.0, so it's scheduled for 4.1.

Ok. That was unfortunate but totally understandable. Good luck with 4.0.0!

jimisola avatar Jul 21 '25 07:07 jimisola

@gnodet Please assign appropriate label to PR according to the type of change.

github-actions[bot] avatar Aug 07 '25 14:08 github-actions[bot]

@gnodet Is there pre-build of Maven 4.1 somewhere that can be used to test Maven Mixins?

When do you think it is likely that 4.1 will be released?

lfvjimisola avatar Aug 27 '25 09:08 lfvjimisola