You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

[IN PROGRESS] Dynamic POM Build Sections: Supporting Change in MavenProject State

Context

Maven builds often include plugins that make changes to the project's internal state. In fact, this is one of the two main activities executed by most plugins - with the other being modification of project files themselves. Whether this involves setting the file in the project's main artifact, re-setting the build-directory paths, or modifying and introducing POM properties, these changes in project state must be reflected in the configurations used for subsequent plugin executions.

Problem

As plugins modify the internal state of the MavenProject instance during a build, these changes must be reflected in the configurations handed to subsequent plugin executions. If a project has a build directory in the POM of "foo", then MojoA calls project.getBuild().setDirectory("bar"), then this implies two things for plugins executing after MojoA:

MojoB, defining a parameter as outputDirectory, and with a configuration in the POM of <outputDirectory>${project.build.directory}/output</outputDirectory>, should use the location "bar/output" when it executes.
MojoC, defining a parameter for injecting the current MavenProject instance (using the ${project} expression), and using the result of project.getBuild().getDirectory(), should use the location "bar" when it executes.

Currently (as of Maven 2.0.9), all expressions in the POM are resolved when the project instance is constructed. This means that expressions like $

Unknown macro: {project.build.directory}

are no longer dynamic, and therefore are locked down to their original values. Previously, this was the case with everything except build-path expressions like project.build.directory - for instance, a change in a POM property <myProperty>originalValue</myProperty> to <myProperty>newValue</myProperty> will still result in a plugin with a declared configuration of <output>${myProperty}</output> using the concrete value of 'originalValue'.

The real problem here has nothing to do with build-path expressions, and everything to do with preserving the dynamism available in the POM as it is read from the filesystem, with expressions intact. If the state of the project instance changes, the values of these expressions must change accordingly.

However, this need for dynamism must be balanced against the need to provide fully-resolved values for API-oriented extraction of project state. For example, this API-orientation becomes a factor if a plugin attempts to use an injected MavenProject instance (parameter using the expression ${project}) and somehow process the set of Resource instances declared within. Consider what happens if the POM declares the following:

<project>
  [...]
  <properties>
    <myResources>conf/resources</myResources>
  </properties>
  [...]
  <build>
    <resources>
      <resource>
        <directory>${myResources}</directory>
      </resource>
    </resources>
  </build>
</project>

If absolute dynamism is preserved by leaving expressions unresolved in the project instance itself, and the plugin retrieves the Resources from this MavenProject instance, the value for the Resource's directory, available through the API, will be the uninterpolated value:

${myResources}

While dynamism for expressions used in the build section of the POM is a good thing, it is definitely not appropriate for everything in the POM. For instance, dynamically injecting new dependencies is absolutely not a good thing, since it affects Maven's ability to understand what external libraries a project depends on - in that scenario, the only way to get the full complement of project dependencies is to first run the plugin, then check the project dependency list.

From this, we can guess that there are certain parts of the POM that need to be variable in order to support things like forked-mojo execution, and other parts that must be as deterministic as possible. When forking a Clover process, project classes must be instrumented, compiled, and tested without interfering with the project classes that are destined to be a part of the resulting project artifact; therefore, the build paths for the project must be variable. By the same token, these build paths must be resolved for build resources (the <resources/> section), since the resources are object instances that contain fields like directory, targetPath, etc. and will be injected into plugins as complex objects - not something that can be interpolated readily. Moreover, these resource directories should be adjusted whenever the build paths they rely on are adjusted.

If we can say that a section of the POM should be preserved uninterpolated until it's time to inject it into a plugin, then we can easily mask out that section before running the interpolator, then reintroduce it afterwards. However, things get complicated when you consider that some plugins may add source directories, resources, and so forth to the build section, which will require resynchronization with the original, uninterpolated build section after the plugin executes.

We can use dirty flags to determine when this needs to happen, but the dirty state of the project instance must be tracked deeply. That is, the entire object graph that includes the project instance and everything reachable from it must be tracked for dirty changes that need synchronization or incorporation of some sort. Obviously, this is problematic to say the least.

Instead, a potential solution might be to make exceptions of the resources and testResources sections of the build, and readjust them each time they - or the project instance itself - is injected into a plugin. We could then mask out the build section from interpolation, and preserve plugin configurations for just-in-time interpolation.

  • No labels