Jakob Odersky Jakob Odersky - 2 months ago 9
Scala Question

Dynamic maven artifactId

Is it possible for a POM to declare (or at least publish) an

artifactId
containing system properties? I mean the artifactId of the actual project, not dependencies.

I am using maven to build a scala project and thus, to allow publishing the project for different scala versions, in the pom.xml I'd like to declare:

<artifactId>myproject_${scalaBinaryVersion}</artifactId>


however maven 3.3. complains


[WARNING] 'artifactId' contains an expression but should be a constant


Since I'd like this project to be interoperable with sbt, what would be the best way to publish an artifact suffixed with the scala binary version?

Answer

The Maven way of doing so would be to use classifiers. From official documentation an example matches exactly your case (for different Java versions, but you can replace Java with Scala):

The classifier allows to distinguish artifacts that were built from the same POM but differ in their content. It is some optional and arbitrary string that - if present - is appended to the artifact name just after the version number. As a motivation for this element, consider for example a project that offers an artifact targeting JRE 1.5 but at the same time also an artifact that still supports JRE 1.4. The first artifact could be equipped with the classifier jdk15 and the second one with jdk14 such that clients can choose which one to use.

You can configure your POM as following:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.5</version>
            <executions>
                <execution>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                    <configuration>
                        <classifier>${scalaBinaryVersion}</classifier>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Note: we are adding an additional execution of the Maven Jar Plugin, so the project would create two jars, the normal one + an additional one with the specified (dynamic) classifier.

Then Maven will automatically publish the classified jar together with the normal jar (since it will be automatically attached to the build). You can then import it as a further Maven dependency in another project specifying its classifier as part of the Maven GAV (GAVC in this case):

<dependency>
    <groupId>your.group.id</groupId>
    <artifactId>your.constant.artifact.id</artifactId>
    <version>your.version</version>
    <classifier>your.dynamic.classifier</classifier>
</dependency>

If you want to only build the classified one and no standard (unused) jar, you can skip the creation of the normal jar as following:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.5</version>
            <executions>
                <execution>
                    <id>default-jar</id>
                    <phase>none</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
                <execution>
                    <id>scala-version-jar</id>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                    <configuration>
                        <classifier>${scalaBinaryVersion}</classifier>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Note: we are simply overriding the default execution of the Jar Plugin and binding it to a non existing phase. Hence Maven will only generate the classified Jar. The Install Plugin will then only install the classified one.


Update: how to have dynamic artifactId installed with dynamic dependencies

If different transitive dependencies are required for different dynamic versions, then indeed classifiers are not suitable.
Dynamic artifactIds with dynamic dependencies (and hence dynamic transitive dependencies) can however be achieved. Here below is the approach I used (and successfully tested):

As preference, I isolated the dynamic behavior in a profile, but you can obviously move it back to the default build (or have the profile active by default).

First of all, let's define in our pom the dependencies requiring a dynamic version, hence via properties as following:

<properties>
    <scalaBinaryVersion>scalaversion</scalaBinaryVersion>
    <dependency.version>4.11</dependency.version>
</properties>

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${dependency.version}</version>
    </dependency>
</dependencies>

Note: for the sake of an example, I'm using Junit as dependency in this case and not in test scope, because I want it as compile dependency (again, just for this example).

Then let's define a profile for our dynamic behavior:

<profiles>
    <profile>
        <id>build-scala-version</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>2.5</version>
                    <configuration>
                        <finalName>${project.artifactId}_${scalaBinaryVersion}-${project.version}</finalName>
                    </configuration>
                </plugin>

                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>2.7</version>
                    <executions>
                        <execution>
                            <id>copy-pom</id>
                            <phase>generate-resources</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${project.build.directory}/${scalaBinaryVersion}</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>${basedir}</directory>
                                        <includes>
                                            <include>pom.xml</include>
                                        </includes>
                                        <filtering>true</filtering>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>

                <plugin>
                    <groupId>com.google.code.maven-replacer-plugin</groupId>
                    <artifactId>replacer</artifactId>
                    <version>1.5.1</version>
                    <executions>
                        <execution>
                            <id>replace-artifactid</id>
                            <phase>prepare-package</phase>
                            <goals>
                                <goal>replace</goal>
                            </goals>
                            <configuration>
                                <file>target/${scalaBinaryVersion}/pom.xml</file>
                                <token>&lt;artifactId&gt;${project.artifactId}&lt;/artifactId&gt;</token>
                                <!-- Replace to -->
                                <value>&lt;artifactId&gt;${project.artifactId}_${scalaBinaryVersion}&lt;/artifactId&gt;</value>
                                <outputDir>target\${scalaBinaryVersion}\replacer</outputDir>
                                <outputFile>pom.xml</outputFile>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>

                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                    <executions>
                        <execution>
                            <id>default-install</id>
                            <configuration>
                                <skip>true</skip>
                            </configuration>
                        </execution>
                        <execution>
                            <id>install-scala-version</id>
                            <phase>install</phase>
                            <goals>
                                <goal>install-file</goal>
                            </goals>
                            <configuration>
                                <groupId>${project.groupId}</groupId>
                                <artifactId>${project.artifactId}_${scalaBinaryVersion}</artifactId>
                                <version>${project.version}</version>
                                <packaging>${project.packaging}</packaging>
                                <file>${project.build.directory}/${project.artifactId}_${scalaBinaryVersion}-${project.version}.jar</file>
                                <pomFile>${project.build.directory}/${scalaBinaryVersion}/replacer/pom.xml</pomFile>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Note, the profile is customizing and providing the following:

  • Changing the final Jar name with a dynamic name, depending on the runtime (aka dynamic) value, to ${project.artifactId}_{scalaBinaryVersion}-${project.version}
  • Filtering the existing pom file via the Maven Resources Plugin and copying it to the directory target\${scalaBinaryVersion}. The copied pom will have the dependencies with the dynamic version because the Resources Plugin will replace them. However, it will not have the dynamic artifactId (yet).
  • Finalizing the dynamic pom file. The Replacer plugin will replace the artifactId XML element with the dynamic value (working on the target folder, hence everything on temporarely files)
  • Skipping the generation of the default installation
  • Performing a custom install-file installation with the dynamic pom file (the filtered, copied and replaced one, providing dynamic dependencies (and as such dynamic transitive dependencies) and a dynamic artifactId

Hence, performing the following maven invocation:

mvn clean install -Pbuild-scala-version -DscalaBinaryVersion=hello -Ddependency.version=4.4

Maven will effectively install a new artifact in the local cache for the dynamic artifactId, the dynamic dependency version and the dynamic pom.
Note: if the concerned dependency version(s) and the dynamic scala version is the same, then you can save up a parameter and make the invocation shorter and more consistent.