View on GitHub

PME @ Red Hat

Standardize Your Builds

Groovy Script Injection

Overview

PME offers the ability to run arbitrary groovy scripts on the sources prior to running the build. This allows PME to be extensible by the user and to process other files not just Maven POMs.

Configuration

If the property -DgroovyScripts=<value>,.... is set, PME will load the remote Groovy script file.

The argument should a comma separated list of either:

If using a GAVTC, the remote groovy file can be deployed by e.g.

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>build-helper-maven-plugin</artifactId>
      <version>1.5</version>
      <inherited>false</inherited>
      <executions>
        <execution>
          <id>attach-artifacts</id>
          <phase>package</phase>
          <goals>
            <goal>attach-artifact</goal>
          </goals>
          <configuration>
            <artifacts>
              <artifact>
                <file>Sample.groovy</file>
                <type>groovy</type>
              </artifact>
            </artifacts>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

The deployed file can then be used with e.g.

mvn -DgroovyScripts=org.commonjava.maven.ext:depMgmt1:groovy:1.0 clean install

Groovy Scripts

Each groovy script will be run on the execution root (i.e. where Maven is invoked).

NOTE : Prior to PME 3.5 groovy manipulator precedence was controlled via a flag ; by setting groovyManipulatorPrecedence to FIRST instead of the default LAST value. Further, annotation was different; the scripts used the BaseScript annotation e.g.
@BaseScript org.commonjava.maven.ext.core.groovy.BaseScript pme
NOTE : As of PME 3.0 the API has changed: org.commonjava.maven.ext.core.groovy.BaseScript instead of org.commonjava.maven.ext.manip.groovy.BaseScript and org.commonjava.maven.ext.common.model.Project instead of org.commonjava.maven.ext.manip.model.Project

Each script must use the following annotations:

NOTE : For PME versions from 3.8.1, the PMEInvocationPoint annotation has been deprecated and developers should use InvocationPoint in preference. From version 4.3, it is not supported.
import org.commonjava.maven.ext.core.groovy.BaseScript
import org.commonjava.maven.ext.core.groovy.InvocationStage
import org.commonjava.maven.ext.core.groovy.PMEBaseScript
import org.commonjava.maven.ext.core.groovy.InvocationPoint
...

@InvocationPoint(invocationPoint = InvocationStage.FIRST)
@PMEBaseScript BaseScript pme

The API can then be invoked by e.g.

pme.getBaseDir()

NOTE : Be careful not to use pme.getProperties() or pme.getProject().getProperties() as that actually calls the Groovy language API

Invocation stages

In the example script, we saw the use of the @InvocationPoint annotation which controls when the script is run. It takes a single argument, invocationPoint, with the type InvocationStage. The possible values for InvocationStage are PREPARSE, FIRST, LAST, and ALL. These values are relative to when the manipulations to the POM files are made. The table below provides a description of the invocation stages available for running a script.

Stage Description Since
PREPARSE Runs the script prior to parsing any POM files and before any manipulations have been performed, thus allowing the modification of POM files on disk before they are read into memory. 4.6
FIRST Runs the script first after all POM files have been read into memory, but before any manipulations have been performed. 3.5
LAST Runs the script last after all modifications to the in-memory POM files have been performed. 3.5
BOTH Runs the script during stages FIRST and LAST. Note that as of version 4.6, BOTH has been replaced by ALL. [3.5, 4.5]
ALL Runs the script during all possible stages: PREPARSE, FIRST, and LAST. The getInvocationStage() API can be used to determine in which stage the script is currently running. 4.6
NOTE : It is safe to modify POM files on disk during the PREPARSE stage. However, if you modify a POM file on disk during any other stage, the modifications will be overwritten when the in-memory POM file is written back out to disk. To alter a POM file in memory, call Project.getModel() to retrieve the org.apache.maven.model.Model instance and modify that instead, .e.g., pme.getProject().getModel().setVersion( "1.0.0" ).

API

The following API is available:

Method Description Since Valid in Stage
Map<ProjectVersionRef, String> translateVersions(List<Project>) Translate the versions. Removed in 4.4; use lookupProjectVersions or lookupVersions instead. [1.7, 4.3] FIRST, LAST
File getBaseDir() Get the working directory (the execution root). 1.10 FIRST, LAST
ProjectVersionRef getGAV() Get the GAV of the current project. 1.10 FIRST, LAST
Project getProject() Return the current Project. 1.10 FIRST, LAST
List<Project> getProjects() Returns the entire collection of Projects. 1.10 FIRST, LAST
Properties getUserProperties() Get the user properties. 2.0 ALL
ModelIO getModelIO() Return a ModelIO instance for artifact resolving. 3.2 ALL
MavenSessionHandler getSession() Return the current session handler. 3.2 ALL
InvocationStage getInvocationStage() Return the current stage of the groovy manipulation. 3.5 ALL
void inlineProperty(Project, String) Allows the specified property to be inlined in any dependencies/dependencyManagement. This is useful to split up properties that cover multiple separate projects. Note that as of version 3.8, the String argument changed from group:artifact to propertyKey. 3.5 FIRST, LAST
void inlineProperty(Project, ProjectRef) Allows the specified group:artifact property to be inlined in any dependencies/dependencyManagement. This is useful to split up properties that cover multiple separate projects. Supports * as a wildcard for artifactId. 3.8 FIRST, LAST
void overrideProjectVersion(ProjectVersionRef) The specified GAV will be queried from DA for its current suffix and that suffix be used in versionSuffix instead of any versionIncrementalSuffix. 3.8 FIRST, LAST
void reinitialiseSessionStates() This will re-initialise any State linked to this session. This is useful if the groovy scripts have altered the user properties. 3.8 ALL
Logger getLogger() This will return the current SLF4J Logger instance. 3.8.2 ALL
FileIO getFileIO() This will return a FileIO instance for remote File resolving. 4.0 ALL
PomIO getPomIO() This will return a PomIO instance for parsing POM models. 4.0 ALL
Translator getRESTAPI() throws ManipulationException Gets a configured VersionTranslator to make REST calls to DA. 4.0 ALL
Map<ProjectVersionRef, String> lookupProjectVersions(List<Project>) throws RestException Lookup versions (e.g. for a project) ignoring DA suffix priority schemes returning the latest version. 4.4 FIRST, LAST
Map<ProjectVersionRef, String> lookupVersions(List<Project>) throws RestException Lookup versions respecting DA suffix priority schemes which will return the best matched version. 4.4 FIRST, LAST

Utility Functions

Currently, two main utility functions are provided:

inlineProperty

This function is typically used to deal with the problem where an upstream may use a single property for several distinct SCM Builds. This can lead to issues, so by ‘inlining’ the property it can avoid them.

NOTE : For PME versions after 3.8, the inlineProperty function that takes a ProjectRef will allow a wildcard (*) for artifactId.

overrideProjectVersion

Occasionally upstream projects have circular dependencies in their build. This can typically happen if the first build creates a BOM that is used by multiple subsequent builds. If rebuilds (primarily with a suffix) occur then the versions can get out of sync and build errors arise. This function effectively ‘locks’ the subsequent builds to the suffix value of the first.

circular example

The above diagram shows two different SCM builds. The first has a sub-module that is a BOM that references the following SCM builds. This BOM is imported into the parent’s dependencyManagement. The second SCM build inherits this parent. If the first has been rebuilt twice (hence the rebuild-2 suffix) then the BOM will refer to the incorrect version of SCM Build 2 (1.0.rebuild-2 instead of 1.0.rebuild-1). By using overrideProjectVersion it is possible to force SCM Build 2 to use suffix rebuild-2 thereby making it have the correct version.

Example

A typical groovy script that alters a JSON file on disk might be:

import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.util.logging.Slf4j
import org.commonjava.maven.ext.core.groovy.BaseScript
import org.commonjava.maven.ext.core.groovy.InvocationStage
import org.commonjava.maven.ext.core.groovy.PMEBaseScript
import org.commonjava.maven.ext.core.groovy.InvocationPoint

@InvocationPoint(invocationPoint = InvocationStage.FIRST)
@PMEBaseScript BaseScript pme

@Slf4j
public class Processor
{
    File basedir

    private void processJson(Map n) {
        ...
    }

    def execute() {
        log.info("Running ShrinkwrapProcessor...")

        def shrinkwrap = new File(basedir.toString() + File.separator + "shrink.json")

        log.info("shrinkwrap json is " + shrinkwrap)

        if (shrinkwrap.exists()) {
            log.info ("Found file {}", shrinkwrap)

            LinkedHashMap json = new JsonSlurper().parseText(shrinkwrap.text)

            processJson(json)

            shrinkwrap.write(JsonOutput.prettyPrint(JsonOutput.toJson(json)))
        }
    }
}

def Processor sp = new Processor(basedir:pme.getBaseDir()))
sp.execute()

Developing Groovy Scripts

To make it easier to develop scripts for both PME (this project) and GME an example project has been set up. The manipulator-groovy-examples provides a framework to develop and test such scripts.