Advanced Mojo Development

The preceding examples showed how to declare basic mojo parameters, and how to annotate the mojo with a name and a preferred phase binding. The next examples cover more advanced topics relating to mojo development. The following sections do not build on one another, and are not required for developing basic mojos. However, if you want to know how to develop plugins that manage dependencies, project source code and resources, and artifact attachments, then read on!

Gaining Access to Maven APIs

Before proceeding, it’s important to mention that the techniques discussed in this section make use of Maven’s project and artifact APIs. Whenever you need direct access to the current project instance, one or more artifacts in the current build, or any related components, you must add a dependency on one or more Maven APIs to your project’s POM.
To enable access to Maven’s project API, including the ability to work with the current project instance, modify your POM to define a dependency on maven-project by adding the following:

 <dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-project</artifactId>
  <version>2.0.2</version>
</dependency>

To enable access to information in artifacts via Maven’s artifact API, modify your POM to define a dependency on maven-artifact by adding the following:

 <dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-artifact</artifactId>
  <version>2.0</version>
</dependency>

It’s important to realize that Maven’s artifact APIs are slightly different from its project API, in that the artifact-related interfaces are actually maintained in a separate artifact from the components used to work with them. Therefore, if you only need to access information inside an artifact, the above dependency declaration is fine. However, if you also need to work with artifacts – including actions like artifact resolution – you must also declare a dependency on maven-artifact-manager in your POM, like this:

 <dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-artifact-manager</artifactId>
  <version>2.0</version>
</dependency>

Accessing Project Dependencies

Many mojos perform tasks that require access to a project’s dependencies. For example, the compile mojo in the maven-compiler-plugin must have a set of dependency paths in order to build the compilation classpath. In addition, the test mojo in the maven-surefire-plugin requires the project’s dependency paths so it can execute the project’s unit tests with a proper classpath. Fortunately, Maven makes it easy to inject a project’s dependencies.
To enable a mojo to work with the set of artifacts that comprise the project’s dependencies, only the following two changes are required:

  • First, the mojo must tell Maven that it requires the project dependency set.
  • Second, the mojo must tell Maven that it requires the project’s dependencies be resolved (this second requirement is critical, since the dependency resolution process is what populates the set of artifacts that make up the project’s dependencies).

Injecting the project dependency set

As described above, if the mojo works with a project’s dependencies, it must tell Maven that it requires access to that set of artifacts. As with all declarations, this is specified via a mojo parameter definition and should use the following syntax:

/**
 * The set of dependencies required by the project
 * @parameter default-value="${project.dependencies}"
 * @required
 * @readonly
 */
private java.util.Set dependencies;

This declaration should be familiar to you, since it defines a parameter with a default value that is required to be present before the mojo can execute. However, this declaration has another annotation, which might not be as familiar: @readonly. This annotation tells Maven not to allow the user to configure this parameter directly, namely it disables configuration via the POM under the following section:

 <configuration>
  <dependencies>...</dependencies>
</configuration>

It also disables configuration via system properties, such as:

-Ddependencies=[...]

So, you may be wondering, “How exactly can I configure this parameter?” The answer is that the mojos parameter value is derived from the dependencies section of the POM, so you configure this parameter by modifying that section directly.
If this parameter could be specified separately from the main dependencies section, users could easily break their builds – particularly if the mojo in question compiled project source code.
In this case, direct configuration could result in a dependency being present for compilation, but being unavailable for testing. Therefore, the @readonly annotation functions to force users to configure the POM, rather than configuring a specific plugin only.

Requiring dependency resolution

Having declared a parameter that injects the projects dependencies into the mojo, the mojo is missing one last important step. To gain access to the project’s dependencies, your mojo must declare that it needs them.
Maven provides a mechanism that allows a mojo to specify whether it requires the project dependencies to be resolved, and if so, at which scope. Maven 2 will not resolve project dependencies until a mojo requires it. Even then, Maven will resolve only the dependencies that satisfy the requested scope. In other words, if a mojo declares that it requires dependencies for the compile scope, any dependencies specific to the test scope will remain unresolved. However, if later in the build process, Maven encounters another mojo that declares a requirement for test-scoped dependencies, it will force all of the dependencies to be resolved (test is the widest possible scope, encapsulating all others).
It’s important to note that your mojo can require any valid dependency scope to be resolved prior to its execution.

If you’ve used Maven 1, you’ll know that one of its major problems is that it always resolves all project dependencies before invoking the first goal in the build (for clarity, Maven 2.0 uses the term ‘mojo’ as roughly equivalent to the Maven 1.x term ‘goal’). Consider the case where a developer wants to clean the project directory using Maven 1.x. If the project’s dependencies aren’t available, the clean process will fail – though not because the clean goal requires the project dependencies. Rather, this is a direct result of the rigid dependency resolution design in Maven 1.x.
Maven 2 addresses this problem by deferring dependency resolution until the project’s dependencies are actually required. If a mojo doesn’t need access to the dependency list, the build process doesn’t incur the added overhead of resolving them.

Returning to the example, if your mojo needs to work with the project’s dependencies, it will have to tell Maven to resolve them. Failure to do so will cause an empty set to be injected into the mojo’s dependencies parameter.
You can declare the requirement for the test-scoped project dependency set using the following
class-level annotation:

/**
 * @requiresDependencyResolution test
 [...]
 */

Now, the mojo should be ready to work with the dependency set.

BuildInfo example: logging dependency versions

Turning once again to the maven-buildinfo-plugin, you will want to log the versions of the dependencies used during the build. This is critical when the project depends on snapshot versions of other libraries. In this case, knowing the specific set of snapshots used to compile a project can lend insights into why other builds are breaking. For example, one of the dependency libraries may have a newer snapshot version available.
To that end, you’ll add the dependency-set injection code discussed previously to the extract mojo in the maven-buildinfo-plugin, so it can log the exact set of dependencies that were used to produce the project artifact.
This will result in the addition of a new section in the buildinfo file, which enumerates all the dependencies used in the build, along with their versions – including those dependencies that are resolved transitively. Once you have access to the project dependency set, you will need to iterate through the set, adding the information for each individual dependency to your buildinfo object.
The code required is as follows:

 public void execute() throws MojoExecutionException {
        [...]
        addResolvedDependencies( buildInfo );
        [...]
     }
     private void addResolvedDependencies( BuildInfo buildInfo ) {
        if ( dependencies != null && !dependencies.isEmpty() ) {
            for ( Iterator it = dependencies.iterator(); it.hasNext(); ) {
                Artifact artifact = (Artifact) it.next();
                ResolvedDependency rd = new ResolvedDependency();
                rd.setGroupId( artifact.getGroupId() );
                rd.setArtifactId( artifact.getArtifactId() );
                rd.setResolvedVersion( artifact.getVersion() );
                rd.setOptional( artifact.isOptional() );
                rd.setScope( artifact.getScope() );
                rd.setType( artifact.getType() );
                if ( artifact.getClassifier() != null ) {
                    rd.setClassifier( artifact.getClassifier() );
                }
                buildInfo.addResolvedDependency( rd );
            }
        }
    }

When you re-build the plugin and re-run the Guinea Pig build, the extract mojo should produce the same buildinfo file, with an additional section called resolvedDependencies that looks similar to the following:

 <resolvedDependencies>
  <resolvedDependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <resolvedVersion>3.8.1</resolvedVersion>
    <optional>false</optional>
    <type>jar</type>
    <scope>test</scope>
  </resolvedDependency>
  [...]
  <resolvedDependency>
    <groupId>com.exist.mvnbook.guineapig</groupId>
    <artifactId>guinea-pig-api</artifactId>
    <resolvedVersion>1.0-SNAPSHOT</resolvedVersion>
    <optional>false</optional>
    <type>jar</type>
    <scope>compile</scope>
  </resolvedDependency>
  [...]
</resolvedDependencies>

The first dependency listed here, junit, has a static version of 3.8.1. This won’t add much insight for debuggers looking for changes from build to build, but consider the next dependency: guinea-pig-api. This dependency is part of the example development effort, and is still listed with the version 1.0-alpha-SNAPSHOT in the POM. The actual snapshot version used for this artifact in a previous build could yield tremendous insight into the reasons for a current build failure, particularly if the newest snapshot version is different.

If you were using a snapshot version from the local repository which has not been deployed, the resolvedVersion in the output above would be 1.0-alpha-SNAPSHOT. This is because snapshot time-stamping happens on deployment only.

Accessing Project Sources and Resources

In certain cases, it’s possible that a plugin may be introduced into the build process when a profile is activated. If this plugin adds resources like images, or new source code directories to the build, it can have dramatic effects on the resulting project artifact. For instance, when a project is built in a JDK 1.4 environment, it may be necessary to augment a project’s code base with an additional source directory. Once this new source directory is in place, the compile mojo will require access to it, and other mojos may need to produce reports based on those same source directories. Therefore, it’s important for mojos to be able to access and manipulate both the source directory list and the resource definition list for a project.

Adding a source directory to the build

Although the POM supports only a single sourceDirectory entry, Maven’s concept of a project can accommodate a whole list of directories. This can be very useful when plugins generate source code, or simply need to augment the basic project code base. Maven’s project API bridges this gap, allowing plugins to add new source directories as they execute. It requires access to the current MavenProject instance only, which can be injected into a mojo using the following code:

/**
 * Project instance, needed for attaching the buildinfo file.
 * Used to add new source directory to the build.
 * @parameter default-value="${project}"
 * @required
 * @readonly
 */
private MavenProject project;

This declaration identifies the project field as a required mojo parameter that will inject the current MavenProject instance into the mojo for use. As in the prior project dependencies discussion, this parameter also adds the @readonly annotation. This annotation tells Maven that users cannot modify this parameter, instead, it refers to a part of the build state that should always be present (a more in-depth discussion of this annotation is available in section 3.6, Chapter 3 of this book). The current project instance is a great example of this; any normal build will have a current project, and no other project contains current state information for this build.

It is possible that some builds won’t have a current project, as in the case where the maven-archetype-plugin is used to create a stub of a new project. However, mojos require a current project instance to be available, unless declared otherwise. Maven will fail the build if it doesn’t have a current project instance and it encounters a mojo that requires one. So, if you expect your mojo to be used in a context where there is no POM – as in the case of the archetype plugin – then simply add the class-level annotation: @requiresProject with a value of false, which tells Maven that it’s OK to execute this mojo in the absence of a POM.

Once the current project instance is available to the mojo, it’s a simple matter of adding a new source root to it, as in the following example:

project.addCompileSourceRoot( sourceDirectoryPath );

Mojos that augment the source-root list need to ensure that they execute ahead of the compile phase. The generally-accepted binding for this type of activity is in the generate-sources life-cycle phase. Further, when generating source code, the accepted default location for the generated source is in:

${project.build.directory}/generated-sources/

While conforming with location standards like this is not required, it does improve the chances that your mojo will be compatible with other plugins bound to the same life cycle.

Adding a resource to the build

Another common practice is for a mojo to generate some sort of non-code resource, which will be packaged up in the same jar as the project classes. This could be a descriptor for binding the project artifact into an application framework, as in the case of Maven itself and the components.xml file found in all maven artifacts. Many different mojo’s package resources with their generated artifacts such as web.xml files for servlet engines, or wsdl files for web services.
Whatever the purpose of the mojo, the process of adding a new resource directory to the current build is straightforward and requires access to the MavenProject and MavenProjectHelper:

/**
 * Project instance, needed for attaching the buildinfo file.
 * Used to add new source directory to the build.
 * @parameter default-value="${project}"
 * @required
 * @readonly
 */
private MavenProject project;

This declaration will inject the current project instance into the mojo, as discussed previously. However, to simplify adding resources to a project, the mojo also needs access to the MavenProjectHelper component. This component is part of the Maven application, which means it’s always present; so your mojo simply needs to ask for it. The project helper component can be injected as follows:

/**
 * Helper class to assist in attaching artifacts to the project instance.
 * project-helper instance, used to make addition of resources simpler.
 * @component
 * @required
 * @readonly
 */
private MavenProjectHelper projectHelper;

Right away, you should notice something very different about this parameter. Namely, that it’s not a parameter at all! In fact, this is what Maven calls a component requirement (it’s a dependency on an internal component of the running Maven application). To be clear, the project helper is not a build state; it is a utility.
Component requirements are simple to declare; in most cases, the unadorned @component annotation – like the above code snippet – is adequate. Component requirements are not available for configuration by users.
Normally, the Maven application itself is well-hidden from the mojo developer. However, in some special cases, Maven components can make it much simpler to interact with the build process. For example, the MavenProjectHelper is provided to standardize the process of augmenting the project instance, and abstract the associated complexities away from the mojo developer. It provides methods for attaching artifacts and adding new resource definitions to the current project.
A complete discussion of Maven’s architecture – and the components available – is beyond the scope of this chapter; however, the MavenProjectHelper component is worth mentioning here, as it is particularly useful to mojo developers.
With these two objects at your disposal, adding a new resource couldn’t be easier. Simply define the resources directory to add, along with inclusion and exclusion patterns for resources within that directory, and then call a utility method on the project helper. The code should look similar to the following:

String directory = "relative/path/to/some/directory";
List includes = Collections.singletonList("**/*");
List excludes = null;

projectHelper.addResource(project, directory, includes, excludes);

The prior example instantiates the resource’s directory, inclusion patterns, and exclusion patterns as local variables, for the sake of brevity. In a typical case, these values would come from other mojo parameters, which may or may not be directly configurable.
Again, it’s important to understand where resources should be added during the build life cycle. Resources are copied to the classes directory of the build during the process-resources phase. If your mojo is meant to add resources to the eventual project artifact, it will need to execute ahead of this phase. The most common place for such activities is in the generate-resources life-cycle phase. Again, conforming with these standards improves the compatibility of your plugin with other plugins in the build.

Accessing the source-root list

Just as some mojos add new source directories to the build, others must read the list of active source directories, in order to perform some operation on the source code. The classic example is the compile mojo in the maven-compiler-plugin, which actually compiles the source code contained in these root directories into classes in the project output directory. Other examples include javadoc mojo in the maven-javadoc-plugin, and the jar mojo in the maven-source-plugin. Gaining access to the list of source root directories for a project is easy; all you have to do is declare a single parameter to inject them, as in the following example:

/**
 * The list of directories which contain source code for the project.
 * List of source roots containing non-test code.
 * @parameter default-value="${project.compileSourceRoots}"
 * @required
 * @readonly
 */
private List sourceRoots;

Similar to the parameter declarations from previous sections, this parameter declaration states that Maven does not allow users to configure this parameter directly; instead, they have to modify the sourceDirectory element in the POM, or else bind a mojo to the life-cycle phase that will add an additional source directory to the build. The parameter is also required for this mojo to execute; if it’s missing, the entire build will fail.
Now that the mojo has access to the list of project source roots, it can iterate through them, applying whatever processing is necessary. Returning to the buildinfo example, it could be critically important to track the list of source directories used in a particular build, for eventual debugging purposes. If a certain profile injects a supplemental source directory into the build (most likely by way of a special mojo binding), then this profile would dramatically alter the resulting project artifact when activated. Therefore, in order to incorporate list of source directories to the buildinfo object, you need to add the following code:

  public void execute() throws MojoExecutionException {
        [...]
        addSourceRoots( buildInfo );
      [...]
    }
    private void addSourceRoots( BuildInfo buildInfo ) {
        if ( sourceRoots != null && !sourceRoots.isEmpty() ) {
            for ( Iterator it = sourceRoots.iterator(); it.hasNext(); ) {
                String sourceRoot = (String) it.next();
                buildInfo.addSourceRoot( makeRelative( sourceRoot ) );
            }
        }
    }

One thing to note about this code snippet is the makeRelative() method. By the time the mojo gains access to them, source roots are expressed as absolute file-system paths. In order to make this information more generally applicable, any reference to the path of the project directory in the local file system should be removed. This involves subtracting ${basedir} from the source-root paths. To be clear, the ${basedir} expression refers to the location of the project directory in the local file system.
When you add this code to the extract mojo in the maven-buildinfo-plugin, it will add a corresponding section to the buildinfo file that looks like the following:

 <sourceRoots>
  <sourceRoot>src/main/java</sourceRoot>
  <sourceRoot>some/custom/srcDir</sourceRoot>
</sourceRoots>

Since a mojo using this code to access project source-roots does not actually modify the build state in any way, it can be bound to any phase in the life cycle. However, as in the case of the extract mojo, it’s better to bind it to a later phase like package if capturing a complete picture of the project is important. Remember, binding this mojo to an early phase of the life cycle increases the risk of another mojo adding a new source root in a later phase. In this case however, binding to any phase later than compile should be acceptable, since compile is the phase where source files are converted into classes.
Accessing the resource list
Non-code resources complete the picture of the raw materials processed by a Maven build. You’ve already learned that mojos can modify the list of resources included in the project artifact; now, let’s learn about how a mojo can access the list of resources used in a build. This is the mechanism used by the resources mojo in the maven-resources-plugin, which copies all non-code resources to the output directory for inclusion in the project artifact.
Much like the source-root list, the resources list is easy to inject as a mojo parameter. The parameter appears as follows:

/**
 * The list of resource definitions to be included in the project jar.
 * List of Resource objects for the current build, containing
 * directory, includes, and excludes.
 * @parameter default-value="${project.resources}"
 * @required
 * @readonly
 */
private List resources;

Just like the source-root injection parameter, this parameter is declared as required for mojo execution and cannot be edited by the user. In this case, the user has the option of modifying the value of the list by configuring the resources section of the POM.
As noted before with the dependencies parameter, allowing direct configuration of this parameter could easily produce results that are inconsistent with other resource-consuming mojos. It’s also important to note that this list consists of Resource objects, which in fact contain information about a resource root, along with some matching rules for the resource files it contains.
Since the resources list is an instance of java.util.List, and Maven mojos must be able to execute in a JDK 1.4 environment that doesn’t support Java generics, mojos must be smart enough to cast list elements as org.apache.maven.model.Resource instances.
Since mojos can add new resources to the build programmatically, capturing the list of resources used to produce a project artifact can yield information that is vital for debugging purposes. For instance, if an activated profile introduces a mojo that generates some sort of supplemental framework descriptor, it can mean the difference between an artifact that can be deployed into a server environment and an artifact that cannot. Therefore, it is important that the buildinfo file capture the resource root directories used in the build for future reference. It’s a simple task to add this capability, and can be accomplished through the following code snippet:

 public void execute() throws MojoExecutionException {
    [...]
    addResourceRoots( buildInfo );
    [...]
}
private void addResourceRoots( BuildInfo buildInfo ) {
    if ( resources != null && !resources.isEmpty() ) {
        for ( Iterator it = resources.iterator(); it.hasNext(); ) {
            Resource resource = (Resource) it.next();
            String resourceRoot = resource.getDirectory();
            buildInfo.addResourceRoot( makeRelative( resourceRoot ) );
        }
    }
}

As with the prior source-root example, you’ll notice the makeRelative() method. This method converts the absolute path of the resource directory into a relative path, by trimming the ${basedir} prefix. All POM paths injected into mojos are converted to their absolute form first, to avoid any ambiguity. It’s necessary to revert resource directories to relative locations for the purposes of the buildinfo plugin, since the ${basedir} path won’t have meaning outside the context of the local file system.
Adding this code snippet to the extract mojo in the maven-buildinfo-plugin will result in a resourceRoots section being added to the buildinfo file. That section should appear as follows:

 <resourceRoots>
  <resourceRoot>src/main/resources</resourceRoot>
  <resourceRoot>target/generated-resources/xdoclet</resourceRoot>
</resourceRoots>

Once more, it’s worthwhile to discuss the proper place for this type of activity within the build life cycle. Since all project resources are collected and copied to the project output directory in the process-resources phase, any mojo seeking to catalog the resources used in the build should execute at least as late as the process-resources phase. This ensures that any resource modifications introduced by mojos in the build process have been completed. Like the vast majority of activities, which may be executed during the build process, collecting the list of project resources has an appropriate place in the life cycle.

Note on testing source-roots and resources

All of the examples in this advanced development discussion have focused on the handling of source code and resources, which must be processed and included in the final project artifact. It’s important to note however, that for every activity examined that relates to source-root directories or resource definitions, a corresponding activity can be written to work with their test-time counterparts.
This chapter does not discuss test-time and compile-time source roots and resources as separate topics; instead, due to the similarities, the key differences are summarized in the table below. The concepts are the same; only the parameter expressions and method names are different.
Table 2-2: Key differences between compile-time and test-time mojo activities

Activity Change This To This
Add testing source root project.addCompileSourceRoot() project.addTestSourceRoot()
Get testing source roots ${project.compileSourceRoots} ${project.testSourceRoots}
Add testing resource projectHelper.addResource() projectHelper.addTestResource()
Get testing resources ${project.resources} ${project.testResources}

Attaching Artifacts for Installation and Deployment
Occasionally, mojos produce new artifacts that should be distributed alongside the main project artifact in the Maven repository system. These artifacts are typically a derivative action or side effect of the main build process. Maven treats these derivative artifacts as attachments to the main project artifact, in that they are never distributed without the project artifact being distributed. Classic examples of attached artifacts are source archives, javadoc bundles, and even the buildinfo file produced in the examples throughout this chapter.
Once an artifact attachment is deposited in the Maven repository, it can be referenced like any other artifact. Usually, an artifact attachment will have a classifier, like sources or javadoc, which sets it apart from the main project artifact in the repository. Therefore, this classifier must also be specified when declaring the dependency for such an artifact, by using the classifier element for that dependency section within the POM.
When a mojo, or set of mojos, produces a derivative artifact, an extra piece of code must be executed in order to attach that artifact to the project artifact. Doing so guarantees that attachment will be distributed when the install or deploy phases are run. This extra step, which is still missing from the maven-buildinfo-plugin example, can provide valuable information to the development team, since it provides information about how each snapshot of the project came into existence.
While an e-mail describing the build environment is transient, and only serves to describe the latest build, the distribution of the buildinfo file via Maven’s repository will provide a more permanent record of the build for each snapshot in the repository, for historical reference.
Including an artifact attachment involves adding two parameters and one line of code to your mojo. First, you’ll need a parameter that references the current project instance as follows:

/**
 * Project instance, needed for attaching the buildinfo file.
 * Used to add new source directory to the build.
 * @parameter default-value="${project}"
 * @required
 * @readonly
 */
private MavenProject project;

The MavenProject instance is the object with which your plugin will register the attachment with for use in later phases of the lifecycle. For convenience you should also inject the following reference to MavenProjectHelper, which will make the process of attaching the buildinfo artifact a little easier:

/**
 * Helper class to assist in attaching artifacts to the project instance.
 * project-helper instance, used to make addition of resources simpler.
 * @component
 */
private MavenProjectHelper projectHelper;

See Section 5.5.2 for a discussion about MavenProjectHelper and component requirements.
Once you include these two fields in the extract mojo within the maven-buildinfo-plugin, the process of attaching the generated buildinfo file to the main project artifact can be accomplished by adding the following code snippet:

projectHelper.attachArtifact( project, "xml", "buildinfo", outputFile );

From the prior examples, the meaning and requirement of project and outputFile references should be clear. However, there are also two somewhat cryptic string values being passed in: “xml” and “buildinfo”. These values represent the artifact extension and classifier, respectively.
By specifying an extension of “xml”, you’re telling Maven that the file in the repository should be named using a.xml extension. By specifying the “buildinfo” classifier, you’re telling Maven that this artifact should be distinguished from other project artifacts by using this value in the classifier element of the dependency declaration. It identifies the file as being produced by the the maven-buildinfo-plugin, as opposed to another plugin in the build process which might produce another XML file with different meaning. This serves to attach meaning beyond simply saying, “This is an XML file”.
Now that you’ve added code to distribute the buildinfo file, you can test it by re-building the plugin, then running Maven to the install life-cycle phase on our test project. If you build the Guinea Pig project using this modified version of the maven-buildinfo-plugin, you should see the buildinfo file appear in the local repository alongside the project jar, as follows:

mvn install
cd C:\Documents and Settings\[user_home]\.m2\repository
cd com\exist\mvnbook\guineapig\guinea-pig-core\1.0-SNAPSHOT
dir
guinea-pig-core-1.0-SNAPSHOT-buildinfo.xml
guinea-pig-core-1.0-SNAPSHOT.jar
guinea-pig-core-1.0-SNAPSHOT.pom

Now, the maven-buildinfo-plugin is ready for action. It can extract relevant details from a running build and generate a buildinfo file based on these details. From there, it can attach the buildinfo file to the main project artifact so that it’s distributed whenever Maven installs or deploys the project.
Finally, when the project is deployed, the maven-buildinfo-plugin can also generate an e-mail that contains the buildinfo file contents, and route that message to other development team members on the project development mailing list.

Thank you for requesting a Maestro evaluation! This is our passion, and we want you to be successful. Please let us know how we may help!

Please enter your name, company email address and phone, and we will send you a link to your pre-built hosted evaluation within minutes.






I have read and agree to the Terms and Conditions.